UniversalDashboard.MaterialUI.psm1

$TAType = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
$TAtype::Add('DashboardColor', 'UniversalDashboard.Models.DashboardColor')
$TAtype::Add('Endpoint', 'UniversalDashboard.Models.Endpoint')
$TAtype::Add('FontAwesomeIcons', 'UniversalDashboard.Models.FontAwesomeIcons')
$TAtype::Add('Category', 'System.ComponentModel.CategoryAttribute')
$TAtype::Add('Description', 'System.ComponentModel.DescriptionAttribute')
$TAtype::Add('DisplayName', 'System.ComponentModel.DisplayNameAttribute')

function New-UDRow {
    [CmdletBinding(DefaultParameterSetName = 'static')]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter(ParameterSetName = "static", Position = 0)]
        [ScriptBlock]$Columns
    )

    End {
        New-UDGrid -Container -Content {
            & $Columns 
        }
    }
}

function New-UDColumn {
    [CmdletBinding(DefaultParameterSetName = 'content')]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),

        [Parameter()]
        [Alias('Size')]
        [ValidateRange(1, 12)]
        [int]$SmallSize = 12,
        [Parameter()]
        [ValidateRange(1, 12)]
        [int]$LargeSize = 12,
        [Parameter()]
        [ValidateRange(1, 12)]
        [int]$MediumSize = 12,
        [Parameter()]
        [ValidateRange(1, 12)]
        [int]$ExtraLargeSize,
        [Parameter()]
        [ValidateRange(1, 12)]
        [int]$ExtraSmallSize,

        [Parameter(Position = 1)]
        [ScriptBlock]$Content
    )

    $Parameters = @{}

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("ExtraLargeSize")) {
        $Parameters.Add("ExtraLargeSize", $ExtraLargeSize)
    }

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("ExtraSmallSize")) {
        $Parameters.Add("ExtraSmallSize", $ExtraSmallSize)
    }
    
    $GridContent = $Content
    New-UDGrid -Item -SmallSize $SmallSize -MediumSize $MediumSize -LargeSize $LargeSize -Content {
        & $GridContent  
    } @Parameters
}


function New-UDNivoTheme {
    param(
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$TickLineColor,
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$TickTextColor,
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$GridLineStrokeColor,
        [Parameter()]
        [int]$GridStrokeWidth
    )

    @{
        axis = @{
            ticks = @{
                line = @{
                    stoke = $TickLineColor.HtmlColor
                }
                text = @{
                    fill = $TickTextColor.HtmlColor
                }
            }
        }
        grid = @{
            line = @{
                stroke      = $GridLineStrokeColor.HtmlColor
                strokeWidth = $GridStrokeWidth
            }
        }
    }
}function New-UDAlert {
    <#
    .SYNOPSIS
    An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
     
    .DESCRIPTION
    An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Severity
    The severity of this alert. Valid values are: "success", "error", "warning", "info"
     
    .PARAMETER Children
    Content of the alert.
 
    .PARAMETER Text
    Text for the body of the alert.
     
    .PARAMETER Title
    A title for this alert.
     
    .PARAMETER ClassName
    A CSS class to apply to the alert.
 
    .PARAMETER Style
    An hashtable of styles to apply to this component.
 
    .PARAMETER Dense
    Reduces the vertical padding of the alert.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDAlert -Severity 'error' -Title "Error" -Id 'alert1'
    PS > New-UDAlert -Severity 'warning' -Title "Warning" -Id 'alert2'
    PS > New-UDAlert -Severity 'info' -Title "Info" -Id 'alert3'
    PS > New-UDAlert -Severity 'success' -Title "Success" -Id 'alert4'
    PS > } -Direction 'column'
 
    Basic alert|The alert offers four severity levels that set a distinctive icon and color.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDAlert -Severity 'error' -Title 'Error' -Text 'This is an error alert' -Id 'alert5'
    PS > New-UDAlert -Severity 'warning' -Title 'Warning' -Text 'This is an warning alert' -Id 'alert6'
    PS > New-UDAlert -Severity 'info' -Title 'Info' -Text 'This is an error info' -Id 'alert7'
    PS > New-UDAlert -Severity 'success' -Title 'Success' -Text 'This is an success alert' -Id 'alert8'
    PS > } -Direction 'column'
 
    Text and Title|You can use the -Title parameter to display a formatted title above the content.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDAlert -Severity 'error' -Title "Error" -Id 'alert9' -Dense
    PS > New-UDAlert -Severity 'warning' -Title "Warning" -Id 'alert10' -Dense
    PS > New-UDAlert -Severity 'info' -Title "Info" -Id 'alert11' -Dense
    PS > New-UDAlert -Severity 'success' -Title "Success" -Id 'alert12' -Dense
    PS > } -Direction 'column'
 
    Dense|You can use the -Dense parameter to reduce the vertical padding of the alert.
     
    #>

    [Category("app/component")]
    [Description("An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.")]
    [DisplayName("Alert")]
    [CmdletBinding(DefaultParameterSetName = 'Text')]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [ValidateSet("success", "error", "warning", "info")]
        [string]$Severity = "success",
        [Parameter(ParameterSetName = "Content")]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter(ParameterSetName = "Text")]
        [string]$Text,
        [Parameter()]
        [string]$Title,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Hashtable]$Style,
        [Parameter()]
        [Switch]$Dense
    )

    if ($PSCmdlet.ParameterSetName -eq 'Text') {
        $Children = { $Text }
    }

    @{
        type      = "mu-alert"
        id        = $id 
        isPlugin  = $true 
        assetId   = $MUAssetId 

        severity  = $Severity.ToLower()
        children  = & $Children
        title     = $Title
        className = $ClassName
        style     = $Style
        dense     = $Dense.IsPresent
    }
}
function New-UDAppBar {
    <#
    .SYNOPSIS
    Creates an AppBar.
     
    .DESCRIPTION
    Creates an AppBar. This can be used to replace the built-in AppBar.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Drawer
    A drawer that can be opened from this AppBar. Use New-UDDrawer to create a drawer to pass to this parameter.
     
    .PARAMETER Children
    Children of this AppBar. The children of an AppBar are commonly text and buttons.
     
    .PARAMETER Position
    The position of this AppBar. A fixed position will override the default AppBar.
 
    .PARAMETER Footer
    Positions the app bar at the bottom of the page to create a footer
 
    .PARAMETER DisableThemeToggle
    Removes the theme toggle switch from the app bar.
     
    .PARAMETER Color
    The theme color to use for the app bar.
     
    .PARAMETER ClassName
    A CSS class to apply to the app bar.
     
    .EXAMPLE
    PS > New-UDAppBar -Children { New-UDTypography -Text 'Hello' } -Position relative
 
    Basic App Bar|Creates a new AppBar that is relative to other components.
 
    .EXAMPLE
    PS > New-UDAppBar -Children { New-UDTypography -Text 'Hello' } -Position relative -Drawer (New-UDDrawer -Content { New-UDTypography -Text 'Drawer' })
 
    App Bar with Drawer|Creates a new AppBar with a drawer.
 
    .EXAMPLE
    PS > New-UDAppBar -Children { New-UDTypography -Text 'Hello' } -Position relative -DisableThemeToggle
 
    App Bar with no theme toggle|Creates a new AppBar with no theme toggle switch.
 
    .EXAMPLE
    PS > New-UDAppBar -Children { New-UDTypography -Text 'Hello' } -Position relative -Color secondary
 
    App Bar with secondary color|Creates a new AppBar with a secondary color.
    #>

    #[Component("AppBar", "Creates an AppBar.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [Hashtable]$Drawer,
        [Parameter()]
        [Alias('Content')]
        [ScriptBlock]$Children,
        [Parameter()]
        [ValidateSet('absolute', 'fixed', 'relative', 'static', 'sticky')]
        [string]$Position = 'sticky',
        [Parameter()]
        [switch]$Footer,
        [Parameter()]
        [Switch]$DisableThemeToggle,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [ValidateSet('default', 'inherit', 'primary', 'secondary', 'transparent')]
        [string]$Color = 'default'
    )

    @{
        id                 = $Id 
        type               = 'mu-appbar'
        assetId            = $MUAssetId 
        isPlugin           = $true 

        children           = & $Children
        drawer             = $Drawer
        position           = $Position
        footer             = $Footer.IsPresent
        disableThemeToggle = $DisableThemeToggle.IsPresent
        className          = $ClassName
        color              = $Color.ToLower()
    }
}
function New-UDAutocomplete {
    <#
    .SYNOPSIS
    The autocomplete is a normal text input enhanced by a panel of suggested options.
     
    .DESCRIPTION
    Autocomplete text boxes can be used to allow the user to select from a large list of static or dynamic items. Typing in the textbox will filter the list.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label to show for the textbox.
     
    .PARAMETER Value
    The value of the textbox.
     
    .PARAMETER OnChange
    A script block that is invoked when the selection changes.
     
    .PARAMETER OnLoadOptions
    A script block that is called when the user starts typing in the text box. The $Body variable will contain the typed text. You should return a JSON array of values that are a result of what the user has typed.
     
    .PARAMETER Options
    Static options to display in the selection drop down. When the user types, these options will be filtered.
 
    .PARAMETER FullWidth
    Whether the component should take up the full width of its parent.
 
    .PARAMETER AutoSelect
    Whether the component should automatically be selected if there was only one total option with the options array.
 
    .PARAMETER Variant
    The variant of the text box. Valid values are: "filled", "outlined", "standard"
 
    .PARAMETER Multiple
    Whether multiple items can be selected.
 
    .PARAMETER ClassName
    A CSS class to apply to the component.
 
    .PARAMETER Icon
    The icon to display before the text box. Use New-UDIcon to create the value for this parameter.
 
    .PARAMETER OnEnter
    A script block to call when the user presses enter within the autocomplete textbox.
 
    .PARAMETER Disabled
    Whether this autocomplete is disabled.
 
    .EXAMPLE
    PS > New-UDAutocomplete -Options @('Test', 'Test2', 'Test3', 'Test4') -Id 'autocomplete1' -FullWidth
 
    Basic autocomplete|The value must be chosen from a predefined set of allowed values.
 
    .EXAMPLE
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -Multiple -Id 'autocomplete2' -FullWidth
 
    Multi-Select|Allow the user to select multiple values.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -Variant 'filled' -Id 'autocomplete3' -FullWidth
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -Variant 'outlined' -Id 'autocomplete4' -FullWidth
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -Variant 'standard' -Id 'autocomplete5' -FullWidth
    PS > } -Direction 'column' -FullWidth
 
 
    Variants|Change the the style of the autocomplete textbox.
 
    .EXAMPLE
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -OnChange {
    PS > Show-UDToast $EventData
    PS > } -Id 'autocomplete6' -FullWidth
 
    Handling changes|Execute PowerShell when the autocomplete changes.
 
    .EXAMPLE
    PS > New-UDAutocomplete -Options @("Test", "No", "Yes") -OnEnter {
    PS > Show-UDToast $EventData
    PS > } -Id 'autocomplete7' -FullWidth
 
    OnEnter|Execute PowerShell when the user presses enter.
 
    .EXAMPLE
    PS > New-UDAutocomplete -OnLoadOptions {
    PS > @('Test', 'Test2', 'Test3', 'Test4') | Where-Object { $_ -match $Body } | ConvertTo-Json
    PS > } -Id 'autocomplete8' -FullWidth
 
    Asynchronous options|Creates an autocomplete with a dynamically filtered set of options
 
    #>

    [Category("app/component")]
    [Description("The autocomplete is a normal text input enhanced by a panel of suggested options.")]
    [DisplayName("Autocomplete")]
    [CmdletBinding(DefaultParameterSetName = "Static")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [string]$Label,
        [Parameter()]
        $Value,
        [Parameter()]
        [Endpoint]$OnChange, 
        [Parameter(Mandatory, ParameterSetName = "Dynamic")]
        [Endpoint]$OnLoadOptions,
        [Parameter(ParameterSetName = "Static")]
        $Options = @(),
        [Parameter()]
        [Switch]$FullWidth,
        [Parameter()]
        [Switch]$AutoSelect,
        [Parameter()]
        [ValidateSet("filled", "outlined", "standard")]
        [string]$Variant = "standard",
        [Parameter()]
        [Switch]$Multiple,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        $Icon,
        [Parameter()]
        [Endpoint]$OnEnter,
        [Parameter()]
        [Switch]$Disabled
    )

    if (-not $Options) {
        $Options = @()
    }
    elseif ($Options -is [scriptblock]) {
        $Options = & $Options
    }
    elseif ($Options.Count -eq 1 -and $Value.Length -eq 0 -and $AutoSelect -eq $true) {
        $Value = $Options | Select-Object -First 1
    }

    if ($Value -isnot [object[]]) {
        $Value = [string]$Value
    }

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    if ($OnChange) {
        $OnChange.ContentType = 'text/plain';
        $OnChange.Register($Id + "onChange", $PSCmdlet)
    }

    if ($OnEnter) {
        $OnEnter.Register($Id + "onEnter", $PSCmdlet)
    }

    if ($PSCmdlet.ParameterSetName -eq 'Dynamic') {
        if ($OnLoadOptions) {
            $OnLoadOptions.ContentType = 'text/plain';
            $OnLoadOptions.Register($Id + "onLoadOptions", $PSCmdlet)
        }
    }
    
    @{
        id            = $id 
        assetId       = $MUAssetId 
        isPlugin      = $true 
        type          = "mu-autocomplete"

        label         = $Label
        value         = $value 
        onChange      = $onChange 
        onLoadOptions = $OnLoadOptions
        options       = $Options
        fullWidth     = $FullWidth.IsPresent
        variant       = $Variant.ToLower()
        multiple      = $Multiple.IsPresent
        className     = $ClassName
        icon          = $icon
        onEnter       = $OnEnter
        disabled      = $Disabled.IsPresent
    }
}


function New-UDAutocompleteOption {
    <#
    .SYNOPSIS
    Creates a new autocomplete option.
     
    .DESCRIPTION
    Creates a new autocomplete option. This cmdlet is to be used with New-UDAutocomplete. Pass the result of this cmdlet to the -Options parameter to create a new option.
     
    .PARAMETER Name
    The name of the autocomplete option. This will be shown in the autocomplete.
     
    .PARAMETER Value
    The value of the autocomplete option. This will be passed back to New-UDForm -OnSubmit or the $EventData for -OnChange on New-UDAutocomplete.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [String]$Name,
        [Parameter(Mandatory = $true)]
        [String]$Value
    )

    @{
        type  = 'mu-autocomplete-option'
        name  = $Name 
        value = $Value 
    }
}

function New-UDAvatar {
    <#
    .SYNOPSIS
    Creates a new Avatar.
     
    .DESCRIPTION
    Creates a new Avatar. An avatar is typically an image of a user.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Image
    The URL of an image to show in the avatar.
     
    .PARAMETER Alt
    The alt text to assign to the avatar.
     
    .PARAMETER ClassName
    Classes to assign to the avatar component.
     
    .PARAMETER Variant
    The variant type of the avatar. Valid values are: "square", "rounded"
 
    .PARAMETER Sx
    The sx property allows you to define custom CSS styles that will be applied to the component.
 
    .PARAMETER Children
    The content to display within the avatar.
     
    .EXAMPLE
    PS > New-UDAvatar -Alt "Remy Sharp" -Image "/admin/logo.png"
 
    Basic avatar|Displays a basic avatar with an image.
 
    .EXAMPLE
    PS > New-UDAvatar -Alt "Remy Sharp" -Content { "B" } -Variant square
 
    Square avatar|Displays a square avatar with an image.
 
    .EXAMPLE
    PS > New-UDAvatar -Alt "Remy Sharp" -Content {
    PS > "A"
    PS > } -Sx @{
    PS > borderRadius = '50%'
    PS > }
 
    Avatar with text|Displays an avatar with text and custom CSS styles.
 
    .EXAMPLE
    PS > New-UDAvatarGroup -Content {
    PS > 1..10 | ForEach-Object {
    PS > New-UDAvatar -Alt "Remy Sharp" -Content {
    PS > "A"
    PS > } -Sx @{
    PS > borderRadius = '50%'
    PS > backgroundColor = 'error.dark'
    PS > }
    PS > }
    PS > } -Sx @{
    PS > width = "20%"
    PS > }
 
    Avatar group|Displays a group of avatars.
    #>

    [Category("app/component")]
    [Description("An avatar is typically an image of a user. ")]
    [DisplayName("Avatar")]
    param(
        [Parameter ()][string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()][string]$Image,
        [Parameter ()][string]$Alt,
        [Parameter ()][string]$ClassName,
        
        [Parameter ()]
        [ValidateSet("square", 'rounded')]
        [string]$Variant,
        [Parameter()]
        $Sx,
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children = { "A" }
    )
    End {
        $Avatar = @{
            type      = 'mu-avatar'
            isPlugin  = $true
            assetId   = $MUAssetId

            id        = $Id
            image     = $Image
            alt       = $Alt
            variant   = $Variant.ToLower()
            className = $ClassName
            sx        = $Sx 
            children  = if ($Children) { & $Children }
        }
        $Avatar.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.Avatar") | Out-Null
        $Avatar
    }
}

function New-UDAvatarGroup {
    <#
    .SYNOPSIS
    A group of avatars.
     
    .DESCRIPTION
    A group of avatars.
     
    .PARAMETER Id
    The ID for this component.
     
    .PARAMETER Total
    If you need to control the total number of avatars not shown, you can use the total prop.
     
    .PARAMETER Maximum
    AvatarGroup renders its children as a stack. Use the max prop to limit the number of avatars.
     
    .PARAMETER Children
    Avatars for the group.
     
    .EXAMPLE
    An example
     
    .NOTES
    General notes
    #>

    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [int]$Total,
        [Parameter()]
        [int]$Maximum,
        [Parameter(Mandatory)]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        $Sx
    )

    $Avatar = @{
        type     = 'mu-avatar-group'
        isPlugin = $true
        assetId  = $MUAssetId

        id       = $Id
        sx       = $Sx
        children = & $Children 
    }

    if ($PSBoundParameters.ContainsKey("Total")) {
        $Avatar['total'] = $Total
    }

    if ($PSBoundParameters.ContainsKey("Maximum")) {
        $Avatar['max'] = $Maximum
    }

    $Avatar
}
function New-UDBackdrop {
    <#
    .SYNOPSIS
    Creates an overlay over the current page.
     
    .DESCRIPTION
    Creates an overlay over the current page.
     
    .PARAMETER Id
    The ID of this component
     
    .PARAMETER Color
    The color of the backdrop.
     
    .PARAMETER Children
    Child components to include in the backdrop.
     
    .PARAMETER Open
    Whether the backdrop is open.
     
    .PARAMETER OnClick
    A script block to invoke when the backdrop is clicked.
     
    .PARAMETER ClassName
    A CSS class to apply to the backdrop.
 
    .EXAMPLE
    PS > New-UDBackdrop -Color '#fff' -Children {
    PS > New-UDTypography -Text 'Hello World'
    PS > } -Open -OnClick {
    PS > Show-UDToast -Message 'Backdrop clicked'
    PS > Set-UDElement -Id 'backdrop1' -Properties @{
    PS > open = $false
    PS > }
    PS > } -Id 'backdrop1'
 
    Basic Backdrop|Creates a basic backdrop.
 
    .EXAMPLE
    PS > New-UDBackdrop -Children {
    PS > New-UDTypography -Text 'Hello World'
    PS > } -OnClick {
    PS > Set-UDElement -Id 'backdrop2' -Properties @{
    PS > open = $false
    PS > }
    PS > } -Id 'backdrop2'
    PS > New-UDButton -Text 'Open Backdrop' -OnClick {
    PS > Set-UDElement -Id 'backdrop2' -Properties @{
    PS > open = $true
    PS > }
    PS > }
 
    Dynamically Open Backdrop|Creates a backdrop that can be opened with a button.
 
    #>

    #[Component("Backdrop", "BoothCurtain", "Creates a new backdrop.")]
    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [DashboardColor]$Color = '#fff', 
        [Parameter(Mandatory)]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        [Switch]$Open,
        [Parameter()]
        [Endpoint]$OnClick,
        [Parameter()]
        [string]$ClassName
    )

    if ($OnClick) {
        $OnClick.Register($Id, $PSCmdlet)
    }

    @{
        type      = 'mu-backdrop'
        isPlugin  = $true
        assetId   = $MUAssetId

        id        = $Id
        color     = $Color.HtmlColor 
        children  = & $Children
        open      = $Open.IsPresent
        onClick   = $OnClick
        className = $ClassName
    }
}
function New-UDBadge {
    <#
    .SYNOPSIS
    Badge generates a small badge to the top-right of its child(ren).
     
    .DESCRIPTION
    Badge generates a small badge to the top-right of its child(ren).
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Color
    The color of the badge. Valid values are: "default", "primary", "secondary", "error", "info", "success", "warning"
     
    .PARAMETER Children
    The content that the badge is attached to.
     
    .PARAMETER BadgeContent
    The content to display within the badge.
     
    .PARAMETER Invisible
    Whether the badge is invisible.
     
    .PARAMETER Max
    The maximum number to display. If the number is greater than this, it will display as "99+". Default is 99.
     
    .PARAMETER Overlap
    The overlap of the badge. Valid values are: "circular", "rectangular"
     
    .PARAMETER ShowZero
    Whether to show the badge when the badgeContent is 0.
     
    .PARAMETER Variant
    The variant of the badge. Valid values are: "standard", "dot"
     
    .PARAMETER Location
    The location of the badge. Valid values are: "topright", "topleft", "bottomright", "bottomleft"
     
    .EXAMPLE
    PS > New-UDBadge -Color primary -BadgeContent { 4 } -Children {
    PS > New-UDButton -Text "Primary"
    PS > } -Id 'badge1'
 
    Primary badge|Displays a badge with the text "4" on a primary button.
 
    .EXAMPLE
    PS > New-UDBadge -Color secondary -BadgeContent { 4 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge2'
    PS > New-UDBadge -Color error -BadgeContent { 4 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge3'
     
    Color|Changes the color of the badge.
 
    .EXAMPLE
    PS > New-UDBadge -ShowZero -BadgeContent { 0 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge4'
 
    ShowZero|Shows the badge when the badgeContent is 0.
 
    .EXAMPLE
    PS > New-UDBadge -Max 10 -BadgeContent { 11 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge5'
 
    Max|The maximum number to display. If the number is greater than this, it will display as "99+". Default is 99.
 
    .EXAMPLE
    PS > New-UDBadge -Overlap circular -BadgeContent { 4 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge6'
 
    Overlap|The overlap of the badge.
 
    .EXAMPLE
    PS > New-UDBadge -Variant dot -BadgeContent { 4 } -Children {
    PS > New-UDIcon -Icon Envelope -Size 2x
    PS > } -Id 'badge7'
 
    Dot|Display a dot badge.
 
    .NOTES
    General notes
    #>

    #[Component("Badge", "Badge", "Creates a new badge.")]
    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [ValidateSet("default", 'primary', 'secondary', 'error', 'info', 'success', 'warning')]
        [string]$Color = 'primary', 
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children = {},
        [Parameter()]
        [scriptblock]$BadgeContent = {},
        [Parameter()]
        [Switch]$Invisible,
        [Parameter()]
        [int]$Max = 99,
        [Parameter()]
        [ValidateSet('circular', 'rectangular')]
        [string]$Overlap = 'rectangular',
        [Parameter()]
        [Switch]$ShowZero,
        [Parameter()]
        [ValidateSet('standard', 'dot')]
        [string]$Variant = 'standard',
        [Parameter()]
        [ValidateSet('topright', 'topleft', 'bottomright', 'bottomleft')]
        [string]$Location = 'topright'
    )

    @{
        type         = 'mu-badge'
        isPlugin     = $true
        assetId      = $MUAssetId
        id           = $Id
        color        = $Color.ToLower()
        children     = & $Children
        badgeContent = & $BadgeContent
        invisible    = $Invisible.IsPresent
        max          = $Max 
        overlap      = $Overlap.ToLower()
        showZero     = $ShowZero.IsPresent
        variant      = $Variant.ToLower()
        location     = $Location.ToLower()
    }
}
function New-UDBreadcrumbs {
    <#
    .SYNOPSIS
    Breadcrumbs consist of a list of links that help a user visualize a page's location within the hierarchical structure of a website, and allow navigation up to any of its "ancestors".
     
    .DESCRIPTION
    Breadcrumbs consist of a list of links that help a user visualize a page's location within the hierarchical structure of a website, and allow navigation up to any of its "ancestors".
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    The components to link.
     
    .PARAMETER Seperator
    The seperator between each breadcrumb.
     
    .PARAMETER MaxItems
    The maximum number of items to display.
     
    .EXAMPLE
    PS > New-UDBreadcrumbs -Content {
    PS > New-UDLink -Url "https://www.google.com" -Text "Google"
    PS > New-UDLink -Url "https://www.google.com" -Text "Google"
    PS > New-UDLink -Url "https://www.google.com" -Text "Google"
    PS > }
 
    Breadcumbs|A basic breadcrumb component.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter(Mandatory)]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        [string]$Seperator = "/",
        [Parameter()]
        [int]$MaxItems
    )

    @{
        type      = 'mu-breadcrumbs'
        isPlugin  = $true
        assetId   = $MUAssetId

        id        = $Id
        seperator = $Seperator
        maxItems  = if ($PSBoundParameters.ContainsKey("MaxItems")) { $MaxItems } else { $null }
        children  = & $Children
    }
}
function New-UDButtonGroup {
    <#
    .SYNOPSIS
    Creates a new button group.
     
    .DESCRIPTION
    Creates a button group. Use New-UDButtonGroupItem to add new items to the group.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    The items in the group.
 
    .PARAMETER ClassName
    A CSS class to apply to the button group.
 
    .PARAMETER Color
    The color of the button group. Valid values are: "primary", "secondary", "success", "warning", "error", "success", "info"
 
    .PARAMETER Disabled
    Disables the button group.
 
    .PARAMETER FullWidth
    Makes the button group full width.
 
    .PARAMETER Orientation
    The orientation of the button group. Valid values are: "horizontal", "vertical"
 
    .PARAMETER Size
    The size of the button group. Valid values are: "small", "medium", "large"
 
    .PARAMETER Sx
    An hashtable of styles to apply to this component.
 
    .PARAMETER Variant
    The variant of the button group. Valid values are: "contained", "outlined", "text"
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > }
 
    Basic button group|The button group offers two items.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Color 'success'
 
    Color|You can use the -Color parameter to change the color of the button group.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Orientation 'vertical'
 
    Vertical|You can use the -Orientation parameter to change the orientation of the button group.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Size 'small'
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Size 'medium'
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Size 'large'
 
    Size|You can use the -Size parameter to change the size of the button group.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Variant 'contained'
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Variant 'outlined'
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Variant 'text'
 
    Variant|You can use the -Variant parameter to change the variant of the button group.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -Disabled
 
    Disabled|You can use the -Disabled parameter to disable the button group.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete'
    PS > New-UDButtonGroupItem -Text 'Copy'
    PS > } -FullWidth
 
    FullWidth|You can use the -FullWidth parameter to make the button group full width.
 
    .EXAMPLE
    PS > New-UDButtonGroup -Content {
    PS > New-UDButtonGroupItem -Text 'Delete' -Icon (New-UDIcon -Icon 'trash')
    PS > New-UDButtonGroupItem -Text 'Copy' -Icon (New-UDIcon -Icon 'copy')
    PS > }
 
    Icon|You can use the -Icon parameter to add an icon to the button group item.
    #>

    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter()]
        [string]$ClassName,
        [ValidateSet('primary', 'secondary', 'success', 'warning', 'error', 'success', 'info')]
        [Parameter()]$Color = "primary",
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [Switch]$FullWidth, 
        [Parameter()]
        [ValidateSet('horizontal', 'vertical')]
        [string]$Orientation = "horizontal",
        [Parameter()]
        [ValidateSet('small', 'medium', 'large')]
        [string]$Size = "medium",
        [Parameter()]
        [Hashtable]$Sx,
        [Parameter()]
        [ValidateSet('contained', 'outlined', 'text')]
        [string]$Variant = 'outlined'
 
    )
    End {
        try {
            $c = & $Children
        }
        catch {
            $c = New-UDError -Message $_
        }

        @{
            type        = 'mu-button-group'
            isPlugin    = $true
            assetId     = $MUAssetId

            id          = $Id
            children    = $c            

            className   = $ClassName
            color       = if ($Color) { $Color.ToLower() } else { $null }
            disabled    = $Disabled.IsPresent
            fullWidth   = $FullWidth.IsPresent
            orientation = $Orientation.ToLower()
            size        = $Size.ToLower()
            sx          = $Sx
            variant     = $Variant.ToLower()
        }
    }
}

function New-UDButtonGroupItem {
    <#
    .SYNOPSIS
    Creates a new button group item.
     
    .DESCRIPTION
    Creates a new button group item. button group items are used with New-UDButtonGroup.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
         
    .PARAMETER OnClick
    A script block to execute when the list item is clicked.
    
    .PARAMETER Href
    A URL that the user should be redirected to when clicking the button.
 
    .PARAMETER Text
    The text to display in the button group item.
 
    .PARAMETER Icon
    An icon to display in the button group item.
     
    .EXAMPLE
    Creates a new button group with two items.
 
    New-UDButtonGroup -Content {
            New-UDButtonGroupItem -Text 'Delete'
            New-UDButtonGroupItem -Text 'Copy'
    }
    #>

    [CmdletBinding()]
    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()]
        [Endpoint]$OnClick, 
        [Parameter ()]
        [string]$Text,
        [Parameter ()]
        [string]$Href,
        [Parameter ()]
        [Hashtable]$Icon
    )
    begin {}
    Process {}
    End {
        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        if ($Color -eq 'default') {
            $Color = 'primary'
        }
        
        @{
            type     = 'mu-button-group-item'
            isPlugin = $true
            assetId  = $MUAssetId

            id       = $Id
            text     = $Text
            onClick  = $OnClick
            href     = $Href
            color    = if ($Color) { $Color.ToLower() } else { $null }   
            icon     = $Icon         
        }
    }
}
function New-UDButton {
    <#
    .SYNOPSIS
    Buttons allow users to take actions, and make choices, with a single tap..
     
    .DESCRIPTION
    Creates a new button. Buttons allow users to take actions, and make choices, with a single tap.
     
    .PARAMETER Text
    The text to show within the button.
     
    .PARAMETER Icon
    An icon to show within the button. Use New-UDIcon to create an icon for this parameter.
     
    .PARAMETER Variant
    The variant type for this button. Valid values are: "text", "outlined", "contained"
     
    .PARAMETER IconAlignment
    How to align the icon within the button. Valid values are: "left", "right"
     
    .PARAMETER FullWidth
    Whether the button takes the full width of the it's container.
     
    .PARAMETER OnClick
    The action to take when the button is clicked.
     
    .PARAMETER Size
    The size of the button. Valid values are: "small", "medium", "large"
     
    .PARAMETER Style
    Styles to apply to the button.
     
    .PARAMETER Href
    A URL that the user should be redirected to when clicking the button.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
 
    .PARAMETER Color
    The color of the component. Valid values are: 'default', 'inherit', 'primary', 'secondary', 'info', 'warning', 'error', 'success'
 
    .PARAMETER Disabled
    Whether the button is disabled.
 
    .PARAMETER ClassName
    The CSS Class to apply to the button.
 
    .PARAMETER ShowLoading
    Displays a loading spinner while the OnClick event handler is running.
 
    .PARAMETER LoadingIndicator
    A custom element to display instead of the built in spinner for -ShowLoading
 
    .PARAMETER LoadingPosition
    The position of the loading indicator. Valid values are: 'start', 'end', 'center'
     
    .EXAMPLE
    PS > New-UDButton -Variant 'text' -Text 'Text' -Id 'button1'
    PS > New-UDButton -Variant 'contained' -Text 'Contained' -Id 'button2'
    PS > New-UDButton -Variant 'outlined' -Text 'Outlined' -Id 'button3'
 
    Basic buttons|UDButton comes with three variants: text, contained, and outlined.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Click me' -OnClick {
    PS > Show-UDToast -Message 'Hello, world!'
    PS > } -Id 'button4'
 
    Handling clicks|PowerShell that is executed when the button is clicked.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Secondary' -Color secondary -Id 'button5'
    PS > New-UDButton -Text 'Success' -Color success -Id 'button6'
    PS > New-UDButton -Text 'Error' -Color error -Id 'button7'
 
    Color|Adjust the button color with -Color
 
    .EXAMPLE
    PS > New-UDButton -Variant 'text' -Text 'small' -Id 'button8' -Size small
    PS > New-UDButton -Variant 'text' -Text 'medium' -Id 'button9' -Size medium
    PS > New-UDButton -Variant 'text' -Text 'large' -Id 'button10' -Size large
    PS > New-UDButton -Variant 'contained' -Text 'small' -Id 'button11' -Size small
    PS > New-UDButton -Variant 'contained' -Text 'medium' -Id 'button12' -Size medium
    PS > New-UDButton -Variant 'contained' -Text 'large' -Id 'button13' -Size large
    PS > New-UDButton -Variant 'outlined' -Text 'small' -Id 'button14' -Size small
    PS > New-UDButton -Variant 'outlined' -Text 'medium' -Id 'button15' -Size medium
    PS > New-UDButton -Variant 'outlined' -Text 'large' -Id 'button16' -Size large
 
    Sizes|For larger or smaller buttons, use the -Size parameter.
 
    .EXAMPLE
    PS > New-UDButton -Icon (New-UDIcon -Icon 'User') -Text 'View User' -Id 'button17'
 
    Icon|Display an icon within the button
 
    .EXAMPLE
    PS > New-UDButton -OnClick { Start-Sleep 3 } -ShowLoading -Text 'Load Data' -Id 'button18'
 
    Loading|Show a loading indicator while the OnClick event handler is running.
    #>

    [Category("app/component")]
    [Description("Buttons allow users to take actions, and make choices, with a single tap.")]
    [DisplayName("Button")]
    param
    (
        [Parameter (Position = 0)]
        [string]$Text = "",

        [Parameter (Position = 1)]
        $Icon,

        [Parameter (Position = 2)]
        [ValidateSet("text", "outlined", "contained")]
        [string]$Variant = "contained",

        [Parameter (Position = 3)]
        [ValidateSet("left", "right")]
        [string]$IconAlignment = "left",

        [Parameter (Position = 6)]
        [switch]$FullWidth,

        [Parameter (Position = 7)]
        [Endpoint]$OnClick,

        [Parameter (Position = 8)]
        [ValidateSet("small", "medium", "large")]
        [string]$Size,

        [Parameter (Position = 9)]
        [Hashtable]$Style,

        [Parameter (Position = 10)]
        [string]$Href,

        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [ValidateSet('default', 'inherit', 'primary', 'secondary', 'success', 'error', 'info', 'warning')]
        [string]$Color = 'primary',

        [Parameter()]
        [Switch]$Disabled,

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [Switch]$ShowLoading,

        [Parameter()]
        [object]$LoadingIndicator,

        [Parameter()]
        [ValidateSet('center', 'start', 'end')]
        [string]$LoadingPosition = "center"

    )

    End {

        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        if ($Color -eq 'default') {
            $Color = 'primary'
        }

        if ($Icon -is [string]) {
            $Icon = New-UDIcon -Icon $Icon
        }

        @{
            # Mandatory properties to load as plugin.
            type             = 'mu-button'
            isPlugin         = $true
            assetId          = $MUAssetId

            # Command properties.
            id               = $Id
            text             = $Text
            variant          = $Variant.ToLower()
            onClick          = $OnClick
            iconAlignment    = $IconAlignment
            disabled         = $Disabled.IsPresent
            icon             = $Icon
            fullWidth        = $FullWidth.IsPresent
            size             = $Size
            href             = $Href
            style            = $Style
            color            = if ($Color) { $Color.ToLower() } else { $null }
            className        = $ClassName
            showLoading      = $ShowLoading.IsPresent
            loadingIndicator = $LoadingIndicator
            loadingPosition  = $LoadingPosition.ToLower()
        }

    }
}
function New-UDCard {
    <#
    .SYNOPSIS
    Creates a new card.
     
    .DESCRIPTION
    Creates a new card. Cards are used to display related content.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER ClassName
    A CSS class to assign to this card.
     
    .PARAMETER ShowToolBar
    Whether to show the toolbar for this card.
     
    .PARAMETER ToolBar
    The toolbar for this card. Use New-UDCardToolbar to create a toolbar.
     
    .PARAMETER Header
    The header for this card. The header typically contains a title for the card. Use New-UDCardHeader to create a header.
     
    .PARAMETER Body
    The body for this card. This is the main content for the card. Use New-UDCardHeader to create a body.
     
    .PARAMETER Expand
    Th expand content for this card. Expand content is show when the user clicks the expansion button. Use New-UDCardExpand to create an expand.
     
    .PARAMETER Footer
    The footer for this card. Footer contents typically contain actions that are relavent to the card. Use New-UDCardFooter to create a footer.
     
    .PARAMETER Style
    Styles to apply to the card.
     
    .PARAMETER Elevation
    The amount of elevation to provide the card. The more elevation, the more it will appear the card is floating off the page.
     
    .PARAMETER Title
    A title for the card.
     
    .PARAMETER TitleAlignment
    The alignment for the title.
     
    .PARAMETER Content
    The content of the card.
     
    .PARAMETER Image
    An image to show in the card.
 
    .PARAMETER Sx
    Theme-based style to apply to the card.
 
    .PARAMETER OnClick
    The action to take when the card is clicked.
     
    .EXAMPLE
    PS > New-UDCard -Title 'My Card' -Text 'This is my card' -Id 'card1'
 
    Basic card|A basic card with a title and text.
 
    .EXAMPLE
    PS > New-UDCard -Title 'My Card' -Text 'This is my card' -Id 'card2' -Image 'https://picsum.photos/200/300'
 
    Card with image|A card with an image.
 
    .EXAMPLE
    PS > New-UDCard -Title 'My Card' -Text 'This is my card' -Id 'card3' -Elevation 10
 
    Card with elevation|A card with a higher elevation.
 
    .EXAMPLE
    PS > New-UDCard -Title 'My Card' -Text 'This is my card' -Id 'card4' -TitleAlignment center
 
    Title Alignment|A card with a centered title.
 
    .EXAMPLE
    PS > New-UDCard -Text 'This is my card' -OnClick {
    PS > Show-UDToast -Message 'You clicked the card'
    PS > } -Id 'card5'
 
    OnClick|A card with an OnClick event handler.
 
    .EXAMPLE
    PS > $Header = New-UDCardHeader -Avatar (New-UDAvatar -Content { "R" } -Sx @{ backgroundColor = "#f44336" }) -Action (New-UDIconButton -Icon (New-UDIcon -Icon 'EllipsisVertical')) -Title 'Shrimp and Chorizo Paella' -SubHeader 'September 14, 2016';
    PS > $Media = New-UDCardMedia -Image 'https://mui.com/static/images/cards/paella.jpg'
    PS > $Body = New-UDCardBody -Content {
    PS > New-UDTypography -Text ' This impressive paella is a perfect party dish and a fun meal to cook together with your guests. Add 1 cup of frozen peas along with the mussels, if you like.' -Sx @{
    PS > color = 'text.secondary'
    PS > } -Variant body2
    PS > }
    PS > $Footer = New-UDCardFooter -Content {
    PS > New-UDIconButton -Icon (New-UDIcon -Icon 'Heart')
    PS > New-UDIconButton -Icon (New-UDIcon -Icon 'ShareAlt')
    PS > }
    PS > $Expand = New-UDCardExpand -Content {
    PS > $Description = "Heat oil in a (14- to 16-inch) paella pan or a large, deep skillet over medium-high heat. Add chicken, shrimp and chorizo, and cook, stirring occasionally until lightly browned, 6 to 8 minutes. Transfer shrimp to a large plate and set aside, leaving chicken and chorizo in the pan. Add pimentón, bay leaves, garlic, tomatoes, onion, salt and pepper, and cook, stirring often until thickened and fragrant, about 10 minutes. Add saffron broth and remaining 4 1/2 cups chicken broth; bring to a boil."
    PS > New-UDTypography -Text $Description
    PS > }
    PS > New-UDCard -Header $Header -Media $Media -Body $Body -Footer $Footer -Expand $Expand -Sx @{
    PS > maxWidth = 345
    PS > border = '2px solid #f0f2f5'
    PS > } -Id 'card5'
 
    Complex Card|A card with all the features.
    #>

    [Category("app/component")]
    [Description("Cards are used to display related content.")]
    [DisplayName("Card")]
    [CmdletBinding(DefaultParameterSetName = "Text")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [string]$ClassName,

        [Parameter(ParameterSetName = "Advanced")]
        [PSTypeName('UniversalDashboard.MaterialUI.CardHeader')]$Header,

        [Parameter(ParameterSetName = "Advanced")]
        [PSTypeName('UniversalDashboard.MaterialUI.CardBody')]$Body,

        [Parameter(ParameterSetName = "Advanced")]
        [PSTypeName('UniversalDashboard.MaterialUI.CardExpand')]$Expand,

        [Parameter(ParameterSetName = "Advanced")]
        [PSTypeName('UniversalDashboard.MaterialUI.CardFooter')]$Footer,

        [Parameter(ParameterSetName = "Advanced")]
        [PSTypeName('UniversalDashboard.MaterialUI.CardMedia')]$Media,

        [Parameter()]
        [Hashtable]$Style,

        [Parameter()]
        [ValidateSet("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24")]
        [int]$Elevation,

        [Parameter(ParameterSetName = "Simple")]
        [Parameter(ParameterSetName = "Text")]
        [String]$Title,
        [Parameter(ParameterSetName = "Simple")]
        [Parameter(ParameterSetName = "Text")]
        [ValidateSet('left', 'center', 'right')]
        [String]$TitleAlignment = 'left',
        [Parameter(ParameterSetName = "Simple")]
        [ScriptBlock]$Content,
        [Parameter(ParameterSetName = "Simple")]
        [string]$Image,
        [Parameter(ParameterSetName = "Text")]
        [string]$Text,
        [Parameter()]
        [Switch]$Raised,
        [Parameter(ParameterSetName = "Simple")]
        $Avatar,
        [Parameter()]
        $Sx,
        [Parameter()]
        [ValidateSet("elevation", 'outlined')]
        [string]$Variant = 'elevation',
        [Parameter()]
        [Endpoint]$OnClick
    )

    End {

        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        if ($PSCMdlet.ParameterSetName -eq 'Advanced') {
            # Card Media checks
            if ($null -ne $Media) {
                if ($Media.psobject.typenames -notcontains "UniversalDashboard.MaterialUI.CardMedia") {
                    throw "Media must be a UniversalDashboard.MaterialUI.CardMedia object, please use New-UDCardMedia command."
                }
            }

            # Card header checks
            if ($null -ne $Header) {
                if ($Header.psobject.typenames -notcontains "UniversalDashboard.MaterialUI.CardHeader") {
                    throw "Header must be a UniversalDashboard.MaterialUI.CardHeader object, please use New-UDCardHeader command."
                }
            }

            # Card Content checks
            if ($null -ne $Content) {
                if ($Content.psobject.typenames -notcontains "UniversalDashboard.MaterialUI.CardBody") {
                    throw "Body must be a UniversalDashboard.MaterialUI.CardBody object, please use New-UDCardBody command."
                }
            }

            # Card Expand checks
            if ($null -ne $Expand) {
                if ($Expand.psobject.typenames -notcontains "UniversalDashboard.MaterialUI.CardExpand") {
                    throw "Expand must be a UniversalDashboard.MaterialUI.CardExpand object, please use New-UDCardExpand command."
                }
            }

            # Card footer checks
            if ($null -ne $Footer) {
                if ($Footer.psobject.typenames -notcontains "UniversalDashboard.MaterialUI.CardFooter") {
                    throw "Footer must be a UniversalDashboard.MaterialUI.CardFooter object, please use New-UDCardFooter command."
                }
            }
            
            $Parts = @{
                header = $Header
                body   = $Body
                expand = $Expand
                footer = $Footer
            }
            $Content = { $Parts }
        }
        else {
            $Header = New-UDCardHeader -Title $Title -TitleAlignment $TitleAlignment -Avatar $Avatar

            if ($Image) {
                $Media = New-UDCardMedia -Height 120 -Image $Image
            }

            if ($PSCmdlet.ParameterSetName -eq 'Text') {
                $Element = New-UDElement -Tag div -Content {
                    $Text -split "`r`n" | ForEach-Object {
                        $_
                        New-UDElement -Tag "br"
                    }
                }
                $Content = { $Element }
            }

            $Body = New-UDCardBody -Content $Content

            $Parts = @{
                header = $Header
                body   = $Body
                expand = $Expand
                footer = $Footer
            }
            $Content = { $Parts }
        }

        $Card = @{
            type        = "mu-card"
            isPlugin    = $true
            assetId     = $MUAssetId
            id          = $Id
            className   = $ClassName
            showToolBar = $ShowToolBar.IsPresent
            media       = $Media
            toolbar     = $ToolBar
            header      = $Header
            body        = $Body
            expand      = $Expand
            footer      = $Footer
            style       = $Style
            elevation   = $Elevation
            raised      = $Raised.IsPresent
            sx          = $sx
            variant     = $variant.ToLower()
            onClick     = $OnClick

        }

        $Card.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.Card") | Out-Null
        $Card
    }
}

function New-UDCardHeader {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [string]$Title,
        [Parameter()]
        [string]$SubHeader,
        [Parameter()]
        [ValidateSet('inherit', 'left', 'justify', 'right', 'center')]
        [string]$TitleAlignment = 'inherit',
        [Parameter()]
        [ValidateSet('inherit', 'left', 'justify', 'right', 'center')]
        [string]$SubHeaderAlignment = 'inherit',
        [Parameter()]
        $Avatar,
        [Parameter()]
        $Action
    )
    End {
        $Header = @{
            type               = "mu-card-header"
            isPlugin           = $true
            assetId            = $MUAssetId
            id                 = $Id
            title              = $Title
            subheader          = $SubHeader
            subHeaderAlignment = $SubHeaderAlignment
            titleAlignment     = $TitleAlignment.ToLower()
            avatar             = $Avatar
            action             = $Action

        }
        $Header.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.CardHeader") | Out-Null
        $Header
    }
}


function New-UDCardBody {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [scriptblock]$Content,

        [Parameter()]
        [Hashtable]$Style
    )
    End {

        $cContent = @{
            type      = "mu-card-body"
            isPlugin  = $true
            assetId   = $MUAssetId
            id        = $Id
            className = $ClassName
            content   = New-UDErrorBoundary -Content $Content
            style     = $Style
            # PSTypeName = "UniversalDashboard.MaterialUI.CardContent"

        }
        $cContent.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.CardBody") | Out-Null
        $cContent
    }
}


function New-UDCardExpand {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [scriptblock]$Content,

        [Parameter()]
        [Hashtable]$Style
    )
    End {
        $Expand = @{
            type            = "mu-card-expand"
            isPlugin        = $true
            assetId         = $MUAssetId
            id              = $Id
            className       = $ClassName
            content         = New-UDErrorBoundary -Content $Content
            style           = $Style
            isEndpoint      = $isEndPoint.IsPresent
            refreshInterval = $RefreshInterval
            autoRefresh     = $AutoRefresh.IsPresent
            # PSTypeName = "UniversalDashboard.MaterialUI.CardExpand"
        }
        $Expand.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.CardExpand") | Out-Null
        $Expand
    }
}


function New-UDCardFooter {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [scriptblock]$Content,

        [Parameter()]
        [Hashtable]$Style
    )
    End {
        $Footer = @{
            type            = "mu-card-footer"
            isPlugin        = $true
            assetId         = $MUAssetId
            id              = $Id
            className       = $ClassName
            content         = New-UDErrorBoundary -Content $Content
            style           = $Style
            isEndpoint      = $isEndPoint.IsPresent
            refreshInterval = $RefreshInterval
            autoRefresh     = $AutoRefresh.IsPresent
            # PSTypeName = "UniversalDashboard.MaterialUI.CardFooter"
        }
        $Footer.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.CardFooter") | Out-Null
        $Footer
    }
}
function New-UDCardMedia {
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [ValidateSet("img", "video", "audio")]       
        [string]$Component = "img",

        [Parameter()]
        [string]$Alt,

        [Parameter()]
        [string]$Height,

        [Parameter (ParameterSetName = 'image')]
        [string]$Image,

        [Parameter()]
        [string]$Title,

        [Parameter(ParameterSetName = 'media')]
        [string]$Source

    )
    End {
        $CardMedia = @{
            type      = "mu-card-media"
            isPlugin  = $true
            assetId   = $MUAssetId
            id        = $Id
            component = $Component
            alt       = $Alt
            height    = $Height
            image     = $Image
            title     = $Title
            source    = $Source
            # PSTypeName = "UniversalDashboard.MaterialUI.CardMedia"
        }
        $CardMedia.PSTypeNames.Insert(0, "UniversalDashboard.MaterialUI.CardMedia") | Out-Null
        $CardMedia
    }
}
function New-UDCheckBox {
    <#
    .SYNOPSIS
    Creates a checkbox.
 
    .DESCRIPTION
    Creates a checkbox. Checkboxes can be used in forms or by themselves.
 
    .PARAMETER Label
    The label to show next to the checkbox.
 
    .PARAMETER Icon
    The icon to show instead of the default icon. Use New-UDIcon to create an icon.
 
    .PARAMETER CheckedIcon
    The icon to show instead of the default checked icon. Use New-UDIcon to create an icon.
 
    .PARAMETER OnChange
    Called when the value of the checkbox changes. The $EventData variable will have the current value of the checkbox.
 
    .PARAMETER Style
    A hashtable of styles to apply to the checkbox.
 
    .PARAMETER Disabled
    Whether the checkbox is disabled.
 
    .PARAMETER Checked
    Whether the checkbox is checked.
 
    .PARAMETER ClassName
    A CSS class to assign to the checkbox.
 
    .PARAMETER LabelPlacement
    Where to place the label. Valid values are: "top","start","bottom","end"
 
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
 
    .PARAMETER Color
    The theme color to apply to this component
 
    .PARAMETER Size
    The size of the checkbox. Valid values are: "small", "medium"
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -Id 'checkbox1'
 
    Basic checkbox|A basic checkbox.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -Disabled -Id 'checkbox2'
 
    Disabled checkbox|A disabled checkbox.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -Checked $true -Id 'checkbox3'
 
    Checked checkbox|A checked checkbox.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -Icon (New-UDIcon -Icon 'user') -CheckedIcon (New-UDIcon -Icon 'check') -Id 'checkbox4'
 
    Checkbox with custom icons|A checkbox with custom icons.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -OnChange {
    PS > Show-UDToast -Title 'Checkbox' -Message $Body
    PS > } -Id 'checkbox5'
 
    Checkbox with OnChange|A checkbox with an OnChange event.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -LabelPlacement 'top' -Id 'checkbox6'
 
    Checkbox with label on top|A checkbox with the label on top.
 
    .EXAMPLE
    PS > New-UDCheckbox -Label 'Demo' -Color 'secondary' -Id 'checkbox7'
 
    Checkbox with secondary color|A checkbox with the secondary color.
    #>

    [Category("app/component")]
    [Description("Toggles true or false states. Checkboxes can be used in forms or by themselves. ")]
    [DisplayName("Checkbox")]
    param
    (
        [Parameter (Position = 0)]
        [string]$Label,

        [Parameter (Position = 1)]
        $Icon,

        [Parameter (Position = 2)]
        $CheckedIcon,

        [Parameter (Position = 3)]
        [Endpoint]$OnChange,

        [Parameter (Position = 4)]
        [Hashtable]$Style,

        [Parameter (Position = 5)]
        [switch]$Disabled,

        [Parameter (Position = 6)]
        [bool]$Checked,

        [Parameter (Position = 7)]
        [string]$ClassName,

        [Parameter (Position = 7)]
        [ValidateSet("top", "start", "bottom", "end")]
        [string]$LabelPlacement,

        [Parameter(Position = 8)]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter (Position = 9)]
        [ValidateSet("small", "medium")]
        [string]$Size = 'medium',

        [Parameter ()]
        [ValidateSet('default', 'primary', 'secondary')]
        [string]$Color = 'default'

    )

    End {

        if ($Icon -is [string]) {
            $Icon = New-UDIcon -Icon $Icon
        }

        if ($CheckedIcon -is [string]) {
            $CheckedIcon = New-UDIcon -Icon $CheckedIcon
        }

        if ($OnChange) {
            $OnChange.Register($Id + "onChange", $PSCmdlet)
        }

        @{
            # Mandatory properties to load as plugin.
            type           = 'mu-checkbox'
            isPlugin       = $true
            assetId        = $MUAssetId

            # Command properties.
            id             = $Id
            className      = $ClassName
            checked        = $Checked
            onChange       = $OnChange
            icon           = $Icon
            checkedIcon    = $CheckedIcon
            disabled       = $Disabled.IsPresent
            style          = $Style
            label          = $Label
            labelPlacement = $LabelPlacement
            color          = $Color.ToLower()
            size           = $Size.ToLower()
        }
    }
}
function New-UDChip {
    <#
    .SYNOPSIS
    Chips are compact elements that represent an input, attribute, or action.
     
    .DESCRIPTION
    Chips are compact elements that represent an input, attribute, or action.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label for the chip.
     
    .PARAMETER OnDelete
    A script block to call when the chip is deleted.
     
    .PARAMETER OnClick
    A script block to call when the chip is clicked.
     
    .PARAMETER Icon
    An icon to show within the chip.
     
    .PARAMETER Style
    CSS styles to apply to the chip.
     
    .PARAMETER Variant
    The theme variant to apply to the chip.
     
    .PARAMETER Avatar
    An avatar to show within the chip.
     
    .PARAMETER AvatarType
    The type of avatar to show in the chip.
     
    .PARAMETER ClassName
    A CSS class to apply to the chip.
     
    .PARAMETER Color
    The color of the chip. Defaults to 'default'. Valid values: "default", "primary", "secondary"
 
    .PARAMETER Size
    The size of the chip. Valid values are: "small", "medium"
     
    .EXAMPLE
    PS > New-UDChip -Label 'Basic' -Icon (New-UDIcon -Icon 'user') -Id 'chip1'
 
    Basic chip|A basic chip with an icon.
 
    .EXAMPLE
 
    PS > New-UDChip -Variant outlined -Label 'Outlined' -Icon (New-UDIcon -Icon 'user') -Id 'chip2'
 
    Outlined chip|An outlined chip with an icon.
 
    .EXAMPLE
    PS > New-UDChip -Label 'Clickable' -Icon (New-UDIcon -Icon 'user') -OnClick {
    PS > Show-UDToast -Message 'Hello!'
    PS > } -Id 'chip3'
 
    Clickable chip|A clickable chip with an icon.
 
    .EXAMPLE
    PS > New-UDChip -Label 'Deletable' -Icon (New-UDIcon -Icon 'user') -OnDelete {
    PS > Show-UDToast -Message 'Deleted!'
    PS > } -Id 'chip4'
 
    Deletable chip|A deletable chip with an icon.
 
    .EXAMPLE
    PS > New-UDChip -Label 'Clickable and deletable' -Icon (New-UDIcon -Icon 'user') -OnClick {
    PS > Show-UDToast -Message 'Hello!'
    PS > } -OnDelete {
    PS > Show-UDToast -Message 'Deleted!'
    PS > } -Id 'chip5'
 
    Clickable and deletable chip|A clickable and deletable chip with an icon.
 
    .EXAMPLE
    PS > New-UDChip -Avatar "A" -AvatarType letter -Label 'Avatar' -Id 'chip6'
 
    Avatar chip|A chip with an avatar.
 
    .EXAMPLE
    PS > New-UDChip -Avatar "https://picsum.photos/200/300" -AvatarType image -Label 'Avatar' -Id 'chip7'
 
    Image chip|A chip with an avatar image.
 
    .EXAMPLE
    PS > New-UDChip -Label 'Small' -Icon (New-UDIcon -Icon 'user') -Size small -Id 'chip8'
 
    Small chip|A small chip with an icon.
 
    .EXAMPLE
    PS > New-UDChip -Style @{background = 'red'; color = 'white'} -Label 'Styled' -Icon (New-UDIcon -Icon 'user') -Id 'chip9'
 
    #>

    [CmdletBinding(DefaultParameterSetName = 'Icon')]
    [Category("app/component")]
    [Description("Chips are compact elements that represent an input, attribute, or action.")]
    [DisplayName("Chip")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter(Position = 0)]
        [string]$Label,

        [Parameter(Position = 8)]
        [Endpoint]$OnDelete,

        [Parameter(Position = 7)]
        [Endpoint]$OnClick,

        [Parameter (Position = 1, ParameterSetName = "Icon")]
        $Icon,

        [Parameter(Position = 2)]
        [Hashtable]$Style,

        [Parameter(Position = 3)]
        [ValidateSet("outlined", "default")]
        [string]$Variant = "default",

        [Parameter(Position = 4, ParameterSetName = "Avatar")]
        [string]$Avatar,

        [Parameter(Position = 5, ParameterSetName = "Avatar" )]
        [ValidateSet("letter", "image")]
        [string]$AvatarType,

        [Parameter (Position = 9)]
        [ValidateSet("small", "medium")]
        [string]$Size = 'medium',

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [ValidateSet("default", "primary", "secondary", "error", "info", "success", "warning")]
        [string]$Color = 'default'
    )

    End {

        if ($OnClick) {
            $OnClick.Register($Id + "OnClick", $PSCmdlet)
        }

        if ($OnDelete) {
            $OnDelete.Register($Id + "OnDelete", $PSCmdlet)
        }

        if ($Icon -is [string]) {
            $Icon = New-UDIcon -Icon $Icon
        }

        @{
            #This needs to match what is in the register function call of chips.jsx
            type       = "mu-chip"
            #Eventually everything will be a plugin so we wont need this.
            isPlugin   = $true
            #This was set in the UniversalDashboard.MaterialUI.psm1 file
            assetId    = $MUAssetId

            id         = $Id
            label      = $Label
            icon       = $Icon 
            style      = $Style 
            variant    = $Variant 
            onClick    = $OnClick
            onDelete   = $OnDelete
            avatar     = $Avatar
            avatarType = $AvatarType
            className  = $ClassName
            color      = $Color.ToLower()
            size       = $Size.ToLower()
        }
    }
}


function New-UDCodeEditor {
    <#
    .SYNOPSIS
    Creates a new Monaco code editor control.
     
    .DESCRIPTION
    Creates a new Monaco code editor control.
     
    .PARAMETER Id
    The ID of this editor
     
    .PARAMETER Language
    The language to use for syntax highlighting.
     
    .PARAMETER Height
    The height of the editor.
     
    .PARAMETER Width
    The width of the editor.
     
    .PARAMETER HideCodeLens
    Hides code lens within the editor.
     
    .PARAMETER DisableCodeFolding
    Disables code folding.
     
    .PARAMETER FormatOnPaste
    Formats on paste.
     
    .PARAMETER GlyphMargin
    Seconds the size of the glyph margin
     
    .PARAMETER DisableLineNumbers
    Disables line numbers
     
    .PARAMETER DisableLinks
    Disables automatically highlighting links.
     
    .PARAMETER DisableBracketMatching
    Disables bracket matching.
     
    .PARAMETER MouseWheelScrollSensitivity
    Sets the mouse wheel scroll sensitivity.
     
    .PARAMETER MouseWheelZoom
    Enables Ctrl+Scroll zooming.
     
    .PARAMETER ReadOnly
    Sets the editor to readonly.
     
    .PARAMETER RenderControlCharacters
    Enables rendering of control characters.
     
    .PARAMETER ShowFoldingControls
    Controls how to show the folding controls.
     
    .PARAMETER SmoothScrolling
    Enables smooth scrolling.
     
    .PARAMETER Theme
    Selects the theme. The default is the 'vs' theme.
     
    .PARAMETER Code
    The code to show in the editor.
 
    .PARAMETER LightTheme
    The light theme for the editor.
 
    .PARAMETER DarkTheme
    The dark theme for the editor.
 
    .PARAMETER Original
    The original code to compare against.
 
    .PARAMETER Options
    A hashtable of options to apply to the editor.
 
    .PARAMETER CanSave
    Whether the code can be saved.
 
    .PARAMETER Extension
    The extension of the file.
     
    .EXAMPLE
    PS > New-UDCodeEditor -Code 'Get-Process' -Theme 'vs-dark' -Language 'powershell' -Id 'codeEditor1'
 
    Basic code editor|Creates a PowerShell code editor
 
    .EXAMPLE
    PS > New-UDCodeEditor -Code 'Get-Process' -Theme 'vs-dark' -Language 'powershell' -Height '200' -Width '100%' -ReadOnly -Id 'codeEditor2'
 
    Read only code editor|Creates a read only PowerShell code editor
 
    .EXAMPLE
    PS > New-UDCodeEditor -Code "Get-Process notepad" -Original "Get-Process code" -Theme 'vs-dark' -Language 'powershell' -Height '200' -Width '100%' -ReadOnly -Id 'codeEditor3'
 
    Diff code editor|Creates a read only PowerShell code editor with diff enabled
    #>

    
    [CmdletBinding(DefaultParameterSetName = "Standard")]
    [Category("app/component")]
    [Description("The Monaco code editor control is used to display and edit code in a variety of languages.")]
    [DisplayName("Code Editor")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ValidateSet('apex', 'azcli', 'bat', 'clojure', 'coffee', 'cpp', 'csharp', 'csp', 'css', 'dockerfile', 'fsharp', 'go', 'handlebars', 'html', 'ini', 'java', 'javascript', 'json', 'less', 'lua', 'markdown', 'msdax', 'mysql', 'objective', 'perl', 'pgsql', 'php', 'postiats', 'powerquery', 'powershell', 'pug', 'python', 'r', 'razor', 'redis', 'redshift', 'ruby', 'rust', 'sb', 'scheme', 'scss', 'shell', 'solidity', 'sql', 'st', 'swift', 'typescript', 'vb', 'xml', 'yaml')]
        [string]$Language,
        [Parameter()]
        [string]$Height = '100ch',
        [Parameter()]
        [string]$Width = '100%',
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$HideCodeLens,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$DisableCodeFolding,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$FormatOnPaste,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$GlyphMargin,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$DisableLineNumbers,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$DisableLinks,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$DisableBracketMatching,
        [Parameter(ParameterSetName = 'Standard')]
        [int]$MouseWheelScrollSensitivity = 1,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$MouseWheelZoom,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$ReadOnly,
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$RenderControlCharacters,
        [Parameter(ParameterSetName = 'Standard')]
        [ValidateSet("always", "mouseover")]
        [string]$ShowFoldingControls = "mouseover",
        [Parameter(ParameterSetName = 'Standard')]
        [Switch]$SmoothScrolling,
        [Parameter(ParameterSetName = 'Standard')]
        [ValidateSet("vs", "vs-dark", "hc-black")]
        [Alias("Theme")]
        [string]$LightTheme = 'vs',
        [Parameter(ParameterSetName = 'Standard')]
        [ValidateSet("vs", "vs-dark", "hc-black")]
        [string]$DarkTheme = 'vs-dark',
        [Parameter()]
        [string]$Code,
        [Parameter()]
        [string]$Original,
        [Parameter(ParameterSetName = 'Options')]
        [Hashtable]$Options = @{},
        [Parameter()]
        [Switch]$CanSave,
        [Parameter()]
        [String]$Extension = 'txt'
    )

    End {
        if ($PSCmdlet.ParameterSetName -eq 'Options') {
            $Options["assetId"] = $AssetId
            $Options["isPlugin"] = $true 
            $Options["type"] = "ud-monaco"
            $Options["id"] = $Id 
            $Options["height"] = $Height 
            $Options["width"] = $Width 
            $Options["language"] = $Language 
            $Options["code"] = $code 
            $Options["original"] = $original 

            return $Options
        }

        @{
            assetId                     = $AssetId 
            isPlugin                    = $true 
            type                        = "ud-monaco"
            id                          = $Id

            height                      = $Height
            width                       = $Width
            language                    = $Language 
            codeLens                    = -not $HideCodeLens.IsPresent
            folding                     = -not $DisableCodeFolding.IsPresent
            formatOnPaste               = $FormatOnPaste.IsPresent
            glyphMargin                 = $GlyphMargin.IsPresent
            lineNumbers                 = if ($DisableLineNumbers.IsPresent) { "off" } else { "on" }
            links                       = -not $DisableLinks.IsPresent
            matchBrackets               = -not $DisableBracketMatching.IsPresent
            mouseWheelScrollSensitivity = $MouseWheelScrollSensitivity
            mouseWheelZoom              = $MouseWheelZoom.IsPresent
            readOnly                    = $ReadOnly.IsPresent
            renderControlCharacters     = $RenderControlCharacters.IsPresent
            showFoldingControls         = $ShowFoldingControls
            smoothScrolling             = $SmoothScrolling.IsPresent
            lightTheme                  = $LightTheme
            darkTheme                   = $DarkTheme
            code                        = $Code
            original                    = $Original
            canSave                     = $CanSave.IsPresent
            extension                   = $Extension
        }
    }
}

function New-UDContainer {
    <#
    .SYNOPSIS
    Containers pad the left and right side of the contained content to center it on larger resolution screens.
     
    .DESCRIPTION
    Creates a Material UI container. Containers pad the left and right side of the contained content to center it on larger resolution screens.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Children
    The child items to include within the container.
     
    .PARAMETER ClassName
    A CSS class to apply to the container.
     
    .EXAMPLE
    PS > New-UDContainer -Content {
    PS > New-UDPaper -Content { } -Elevation 3
    PS > }
 
    Basic Container|Creates a basic container with a typography component inside of it.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Alias("Content")]
        [Parameter(Mandatory, Position = 0)]
        [ScriptBlock]$Children,

        [Parameter()]
        [string]$ClassName
    )

    Process {
        try {
            $c = New-UDErrorBoundary -Content $Children    
        }
        catch {
            $c = New-UDError -Message $_
        }
        

        @{
            isPlugin  = $true 
            id        = $id 
            assetId   = $MUAssetId
            type      = "mu-container"

            children  = $c
            className = $ClassName
        }
    }
}
New-Alias -Name 'New-UDDashboard' -Value 'New-UDApp'

function New-UDApp {
    <#
    .SYNOPSIS
    Creates a new dashboard.
     
    .DESCRIPTION
    Creates a new dashboard. This component is the root element for all dashboards. You can define content, pages, themes and more.
     
    .PARAMETER Title
    The title of the dashboard.
     
    .PARAMETER Content
    The content for this dashboard. When using content, it creates a dashboard with a single page.
     
    .PARAMETER Pages
    Pages for this dashboard. Use New-UDPage to define a page and pass an array of pages to this parameter.
     
    .PARAMETER Theme
    The theme for this dashboard. You can define a theme with New-UDTheme.
     
    .PARAMETER Scripts
    JavaScript files to run when this dashboard is loaded. These JavaScript files can be absolute and hosted in a third-party CDN or you can host them yourself with New-PSUPublishedFolder.
     
    .PARAMETER Stylesheets
    CSS files to run when this dashboard is loaded. These CSS files can be absolute and hosted in a third-party CDN or you can host them yourself with New-PSUPublishedFolder.
     
    .PARAMETER Logo
    A logo to display in the navigation bar. You can use New-PSUPublishedFolder to host this logo file.
     
    .PARAMETER DefaultTheme
    The default theme to show when the page is loaded. The default is to use the light theme.
 
    .PARAMETER DisableThemeToggle
    Disables the toggle for the theme.
 
    .PARAMETER HeaderPosition
    Position of the header within the dashboard.
     
    .PARAMETER HeaderBackgroundColor
    The background color of the header. This will override the theme colors.
 
    .PARAMETER HeaderColor
    The color of the header. This will override the theme colors.
 
    .PARAMETER HideNavigation
    Obsolete. This parameter was intended for testing purposes.
 
    .PARAMETER HideUserName
    Obsolete. This parameter was intended for testing purposes.
 
    .PARAMETER NavigationLayout
    The layout of the navigation. Valid values are 'Temporary' and 'Permanent'.
 
    .PARAMETER Navigation
    The navigation for the dashboard. This is an array of navigation items.
 
    .PARAMETER LoadNavigation
    An endpoint to load navigation items. This endpoint should return an array of navigation items.
 
    .PARAMETER HeaderContent
    An endpoint to load content into the header.
 
    .PARAMETER PageNotFound
    An endpoint to load content when a page is not found.
 
    .PARAMETER NotAuthorized
    An endpoint to load content when a user is not authorized.
 
    .PARAMETER SessionTimeoutModal
    A script block that will be executed when the session times out.
 
    .PARAMETER Menu
    An endpoint to load a menu into the dashboard.
 
    .EXAMPLE
    Creates a new dashboard with a single page.
 
    New-UDDashboard -Title 'My Dashboard' -Content {
        New-UDTypography -Text 'Hello, world!'
    }
 
    .EXAMPLE
    Creates a new dashboard with multiple pages.
 
    $Pages = @(
        New-UDPage -Name 'HomePage' -Content {
            New-UDTypography -Text 'Home Page'
        }
        New-UDPage -Name 'Page2' -Content {
            New-UDTypography -Text 'Page2'
        }
    )
 
    New-UDDashboard -Title 'My Dashboard' -Pages $Pages
     
    #>

    param(
        [Parameter()]
        [string]$Title = "PowerShell Universal",
        [Parameter(ParameterSetName = "Content", Mandatory)]
        [Endpoint]$Content,
        [Parameter(ParameterSetName = "Pages", Mandatory)]
        [PowerShellUniversal.DashboardPage[]]$Pages = @(),
        [Parameter()]
        [Hashtable]$Theme = (Get-UDTheme -Name $UDDefaultTheme),
        [Parameter()]
        [string[]]$Scripts = @(),
        [Parameter()]
        [string[]]$Stylesheets = @(),
        [Parameter()]
        [string]$Logo,
        [Parameter()]
        [ValidateSet("Light", "Dark")]
        [string]$DefaultTheme = "Light",
        [Parameter()]
        [switch]$DisableThemeToggle,
        [ValidateSet('absolute', 'fixed', 'relative', 'static', 'sticky')]
        [Parameter()]
        [string]$HeaderPosition = 'static',
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$HeaderColor,
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$HeaderBackgroundColor,
        [Parameter()]
        [ValidateSet("Temporary", "Permanent")]
        [string]$NavigationLayout = 'Temporary',
        [Parameter()]
        [Hashtable[]]$Navigation,
        [Parameter()]
        [Switch]$HideUserName,
        [Parameter()]
        [Switch]$HideNavigation,
        [Parameter()]
        [Endpoint]$LoadNavigation,
        [Parameter()]
        [Endpoint]$HeaderContent,
        [Parameter()]
        [Endpoint]$PageNotFound,
        [Parameter()]
        [Endpoint]$NotAuthorized,
        [Parameter()]
        [scriptblock]$SessionTimeoutModal,
        [Parameter()]
        [Endpoint]$Menu
    )    

    if ($HeaderContent) {
        $HeaderContent.Register("DashboardHeaderContent", $PSCmdlet);
    }

    if ($LoadNavigation) {
        $LoadNavigation.Register("DashboardLoadNavigation", $PSCmdlet);
    }

    if ($PageNotFound) {
        $PageNotFound.Register('PageNotFound', $Role, $PSCmdlet)
    }
    
    if ($NotAuthorized) {
        $NotAuthorized.Register('NotAuthorized', $Role, $PSCmdlet)
    }

    if ($Menu) {
        $Menu.Register('DashboardMenu', $Role, $PSCmdlet)
    }

    if ($PSCmdlet.ParameterSetName -eq 'Content') {
        $Parameters = @{
            Name                  = $Title
            Url                   = "Home"
            Content               = $Content
            Logo                  = $Logo
            HeaderPosition        = $HeaderPosition
            HeaderColor           = $HeaderColor
            HeaderBackgroundColor = $HeaderBackgroundColor
            NavigationLayout      = $NavigationLayout.ToLower()
            HideUserName          = $HideUserName.IsPresent
            HideNavigation        = $HideNavigation
        }

        if ($HeaderContent) {
            $Parameters['HeaderContent'] = $HeaderContent
        }

        if ($Navigation) {
            $Parameters['Navigation'] = $Navigation
        }

        $Pages += New-UDPage @Parameters
    }
    else {
        if ($HideNavigation) {
            $Pages.ForEach({ $_.hideNavigation = $true })
        }

        if ($Navigation) {
            $Pages.Where({ -not $_.Navigation }).ForEach({ $_.navigation = $Navigation })
        }

        if ($NavigationLayout) {
            $Pages.Where({ -not $_.navLayout }).ForEach({ $_.navLayout = $NavigationLayout.ToLower() })
        }

        if ($HeaderContent) {
            $Pages.Where({ -not $_.HeaderContent }).ForEach({ $_.headerContent = $HeaderContent })
        }

        if ($HeaderPosition) {
            $Pages.Where({ -not $_.HeaderPosition }).ForEach({ $_.headerPosition = $HeaderPosition })
        }

        if ($HeaderBackgroundColor) {
            $Pages.Where({ -not $_.HeaderBackgroundColor }).ForEach({ $_.headerBackgroundColor = $HeaderBackgroundColor })
        }

        if ($HeaderColor) {
            $Pages.Where({ -not $_.HeaderColor }).ForEach({ $_.headerColor = $HeaderColor })
        }

        if ($Logo) {
            $Pages.Where({ -not $_.Logo }).ForEach({ $_.logo = $Logo })
        }
    }

    $ParentPSScriptRoot = Get-Variable -Name PSScriptRoot -Scope 1
    $ThemeFile = Join-Path $ParentPSScriptRoot "theme.ps1"

    if (Test-Path $ThemeFile) {
        $Theme = & $ThemeFile
    }

    $Cache:Pages = $Pages

    @{
        title              = $Title 
        pages              = $Pages
        theme              = $Theme | ConvertTo-Json -Depth 10
        scripts            = $Scripts
        stylesheets        = $Stylesheets
        defaultTheme       = $DefaultTheme.ToLower()
        disableThemeToggle = $DisableThemeToggle.IsPresent
        navigation         = $Navigation
        navigationLayout   = $NavigationLayout
        headerContent      = $HeaderContent
        loadNavigation     = $LoadNavigation
        pageNotFound       = $PageNotFound
        notAuthorized      = $NotAuthorized
        translations       = $Translations
        hideUserName       = $HideUserName.IsPresent
        sessionTimeout     = if ($SessionTimeoutModal) { & $SessionTimeoutModal } else { $null }
        menu               = $Menu
    }
}

function New-UDDataGrid {
    <#
    .SYNOPSIS
    Displays data in a table-style grid.
     
    .DESCRIPTION
    Displays data in a table-style grid. Provides support for sorting, paging, and filtering of large data sets.
     
    .PARAMETER Id
    The ID of this data grid.
     
    .PARAMETER Columns
    An array of column to display in this table.
     
    .PARAMETER LoadRows
    The script block that loads the data for this grid.
     
    .PARAMETER Height
    The static height for this data grid.
     
    .PARAMETER AutoHeight
    Whether to calculate the height of this data grid.
     
    .PARAMETER AutoPageSize
    Automatically determines the page size.
     
    .PARAMETER CheckboxSelection
    Checkbox selection for rows.
     
    .PARAMETER CheckboxSelectionVisibleOnly
    Parameter description
     
    .PARAMETER ColumnBuffer
    Parameter description
     
    .PARAMETER ColumnThreshold
     
     
    .PARAMETER Density
    The visible density of the table.
     
    .PARAMETER PageSize
    The default page size.
     
    .PARAMETER RowsPerPageOptions
    An array of page sizes.
     
    .PARAMETER ShowPagination
    Whether to show Pagination.
     
    .PARAMETER Language
    The language to use for text in the data grid.
     
    .PARAMETER LoadDetailContent
    A script block that is called when rows are expanded. $EventData will contain the row's data.
     
    .PARAMETER DetailHeight
    The static height of the detail pane.
     
    .PARAMETER OnEdit
    A script block that is called when the row is edited. $EventData will include the edited data. Return an object to update the data grid row.
     
    .PARAMETER OnExport
    A script block that is called when data is exported.
     
    .PARAMETER ShowQuickFilter
    Shows a quick filter (search) box.
 
    .PARAMETER DefaultSortColumn
    The default column to sort by.
 
    .PARAMETER DefaultSortDirection
    The default sort direction.
 
    .PARAMETER DisableRowSelectionOnClick
    Disables row selection on click.
 
    .PARAMETER HideExport
    Hides the export button.
 
    .PARAMETER RowHeight
    The height of the rows.
 
    .PARAMETER IdentityColumn
    The identity column for the data grid.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = @(
    PS > @{ Name = 'Adam'; Number = Get-Random}
    PS > @{ Name = 'Tom'; Number = Get-Random}
    PS > @{ Name = 'Sarah'; Number = Get-Random}
    PS > )
    PS > @{
    PS > rows = $Data
    PS > rowCount = $Data.Length
    PS > }
    PS > } -Columns @(
    PS > New-UDDataGridColumn -Field 'Name'
    PS > New-UDDataGridColumn -Field 'Number'
    PS > ) -Id 'dataGrid1'
 
    Basic data grid|Creates a basic data grid with columns.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = 1..1000 | ForEach-Object {
    PS > @{ Name = "User$($_)"; Number = Get-Random }
    PS > }
    PS > Out-UDDataGridData -Data $Data -Total $Data.Length -Context $EventData
    PS > } -Columns @(
    PS > New-UDDataGridColumn -Field 'Name'
    PS > New-UDDataGridColumn -Field 'Number'
    PS > ) -Pagination -Id 'dataGrid2'
 
    Out-UDDataGridData|Adds support for filtering, sorting, and paging.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = 1..100 | % {
    PS > @{ Name = 'Adam'; Number = Get-Random}
    PS > }
    PS > Out-UDDataGridData -Data $Data -Total $Data.Length -Context $EventData
    PS >
    PS > } -Columns @(
    PS > New-UDDataGridColumn -Field 'Name' -Render {
    PS > New-UDAlert -Text $EventData.Name -Dense
    PS > }
    PS > New-UDDataGridColumn -Field 'Number'
    PS > ) -Pagination -Id 'dataGrid3'
 
    Render|Adds support for custom rendering of columns.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = @(
    PS > @{ Name = 'Adam'; Number = Get-Random }
    PS > @{ Name = 'Tom'; Number = Get-Random }
    PS > @{ Name = 'Sarah'; Number = Get-Random }
    PS > )
    PS > @{
    PS > rows = $Data
    PS > rowCount = $Data.Length
    PS > }
    PS > } -Columns @(
    PS > New-UDDataGridColumn -Field 'Name'
    PS > New-UDDataGridColumn -Field 'Number'
    PS > ) -LoadDetailContent {
    PS > New-UDAlert -Text $EventData.row.Name
    PS > } -Id 'dataGrid4'
 
    Detailed Content|Adds support for detailed content.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = @(
    PS > @{ Name = 'Adam'; number = Get-Random }
    PS > @{ Name = 'Tom'; number = Get-Random }
    PS > @{ Name = 'Sarah'; number = Get-Random }
    PS > )
    PS > @{
    PS > rows = $Data
    PS > rowCount = $Data.Length
    PS > }
    PS > } -Columns @(
    PS > @{ field = "Name"; editable = $true }
    PS > @{ field = "number" ; editable = $true }
    PS > ) -OnEdit {
    PS > Show-UDToast "Editing $Body"
    PS > } -Id 'dataGrid5'
 
    Editing|Adds support for editing rows.
 
    .EXAMPLE
    PS > New-UDDataGrid -LoadRows {
    PS > $Data = @(
    PS > @{ Name = 'Adam'; number = Get-Random }
    PS > @{ Name = 'Tom'; number = Get-Random }
    PS > @{ Name = 'Sarah'; number = Get-Random }
    PS > )
    PS > @{
    PS > rows = $Data
    PS > rowCount = $Data.Length
    PS > }
    PS > } -Columns @(
    PS > New-UDDataGridColumn -Field 'Name' -HeaderName 'A Name' -Flex 1 -HeaderAlign 'center' -Align 'center' -DisableColumnMenu
    PS > New-UDDataGridColumn -Field 'Number' -HeaderName 'A Number' -Flex 1 -HeaderAlign 'right' -Align 'right' -DisableColumnMenu
    PS > ) -Id 'dataGrid6'
 
    Column Options|
    #>

    #[Component("Data Grid", "Table", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [Hashtable[]]$Columns,
        [Parameter()]
        [Endpoint]$LoadRows,
        [Parameter()]
        [string]$Height,
        [Parameter()]
        [bool]$AutoHeight = $true,
        [Parameter()]
        [Switch]$AutoPageSize,
        [Parameter()]
        [Switch]$CheckboxSelection,
        [Parameter()]
        [Switch]$CheckboxSelectionVisibleOnly,
        [Parameter()]
        [int]$ColumnBuffer = 3,
        [Parameter()]
        [int]$ColumnThreshold = 3,
        [Parameter()]
        [ValidateSet("compact", "standard", "comfortable")]
        [string]$Density = "standard",
        [Parameter()]
        [int]$PageSize = 5,
        [Parameter()]
        [int[]]$RowsPerPageOptions = @(5, 10, 25),
        [Parameter()]
        [Alias("Pagination")]
        [Switch]$ShowPagination,
        [Parameter()]
        [Endpoint]$OnSelectionChange,
        [Parameter()]
        [ValidateSet("ar-SD", "bg-BG", "cs-CZ", "da-DK", "nl-NL", "en-US", "fi-FI", "fr-FR", "de-DE", "el-GR", "he-IL", "hu-HU", "it-IT", "ja-JP", "ko-KR", "nb-NO", "fa-IR", "pl-PL", "pt-BR", "ro-RO", "ru-RU", "sk-SK", "es-ES", "sv-SE", "tr-TR", "uk-UA", "zh-CN")]
        [string]$Language = "en-US",
        [Parameter()]
        [Endpoint]$LoadDetailContent,
        [Parameter()]
        [string]$DetailHeight = "auto",
        [Parameter()]
        [Endpoint]$OnEdit,
        [Parameter()]
        [Endpoint]$OnExport,
        [Parameter()]
        [Switch]$ShowQuickFilter,
        [Parameter()]
        [string]$DefaultSortColumn,
        [Parameter()]
        [ValidateSet("asc", "desc")]
        [string]$DefaultSortDirection = "asc",
        [Parameter()]
        [Switch]$DisableRowSelectionOnClick,
        [Parameter()]
        [Switch]$HideExport,
        [Parameter()]
        [int]$RowHeight = -1,
        [Parameter()]
        [string]$IdentityColumn
    )

    if ($LoadRows) {
        $RenderedColumns = $Columns.Where( { $_.ContainsKey("render") })

        $LoadRows.PostProcess = {
            $Data = $Args[0]

            if ($Data.Rows.Count -ge 1) {
                foreach ($Item in $Data.Rows) {
                    Set-Variable -Name 'EventData' -Value $Item
                    Set-Variable -Name 'Row' -Value $Row

                    foreach ($Column in $RenderedColumns) {
                        $Render = $Column['render'].GetNewClosure()
                        $RenderedData = $Render.Invoke()

                        if (-not $RenderedData) {
                            $RenderedData = ""
                        }

                        if ($Item -isnot [hashtable]) {
                            Add-Member -InputObject $Item -MemberType NoteProperty -Name "rendered$($Column.field)" -Value $RenderedData -Force
                        }
                        else {
                            $Item["rendered$($Column.field)"] = $RenderedData
                        }
                    }
                }
            }

            $Data
        }

        $RenderedColumns | ForEach-Object {
            $LoadRows.PostProcessVariables.AddRange(([Endpoint]::GetVariablesInAst($_['render'].Ast)))
        }

        $LoadRows.Register($Id, $PSCmdlet, @{
                RenderedColumns = $RenderedColumns
            })
    }

    if ($Columns) {
        $Columns = $Columns | ForEach-Object {
            if ($_.Field) {
                $_.Field = [char]::ToLower($_.Field[0]) + $_.Field.Substring(1)
            }
            $_
        }
    }

    if ($OnEdit) {
        $OnEdit.Register($Id + "Edit", $PSCmdlet)
    }

    if ($OnExport) {
        $OnExport.Register($Id + "Export", $PSCmdlet)
    }

    if ($LoadDetailContent) {
        $LoadDetailContent.Register($Id + "MasterDetail", $PSCmdlet)
    }

    if ($OnSelectionChange) {
        $OnSelectionChange.Register($Id + "Selection", $PSCmdlet)
    }

    @{
        type                         = "mu-datagrid"
        id                           = $Id
        columns                      = $Columns
        loadRows                     = $LoadRows
        autoHeight                   = $AutoHeight
        autoPageSize                 = $AutoPageSize.IsPresent
        checkboxSelection            = $CheckboxSelection.IsPresent
        checkboxSelectionVisibleOnly = $CheckboxSelectionVisibleOnly.IsPresent
        columnBuffer                 = $ColumnBuffer
        columnThreshold              = $ColumnThreshold
        density                      = $Density.ToLower()
        pageSize                     = $PageSize
        pageSizeOptions              = $RowsPerPageOptions 
        pagination                   = $ShowPagination.IsPresent
        language                     = $Language.ToLower()
        loadDetailContent            = $LoadDetailContent
        detailHeight                 = $DetailHeight
        onEdit                       = $OnEdit
        onExport                     = $OnExport
        showQuickFilter              = $ShowQuickFilter.IsPresent
        onSelectionChange            = $OnSelectionChange        
        defaultSortColumn            = $DefaultSortColumn
        defaultSortDirection         = $DefaultSortDirection
        height                       = $Height
        disableRowSelectionOnClick   = $DisableRowSelectionOnClick.IsPresent
        hideExport                   = $HideExport.IsPresent
        rowHeight                    = $RowHeight
        identityColumn               = if ($IdentityColumn) { [char]::ToLower($IdentityColumn[0]) + $IdentityColumn.Substring(1) } else { $null }
    }
}

function New-UDDataGridColumn {
    <#
    .SYNOPSIS
    Creates a new data grid column.
     
    .DESCRIPTION
    Creates a new data grid column.
     
    .PARAMETER Align
    The alignment of the cell data. Possible values are 'left', 'center' and 'right'.
     
    .PARAMETER CellClassName
    CSS class name to be applied on cell element.
     
    .PARAMETER ColumnSpan
    The number of columns that the column should span.
     
    .PARAMETER Description
    The description of the column that appears when hovering the header cell.
     
    .PARAMETER DisableColumnMenu
    If true, the column menu is disabled.
     
    .PARAMETER DisableExport
    If true, the column is not exported.
     
    .PARAMETER DisableReorder
    If true, the column cannot be reordered.
     
    .PARAMETER Editable
    If true, the column is editable.
     
    .PARAMETER Field
    The field of the row data that the column represents.
     
    .PARAMETER Filterable
    If true, the column is filterable.
     
    .PARAMETER Flex
    The flex value used to set the column width.
     
    .PARAMETER Groupable
    If true, the column is groupable.
     
    .PARAMETER HeaderAlign
    The alignment of the column header cell. Possible values are 'left', 'center' and 'right'.
     
    .PARAMETER HeaderName
    The name of the column header.
     
    .PARAMETER Hideable
    If true, the column is hideable.
     
    .PARAMETER HideSortIcons
    If true, the sort icons are hidden.
     
    .PARAMETER MaxWidth
    The maximum width of the column.
     
    .PARAMETER MinWidth
    The minimum width of the column.
     
    .PARAMETER Pinnable
    If true, the column is pinnable.
     
    .PARAMETER Resizable
    If true, the column is resizable.
     
    .PARAMETER Sortable
    If true, the column is sortable.
     
    .PARAMETER SortingOrder
    The sorting order of the column. Possible values are 'asc', 'desc' and null.
     
    .PARAMETER Type
    The type of the column. Possible values are 'string' | 'number' | 'date' | 'dateTime' | 'boolean' | 'singleSelect';
     
    .PARAMETER Width
    The width of the column.
 
    .PARAMETER Render
    A script block that is used to render the column. The script block is passed the row data as $EventData.
 
    .PARAMETER ValueOptions
    An array of options for the column filter when the filter is set to singleSelect.
    #>

    param(
        [Parameter()]
        [string]$CellClassName,
        [Parameter()]
        [ValidateSet("left", "center", "right")]
        [string]$Align = 'left',
        [Parameter()]
        [int]$ColumnSpan = 1,
        [Parameter()]
        [string]$Description,
        [Parameter()]
        [switch]$DisableColumnMenu,
        [Parameter()]
        [switch]$DisableExport, 
        [Parameter()]
        [switch]$DisableReorder,
        [Parameter()]
        [switch]$Editable,
        [Parameter()]
        [Alias("Property")]
        [string]$Field,
        [Parameter()]
        [Switch]$Filterable,
        [Parameter()]
        [float]$Flex = 1.0,
        [Parameter()]
        [switch]$Groupable,
        [Parameter()]
        [ValidateSet("left", "center", "right")]
        [string]$HeaderAlign = 'left',
        [Parameter()]
        [Alias("Title")]
        [string]$HeaderName,
        [Parameter()]
        [switch]$Hideable,
        [Parameter()]
        [switch]$HideSortIcons,
        [Parameter()]
        [int]$MaxWidth,
        [Parameter()]
        [int]$MinWidth,
        [Parameter()]
        [switch]$Pinnable,
        [Parameter()]
        [switch]$Resizable = $true,
        [Parameter()]
        [switch]$Sortable,
        [Parameter()]
        [string[]]$SortingOrder,
        [Parameter()]
        [string]$Type,
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [ScriptBlock]$Render,
        [Parameter()]
        [string[]]$ValueOptions,
        [Parameter()]
        [Switch]$Hide
    )

    $Result = [PowerShellUniversal.UDHashtable]::new()
    $Result.AddString("align", $Align, "left", $true)
    $Result.AddString("cellClassName", $CellClassName, "", $false)
    $Result.AddInt("colSpan", $ColumnSpan, 1)
    $Result.AddString("description", $Description, "", $false)
    $Result.AddBool("disableColumnMenu", $DisableColumnMenu.IsPresent, $false)
    $Result.AddBool("disableExport", $DisableExport.IsPresent, $false)
    $Result.AddBool("disableReorder", $DisableReorder.IsPresent, $false)
    $Result.AddBool("editable", $Editable.IsPresent, $false)
    $Result.AddString("field", $Field, $true)
    $Result.AddBool("filterable", $Filterable.IsPresent, $false)
    $Result.AddFloat("flex", $Flex, 1.0)
    $Result.AddBool("groupable", $Groupable.IsPresent, $false)
    $Result.AddString("headerAlign", $HeaderAlign, "left", $true)
    $Result.AddString("headerName", $HeaderName, $Field)
    $Result.AddBool("hideable", $Hideable.IsPresent, $false)
    $Result.AddBool("hide", $Hide.IsPresent, $false)
    $Result.AddBool("hideSortIcons", $HideSortIcons.IsPresent, $false)
    $Result.AddInt("maxWidth", $MaxWidth, 0)
    $Result.AddInt("minWidth", $MinWidth, 0)
    $Result.AddBool("pinnable", $Pinnable.IsPresent, $false)
    $Result.AddBool("resizable", $Resizable.IsPresent, $true)
    $Result.AddBool("sortable", $Sortable.IsPresent, $false)
    $Result.AddStringArray("sortingOrder", $SortingOrder)
    $Result.AddString("type", $Type, "", $false)
    $Result.AddInt("width", $Width, 0)
    $Result.AddScriptBlock("render", $Render)
    $Result.AddStringArray("valueOptions", $ValueOptions)
    $Result
}

function Out-UDDataGridData {
    <#
    .SYNOPSIS
    Outputs data for a data grid.
     
    .DESCRIPTION
    Outputs data for a data grid.
     
    .PARAMETER Context
    The context of the data grid.
     
    .PARAMETER Data
    The data to output.
     
    .PARAMETER TotalRows
    The total number of rows in the data set.
    #>

    [CmdletBinding()] 
    param(
        [Parameter(Mandatory)]
        $Context,
        [Parameter(Mandatory, ValueFromPipeline)]
        [object]$Data,
        [Parameter()]
        [int]$TotalRows = -1
    )
    Begin {
        $Items = [System.Collections.ArrayList]::new()
    }
    Process {
        if ($Data -is [Array]) {
            $Items = $Data
        }
        else {
            $Items.Add($Data) | Out-Null
        }
    }
 
    End {
        $Result = $Items
        if ($null -ne $Context.Filter.Items -and $Context.Filter.Items.Count -gt 0) {
            $linkOperator = $Context.Filter.logicOperator
            $filterTextArray = ""
            $filterTextArray = @()
            foreach ($filter in $Context.Filter.Items) {
                $property = $Filter.field
                $val = $filter.Value
                switch ($filter.operator) {
                    "contains" { $filterTextArray += "`$PSItem.$Property -match '$val'" }
                    "equals" { $filterTextArray += "`$_.$property -eq '$val'" }
                    "startsWith" { $filterTextArray += "`$_.$property -like '$val*'" }
                    "endsWith" { $filterTextArray += "`$_.$property -like '*$val'" }
                    "isAnyOf" { $filterTextArray += "`$_.$property -in '$val'" }
                    "notequals" { $filterTextArray += "`$_.$property -ne '$val'" }
                    "notcontains" { $filterTextArray += "`$_.$property -notmatch '$val'" }
                    "isEmpty" { $filterTextArray += "`$_.$property -eq `$null" }
                    "isNotEmpty" { $filterTextArray += "`$_.$property -ne `$null" }
                }
            }
            
            if ($linkOperator -eq 'and') {
                [string]$filterTextLine = $filterTextArray -join " -and "
            }
            else {
                [string]$filterTextLine = $filterTextArray -join " -or "
            }

            $filterScriptBlock = [Scriptblock]::Create($filterTextLine).GetNewClosure()
            $Result = $Result | Where-Object -FilterScript $filterScriptBlock
        }

        if ($Context.filter.quickfiltervalues -and $Context.filter.quickfiltervalues.length -gt 0) {
            $FilterString = $Context.filter.quickfiltervalues[0]

            $Result = $Result | Where-Object {
                $PSObj = $_ 
            
                if ($PSObj -is [Hashtable]) {
                    $PSObj.Keys | ForEach-Object {
                        if ($PSObj[$_] -and $PSObj[$_].ToString() -like $FilterString) {
                            $true
                            return
                        }
                    }
                }
                else {
                    $PSObj.PSObject.Properties | ForEach-Object {
                        if ($_.Value -and $_.Value.ToString() -like $FilterString) {
                            $true
                            return
                        }
                    }
                }
                $false
            }
        }

        if ($null -ne $Result) {
            $TotalRows = $Result.Count
        }
        else {
            $TotalRows = 0
        }
 
        if ($Context.Sort) {
            $Sort = $Context.Sort[0]
            $Result = $Result | Sort-Object -Property $Sort.field -Descending:$($Sort.Sort -eq 'desc')
        }

        $Result = $Result | Select-Object -Skip ($Context.Page * $Context.pageSize) -First $Context.PageSize
 
        @{
            rows     = [Array]$Result
            rowCount = $TotalRows
        }
    }   
}

function Out-UDDataGridExport {
    <#
    .SYNOPSIS
    Exports data from the data grid.
     
    .DESCRIPTION
    Exports data from the data grid.
     
    .PARAMETER Data
    The data to export.
     
    .PARAMETER MimeType
    The mime type to export.
     
    .PARAMETER FileName
    The file name to export.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Data,
        [Parameter()]
        [string]$MimeType = 'text/plain',
        [Parameter()]
        [string]$FileName = 'export.txt'
    )

    @{
        data     = $Data 
        mimeType = $MimeType 
        fileName = $FileName
    }
}
function New-UDDatePicker {
    <#
    .SYNOPSIS
    Creates a new date picker.
     
    .DESCRIPTION
    Creates a new date picker. Date pickers can be used stand alone or within New-UDForm.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label to show next to the date picker.
     
    .PARAMETER Variant
    The theme variant to apply to the date picker.
     
    .PARAMETER DisableToolbar
    Disables the date picker toolbar.
     
    .PARAMETER OnChange
    A script block to call with the selected date. The $EventData variable will be the date selected.
     
    .PARAMETER Format
    The format of the date when it is selected.
     
    .PARAMETER Value
    The current value of the date picker.
 
    .PARAMETER Locale
    Change the language of the date picker.
     
    .PARAMETER ClassName
    A CSS class to apply to the date picker.
 
    .PARAMETER MinimumDate
    The minimum date that can be selected.
 
    .PARAMETER MaximumDate
    The maximum date that can be selected.
 
    .PARAMETER TimeZone
    The time zone to use for the date picker. This should be an IANA time zone string.
 
    .PARAMETER Disabled
    Disables the date picker.
 
    .PARAMETER Views
    The views to show in the date picker. Valid values are: "day", "year", "month"
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker1' -Value '1-2-2020'
 
    Basic date picker|Creates a new date picker with the default date value.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker2' -Value '1-2-2020' -OnChange {
    PS > Show-UDToast -Message "Date selected: $EventData"
    PS > }
 
    Date picker with OnChange|Creates a new date picker with the default date value and an OnChange script block.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker3' -Value '1-2-2020' -Variant static
 
    Static date picker|Creates a new date picker with the default date value and the static variant.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker4' -Value '1-2-2020' -Format 'dd/MM/yyyy'
 
    Date picker with custom format|Creates a new date picker with the default date value and a custom format.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker5' -Value '1-2-2020' -Locale 'de'
 
    Date picker with German locale|Creates a new date picker with the default date value and a German locale.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker6' -Value '1-2-2020' -DisableToolbar
 
    Date picker with disabled toolbar|Creates a new date picker with the default date value and a disabled toolbar.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker7' -Value '1-2-2020' -MinimumDate '1-1-2020'
 
    Date picker with minimum date|Creates a new date picker with the default date value and a minimum date.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker8' -Value '1-2-2020' -MaximumDate '1-1-2021'
 
    Date picker with maximum date|Creates a new date picker with the default date value and a maximum date.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker9' -Value '1-2-2020' -TimeZone 'America/Toronto'
 
    Date picker with time zone|Creates a new date picker with the default date value and a time zone.
 
    .EXAMPLE
    PS > New-UDDatePicker -Id 'datepicker10' -Value '1-2-2020' -Disabled
 
    Disabled date picker|Creates a new date picker with the default date value and disabled.
    #>

    [Category("app/component")]
    [Description("Allow the user to select a date.")]
    [DisplayName("Date Picker")]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [ValidateSet('inline', 'static')]
        [string]$Variant = 'inline',
        [Parameter()]
        [Switch]$DisableToolbar,
        [Parameter()]
        [Endpoint]$OnChange, 
        [Parameter()]
        [string]$Format = 'MM/DD/YYYY',
        [Parameter()]
        [DateTime]$Value,
        [Parameter()]
        [ValidateSet("en", "de", 'ru', 'fr', 'nl', 'it')]
        [string]$Locale = "en",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [DateTime]$MinimumDate,
        [Parameter()]
        [DateTime]$MaximumDate,
        [Parameter()]
        $TimeZone,
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [string[]]$Views = @("day", "year")
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    if ($TimeZone -is [System.TimeZoneInfo]) {
        Write-Warning "TimezoneInfo is not supported in UDDatePicker. Please use an IANA string instead."
    }

    $Arguments = @{
        id        = $Id 
        type      = 'mu-datepicker'
        asset     = $MUAssetId
        isPlugin  = $true 

        onChange  = $OnChange 
        variant   = $Variant 
        format    = $Format 
        value     = $Value
        label     = $Label
        locale    = $Locale.ToLower()
        className = $ClassName
        timezone  = $TimeZone
        disabled  = $Disabled.IsPresent
        views     = $Views
    }

    if ($PSBoundParameters.ContainsKey('MinimumDate')) {
        $Arguments['minDate'] = $MinimumDate
    }

    if ($PSBoundParameters.ContainsKey('MaximumDate')) {
        $Arguments['maxDate'] = $MaximumDate
    }

    if ($PSBoundParameters.ContainsKey('Value')) {
        $Arguments['value'] = $Value
    }

    $Arguments
}
function New-UDDateRangePicker {
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [Switch]$AutoFocus,
        [Parameter()]
        [ValidateSet(1, 2, 3)]
        [int]$Calendars = 2,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$CloseOnSelect,
        [Parameter()]
        [ValidateSet(1, 2, 3)]
        [int]$CurrentMonthCalendarPositition = 1,
        [Parameter()]
        [ValidateSet("start", "end")]
        [string]$DefaultRangePosition = "start",
        [Parameter()]
        [string]$DesktopModeMediaQuery = '@media (pointer: fine)',
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [Switch]$DisableDragEditing,
        [Parameter()]
        [Switch]$DisableFuture,
        [Parameter()]
        [Switch]$DisableHightlightToday,
        [Parameter()]
        [Switch]$DisableOpenPicker,
        [Parameter()]
        [Switch]$DisablePast,
        [Parameter()]
        [Switch]$DisableWeekNumber,
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [DateTime]$MaxDate,
        [Parameter()]
        [DateTime]$MinDate,
        [Parameter()]
        [Endpoint]$OnAccept,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [Endpoint]$OnClose,
        [Parameter()]
        [Endpoint]$OnError,
        [Parameter()]
        [Endpoint]$OnMonthChange,
        [Parameter()]
        [Endpoint]$OnOpen,
        [Parameter()]
        [Endpoint]$onRangePositionChange,
        [Parameter()]
        [Endpoint]$onSelectedSectionsChange,
        [Parameter()]
        [ValidateSet("end", "start")]
        [string]$RangePosition = 'start',
        [Parameter()]
        [Switch]$ReduceAnimations,
        [Parameter()]
        [DateTime[]]$DisabledDates,
        [Parameter()]
        [Switch]$showDaysOutsideCurrentMonth,
        [Parameter()]
        [hashtable]$Sx,
        [Parameter()]
        [string]$Timezone,
        [Parameter()]
        [DateTime[]]$Value,
        [Parameter()]
        [Hashtable]$LocaleText,
        [Parameter()]
        [string]$Format
    )

    if ($OnAccept) {
        $OnAccept.Register($id + 'OnAccept', $PSCmdlet)
    }

    if ($OnChange) {
        $OnChange.Register($id + 'OnChange', $PSCmdlet)
    }

    if ($OnClose) {
        $OnClose.Register($id + 'OnClose', $PSCmdlet)
    }

    if ($OnError) {
        $OnError.Register($id + 'OnError', $PSCmdlet)
    }

    if ($OnMonthChange) {
        $OnMonthChange.Register($id + 'OnMonthChange', $PSCmdlet)
    }

    if ($OnOpen) {
        $OnOpen.Register($id + 'OnOpen', $PSCmdlet)
    }

    if ($onRangePositionChange) {
        $onRangePositionChange.Register($id + 'onRangePositionChange', $PSCmdlet)
    }

    if ($onSelectedSectionsChange) {
        $onSelectedSectionsChange.Register($id + 'onSelectedSectionsChange', $PSCmdlet)
    }

    @{
        id                             = $Id 
        type                           = 'mui-date-range-picker'

        autoFocus                      = $AutoFocus.IsPresent
        calendars                      = $Calendars
        className                      = $ClassName
        closeOnSelect                  = $CloseOnSelect.IsPresent
        currentMonthCalendarPositition = $CurrentMonthCalendarPositition
        defaultRangePosition           = $DefaultRangePosition
        desktopModeMediaQuery          = $DesktopModeMediaQuery
        disabled                       = $Disabled.IsPresent
        disableDragEditing             = $DisableDragEditing.IsPresent
        disableFuture                  = $DisableFuture.IsPresent
        disableHightlightToday         = $DisableHightlightToday.IsPresent
        disableOpenPicker              = $DisableOpenPicker.IsPresent
        disablePast                    = $DisablePast.IsPresent
        disableWeekNumber              = $DisableWeekNumber.IsPresent
        label                          = $Label
        maxDate                        = $MaxDate
        minDate                        = $MinDate
        onAccept                       = $OnAccept
        onChange                       = $OnChange
        onClose                        = $OnClose
        onError                        = $OnError
        onMonthChange                  = $OnMonthChange
        onOpen                         = $OnOpen
        onRangePositionChange          = $onRangePositionChange
        onSelectedSectionsChange       = $onSelectedSectionsChange
        rangePosition                  = $RangePosition
        reduceAnimations               = $ReduceAnimations.IsPresent
        sx                             = $Sx
        timezone                       = $Timezone
        value                          = $Value
        disabledDates                  = $DisabledDates
        showDaysOutsideCurrentMonth    = $showDaysOutsideCurrentMonth.IsPresent
        localeText                     = $LocaleText
        format                         = $Format
    }
}
function New-UDDateTime {
    <#
    .SYNOPSIS
    This date and time component is used for formatting dates and times using the user's browser settings.
     
    .DESCRIPTION
    This date and time component is used for formatting dates and times using the user's browser settings. Since Universal Dashboard PowerShell scripts run within the server, the date and time settings of the user's system are not taken into account. This component formats date and time within the client's browser to take into account their locale and time zone.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER InputObject
    The date and time object to format.
     
    .PARAMETER Format
    The format of the date and time. This component uses Day.JS. You can learn more about formatting options on their documentation: https://day.js.org/docs/en/display/format
     
    .PARAMETER LocalizedFormat
    The localized format for the date and time. Use this format if you would like to take the user's browser locale and time zone settings into account.
     
    .PARAMETER Locale
    The locale to use when formatting the date time. Defaults to not set.
 
    .EXAMPLE
    PS > New-UDDateTime -InputObject (Get-Date) -Format 'DD/MM/YYYY' -Id 'dateTime1'
 
    Formats a date and time using the format 'DD/MM/YYYY'
 
    .EXAMPLE
    PS > New-UDDateTime -InputObject (Get-Date) -LocalizedFormat 'LLL' -Id 'dateTime2'
 
    Localized Format|Formats a date and time using the localized format 'LLL'
 
    .EXAMPLE
    PS > New-UDDateTime -InputObject (Get-Date) -Locale 'es' -Id 'dateTime3'
  
    Locale|Formats a date and time using the locale 'es'
    #>

    [CmdletBinding(DefaultParameterSetName = "LocalizedFormat")]
    [Category("app/component")]
    [Description("Format a date and time.")]
    [DisplayName("Date and Time")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter(Position = 0)]
        [DateTime]$InputObject = (Get-Date),
        [Parameter(ParameterSetName = "Format")]
        [string]$Format = "DD/MM/YYYY",
        [Parameter(ParameterSetName = "LocalizedFormat")]
        [ValidateSet("LT", "LTS", "L", "LL", "LLL", "LLLL", "l", "ll", "lll", "llll")]
        [string]$LocalizedFormat = "LLL",
        [Parameter()]
        [ValidateSet('af', 'am', 'ar-dz', 'ar-iq', 'ar-kw', 'ar-ly', 'ar-ma', 'ar-sa', 'ar-tn', 'ar', 'az', 'be', 'bg', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'cs', 'cv', 'cy', 'da', 'de-at', 'de-ch', 'de', 'dv', 'el', 'en-au', 'en-ca', 'en-gb', 'en-ie', 'en-il', 'en-in', 'en-nz', 'en-sg', 'en-tt', 'en', 'eo', 'es-do', 'es', 'et', 'eu', 'fa', 'fi', 'fo', 'fr-ca', 'fr-ch', 'fr', 'fy', 'ga', 'gd', 'gl', 'gom-latn', 'gu', 'he', 'hi', 'hr', 'ht', 'hu', 'hy-am', 'id', 'is', 'it-ch', 'it', 'ja', 'jv', 'ka', 'kk', 'km', 'kn', 'ko', 'ku', 'ky', 'lb', 'lo', 'lt', 'lv', 'me', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms-my', 'ms', 'mt', 'my', 'nb', 'ne', 'nl-be', 'nl', 'nn', 'oc-lnc', 'pa-in', 'pl', 'pt-br', 'pt', 'ro', 'ru', 'rw', 'sd', 'se', 'si', 'sk', 'sl', 'sq', 'sr-cyrl', 'sr', 'ss', 'sv-fi', 'sv', 'sw', 'ta', 'te', 'tet', 'tg', 'th', 'tk', 'tl-ph', 'tlh', 'tr', 'tzl', 'tzm-latn', 'tzm', 'ug-cn', 'uk', 'ur', 'uz-latn', 'uz', 'vi', 'x-pseudo', 'yo', 'zh-cn', 'zh-hk', 'zh-tw', 'zh', 'es-pr', 'es-mx', 'es-us')]
        [string]$Locale
    )

    $f = $Format 
    if ($PSCmdlet.ParameterSetName -eq 'LocalizedFormat') {
        $f = $LocalizedFormat
    }

    @{
        type        = 'mu-datetime'
        id          = $Id 
        isPlugin    = $true 
        assetId     = $MUAssetId

        inputObject = $InputObject.ToString("O")
        format      = $f
        locale      = $Locale.ToLower()
    }
}
function  Debug-PSUDashboard {
    <#
    .SYNOPSIS
    Provides a utility function for debugging scripts running PowerShell Universal Dashboard.
     
    .DESCRIPTION
    Provides a utility function for debugging scripts running PowerShell Universal Dashboard. This cmdlet integrates with the VS Code PowerShell Universal extension to automatically connect the debugger to endpoints running in UD.
     
    .EXAMPLE
    Creates an element that invokes the Debug-PSUDashboard cmdlet.
 
    New-UDElement -Tag div -Endpoint {
        Debug-PSUDashboard
    }
    #>

    [CmdletBinding()]
    param()

    $DebugPreference = 'continue'

    $Runspace = ([runspace]::DefaultRunspace).id

    Show-UDModal -Header {
        New-UDTypography -Text 'Debug Dashboard' -Variant h4
    } -Content {
        Write-Debug "IN DEBUG MODE: Enter-PSHostProcess -Id $PID then Debug-Runspace -Id $Runspace"
        New-UDTypography -Text "You can run the following PowerShell commands in any PowerShell host to debug this dashboard."
        New-UDElement -Tag 'pre' -Content {
            "Enter-PSHostProcess -Id $PID`r`nDebug-Runspace -Id $Runspace"
        }
    } -Footer {
        New-UDLink -Children {
            New-UDButton -Text 'Debug with VS Code' 
        } -Url "vscode://ironmansoftware.powershell-universal/debug?PID=$PID&RS=$Runspace" 
        New-UDLink -Children {
            New-UDButton -Text 'Debug with VS Code Insiders' 
        } -Url "vscode-insiders://ironmansoftware.powershell-universal/debug?PID=$PID&RS=$Runspace" 
        New-UDButton -Text 'Close' -OnClick { Hide-UDModal }
    }

    Wait-Debugger 
}
function New-UDDivider {
    <#
    .SYNOPSIS
    A divider is a thin line that groups content in lists and layouts.
     
    .DESCRIPTION
    A divider is a thin line that groups content in lists and layouts.
     
    .PARAMETER Id
    ID of this component.
     
    .PARAMETER Absolute
    Absolutely position the element.
     
    .PARAMETER Children
    The content of the component.
     
    .PARAMETER FlexItem
    If true, a vertical divider will have the correct height when used in flex container. (By default, a vertical divider will have a calculated height of 0px if it is the child of a flex container.)
     
    .PARAMETER Light
    If true, the divider will have a lighter color.
     
    .PARAMETER Orientation
    The component orientation.
     
    .PARAMETER Sx
    Custom styling
     
    .PARAMETER TextAlign
    The text alignment.
     
    .PARAMETER Variant
    The variant to use.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider1'
 
    Basic Divider|Creates a basic divider.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider2' -Variant 'inset'
    PS > New-UDDivider -Id 'divider3' -Variant 'middle'
 
    Variants|Creates a divider with the inset and middle variants.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider4' -Orientation 'vertical'
 
    Vertical Divider|Creates a vertical divider.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider5' -Light
 
    Light Divider|Creates a light divider.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider6' -Children {
    PS > New-UDTypography -Text 'Examples'
    PS > }
 
    Divider with Text|Creates a divider with text.
 
    .EXAMPLE
    PS > New-UDDivider -Id 'divider6' -Children {
    PS > New-UDTypography -Text 'Examples'
    PS > } -TextAlign left
 
    Algin Text|Creates a divider with text aligned to the left.
 
    #>

    [Category("app/component")]
    [Description("A divider is a thin line that groups content in lists and layouts.")]
    [DisplayName("Divider")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [Switch]$Absolute,
        [Parameter()]
        [Alias("Content")]
        [scriptblock]$Children = {},
        [Parameter()]
        [switch]$FlexItem,
        [Parameter()]
        [switch]$Light,
        [Parameter()]
        [ValidateSet("horizontal", "vertical")]
        [string]$Orientation = "horizontal",
        [Parameter()]
        [Hashtable]$Sx,
        [Parameter()]
        [ValidateSet("left", "center", "right")]
        [string]$TextAlign = "center",
        [Parameter()]
        [ValidateSet("fullWidth", "inset", "middle")]
        [string]$Variant = "fullWidth"
    )

    @{
        id          = $Id 
        type        = 'mu-divider'
        assetId     = $MUAssetId 
        isPlugin    = $true

        absolute    = $Absolute.IsPresent
        children    = & $Children
        flexItem    = $FlexItem.IsPresent
        light       = $Light.IsPresent
        orientation = $Orientation.ToLower()
        sx          = $Sx
        textAlign   = $TextAlign.ToLower()
        variant     = $Variant.ToLower()
    }
}
function New-UDDrawer {
    <#
    .SYNOPSIS
    Creates a new drawer.
     
    .DESCRIPTION
    Creates a new drawer. A drawer is a navigational component that is typically used for navigating between pages. It can be used with New-UDAppBar to provide a custom nav bar.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    Navgiation controls to show within the drawer. Use New-UDList and New-UDListItem to generate links within the drawer.
 
    .PARAMETER Variant
    The type of drawer. Valid values include "persistent", "permanent", "temporary"
 
    .PARAMETER Anchor
    Where to anchor the drawer. Valid values incldue "left", "right", "top", "bottom"
 
    .PARAMETER ShowCollapse
    Show the collapse button
     
    .PARAMETER ClassName
    A CSS class to apply to the drawer.
     
    .EXAMPLE
    Creates a custom navbar using New-UDDrawer
 
    $Drawer = New-UDDrawer -Id 'drawer' -Children {
        New-UDList -Content {
            New-UDListItem -Id 'lstHome' -Label 'Home' -OnClick {
                Set-TestData 'Home'
                } -Content {
                    New-UDListItem -Id 'lstNested' -Label 'Nested' -OnClick {
                    Set-TestData 'Nested'
                    }
                }
        }
    }
 
    New-UDElement -Tag 'main' -Content {
        New-UDAppBar -Children { New-UDTypography -Text 'Hello' -Paragraph } -Position relative -Drawer $Drawer
    }
    #>

    #[Component("Drawer", "WindowMaximize", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [ValidateSet("persistent", "permanent", "temporary")]
        [string]$Variant = "temporary",
        [ValidateSet("left", "right", "top", "bottom")]
        [string]$Anchor = "left",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$ShowCollapse
    )

    try {
        $c = & $Children
    }
    catch {
        $c = New-UDError -Message $_
    }

    @{
        type         = 'mu-drawer'
        id           = $Id 
        isPlugin     = $true 
        assetId      = $MUAssetId
        children     = $c
        variant      = $Variant.ToLower()
        anchor       = $Anchor.ToLower()
        className    = $ClassName
        ShowCollapse = $ShowCollapse.IsPresent
    }
}

function New-UDDynamic {
    <#
    .SYNOPSIS
    Defines a new dynamic region in a dashboard.
     
    .DESCRIPTION
    Defines a new dynamic region in a dashboard. Dynamic regions are used for loading data when the page is loaded or for loading data dynamically through user interaction or auto-reloading.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER ArgumentList
    Arguments to pass to this dynamic endpoint.
     
    .PARAMETER Content
    The content of this dynamic region.
     
    .PARAMETER AutoRefresh
    Whether this dynamic region should refresh on an interval.
     
    .PARAMETER AutoRefreshInterval
    The amount of seconds between refreshes for this dynamic region.
     
    .PARAMETER LoadingComponent
    A component to display while this dynamic region is loading.
     
    .EXAMPLE
    PS > New-UDDynamic -Content {
    PS > New-UDTypography -Text (Get-Date) -Id 'text1'
    PS > } -Id 'dynamic1'
 
    Basic dynamic region|A simple dynamic region that executes when the user loads the page.
 
    .EXAMPLE
    PS > New-UDDynamic -Content {
    PS > New-UDTypography -Text (Get-Date) -Id 'text2'
    PS > } -AutoRefresh -AutoRefreshInterval 3 -Id 'dynamic2'
 
    Auto refresh|A dynamic region that refreshes every 3 seconds
 
    .EXAMPLE
    PS > New-UDDynamic -Content {
    PS > New-UDTypography -Text (Get-Date) -Id 'text3'
    PS > } -Id 'dynamic3'
    PS > New-UDButton -Text 'Refresh' -OnClick {
    PS > Sync-UDElement -Id 'dynamic3'
    PS > }
 
    Sync-UDElement|A dynamic region that is updated when a button is clicked.
 
    .EXAMPLE
    PS > New-UDDynamic -Content {
    PS > Start-Sleep 5
    PS > New-UDTypography -Text (Get-Date) -Id 'text5'
    PS > } -Id 'dynamic4' -LoadingComponent {
    PS > New-UDTypography -Text 'Loading...' -Id 'text4'
    PS > }
    PS > New-UDButton -Text 'Refresh' -OnClick {
    PS > Sync-UDElement -Id 'dynamic4'
    PS > }
 
    Loading component|A dynamic region that displays a loading component while the dynamic region is loading.
 
    #>

    #[Component("Dynamic Region", "BoltLightning", "Creates a dynamic region.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [object[]]$ArgumentList,
        [Parameter(Position = 0, Mandatory)]
        [Endpoint]$Content,
        [Parameter()]
        [Switch]$AutoRefresh,
        [Parameter()]
        [int]$AutoRefreshInterval = 10,
        [Parameter()]
        [scriptblock]$LoadingComponent
    )

    $Content.ArgumentList = $ArgumentList
    $Content.Register($Id, $PSCmdlet)

    @{
        id                  = $Id 
        autoRefresh         = $AutoRefresh.IsPresent
        autoRefreshInterval = $AutoRefreshInterval
        type                = "dynamic"
        isPlugin            = $true 
        loadingComponent    = if ($LoadingComponent) { & $LoadingComponent } else { $null }
    }
}
function New-UDEditor {
    <#
    .SYNOPSIS
    Creates an Editor.JS editor.
     
    .DESCRIPTION
    Creates an Editor.JS editor.
     
    .PARAMETER Id
    The id of the editor. This defaults to a new GUID.
     
    .PARAMETER Data
    The data to use for the editor.
     
    .PARAMETER OnChange
    The endpoint to call when the editor changes.
     
    .PARAMETER Format
    The format of the data. This can be json or html.
     
    .PARAMETER PublishedFolder
    A published folder to upload files to. This is required for the image upload plugin.
     
    .EXAMPLE
    PS > New-UDEditor -Id 'editor1'
 
    Basic Editor|A basic editor.
 
    .EXAMPLE
    PS > New-UDEditor -Id 'editor2' -Format 'html'
 
    HTML Editor|An editor that uses HTML.
 
    .EXAMPLE
    PS > New-UDEditor -Id 'editor3' -OnChange {
    PS > Show-UDToast ($EventData | ConvertTo-Json)
    PS > }
 
    OnChange|An editor with an OnChange script block.
    #>

    [Category("app/component")]
    [Description("A WYSIWYG editor.")]
    [DisplayName("Editor")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [Hashtable]$Data,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [ValidateSet("json", "html")]
        [string]$Format = "json",
        [Parameter()]
        [string]$PublishedFolder
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet);
    }

    @{
        assetId         = $AssetId 
        isPlugin        = $true 
        type            = "ud-editor"
        id              = $Id

        onChange        = $OnChange
        data            = $Data
        format          = $Format.ToLower()
        publishedFolder = $PublishedFolder
    }
}
function New-UDError {
    <#
    .SYNOPSIS
    Creates a new error card.
     
    .DESCRIPTION
    Creates a new error card.
     
    .PARAMETER Message
    The message to display.
     
    .PARAMETER Title
    A title for the card.
     
    .EXAMPLE
    Displays the error 'Invalid data' on the page.
 
    New-UDError -Message 'Invalid data'
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [Parameter()]
        [string]$Title
    )

    @{
        type = "error"
        isPlugin = $true 
        assetId = $AssetId 

        errorRecords = @(@{message= $Message})
        title = $Title
    }
}
function New-UDErrorBoundary {
    <#
    .SYNOPSIS
    Defines a new error boundary around a section of code.
     
    .DESCRIPTION
    Defines a new error boundary around a section of code. Error boundaries are used to trap errors and display them on the page.
     
    .PARAMETER Content
    The content to trap in an error boundary.
     
    .EXAMPLE
    PS > New-UDErrorBoundary -Content {
    PS > throw 'This is an error'
    PS > }
 
    Error Boundary|Defines an error boundary that traps the exception that is thrown and displays it on the page.
 
    #>

    #[Component("Error Boundary", "Bug", "Creates a new card.")]
    param(
        [Parameter(Mandatory)]
        [ScriptBlock]$Content
    )

    try {
        & $Content 
    }
    catch {
        New-UDError -Message $_
    }
}
function Invoke-UDEvent {
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            Position = 0
        )]
        [String]$Id,
        [Parameter(
            Mandatory = $true,
            Position = 1,
            ParameterSetName = "onClick"
        )]
        [ValidateSet("onClick")]
        [string]$event
    )

    Begin {

    }

    Process {
        if ($PSCmdlet.ParameterSetName -eq "onClick") {
            Invoke-UDJavaScript -javaScript "
                document.getElementById('$Id').click();
            "

        }
    }

    End {

    }
}

function New-UDExpansionPanelGroup {
    <#
    .SYNOPSIS
    The expansion panel component allows the user to show and hide sections of related content on a page.
     
    .DESCRIPTION
    Creates a group of expansion panels. Use New-UDExpansionPanel to create children for a group.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    Expansion panels to include in this group.
     
    .PARAMETER Popout
    Creates a popout style expansion panel group.
     
    .PARAMETER Type
    The type of expansion panel group.
 
    .PARAMETER ClassName
    A CSS class to apply to the expansion panel group.
     
    .EXAMPLE
    PS > New-UDExpansionPanelGroup -Id 'expandsionPanelGroup1' -Children {
    PS > New-UDExpansionPanel -Title "Hello" -Content {} -Active -Id 'expansionPanel1'
    PS > New-UDExpansionPanel -Title "Hello" -Content {
    PS > New-UDElement -Tag 'div' -Content { "Hello" }
    PS > } -Id 'expansionPanel2'
    PS > New-UDExpansionPanel -Title "Hello" -Content {
    PS > New-UDElement -Tag 'div' -id 'expEndpointDiv' -Content { "Hello" }
    PS > } -Id 'expansionPanel3'
    PS > }
 
    Expansion Panel|Creates an expansion panel group.
 
    .EXAMPLE
    PS > New-UDExpansionPanelGroup -Id 'expandsionPanelGroup3' -Children {
    PS > New-UDExpansionPanel -Title "Hello" -Content {} -Id 'expansionPanel6'
    PS > New-UDExpansionPanel -Title "Hello" -Content {} -Id 'expansionPanel7'
    PS > } -Type 'Accordion'
 
    Accordion|Creates an accordion style expansion panel group.
 
    .EXAMPLE
    PS > New-UDExpansionPanelGroup -Id 'expandsionPanelGroup4' -Children {
    PS > New-UDExpansionPanel -Title "Hello" -Content {} -Icon (New-UDIcon -Icon 'Users') -Id 'expansionPanel8'
    PS > New-UDExpansionPanel -Title "Hello" -Content {} -Icon (New-UDIcon -Icon 'User') -Id 'expansionPanel9'
    PS > }
 
    Icons|Creates an expansion panel group with icons.
    #>

    #[Component("Expansion Panel", "Expand", "Creates a new card.")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter(Position = 0)]
        [Alias("Content")]
        [ScriptBlock]$Children = {},
        [Parameter()]
        [ValidateSet("Expandable", "Accordion")]
        [String]$Type = 'Expandable',
        [Parameter()]
        [string]$ClassName
    )
    
    $c = New-UDErrorBoundary -Content $Children

    @{
        type      = 'mu-expansion-panel-group'
        isPlugin  = $true
        assetId   = $AssetId

        id        = $id
        children  = $c
        accordion = $Type -eq 'Accordion'
        className = $ClassName
    }
}

function New-UDExpansionPanel {
    <#
    .SYNOPSIS
    Creates an expansion panel.
     
    .DESCRIPTION
    Creates an expansion panel. An expansion panel can hide content that isn't necessary to view when a page is loaded.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Title
    The title show within the header of the expansion panel.
     
    .PARAMETER Icon
    An icon to show within the header of the expansion panel.
     
    .PARAMETER Children
    Children components to put within the expansion panel.
     
    .PARAMETER Active
    Whether the expansion panel is currently active (open).
     
    .EXAMPLE
 
    Creates an expansion panel with some content.
     
    New-UDExpansionPanel -Title "Hello" -Id 'expContent' -Content {
        New-UDElement -Tag 'div' -id 'expContentDiv' -Content { "Hello" }
    }
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [String]$Title,
        [Parameter()]
        $Icon,
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        [Switch]$Active,
        [Parameter()]
        [string]$ClassName
    )

    $iconName = $Icon
    if ($PSBoundParameters.ContainsKey("Icon")) {
        if ($Icon -is [string]) {
            $iconName = [UniversalDashboard.Models.FontAwesomeIconsExtensions]::GetIconName($Icon)
        }
    }

    @{
        id        = $Id 
        title     = $Title 
        children  = & $Children
        active    = $Active.IsPresent
        icon      = $iconName
        className = $ClassName

    }
}
function New-UDFloatingActionButton {
    <#
    .SYNOPSIS
    A Floating Action Button (FAB) performs the primary, or most common, action on a screen.
 
    .DESCRIPTION
    Creates a new floating action button. Floating action buttons are good for actions that make sense for an entire page. They can be pinned to the bottom of a page.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Icon
    The icon to put within the floating action button.
     
    .PARAMETER Size
    The size of the button. Valid values are: "small", "medium", "large"
     
    .PARAMETER OnClick
    A script block to execute when the floating action button is clicked.
 
    .PARAMETER Color
    The theme color to apply to the button. Valid values are: "Default", "Primary", "Secondary"
     
    .PARAMETER ClassName
    A CSS class to apply to the floating action button.
 
    .PARAMETER Position
    The position of the floating action button. Valid values are: "Relative", "BottomLeft", "BottomRight"
     
    .EXAMPLE
    PS > New-UDFloatingActionButton -Icon (New-UDIcon -Icon 'user') -OnClick {
    PS > Show-UDToast -Message 'Hello'
    PS > } -Id 'fab1' -Position Relative
 
    Basic floating action button|Creates a floating action button with a user icon and shows a toast when clicked.
 
    .EXAMPLE
    PS > New-UDFloatingActionButton -Icon (New-UDIcon -Icon 'user') -Size Small -OnClick {
    PS > Show-UDToast -Message 'Hello'
    PS > } -Id 'fab2' -Position Relative
 
    Small floating action button|Creates a small floating action button with a user icon and shows a toast when clicked.
 
    .EXAMPLE
    PS > New-UDFloatingActionButton -Icon (New-UDIcon -Icon 'user') -Position BottomRight -OnClick {
    PS > Show-UDToast -Message 'Hello'
    PS > } -Id 'fab3'
 
    Position|Creates a floating action button with a user icon and shows a toast when clicked. The button is positioned in the bottom right of the page.
 
    #>

    [Category("app/component")]
    [Description("Floating action buttons are good for actions that make sense for an entire page. They can be pinned to the bottom of a page.")]
    [DisplayName("Floating Action Button")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        $Icon,
        [Parameter()]
        [ValidateSet("small", "medium", "large")]
        [string]$Size = "large",
        [Parameter()]
        [Endpoint]$OnClick,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [ValidateSet('default', 'primary', 'secondary')]
        [string]$Color,
        [Parameter()]
        [ValidateSet('Relative', 'BottomLeft', 'BottomRight')]
        [string]$Position = 'BottomRight'
    )

    if ($OnClick) {
        $OnClick.Register($Id, $PSCmdlet)
    }

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    @{
        type            = "mu-fab"
        assetId         = $AssetId
        isPlugin        = $true 

        id              = $id
        size            = $Size.tolower()
        backgroundColor = $ButtonColor.HtmlColor
        color           = $IconColor.HtmlColor
        icon            = $icon
        onClick         = $OnClick
        className       = $ClassName
        themeColor      = $Color.ToLower()
        position        = $Position.ToLower()
    }
}
function New-UDForm {
    <#
    .SYNOPSIS
    Forms can contain any set of input controls. Each of the controls will report its value back up to the form when the submit button is clicked.
     
    .DESCRIPTION
    Creates a new form. Forms can contain any set of input controls. Each of the controls will report its value back up to the form when the submit button is clicked.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    Controls that make up this form. This can be any combination of controls. Input controls will report their state to the form.
     
    .PARAMETER OnSubmit
    A script block that is execute when the form is submitted. You can return controls from this script block and the form will be replaced by the script block. The $EventData variable will contain a hashtable of all the input fields and their values.
     
    .PARAMETER OnValidate
    A script block that validates the form. Return the result of a call to New-UDFormValidationResult.
 
    .PARAMETER OnProcessing
    A script block that is called when the form begins processing. The return value of this script block should be a component that displays a loading dialog. The script block will receive the current form data.
 
    .PARAMETER OnCancel
    A script block that is called when a form is cancelled. Useful for closing forms in modals.
 
    .PARAMETER SubmitText
    Text to show within the submit button. Defaults to 'Submit'.
 
    .PARAMETER CancelText
    Text to show within the cancel button. Defaults to 'Cancel'.
 
    .PARAMETER ButtonVariant
    Type of button to display. Defaults to text. Valid values are contained, outlined, text.
     
    .PARAMETER ClassName
    A CSS class to apply to the form.
 
    .PARAMETER Schema
    Defines a form based on a hashtable of schema. This version of forms is based on react-jsonschema-form.
 
    .PARAMETER UISchema
    Used to modify the ordering of the fields (see documentation)
     
    .PARAMETER DisableSubmitOnEnter
    Disables the functionality where pressing enter in a textbox submits the form it is a part of.
 
    .PARAMETER Script
    Used to automatically generate forms based on scripts in your PowerShell Universal environment. Script forms will generate input components based on the param block. Script forms automatically support progress and feedback.
     
    .PARAMETER OutputType
    The type of output to return from the script. Valid values are: "Text", "Table"
 
    .PARAMETER ShowBackButton
    Shows a back button after the form is submitted. This is only used for script forms.
 
     
    .EXAMPLE
    PS > New-UDForm -Id 'form1' -Content {
    PS > New-UDTextbox -Id 'form1Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form1Textbox)
    PS > }
 
    Basic|Creates a basic form.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form2' -Content {
    PS > New-UDTextbox -Id 'form2Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form2Textbox)
    PS > } -OnValidate {
    PS > if ($EventData.form2Textbox -eq 'Bob') {
    PS > New-UDValidationResult -ValidationError 'Bob is not allowed'
    PS > }
    PS > else {
    PS > New-UDValidationResult -Valid
    PS > }
    PS > }
 
    Validation|Creates a form with validation.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form3' -Content {
    PS > New-UDTextbox -Id 'form3Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Start-Sleep -Seconds 5
    PS > Show-UDToast -Message ($EventData.form3Textbox)
    PS > } -OnProcessing {
    PS > New-UDCard -Title 'Processing' -Content {
    PS > New-UDProgress
    PS > }
    PS > }
 
    Processing|Creates a form with a processing dialog.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form4' -Content {
    PS > New-UDTextbox -Id 'form4Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form4Textbox)
    PS > } -OnCancel {
    PS > Show-UDToast -Message 'Form was cancelled'
    PS > }
 
    Cancel|Creates a form with a cancel button.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form5' -Content {
    PS > New-UDTextbox -Id 'form5Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form5Textbox)
    PS > } -SubmitText 'Save' -CancelText 'Close' -OnCancel {
    PS > Show-UDToast -Message 'Form was cancelled'
    PS > }
 
    Submit and Cancel Text|Creates a form with custom submit and cancel text.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form6' -Content {
    PS > New-UDTextbox -Id 'form6Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form6Textbox)
    PS > } -ButtonVariant 'contained'
 
    Button Variant|Creates a form with a contained button variant.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form7' -Content {
    PS > New-UDTextbox -Id 'form7Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form7Textbox)
    PS > } -ClassName 'my-form'
 
    Class Name|Creates a form with a custom CSS class name.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form8' -Schema @{
    PS > title = "Test Form"
    PS > type = "object"
    PS > properties = @{
    PS > name = @{
    PS > type = "string"
    PS > }
    PS > age = @{
    PS > type = "number"
    PS > }
    PS > }
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.name)
    PS > Show-UDToast -Message ($EventData.age)
    PS > }
 
    Schema|Creates a form with a schema. You can find out more about JSON Schema here: https://rjsf-team.github.io/react-jsonschema-form/docs/
 
    .EXAMPLE
    PS > New-UDForm -Id 'form9' -Schema @{
    PS > title = "Test Form"
    PS > type = "object"
    PS > properties = @{
    PS > name = @{
    PS > type = "string"
    PS > }
    PS > age = @{
    PS > type = "number"
    PS > }
    PS > }
    PS > } -uiSchema @{
    PS > "ui:order" = @('age','name')
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.name)
    PS > }
 
    UI Schema|Creates a form with a schema and a UI schema. You can find out more about JSON Schema here: https://rjsf-team.github.io/react-jsonschema-form/docs/
 
    .EXAMPLE
    PS > $Script = Get-PSUScript -Name "TestScript.ps1"
    PS > if ($Script -eq $null) {
    PS > New-UDAlert -Text "Create a script in the admin console named 'TestScript.ps1' to run this example."
    PS > }
    PS > else {
    PS > New-UDForm -Id 'form10' -Script "TestScript.ps1" -OutputType "Table"
    PS > }
 
    Script|Creates a form from a script. Parameters of the script will define the form. Output from the script will be displayed in a table.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form11' -Content {
    PS > New-UDTextbox -Id 'form11Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form11Textbox)
    PS > }
    PS > New-UDButton -Text "A submit button outside the form" -OnClick {
    PS > Invoke-UDForm -Id "form11"
    PS > }
 
    Invoke-UDForm|Creates a form with a submit button outside the form.
 
    .EXAMPLE
    PS > New-UDForm -Id 'form12' -Content {
    PS > New-UDTextbox -Id 'form12Textbox' -Label 'Name'
    PS > } -OnSubmit {
    PS > Show-UDToast -Message ($EventData.form12Textbox)
    PS > } -OnValidate {
    PS > if ($EventData.form12Textbox -eq 'Hello') {
    PS > New-UDValidationResult -Valid
    PS > }
    PS > else {
    PS > New-UDValidationResult -ValidationError "Name must be 'Hello'"
    PS > }
    PS > }
    PS > New-UDButton -Text "Validate form" -OnClick {
    PS > Test-UDForm -Id "form12"
    PS > }
     
    Test-UDForm|Creates a form with a validate button outside the form.
    #>

    [Category("app/component")]
    [Description("A form that allows the end user to enter data and submit it to the server.")]
    [DisplayName("Form")]
    [CmdletBinding(DefaultParameterSetName = 'script')]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter(Mandatory, ParameterSetName = 'form')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter(Mandatory, ParameterSetName = 'form')]
        [Parameter(Mandatory, ParameterSetName = 'schema')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Endpoint]$OnSubmit,
        [Parameter(ParameterSetName = 'form')]
        [Parameter(ParameterSetName = 'script')]
        [Endpoint]$OnValidate,
        [Parameter(ParameterSetName = 'form')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [ScriptBlock]$OnProcessing,
        [Parameter(ParameterSetName = 'form')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Endpoint]$OnCancel,
        [Parameter(ParameterSetName = 'form')]
        [Parameter(ParameterSetName = 'script')]
        [string]$SubmitText = 'Submit',
        [Parameter(ParameterSetName = 'form')]
        [Parameter(ParameterSetName = 'script')]
        [string]$CancelText = 'Cancel',
        [ValidateSet('text', 'contained', 'outlined')]
        [Parameter()]
        [string]$ButtonVariant = 'text',
        [Parameter()]
        [string]$ClassName,
        [Parameter(Mandatory = $true, ParameterSetName = 'schema')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Hashtable]$Schema,
        [Parameter(ParameterSetName = 'schema')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Hashtable]$UiSchema = @{},
        [Parameter()]
        [switch]$DisableSubmitOnEnter,
        [Parameter(ParameterSetName = 'script')]
        [string]$Script = "None",
        [Parameter(ParameterSetName = 'script')]
        [ValidateSet("Text", "Table")]
        [string]$OutputType = "Text",
        [Parameter(ParameterSetName = 'script')]
        [Switch]$ShowBackButton
    )

    if ($null -ne $OnValidate) {
        $OnValidate.Register($id + "validate", $PSCmdlet) 
    }

    $LoadingComponent = $null 
    if ($null -ne $OnProcessing) {
        $LoadingComponent = New-UDErrorBoundary -Content $OnProcessing
    }

    if ($OnCancel) {
        $OnCancel.Register($id + 'cancel', $PSCmdlet)
    }

    if ($OnSubmit) {
        $OnSubmit.Register($id, $PSCmdlet)
    }

    try {
        $c = New-UDErrorBoundary -Content $Children 
    }
    catch {
        $c = New-UDError -Message $_
    }

    if ($PSCmdlet.ParameterSetName -eq 'schema') {
        @{
            id       = $Id 
            assetId  = $MUAssetId 
            isPlugin = $true 
            type     = "mu-schema-form"
    
            onSubmit = $OnSubmit 
            schema   = $Schema
            uiSchema = $UiSchema
        }
    } 
    elseif ($PSCmdlet.ParameterSetName -eq 'script') {
        if ($Script -eq 'None' -or $Script -eq $null) {
            New-UDAlert -Text 'Select a script to create this form.'
            return
        }

        New-UDDynamic -Id $Id -Content {
            $ScriptObj = Get-PSUScript -Name $Script  -Integrated

            if (-not $ScriptObj) {
                New-UDAlert -Text 'Script Not Found!' -Severity 'error'
                return
            }

            $Parameters = Get-PSUScriptParameter -Script $ScriptObj -Integrated

            $validate = $OnValidate 
            if (-not $OnValidate) {
                $validate = {
                    foreach ($Parameter in $Parameters) {
                        $Value = $EventData.($Parameter.Name)
                        if ($Parameter.Required -and [string]::IsNullOrEmpty($Value)) {
                            New-UDValidationResult -ValidationError "$($Parameter.Name) is required."
                            return
                        }
                    }
    
                    New-UDValidationResult -Valid
                } 
            }
    
            New-UDForm -Id $Id -Content {
                New-UDStack -Spacing 1 -Direction 'column' -Content {
                    $Parameters | ForEach-Object {
                        $Parameter = $_
                        $Label = if ($Parameter.DisplayName) { $Parameter.DisplayName } else { $Parameter.Name }

                        switch ($_.DisplayType) {
                            "String" { New-UDTextbox -Id $Parameter.Name -Label $Label -HelperText $Parameter.HelpText }
                            "Integer" { New-UDTextbox -Id $Parameter.Name -Label $Label -HelperText $Parameter.HelpText }
                            "Boolean" { New-UDSwitch -Id $Parameter.Name -Label $Label }
                            "DateTime" { New-UDDatePicker -Id $Parameter.Name -Label $Label }
                            "Select" {
                                New-UDSelect -Id $Parameter.Name -Label $Label -Option {
                                    $Parameter.ValidValues | ForEach-Object {
                                        New-UDSelectOption -Name $_ -Value $_
                                    }
                                } 
                            }
                            default { New-UDTextbox -Id $Parameter.Name -Label $Label -HelperText $Parameter.HelpText }
                        }
                    }
                }
            } -OnSubmit {
                $Values = @{}
                $Parameters | ForEach-Object {
                    $Values[$_.Name] = $EventData.($_.Name)
                }
                try {
                    $Result = Invoke-PSUScript -Script $ScriptObj -Integrated -Wait @Values -ErrorAction Stop
                    if ($OutputType -eq 'Table' -and $Result) {
                        New-UDTable -Data $Result -ShowPagination
                    }
                    elseif ($OutputType -eq 'Text' -and $Result) {
                        New-UDSyntaxHighlighter -Code ($Result | Out-String) -Language batch
                    }
                    else {
                        New-UDAlert -Text 'Form submitted successfully!'
                    }
                }
                catch {
                    New-UDAlert -Text "Failed to submit form! $_" -Severity error 
                }

                if ($ShowBackButton) {
                    New-UDButton -Text 'Back' -Icon (New-UDIcon -Icon 'BackwardStep') -OnClick {
                        Sync-UDElement -Id $Id
                    }
                }

            } -OnValidate $validate
        }
    }
    else {
        @{
            id                   = $Id 
            assetId              = $MUAssetId 
            isPlugin             = $true 
            type                 = "mu-form"

            onSubmit             = $OnSubmit 
            onValidate           = $OnValidate
            loadingComponent     = $LoadingComponent
            children             = $c
            onCancel             = $OnCancel
            cancelText           = $CancelText
            submitText           = $SubmitText
            buttonVariant        = $ButtonVariant 
            className            = $ClassName
            disableSubmitOnEnter = $DisableSubmitOnEnter.IsPresent
        }
    }
}

New-Alias -Name 'New-UDFormValidationResult' -Value 'New-UDValidationResult'

function New-UDValidationResult {
    <#
    .SYNOPSIS
    Creates a new validation result.
     
    .DESCRIPTION
    Creates a new validation result. This cmdlet should return its value from the OnValidate script block parameter on New-UDForm or New-UDStepper.
     
    .PARAMETER Valid
    Whether the status is considered valid.
     
    .PARAMETER ValidationError
    An error to display if the is not valid.
 
    .PARAMETER Context
    Update the context based on validation. This is only used for New-UDStepper.
 
    .PARAMETER DisablePrevious
    Disables the previous button. This is only used for New-UDStepper.
 
    .PARAMETER ActiveStep
    Sets the active step. This is only used for New-UDStepper.
 
    #>

    param(
        [Parameter()]
        [Switch]$Valid,
        [Parameter()]
        [string]$ValidationError = "Form is invalid.",
        [Parameter()]
        [HashTable]$Context,
        [Parameter()]
        [Switch]$DisablePrevious,
        [Parameter()]
        [int]$ActiveStep = -1
    )

    @{
        valid           = $Valid.IsPresent
        validationError = $ValidationError
        context         = $Context 
        disablePrevious = $DisablePrevious.IsPresent
        activeStep      = $ActiveStep
    }
}

function Test-UDForm {
    <#
    .SYNOPSIS
    Invokes validation for a form.
     
    .DESCRIPTION
    Invokes validation for a form.
     
    .PARAMETER Id
    Id of the form to invoke validation for.
     
    .EXAMPLE
    New-UDButton -Text 'Validate' -OnClick {
        Test-UDForm -Id 'myForm'
    }
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Id
    )

    $DashboardHub.SendWebSocketMessage($ConnectionId, "testForm", $Id)
}

function Invoke-UDForm {
    <#
    .SYNOPSIS
    Invokes a form.
     
    .DESCRIPTION
    Invokes a form and optionally validates it.
     
    .PARAMETER Id
    The ID of the form to invoke.
     
    .PARAMETER Validate
    Whether to run form validation.
     
    .EXAMPLE
    Invoke-UDForm -Id "MyForm" -Validate
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Id,
        [Parameter()]
        [Switch]$Validate
    )

    $Data = @{
        method   = "invokeForm"
        id       = $Id 
        validate = $Validate.IsPresent
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "invokeMethod", $Data)
}
function New-UDGridLayout {
    <#
    .SYNOPSIS
    Creates a new grid layout. Grid layout allows you to drag and drop components into a grid.
     
    .DESCRIPTION
    Creates a new grid layout. Grid layout allows you to drag and drop components into a grid.
     
    .PARAMETER Id
    The ID of this component. The default value is a random GUID.
     
    .PARAMETER RowHeight
    The height of each row in pixels. The default value is 30.
     
    .PARAMETER Content
    The content of the grid layout. This is a scriptblock that returns a list of components. Components are required to have static IDs.
     
    .PARAMETER Layout
    The layout of the grid. This should be a JSON string.
     
    .PARAMETER LargeColumns
    The number of columns in the large breakpoint. The default value is 12.
     
    .PARAMETER MediumColumns
    The number of columns in the medium breakpoint. The default value is 10.
     
    .PARAMETER SmallColumns
    The number of columns in the small breakpoint. The default value is 6.
     
    .PARAMETER ExtraSmallColumns
    The number of columns in the extra small breakpoint. The default value is 4.
     
    .PARAMETER ExtraExtraSmallColumns
    The number of columns in the extra extra small breakpoint. The default value is 2.
     
    .PARAMETER LargeBreakpoint
    The width of the large breakpoint in pixels. The default value is 1200.
     
    .PARAMETER MediumBreakpoint
    The width of the medium breakpoint in pixels. The default value is 996.
     
    .PARAMETER SmallBreakpoint
    The width of the small breakpoint in pixels. The default value is 768.
     
    .PARAMETER ExtraSmallBreakpoint
    The width of the extra small breakpoint in pixels. The default value is 480.
     
    .PARAMETER ExtraExtraSmallBreakpoint
    The width of the extra extra small breakpoint in pixels. The default value is 0.
     
    .PARAMETER Draggable
    Whether or not the components can be dragged.
     
    .PARAMETER Resizable
    Whether or not the components can be resized.
     
    .PARAMETER Persist
    Whether or not the layout should be persisted in the current browser session.
     
    .PARAMETER Design
    Whether or not the grid layout is in design mode. Design mode allows for copying of the current JSON layout.
     
    .EXAMPLE
    PS > New-UDGridLayout -Content {
    PS > New-UDCard -Text "Card 1" -Id "card1"
    PS > New-UDCard -Text "Card 2" -Id "card2"
    PS > } -Id 'gridLayout1' -Draggable -Resizable -Layout '{"lg":[{"w":2,"h":3,"x":0,"y":0,"i":"grid-element-card1","moved":false,"static":false},{"w":2,"h":3,"x":2,"y":0,"i":"grid-element-card2","moved":false,"static":false}]}'
 
    Basic Grid Layout|Create a basic grid layout with two cards.
 
    .EXAMPLE
    PS > New-UDGridLayout -Content {
    PS > New-UDCard -Text "Card 3" -Id "card3"
    PS > New-UDCard -Text "Card 4" -Id "card4"
    PS > } -Id 'gridLayout2' -Design -Draggable -Resizable -Layout '{"lg":[{"w":2,"h":3,"x":0,"y":0,"i":"grid-element-card3","moved":false,"static":false},{"w":2,"h":3,"x":2,"y":0,"i":"grid-element-card4","moved":false,"static":false}]}'
 
    Draggable and Resizable|Create a grid layout with two cards that can be dragged and resized. This layout is also in design mode which allows you to copy the current layout as JSON.
 
    .EXAMPLE
    PS > New-UDGridLayout -Content {
    PS > New-UDCard -Text "Card 5" -Id "card5"
    PS > New-UDCard -Text "Card 6" -Id "card6"
    PS > } -Id 'gridLayout3' -Design -Draggable -Resizable -Persist -Layout '{"lg":[{"w":2,"h":3,"x":0,"y":0,"i":"grid-element-card5","moved":false,"static":false},{"w":2,"h":3,"x":2,"y":0,"i":"grid-element-card6","moved":false,"static":false}]}'
 
    Persisted Layout|This layout is also persisted in the current browser session. Refresh the page to see the persisted layout.
 
    #>

    #[Component("Grid Layout", "TableLayout", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [int]$RowHeight = 30,
        [Parameter(Mandatory)]
        [scriptblock]$Content,
        [Parameter()]
        [string]$Layout,
        [Parameter()]
        [int]$LargeColumns = 12,
        [Parameter()]
        [int]$MediumColumns = 10,
        [Parameter()]
        [int]$SmallColumns = 6,
        [Parameter()]
        [int]$ExtraSmallColumns = 4,
        [Parameter()]
        [int]$ExtraExtraSmallColumns = 2,
        [Parameter()]
        [int]$LargeBreakpoint = 1200,
        [Parameter()]
        [int]$MediumBreakpoint = 996,
        [Parameter()]
        [int]$SmallBreakpoint = 768,
        [Parameter()]
        [int]$ExtraSmallBreakpoint = 480,
        [Parameter()]
        [int]$ExtraExtraSmallBreakpoint = 0,
        [Parameter()]
        [switch]$Draggable,
        [Parameter()]
        [switch]$Resizable,
        [Parameter()]
        [switch]$Persist,
        [Parameter()]
        [switch]$Design
    )

    End {
        $Breakpoints = @{
            lg  = $LargeBreakpoint
            md  = $MediumBreakpoint
            sm  = $SmallBreakpoint
            xs  = $ExtraSmallBreakpoint
            xxs = $ExtraExtraSmallBreakpoint
        }

        $Columns = @{
            lg  = $LargeColumns
            md  = $MediumColumns
            sm  = $SmallColumns
            xs  = $ExtraSmallColumns
            xxs = $ExtraExtraSmallColumns
        }

        @{
            type        = "ud-grid-layout"
            isPlugin    = $true
            id          = $Id
            assetId     = $MUAssetId
            className   = "layout"
            rowHeight   = $RowHeight
            children    = & $Content
            layout      = $Layout
            cols        = $Columns
            breakpoints = $Breakpoints
            isDraggable = $Draggable.IsPresent
            isResizable = $Resizable.IsPresent
            persist     = $Persist.IsPresent
            design      = $Design.IsPresent
        }
    }
}

function New-UDPageLayout {
    param(
        [Parameter()]
        [PowerShellUniversal.DesignerItemLayout[]]$Large,
        [Parameter()]
        [PowerShellUniversal.DesignerItemLayout[]]$Medium,
        [Parameter()]
        [PowerShellUniversal.DesignerItemLayout[]]$Small,
        [Parameter()]
        [PowerShellUniversal.DesignerItemLayout[]]$ExtraSmall,
        [Parameter()]
        [PowerShellUniversal.DesignerItemLayout[]]$ExtraExtraSmall
    )

    $Layout = [PowerShellUniversal.DesignerPageLayout]::new()
    $Layout.Large = $Large
    $Layout.Medium = $Medium
    $Layout.Small = $Small
    $Layout.ExtraSmall = $ExtraSmall
    $Layout.ExtraExtraSmall = $ExtraExtraSmall
    $Layout
}

function New-UDItemLayout {
    param(
        [Parameter(Mandatory)]
        [string]$Id,
        [Parameter(Mandatory)]
        [int]$Row,
        [Parameter(Mandatory)]
        [int]$Column,
        [Parameter()]
        [int]$ColumnSpan = 1,
        [Parameter()]
        [int]$RowSpan = 1
    )

    $Layout = [PowerShellUniversal.DesignerItemLayout]::new()
    $Layout.Id = $Id
    $Layout.Row = $Row
    $Layout.Column = $Column
    $Layout.ColumnSpan = $ColumnSpan
    $Layout.RowSpan = $RowSpan
    $Layout
}
function New-UDGrid {
    <#
    .SYNOPSIS
    The grid is a 12-point grid system that can adapt based on the size of the screen that is showing the controls.
     
    .DESCRIPTION
    Creates a grid to layout components. The grid is a 12-point grid system that can adapt based on the size of the screen that is showing the controls.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER ExtraSmallSize
    The size (1-12) for extra small devices.
     
    .PARAMETER SmallSize
    The size (1-12) for small devices.
     
    .PARAMETER MediumSize
    The size (1-12) for medium devices.
     
    .PARAMETER LargeSize
    The size (1-12) for large devices.
     
    .PARAMETER ExtraLargeSize
    The size (1-12) for extra large devices.
     
    .PARAMETER Container
    Whether this is a container. A container can be best described as a row.
     
    .PARAMETER Spacing
    Spacing between the items.
     
    .PARAMETER Item
    Whether this is an item in a container.
     
    .PARAMETER Children
    Components included in this grid item.
     
    .PARAMETER ClassName
    A CSS class to apply to the grid.
 
    .PARAMETER Direction
    The direction of the grid. Can be row, column, row-reverse or column-reverse.
 
    .PARAMETER RowSpacing
    The spacing between rows.
 
    .PARAMETER ColumnSpacing
    The spacing between columns.
 
    .PARAMETER JustifyContent
    The alignment of the grid items along the main axis. Can be flex-start, flex-end, center, space-between, space-around or space-evenly.
 
    .PARAMETER AlignItems
    The alignment of the grid items along the cross axis. Can be flex-start, flex-end, center, baseline or stretch.
 
    .EXAMPLE
    PS > New-UDGrid -Container -Children {
    PS > New-UDGrid -Item -ExtraSmallSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 1"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 2"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 3"
    PS > }
    PS > }
    PS > } -Id "grid1"
 
    Basic grid|A basic grid with 3 cards in a row.
 
    .EXAMPLE
    PS > New-UDGrid -Container -Children {
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 1"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 2"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 3"
    PS > }
    PS > }
    PS > } -Id "grid2"
 
    Responsive Grid|A responsive grid with 3 cards in a row. The cards will stack on small device screens.
 
    .EXAMPLE
    PS > New-UDDynamic -Id 'grid4' -Content {
    PS > $Spacing = (Get-UDElement -Id 'gridSpacingSelect').value
    PS > New-UDGrid -Container -Children {
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 1"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 2"
    PS > }
    PS > }
    PS > New-UDGrid -Item -ExtraSmallSize 12 -LargeSize 4 -Children {
    PS > New-UDCard -Title "Card 1" -Content {
    PS > New-UDButton -Text "Button 3"
    PS > }
    PS > }
    PS > } -Spacing $Spacing
    PS > }
    PS > New-UDSelect -Id 'gridSpacingSelect' -Label Spacing -Option {
    PS > for ($i = 0; $i -lt 10; $i++) {
    PS > New-UDSelectOption -Name $i -Value $i
    PS > }
    PS >} -OnChange { Sync-UDElement -Id 'grid4' } -DefaultValue 3
 
    Grid Spacing|A responsive grid with 3 cards in a row. The spacing between the cards can be changed with the select box.
 
    .EXAMPLE
    PS > New-UDRow -Columns {
    PS > New-UDColumn -Size 4 -Content {
    PS > New-UDCard -Title "Card 1" -Content {}
    PS > }
    PS > New-UDColumn -Size 4 -Content {
    PS > New-UDCard -Title "Card 2" -Content {}
    PS > }
    PS > New-UDColumn -Size 4 -Content {
    PS > New-UDCard -Title "Card 3" -Content {}
    PS > }
    PS > } -Id 'grid5'
 
    Row and Columns|The row and column aliases can be used to create a grid.
 
    .EXAMPLE
    PS > $Session:direction = 'row'
    PS > $Session:justifyContent = 'center'
    PS > $Session:alignItems = 'center'
    PS > New-UDSelect -Label 'direction' -Option {
    PS > New-UDSelectOption -Name 'row'
    PS > New-UDSelectOption -Name 'row-reverse'
    PS > New-UDSelectOption -Name 'column'
    PS > New-UDSelectOption -Name 'column-reverse'
    PS > } -DefaultValue 'row' -OnChange {
    PS > $Session:direction = $EventData
    PS > Sync-UDElement -Id 'example'
    PS > }
    PS > New-UDSelect -Label 'justifyContent' -Option {
    PS > New-UDSelectOption -Name 'flex-start'
    PS > New-UDSelectOption -Name 'center'
    PS > New-UDSelectOption -Name 'flex-end'
    PS > New-UDSelectOption -Name 'space-between'
    PS > New-UDSelectOption -Name 'space-around'
    PS > New-UDSelectOption -Name 'space-evenly'
    PS > } -DefaultValue 'center' -OnChange {
    PS > $Session:justifyContent = $EventData
    PS > Sync-UDElement -Id 'example'
    PS > }
    PS > New-UDSelect -Label 'alignItems' -Option {
    PS > New-UDSelectOption -Name 'flex-start'
    PS > New-UDSelectOption -Name 'center'
    PS > New-UDSelectOption -Name 'flex-end'
    PS > New-UDSelectOption -Name 'stretch'
    PS > New-UDSelectOption -Name 'baseline'
    PS > } -DefaultValue 'center' -OnChange {
    PS > $Session:alignItems = $EventData
    PS > Sync-UDElement -Id 'example'
    PS > }
    PS > New-UDDynamic -Id 'example' -Content {
    PS > New-UDGrid -Container -Children {
    PS > New-UDGrid -Item -Children {
    PS > New-UDPaper -Children { "Cell 1" } -Style @{
    PS > backgroundColor = 'rgb(26, 32, 39)'
    PS > color = 'white'
    PS > }
    PS > }
    PS > New-UDGrid -Item -Children {
    PS > New-UDPaper -Children { "Cell 2" } -Style @{
    PS > backgroundColor = 'rgb(26, 32, 39)'
    PS > color = 'white'
    PS > }
    PS > }
    PS > New-UDGrid -Item -Children {
    PS > New-UDPaper -Children { "Cell 3" } -Style @{
    PS > backgroundColor = 'rgb(26, 32, 39)'
    PS > color = 'white'
    PS > }
    PS > }
    PS > } -Direction $Session:Direction -JustifyContent $Session:justifyContent -AlignItems $Session:alignItems
    PS > }
 
    Alignment|The alignment of the grid items can be changed with the direction, justifyContent and alignItems parameters.
 
    #>

    #[Component("Grid", "Grid", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Alias("Size")]
        [ValidateRange(1, 12)]
        [Parameter(ParameterSetName = "Item")]
        [int]$ExtraSmallSize,
        [Parameter(ParameterSetName = "Item")]
        [ValidateRange(1, 12)]
        [int]$SmallSize,
        [Parameter(ParameterSetName = "Item")]
        [ValidateRange(1, 12)]
        [int]$MediumSize,
        [Parameter(ParameterSetName = "Item")]
        [ValidateRange(1, 12)]
        [int]$LargeSize,
        [Parameter(ParameterSetName = "Item")]
        [ValidateRange(1, 12)]
        [int]$ExtraLargeSize,
        [Parameter(ParameterSetName = "Container")]
        [Switch]$Container,
        [Parameter(ParameterSetName = "Container")]
        [ValidateRange(0, 10)]
        [int]$Spacing,
        [Parameter(ParameterSetName = "Item")]
        [Switch]$Item,
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [ValidateSet('row', 'column', 'column-reverse', 'row-reverse')]
        [string]$Direction = 'row',
        [Parameter()]
        $RowSpacing,
        [Parameter()]
        $ColumnSpacing,
        [Parameter()]
        [ValidateSet('flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly')]
        [string]$JustifyContent,
        [Parameter()]
        [ValidateSet('flex-start', 'flex-end', 'center', 'baseline', 'stretch')]
        [string]$AlignItems 
    )

    End {
        $c = New-UDErrorBoundary -Content $Children

        @{
            id             = $Id 
            isPlugin       = $true 
            type           = "mu-grid"
            assetId        = $MUAssetId

            xs             = $ExtraSmallSize
            sm             = $SmallSize
            md             = $MediumSize
            lg             = $LargeSize
            xl             = $ExtraLargeSize
            spacing        = $Spacing

            container      = $Container.IsPresent
            item           = $Item.IsPresent

            children       = $c

            className      = $ClassName

            direction      = $Direction.ToLower()
            rowSpacing     = $RowSpacing
            columnSpacing  = $ColumnSpacing
            justifyContent = $JustifyContent.ToLower()
            alignItems     = $AlignItems.ToLower()
        }
    }
}
function New-UDHeading {
    <#
    .SYNOPSIS
    Defines a new heading
     
    .DESCRIPTION
    Defines a new heading. This generates a new H tag.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Content
    The content of the heading.
     
    .PARAMETER Text
    The text of the heading.
     
    .PARAMETER Size
    This size of this heading. This can be 1,2,3,4,5 or 6.
     
    .PARAMETER Color
    The text color.
     
    .PARAMETER ClassName
    A CSS class to apply to the heading.
 
    .EXAMPLE
    PS > New-UDHeading -Text 'Hello World' -Size 1 -Color 'red'
 
    Basic Heading|Creates a basic heading.
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter(ParameterSetName = "Content")]
        [ScriptBlock]$Content,
        [Parameter(ParameterSetName = "Text")]
        [string]$Text,
        [Parameter()]
        [ValidateSet("1", "2", "3", "4", "5", "6")]
        [string]$Size,
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$Color = 'black',
        [Parameter()]
        [string]$ClassName
    )

    if ($PSCmdlet.ParameterSetName -eq "Text") {
        $Content = { $Text }
    }

    New-UDElement -Id $Id -Tag "h$size" -Content $Content -Attributes @{
        className = $className
        style     = @{
            color = $Color.HtmlColor
        }
    }
}
function New-UDHelmet {
    <#
    .SYNOPSIS
    Add elements to the head section of the page. This is useful for adding custom CSS or JavaScript.
     
    .DESCRIPTION
    Add elements to the head section of the page. This is useful for adding custom CSS or JavaScript.
     
    .PARAMETER Tag
    The HTML tag to add.
     
    .PARAMETER Attributes
    A hashtable of attributes to add to the tag.
     
    .PARAMETER Children
    A script block that returns a hashtable of children to add to the tag.
     
    .EXAMPLE
    PS > New-UDHelmet -Tag 'style' -Attributes @{ type = 'text/css' } -Children { "body { background-color: 'red'}" }
 
    Style Tag|Adds a style tag to the head section of the page.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Tag, 
        [Parameter()]
        [Hashtable]$Attributes = @{},
        [Parameter()]
        [string]$Content
    )

    @{
        type       = 'mu-helmet'
        tag        = $Tag
        attributes = $Attributes
        content    = $Content
    }
}
function New-UDHidden {
    <#
    .SYNOPSIS
    Creates a new hidden element.
     
    .DESCRIPTION
    Creates a new hidden element.
     
    .PARAMETER Id
    The ID of this component. Defaults to a random GUID.
     
    .PARAMETER Down
    Content will be hidden at or below the specified breakpoint.
     
    .PARAMETER Up
    Content will be hidden at or above the specified breakpoint.
     
    .PARAMETER Only
    Content will be hidden at the specified breakpoint.
     
    .PARAMETER Children
    The content to be hidden.
     
    .EXAMPLE
    PS > New-UDHidden -Up md -Content {
    PS > New-UDTypography 'md'
    PS > }
 
    Up|Content is hidden above the md breakpoint.
 
    .EXAMPLE
    PS > New-UDHidden -Down md -Content {
    PS > New-UDTypography 'md'
    PS > }
 
    Down|Content is hidden below the md breakpoint.
 
    .EXAMPLE
    PS > New-UDHidden -Only md -Content {
    PS > New-UDTypography 'md'
    PS > }
 
    Only|Content is hidden at the md breakpoint.
    #>

    #[Component("Hidden", "Ghost", "Creates a new hidden element.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter(ParameterSetName = 'Down')]
        [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')]
        [string]$Down,
        [Parameter(ParameterSetName = 'Up')]
        [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')]
        [string]$Up,
        [Parameter(ParameterSetName = 'Only')]
        [ValidateSet('xs', 'sm', 'md', 'lg', 'xl')]
        [string[]]$Only,
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children
    )

    $Component = @{
        type     = 'mu-hidden'
        id       = $Id
        isPlugin = $true 

        children = & $Children
    }

    if ($PSCmdlet.ParameterSetName -eq 'Only') {
        $Component['only'] = $Only 
    }

    if ($PSCmdlet.ParameterSetName -eq 'Down') {
        $Component["$($Down.ToLower())Down"] = $true 
    }

    if ($PSCmdlet.ParameterSetName -eq 'Up') {
        $Component["$($Up.ToLower())Up"] = $true 
    }

    $Component 
} 
function New-UDHtml {
    <#
    .SYNOPSIS
    Display HTML on an app.
     
    .DESCRIPTION
    Display HTML on an app.
     
    .PARAMETER Id
    The ID of this component. The default value is a random GUID.
     
    .PARAMETER Markup
    The HTML markup to display.
     
    .EXAMPLE
    PS > New-UDHtml -Markup '<h1>Hello World</h1>'
 
    HTML|Display HTML on an app.
    #>

    [Category("app/component")]
    [Description("Display HTML on an app.")]
    [DisplayName("HTML")]
    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter (Position = 1, Mandatory)]
        [string]$Markup
    )

    @{
        type   = 'rawHtml'
        id     = $Id
        markup = $Markup
    }
}
function New-UDIconButton {
    <#
    .SYNOPSIS
    Creates a button with an icon.
     
    .DESCRIPTION
    Creates a button with an icon.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Icon
    The icon to display within the button.
     
    .PARAMETER OnClick
    A script block to execute when the button is clicked.
     
    .PARAMETER Disabled
    Whether the button is disabled.
     
    .PARAMETER Href
    A link to navigate to when the button is clicked.
     
    .PARAMETER Style
    The CSS sytle to apply to the icon.
     
    .PARAMETER ClassName
    A CSS class to apply to the icon.
     
    .PARAMETER Size
    The size of the icon button. Valid values are: "small", "medium", "large"
 
    .EXAMPLE
    PS > New-UDIconButton -Icon (New-UDIcon -Icon user -Size sm) -Id 'iconButton1'
 
    Basic Icon Button|Creates a basic icon button.
 
    .EXAMPLE
    PS > New-UDIconButton -Icon (New-UDIcon -Icon user -Size sm) -OnClick {
    PS > Show-UDToast -Message 'Icon Button Clicked'
    PS > } -Id 'iconButton2'
 
    Icon Button with OnClick|Creates an icon button with an OnClick script block.
 
    .EXAMPLE
    PS > New-UDIconButton -Icon (New-UDIcon -Icon user -Size sm) -Href 'https://www.google.com' -Id 'iconButton3'
 
    Icon Button with Href|Creates an icon button that navigates to a URL.
 
    .EXAMPLE
    PS > New-UDIconButton -Icon (New-UDIcon -Icon user -Size sm) -Disabled -Id 'iconButton4'
 
    Disabled Icon Button|Creates a disabled icon button.
    #>

    [Category("app/component")]
    [Description("Creates a button with an icon")]
    [DisplayName("Icon Button")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()]
        $Icon = "User",
        [Parameter ()]
        [Endpoint] $OnClick, 
        [Parameter ()]
        [Switch] $Disabled, 
        [Parameter ()]
        [string] $Href, 
        [Parameter ()]
        [Hashtable] $Style,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [ValidateSet('small', 'medium', 'large')]
        [string]$Size = 'medium',
        [Parameter()]
        [ValidateSet('text', 'outlined')]
        [string]$Variant = 'text'
    )

    End {

        if ($Icon -is [string]) {
            $Icon = New-UDIcon -Icon $Icon
        }
        
        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        @{
            type      = 'mu-icon-button'
            isPlugin  = $true
            assetId   = $MUAssetId
            id        = $Id
            disabled  = $Disabled.IsPresent
            style     = $Style
            onClick   = $OnClick
            icon      = $Icon
            href      = $Href
            className = $ClassName
            size      = $size.ToLower()
            variant   = $variant.ToLower()
        }
    }
}
[PowerShellUniversal.IconService]::LoadIcons()

function Find-UDIcon {
    <#
    .SYNOPSIS
    Find an icon.
     
    .DESCRIPTION
    Find an icon.
     
    .PARAMETER Name
    The name of the icon to find. Wild cards are supported.
     
    .EXAMPLE
    Find-UDIcon -Name 'User'
    #>

    param(
        [Parameter(Mandatory)]
        $Name
    )

    $Name = $Name.Replace("-", "").Replace("*", ".*")

    [PowerShellUniversal.IconService]::Icons.Keys | Where-Object { $_ -match $Name } 

}

function New-UDIcon {
    <#
    .SYNOPSIS
    FontAwesome 6 icons.
     
    .DESCRIPTION
    Creates a new icon.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Icon
    The Icon to display. This is a FontAwesome 6 icon name.
     
    .PARAMETER FixedWidth
    Whether to use a fixed with.
     
    .PARAMETER Inverse
    Whether to invert the colors of the icon.
     
    .PARAMETER Rotation
    The amount of degrees to rotate the icon.
     
    .PARAMETER ClassName
    Custom CSS class names to apply to the icon.
     
    .PARAMETER Transform
    A CSS transform to apply to the icon.
     
    .PARAMETER Flip
    Whether to flip the icon.
     
    .PARAMETER Pull
    Whether to flip the icon.
     
    .PARAMETER ListItem
     
     
    .PARAMETER Spin
    Whether the icon should spin.
     
    .PARAMETER Border
    Defines the border for this icon.
     
    .PARAMETER Pulse
    Whether this icon should publse.
     
    .PARAMETER Size
    The size of the icon. Valid values are: "xs", "sm", "lg", "1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x", "9x", "10x"
     
    .PARAMETER Style
    A CSS style to apply to the icon.
     
    .PARAMETER Title
     
    .PARAMETER Solid
    Uses the solid icon style.
     
     
    .PARAMETER Color
    The color of this icon.
     
    .EXAMPLE
    PS > New-UDIcon -Icon User -Id 'icon1'
     
    Basic Icon|Displays a user icon.
 
    .EXAMPLE
    PS > New-UDStack -Direction 'row' -Content {
    PS > New-UDIcon -Icon 'AddressBook' -Size 'sm' -Id 'icon2'
    PS > New-UDIcon -Icon 'AddressBook' -Size 'lg' -Id 'icon3'
    PS > New-UDIcon -Icon 'AddressBook' -Size '5x' -Id 'icon4'
    PS > New-UDIcon -Icon 'AddressBook' -Size '10x' -Id 'icon5'
    PS > }
 
    Icon Size|Displays icons in different sizes.
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'AddressBook' -Size '5x' -Rotation 90 -Id 'icon6'
 
    Rotated Icon|Rotates an icon 90 degrees.
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'AddressBook' -Size '5x' -Border -Id 'icon7'
 
    Border Icon|Adds a border to an icon.
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'AddressBook' -Size '5x' -Color 'red' -Id 'icon8'
 
    Color|Adds a color to an icon
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'AddressBook' -Flip 'horizontal' -Size '5x' -Id 'icon9'
 
    Flip|Flips an icon horizontally.
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'Spinner' -Spin -Size '5x' -Id 'icon10'
 
    Spin|Spins an icon.
 
    .EXAMPLE
    PS > New-UDIcon -Icon 'Spinner' -Pulse -Size '5x' -Id 'icon11'
 
    Pulse|Pulses an icon.
 
    .EXAMPLE
    PS > New-UDHelmet -Tag 'link' -Attributes @{
    PS > href = 'https://css.gg/css'
    PS > rel = 'stylesheet'
    PS > }
    PS >
    PS > New-UDIcon -Children {
    PS > New-UDHtml '<i class="gg-adidas"></i>'
    PS > }
 
    Custom Icon Set|Uses the css.gg icon set.
     
    #>

    [Category("app/component")]
    [Description("FontAwesome 6 icons you can display within your app.")]
    [DisplayName("Icon")]
    [CmdletBinding(DefaultParameterSetName = "FontAwesome")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter(ParameterSetName = "FontAwesome")]
        [string]$Icon = "User", 
        [Parameter(ParameterSetName = "Content")]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter(ParameterSetName = "FontAwesome")]
        [Switch]$FixedWidth,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Inverse,
        [Parameter(ParameterSetName = "FontAwesome")]
        [int]$Rotation,
        [Parameter()]
        [string]$ClassName,
        [Parameter(ParameterSetName = "FontAwesome")]
        [string]$Transform,
        [Parameter(ParameterSetName = "FontAwesome")]
        [ValidateSet("horizontal", 'vertical', 'both')]
        [string]$Flip,
        [Parameter(ParameterSetName = "FontAwesome")]
        [ValidateSet('right', 'left')]
        [string]$Pull,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$ListItem,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Spin,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Border,
        [Parameter(ParameterSetName = "FontAwesome")]
        [Alias('Pulse')]
        [switch]$Beat,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Fade,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Bounce,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$BeatFade,
        [Parameter(ParameterSetName = "FontAwesome")]
        [switch]$Shake,
        [Parameter(ParameterSetName = "FontAwesome")]
        [ValidateSet("xs", "sm", "md", "lg", "1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x", "9x", "10x")]
        [string]$Size = "sm",
        [Parameter()]
        [Hashtable]$Style = @{},
        [Parameter(ParameterSetName = "FontAwesome")]
        [string]$Title,
        [Parameter(ParameterSetName = "FontAwesome")]
        [UniversalDashboard.Models.DashboardColor]
        $Color,
        [Parameter(ParameterSetName = "FontAwesome")]
        [Switch]$Solid
    )

    End {   
        if ($Children) {
            @{
                type      = "icon"
                id        = $id
                content   = & $Children
                style     = $Style
                className = $ClassName
            }
            return
        }

        $iconName = $Icon.ToLower().Replace("_", "")

        if ([PowerShellUniversal.IconService]::Icons.ContainsKey($iconName)) {
            $IconObj = [PowerShellUniversal.IconService]::Icons[$iconName]
            $IconName = ""
            if ($IconObj.Styles[0] -eq 'brands') {
                $IconName += "fa-brands"
            }
            else {
                if ($Solid -or $IconObj.Styles -notcontains 'regular') {
                    $IconName += "fa-solid"
                }
                else {
                    $IconName += "fa-regular"
                }
            }
            $IconName += " fa-" + $IconObj.Name
        }
        else {
            $IconName = "fa-solid fa-circle-question";
        }

        $MUIcon = @{
            type       = "icon"
            id         = $id 
            size       = $Size
            fixedWidth = $FixedWidth.IsPresent
            color      = $Color.HtmlColor
            listItem   = $ListItem.IsPresent
            inverse    = $Inverse.IsPresent
            rotation   = $Rotation
            flip       = $Flip
            spin       = $Spin.IsPresent
            beat       = $Beat.IsPresent
            shake      = $Shake.IsPresent
            fade       = $Fade.IsPresent
            bounce     = $Bounce.IsPresent  
            beatFade   = $BeatFade.IsPresent
            border     = $Border.IsPresent
            pull       = $Pull
            className  = $ClassName
            transform  = $Transform
            style      = $Style
            title      = $Title
            icon       = $IconName
        }

        $MUIcon
    }
}

$scriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    if ($fakeBoundParameters.ContainsKey('Solid')) {
        $Script:FontAwesomeSolid | Where-Object {
            $_ -like "$wordToComplete*"
        }
    }
    else {
        $Script:FontAwesomeRegular | Where-Object {
            $_ -like "$wordToComplete*"
        } 
        $Script:FontAwesomeBrands | Where-Object {
            $_ -like "$wordToComplete*"
        } 
    }   
}

Register-ArgumentCompleter -CommandName New-UDIcon -ParameterName Icon -ScriptBlock $scriptBlock
function New-UDIFrame {
    <#
    .SYNOPSIS
    An HTML IFrame component.
     
    .DESCRIPTION
    An HTML IFrame component.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Uri
    The URI for the iframe.
     
    .EXAMPLE
    Defines an IFrame for Google.
 
    New-UDIFrame -Uri https://www.google.com
    #>

    [Category("app/component")]
    [Description("An HTML IFrame component.")]
    [DisplayName("iFrame")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [String]$Uri = "https://ironmansoftware.com"
    )

    New-UDElement -Id $Id -Tag "iframe" -Attributes @{
        src = $Uri
    }
}
function New-UDImage {
    <#
    .SYNOPSIS
    An image component.
     
    .DESCRIPTION
    An image component.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Url
    The URL to the image.
     
    .PARAMETER Path
    The path to a local image.
     
    .PARAMETER Height
    The height in pixels.
     
    .PARAMETER Width
    The width in pixels.
     
    .PARAMETER Attributes
    Additional attributes to apply to the img tag.
     
    .PARAMETER ClassName
    A CSS class to apply to the image.
 
    .EXAMPLE
    PS > New-UDImage -Url "https://ironmansoftware.com/img/ps-logo.png"
     
    Basic image|Displays an image.
 
    .EXAMPLE
    PS > $Image = Get-ChildItem "$PSScriptRoot\*.png" | Select-Object -First 1
    PS > New-UDImage -Path ($Image.FullName)
 
    Local image|Displays a local image.
 
    .EXAMPLE
    PS > New-UDImage -Url "https://ironmansoftware.com/img/ps-logo.png" -Height 100 -Width 100
 
    Image with height and width|Displays an image with a height and width.
 
    .EXAMPLE
    PS > New-UDImage -Url "https://ironmansoftware.com/img/ps-logo.png" -Attributes @{
    PS > style = @{
    PS > borderRadius = '50%'
    PS > }
    PS > }
 
    Image with style|Displays an image with a style attribute.
    #>

    [CmdletBinding(DefaultParameterSetName = 'url')]
    [Category("app/component")]
    [Description("An image component.")]
    [DisplayName("Image")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter(ParameterSetName = 'url')]
        [String]$Url = "https://ironmansoftware.com/img/ps-logo.png",
        [Parameter(ParameterSetName = 'path')]
        [String]$Path,
        [Parameter()]
        [int]$Height,
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [Hashtable]$Attributes = @{},
        [Parameter()]
        [string]$ClassName
    )

    switch ($PSCmdlet.ParameterSetName) {
        'path' {
            if (-not [String]::IsNullOrEmpty($Path)) {
                if (-not (Test-Path $Path)) {
                    throw "$Path does not exist."
                }
        
                $mimeType = 'data:image/png;base64, '
                if ($Path.EndsWith('jpg') -or $Path.EndsWith('jpeg')) {
                    $mimeType = 'data:image/jpg;base64, '
                }
        
                $base64String = [Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Path))
        
                $Attributes.'src' = "$mimeType $base64String"
            }
        }
        'url' {
            $Attributes.'src' = $Url
        }
    }
    if ($PSBoundParameters.ContainsKey('ClassName')) {
        $Attributes.'className' = $ClassName
    }
    if ($PSBoundParameters.ContainsKey('Height')) {
        $Attributes.'height' = $Height
    }
    if ($PSBoundParameters.ContainsKey('Width')) {
        $Attributes.'width' = $Width
    }

    $Attributes["id"] = $Id

    New-UDElement -Tag 'img' -Attributes $Attributes
}

function New-UDLayout {
    <#
    .SYNOPSIS
    Create a layout based on the number of components within the layout.
     
    .DESCRIPTION
    Create a layout based on the number of components within the layout. The component wraps New-UDRow and New-UDColumn to automatically layout components in the content.
     
    .PARAMETER Columns
    The number of columns in this layout.
     
    .PARAMETER Content
    The content for this layout.
     
    .EXAMPLE
    PS > New-UDContainer -Content {
    PS > New-UDLayout -Columns 2 -Content {
    PS > New-UDTypography "Row 1, Col 1"
    PS > New-UDTypography "Row 1, Col 2"
    PS > New-UDTypography "Row 2, Col 1"
    PS > New-UDTypography "Row 2, Col 2"
    PS > }
    PS > }
 
 
    Layout|Creates a layout with 2 columns.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [ValidateRange(1, 12)]
        [int]$Columns,
        [Parameter(Mandatory = $true)]
        [ScriptBlock]$Content
    )

    $Components = $Content.Invoke()

    if ($Columns -eq 1) {
        $LargeColumnSize = 12
        $MediumColumnSize = 12
        $SmallColumnSize = 12
    }
    else {
        $LargeColumnSize = 12 / $Columns
        $MediumColumnSize = 12 / ($Columns / 2)
        $SmallColumnSize = 12
    }

    $Offset = 0
    $ComponentCount = ($Components | Measure-Object).Count

    while ($Offset -lt $ComponentCount) {
        $ColumnObjects = $Components | Select-Object -Skip $Offset -First $Columns | ForEach-Object {
            New-UDColumn -SmallSize $SmallColumnSize -MediumSize $MediumColumnSize -LargeSize $LargeColumnSize -Content {
                $_
            }
        }

        New-UDRow -Columns {
            $ColumnObjects
        }
        
        $Offset += $Columns
    }
}
function New-UDLink {
    <#
    .SYNOPSIS
    The Link component allows you to easily customize anchor elements with your theme colors and typography styles.
     
    .DESCRIPTION
    The Link component allows you to easily customize anchor elements with your theme colors and typography styles.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Url
    The URL of the link.
     
    .PARAMETER Underline
    Whether to underline the link.
     
    .PARAMETER Style
    A custom style to apply to the link.
     
    .PARAMETER Variant
    The theme variant to apply to the link.
     
    .PARAMETER ClassName
    The CSS class to apply to the link.
     
    .PARAMETER OpenInNewWindow
    Opens the link in a new window.
     
    .PARAMETER Children
    The components to link.
     
    .PARAMETER Text
    Text to include in the link.
     
    .PARAMETER OnClick
    A script block to invoke when clicked.
 
    .PARAMETER Download
    Adds the download attribute to this link.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Text "Google" -Id 'link1'
 
    Basic link|A basic link to Google.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Text "Google" -OpenInNewWindow -Id 'link2'
 
    Link in new window|A link to Google that opens in a new window.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.ironmansoftware.com/img/ps-logo.png" -Text "Download" -OpenInNewWindow -Download -Id 'link3'
 
    Download link|A link that downloads a file.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Text "Google" -Style @{ 'color' = 'red' } -Id 'link4'
 
    Style|A link with a custom style.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Text "Google" -Variant "h1" -Id 'link5'
 
    Variant|A link with a variant header of 1.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Text "Google" -Underline "hover" -Id 'link6'
 
    Underline|A link with an underline on hover.
 
    .EXAMPLE
    PS > New-UDLink -Url "https://www.google.com" -Content {
        New-UDImage -Url "https://www.ironmansoftware.com/img/ps-logo.png" -Height 20 -Width 20
    } -Id 'link7'
 
    Content|A link with an image as content.
 
    .EXAMPLE
    PS > New-UDLink -Text "Click" -OnClick {
        Show-UDToast -Message "Clicked!"
    } -Id 'link8'
 
    OnClick|A link with an onclick event.
    #>

    [Category("app/component")]
    [Description("The Link component allows you to easily customize anchor elements with your theme colors and typography styles")]
    [DisplayName("Link")]
    [CmdletBinding(DefaultParameterSetName = "text")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [string]$Url = "https://www.ironmansoftware.com",
        [Parameter()]
        [ValidateSet('none', 'hover', 'always')]
        [string]$Underline = "none",
        [Parameter()]
        [hashtable]$Style,
        [Parameter()]
        [ValidateSet('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'subtitle1', 'subtitle2', 'body1', 'body2', 'caption', 'button', 'overline', 'srOnly', 'inherit')]
        [string]$Variant,
        [Parameter ()]
        [string]$ClassName,
        [Parameter()]
        [switch]$OpenInNewWindow,
        [Parameter(ParameterSetName = 'content')]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter(ParameterSetName = 'text')]
        [string]$Text = "Ironman Software",
        [Parameter()]
        [Endpoint]$OnClick,
        [Parameter()]
        [switch]$Download
    )
    End {
        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        if ($null -ne $Children) {
            $Object = & $Children
        }
        else {
            $Object = $null
        }

        if ($Children) {
            $Text = $null
        }

        @{
            type            = 'mu-link'
            isPlugin        = $true
            assetId         = $MUAssetId

            id              = $Id
            url             = $url
            underline       = $underline
            style           = $style
            variant         = $variant
            className       = $ClassName
            openInNewWindow = $openInNewWindow.IsPresent
            content         = $Object
            text            = $text
            onClick         = $OnClick
            download        = $Download.IsPresent
        }
    }
}
function New-UDList {
    <#
    .SYNOPSIS
    Lists are continuous, vertical indexes of text or images.
     
    .DESCRIPTION
    Creates a list. Use New-UDListItem to add new items to the list. Lists are good for links in UDDrawers.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    The items in the list.
     
    .PARAMETER SubHeader
    Text to show within the sub header.
 
    .PARAMETER ClassName
    A CSS class to apply to the list.
 
    .PARAMETER Dense
    Whether the list is dense.
 
    .PARAMETER Sx
    An hashtable of styles to apply to this component.
     
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -Icon (New-UDIcon -Icon envelope -Size 3x) -SubTitle 'New Stuff'
    PS > New-UDListItem -Label 'Drafts' -Icon (New-UDIcon -Icon edit -Size 3x) -SubTitle "Stuff I'm working on "
    PS > New-UDListItem -Label 'Trash' -Icon (New-UDIcon -Icon trash -Size 3x) -SubTitle 'Stuff I deleted'
    PS > New-UDListItem -Label 'Spam' -Icon (New-UDIcon -Icon bug -Size 3x) -SubTitle "Stuff I didn't want"
    PS > } -Id 'list1'
 
    Basic list|A basic list of items with icons and a subtitle.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox'
    PS > New-UDListItem -Label 'Drafts'
    PS > New-UDListItem -Label 'Trash'
    PS > New-UDListItem -Label 'Spam'
    PS > } -Id 'list2' -Dense
 
    Dense list|A dense list.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -Icon (New-UDIcon -Icon envelope -Size 3x) -SubTitle 'New Stuff' -OnClick {
    PS > Show-UDToast -Message 'Inbox Clicked'
    PS > }
    PS > New-UDListItem -Label 'Drafts' -Icon (New-UDIcon -Icon edit -Size 3x) -SubTitle "Stuff I'm working on " -OnClick {
    PS > Show-UDToast -Message 'Drafts Clicked'
    PS > }
    PS > } -Id 'list3'
 
    OnClick|Respond to clicks on list items.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -Icon (New-UDIcon -Icon envelope -Size 3x) -SubTitle 'New Stuff' -SecondaryAction {
    PS > New-UDButton -Text 'Secondary Action'
    PS > }
    PS > New-UDListItem -Label 'Drafts' -Icon (New-UDIcon -Icon edit -Size 3x) -SubTitle "Stuff I'm working on " -SecondaryAction {
    PS > New-UDButton -Text 'Secondary Action'
    PS > }
    PS > } -Id 'list4'
 
    Secondary Action|Respond to secondary actions on list items.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -Icon (New-UDIcon -Icon envelope -Size 3x) -SubTitle 'New Stuff' -Children {
    PS > New-UDListItem -Label 'Inbox Item 1' -Nested
    PS > New-UDListItem -Label 'Inbox Item 2' -Nested
    PS > New-UDListItem -Label 'Inbox Item 3' -Nested
    PS > }
    PS > New-UDListItem -Label 'Drafts' -Icon (New-UDIcon -Icon edit -Size 3x) -SubTitle "Stuff I'm working on " -Children {
    PS > New-UDListItem -Label 'Drafts Item 1' -Nested
    PS > New-UDListItem -Label 'Drafts Item 2' -Nested
    PS > New-UDListItem -Label 'Drafts Item 3' -Nested
    PS > }
    PS > } -Id 'list5'
 
    Nested List Items|Create nested list items.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -Icon (New-UDIcon -Icon envelope -Size 3x) -SubTitle 'New Stuff' -Switch (New-UDSwitch) -SwitchAlignment 'right'
    PS > New-UDListItem -Label 'Drafts' -Icon (New-UDIcon -Icon edit -Size 3x) -SubTitle "Stuff I'm working on " -Switch (New-UDSwitch) -SwitchAlignment 'right'
    PS > New-UDListItem -Label 'Trash' -Icon (New-UDIcon -Icon trash -Size 3x) -SubTitle 'Stuff I deleted' -Switch (New-UDSwitch) -SwitchAlignment 'right'
    PS > New-UDListItem -Label 'Spam' -Icon (New-UDIcon -Icon bug -Size 3x) -SubTitle "Stuff I didn't want" -Switch (New-UDSwitch) -SwitchAlignment 'right'
    PS > } -Id 'list6'
 
    Switch|Create a list item with a switch.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -SubTitle 'New Stuff' -CheckBox (New-UDCheckbox) -CheckBoxAlignment 'left'
    PS > New-UDListItem -Label 'Drafts' -SubTitle "Stuff I'm working on " -CheckBox (New-UDCheckbox) -CheckBoxAlignment 'left'
    PS > New-UDListItem -Label 'Trash' -SubTitle 'Stuff I deleted' -CheckBox (New-UDCheckbox) -CheckBoxAlignment 'left'
    PS > New-UDListItem -Label 'Spam' -SubTitle "Stuff I didn't want" -CheckBox (New-UDCheckbox) -CheckBoxAlignment 'left'
    PS > } -Id 'list7'
 
    CheckBox|Create a list item with a checkbox.
 
    .EXAMPLE
    PS > New-UDList -Content {
    PS > New-UDListItem -Label 'Inbox' -SubTitle 'New Stuff' -Href 'https://ironmansoftware.com' -OpenInNewWindow
    PS > New-UDListItem -Label 'Drafts' -SubTitle "Stuff I'm working on " -Href 'https://powershellgallery.com' -OpenInNewWindow
    PS > New-UDListItem -Label 'Trash' -SubTitle 'Stuff I deleted' -Href 'https://github.com/powershell/powershell' -OpenInNewWindow
    PS > New-UDListItem -Label 'Spam' -SubTitle "Stuff I didn't want"
    PS > } -Id 'list7'
 
    Links|Create a list item with links
    #>

    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter ()]
        [string]$SubHeader,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$Dense,
        [Parameter()]
        [Hashtable]$Sx
    )
    End {
        try {
            $c = & $Children
        }
        catch {
            $c = New-UDError -Message $_
        }

        @{
            type      = 'mu-list'
            isPlugin  = $true
            assetId   = $MUAssetId

            id        = $Id
            children  = $c
            subHeader = $SubHeader
            style     = $Style
            className = $ClassName
            dense     = $Dense.IsPresent
            sx        = $sx
        }
    }
}

function New-UDListItem {
    <#
    .SYNOPSIS
    Creates a new list item.
     
    .DESCRIPTION
    Creates a new list item. List items are used with New-UDList.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER AvatarType
    The type of avatar to show within the list item.
     
    .PARAMETER OnClick
    A script block to execute when the list item is clicked.
     
    .PARAMETER Label
    The label to show within the list item.
     
    .PARAMETER Children
    Nested list items to show underneath this list item.
     
    .PARAMETER SubTitle
    The subtitle to show within the list item.
     
    .PARAMETER Icon
    The icon to show within the list item.
 
    .PARAMETER Switch
    The switch to show within the list item.
 
    .PARAMETER SwitchAlignment
    How to align the sqitch within the listitem. Valid values are: "left", "right"
 
    .PARAMETER CheckBox
    The checkbox to show within the list item.
 
    .PARAMETER CheckBoxAlignment
    How to align the checkbox within the listitem. Valid values are: "left", "right"
     
    .PARAMETER URL
    The URL for the avatar image.
     
    .PARAMETER SecondaryAction
    The secondary action to issue with this list item.
 
    .PARAMETER Href
    A URL to navigate to when clicked.
 
    .PARAMETER OpenInNewWindow
    Open the HREF in a new window.
    #>

    [CmdletBinding()]
    param(
        [Parameter ()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter ()]
        [ValidateSet("Icon", "Avatar")][string]$AvatarType = 'Icon',
        [Parameter ()]
        [Endpoint]$OnClick, 
        [Parameter ()]
        [object]$Label, 
        [Parameter ()]
        [Alias("Content")]
        [scriptblock]$Children, 
        [Parameter ()]
        [string]$SubTitle,
        [Parameter ()]
        $Icon,
        [Parameter ()]
        [Alias("Source")]
        [string]$Url,
        [Parameter ()]
        [Switch]$OpenInNewWindow,
        [Parameter ()]
        [scriptblock]$SecondaryAction,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$Open,
        [Parameter()]
        [Switch]$Nested,
        [Parameter()]
        [string]$Href,
        [Parameter ()]
        $Switch,
        [ValidateSet("left", "right")]
        [string]$SwitchAlignment = "right",
        [Parameter ()]
        $CheckBox,
        [ValidateSet("left", "right")]
        [string]$CheckBoxAlignment = "right"
    )
    begin {}
    Process {}
    End {
        if ($OnClick) {
            $OnClick.Register($Id, $PSCmdlet)
        }

        if ($null -ne $Children) {
            try {
                $CardContent = & $Children    
            }
            catch {
                $CardContent = New-UDError -Message $_
            }
            
        }
        else {
            $CardContent = $null
        }

        if ($null -ne $SecondaryAction) {
            $Action = $SecondaryAction.Invoke()
        }
        else {
            $Action = $null
        }
        
        @{
            type              = 'mu-list-item'
            isPlugin          = $true
            assetId           = $MUAssetId

            id                = $Id
            subTitle          = $SubTitle
            label             = if ($Label -is [ScriptBlock]) { & $Label } else { [string]$Label }
            onClick           = $OnClick
            children          = $CardContent
            secondaryAction   = $Action
            icon              = $Icon
            source            = $Source
            avatarType        = $AvatarType
            labelStyle        = $LabelStyle
            style             = $Style
            className         = $ClassName
            open              = $Open.IsPresent
            nested            = $Nested.IsPresent
            href              = $Href
            switch            = $Switch
            checkBox          = $CheckBox
            switchAlignment   = $SwitchAlignment.ToLower()
            checkBoxAlignment = $CheckBoxAlignment.ToLower()
            openInNewWindow   = $OpenInNewWindow.IsPresent
        }
    }
}
function New-UDMarkdown {
    <#
    .SYNOPSIS
    Converts markdown to HTML.
     
    .DESCRIPTION
    Converts markdown to HTML.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Markdown
    The markdown to convert to HTML.
 
    .EXAMPLE
    PS > New-UDMarkdown -Markdown '# Heading'
 
    Basic Markdown|Creates a heading based on markdown.
 
    #>

    [Category("app/component")]
    [Description("Converts markdown to HTML.")]
    [DisplayName("Markdown")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [Alias("Markdown")]
        [String]$Children = "# Header"
    )

    @{
        type     = 'mu-markdown'
        id       = $Id
        children = $Children
    }
}
function New-UDMenu {
    <#
    .SYNOPSIS
    Menus display a list of choices on temporary surfaces.
     
    .DESCRIPTION
    Creates a pop up menu.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Text
    The text to display in the button that opens this menu.
     
    .PARAMETER OnChange
    An event handler to call when the user selects an item from the menu. $EventData will include the selected item.
     
    .PARAMETER Children
    The items to display in the menu.
     
    .PARAMETER ClassName
    The class name of the menu.
     
    .PARAMETER Variant
    The button variant for the menu.
 
    .PARAMETER Size
    The size of the menu button. Valid values are: "small", "medium"
     
    .EXAMPLE
    PS > New-UDMenu -Text 'Click Me' -Children {
    PS > New-UDMenuItem -Text 'Test'
    PS > New-UDMenuItem -Text 'Test2'
    PS > New-UDMenuItem -Text 'Test3'
    PS > } -Id 'menu1'
 
    Basic menu|Creates a simple menu.
 
    .EXAMPLE
    PS > New-UDMenu -Text 'Click Me' -OnChange {
    PS > Show-UDToast -Message $EventData
    PS > } -Children {
    PS > New-UDMenuItem -Text 'Test'
    PS > New-UDMenuItem -Text 'Test2'
    PS > New-UDMenuItem -Text 'Test3'
    PS > } -Id 'menu2'
 
    Menu with event handler|Creates a menu with an event handler.
 
    .EXAMPLE
    PS > New-UDMenu -Text 'Click Me' -Children {
    PS > New-UDMenuItem -Text 'Test'
    PS > New-UDMenuItem -Text 'Test2'
    PS > New-UDMenuItem -Text 'Test3'
    PS > } -Variant text -Id 'menu3'
 
    Text variant|Creates a menu with a text variant.
 
    .EXAMPLE
    PS > New-UDMenu -Text 'Click Me' -Children {
    PS > New-UDMenuItem -Text 'Test'
    PS > New-UDMenuItem -Text 'Test2'
    PS > New-UDMenuItem -Text 'Test3'
    PS > } -Size small -Id 'menu4'
 
    Small menu|Creates a small menu.
 
    .EXAMPLE
    PS > New-UDMenu -Text 'Click Me' -Children {
    PS > New-UDMenuItem -Text 'Test' -Value 1
    PS > New-UDMenuItem -Text 'Test2' -Value 2
    PS > New-UDMenuItem -Text 'Test3' -Value 3
    PS > } -Id 'menu5'
 
    Menu with values|Creates a menu with values.
 
    #>

    # [Component("Menu", "Bars", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [string]$Text,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter(Mandatory)]
        [Alias('Items')]
        [Alias('Content')]
        [ScriptBlock]$Children,
        [Parameter]
        [string]$ClassName,
        [ValidateSet("text", "outlined", "contained")]
        [string]$Variant = "contained",
        [Parameter()]
        [Hashtable]$Icon,        
        [ValidateSet("small", "medium", "large")]
        [string]$Size = 'medium'
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    @{
        assetId   = $MUAssetId 
        id        = $Id 
        isPlugin  = $true 
        type      = 'mu-menu'

        text      = $Text
        onChange  = $OnChange
        children  = & $Children
        className = $ClassName
        icon      = $Icon
        variant   = $Variant.ToLower()
        size      = $Size.ToLower()
    }
}

function New-UDMenuItem {
    <#
    .SYNOPSIS
    Creates a menu item.
     
    .DESCRIPTION
    Creates a menu item.
     
    .PARAMETER Text
    The text to display in the menu item.
     
    .PARAMETER Value
    The value of the menu item. If not specified, the text will be used.
 
    .PARAMETER Icon
    The icon to display in the menu item. Use New-UDIcon to create an icon.
 
    .PARAMETER OnClick
    An event handler to call when the user selects the menu item.
     
    .EXAMPLE
    New-UDMenu -Text 'Click Me' -OnChange {
        Show-UDToast $EventData
    } -Children {
        New-UDMenuItem -Text 'Test'
        New-UDMenuItem -Text 'Test2'
        New-UDMenuItem -Text 'Test3'
    }
 
    Creates a simple menu.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter(Mandatory)]
        [string]$Text,
        [Parameter()]
        [string]$Value,
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter()]
        [Endpoint]$OnClick
    )

    if ($OnClick) {
        $OnClick.Register($Id, $PSCmdlet)
    }

    @{

        assetId  = $MUAssetId 
        id       = $Id 
        isPlugin = $true 
        type     = 'mu-menu-item'

        text     = $Text 
        value    = if ($Value) { $Value } else { $Text }
        icon     = $Icon  
        onClick  = $OnClick      
    }
}
function New-UDPaper {
    <#
    .SYNOPSIS
    Paper is used to highlight content within a page.
     
    .DESCRIPTION
    Creates a paper. Paper is used to highlight content within a page.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Children
    The content of this paper element.
     
    .PARAMETER Width
    The width of this paper.
     
    .PARAMETER Height
    The height of this paper.
     
    .PARAMETER Square
    Whether this paper is square.
     
    .PARAMETER Style
    The CSS style to apply to this paper.
     
    .PARAMETER Elevation
    The elevation of this paper.
     
    .PARAMETER ClassName
    A CSS class to apply to the paper.
 
    .EXAMPLE
    PS > New-UDPaper -Id 'paper1' -Children {
    PS > New-UDButton -Text 'Click Me'
    PS > }
 
    Basic paper|Creates a new paper with a button inside of it.
 
    .EXAMPLE
    PS > New-UDPaper -Id 'paper2' -Children {
    PS > New-UDButton -Text 'Click Me'
    PS > } -Elevation 5
 
    Paper with elevation|Creates a new paper with a button inside of it and an elevation of 5.
 
    .EXAMPLE
    PS > New-UDPaper -Id 'paper3' -Children {
    PS > New-UDButton -Text 'Click Me'
    PS > } -Width 1000 -Height 1000 -Elevation 5
 
    Width and height|Creates a new paper with a button inside of it and a width and height of 1000.
 
    .EXAMPLE
    PS > New-UDPaper -Id 'paper4' -Children {
    PS > New-UDButton -Text 'Click Me'
    PS > } -Square -Elevation 5
 
    Square paper|Creates a new paper with a button inside of it and a square shape.
 
    .EXAMPLE
    PS > New-UDPaper -Id 'paper5' -Children {
    PS > New-UDButton -Text 'Click Me'
    PS > } -Style @{ backgroundColor = 'red' }
 
    Style|Creates a new paper with a button inside of it and a red background color.
 
    #>

    # [Component("Paper", "File", "Creates a paper component.")]
    param(
        [Parameter()][string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()][Alias("Content")][ScriptBlock]$Children,
        [Parameter()][string]$Width = '500',
        [Parameter()][string]$Height = '500',
        [Parameter()][Switch]$Square,
        [Parameter()][Hashtable]$Style = @{},
        [Parameter()][int]$Elevation,
        [Parameter()]
        [string]$ClassName
    )

    End {
        $c = New-UDErrorBoundary -Content $Children
        @{
            type      = 'mu-paper'
            isPlugin  = $true
            assetId   = $MUAssetId
            
            id        = $Id
            width     = $Width 
            children  = $c
            height    = $Height
            square    = $Square.IsPresent
            style     = $Style
            elevation = $Elevation
            className = $ClassName
        }
    }
}
function New-UDParagraph {
    <#
    .SYNOPSIS
    A paragraph.
     
    .DESCRIPTION
    A paragraph. Used to define a P HTML tag.
     
    .PARAMETER Content
    The content of the paragraph.
     
    .PARAMETER Text
    The text of the paragraph.
     
    .PARAMETER Color
    The font color of the paragraph.
     
    .EXAMPLE
    A simple paragraph.
 
    New-UDParagraph -Text 'Hello, world!'
    #>

    [CmdletBinding(DefaultParameterSetName = "text")]
    [Category("app/component")]
    [Description("A paragraph.")]
    [DisplayName("Paragraph")]
    param(
        [Parameter(ParameterSetName = 'content')]
        [ScriptBlock]$Content,
        [Parameter(ParameterSetName = 'text')]
        [string]$Text = "Hello",
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$Color = 'black'
    )

    if ($PSCmdlet.ParameterSetName -eq 'content') {
        New-UDElement -Tag 'p' -Content $Content -Attributes @{
            style = @{
                color = $Color.HtmlColor
            }
        }
    }
    else {
        New-UDElement -Tag 'p' -Content {
            $Text
        } -Attributes @{
            style = @{
                color = $Color.HtmlColor
            }
        }
    }
   
}
function New-UDProgress {
    <#
    .SYNOPSIS
    Progress dialogs can show both determinate and indeterminate progress. They can also be circular or linear.
     
    .DESCRIPTION
    Creates a progress dialog. Progress dialogs can show both determinate and indeterminate progress. They can also be circular or linear.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER PercentComplete
    The percent complete for the progress.
     
    .PARAMETER BackgroundColor
    The background color.
     
    .PARAMETER ProgressColor
    The progress bar color.
     
    .PARAMETER Circular
    Whether the progress is circular.
     
    .PARAMETER Size
    The size of the progress. Valid values are: "small", "medium", "large"
 
    .PARAMETER Label
    Adds a label to a circle progress.
     
    .EXAMPLE
    PS > New-UDProgress -PercentComplete 75 -Id 'progress1'
 
    Determinate|A determinate progress bar.
 
    .EXAMPLE
    PS > New-UDProgress -Circular -Size large -Id 'progress2'
 
    Circular|A circular progress bar.
 
    .EXAMPLE
    PS > New-UDProgress -BackgroundColor 'red' -ProgressColor 'blue' -Id 'progress3'
 
    Color|Define the background and progress colors.
 
    .EXAMPLE
    PS > New-UDProgress -Circular -Size large -Id 'progress4' -PercentComplete 75 -Label '75%'
 
    Circular with Label|Create a circle progress with a label
 
 
    .EXAMPLE
    PS > New-UDProgress -Circular -Size large -Id 'progress4'
    #>

    [Category("app/component")]
    [Description("Display a progress indicator")]
    [DisplayName("Progress")]
    [CmdletBinding(DefaultParameterSetName = "indeterminate")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [ValidateRange(0, 100)]
        [int32]$PercentComplete,
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$BackgroundColor,
        [Parameter()]
        [Alias("Color")]
        [UniversalDashboard.Models.DashboardColor]$ProgressColor,
        [Parameter()]
        [Switch]$Circular,
        [ValidateSet('small', 'medium', 'large')]
        [Parameter()]
        [string]$Size,
        [Parameter()]
        [string]$Label
    )

    End {
        @{
            id              = $Id
            assetId         = $MUAssetId 
            isPlugin        = $true 
            type            = "mu-progress"
          
            variant         = if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("PercentComplete")) { "determinate" } else { "indeterminate" }
            percentComplete = if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("PercentComplete")) { $PercentComplete } else { $null }
            backgroundColor = $BackgroundColor.HtmlColor
            progressColor   = $ProgressColor.HtmlColor
            circular        = $Circular.IsPresent
            color           = $Color
            size            = $Size
            label           = $Label
        }          
    }


}
function Protect-UDSection {
    <#
    .SYNOPSIS
    Hides a section of a dashboard if the user is not part of the specified role.
     
    .DESCRIPTION
    Hides a section of a dashboard if the user is not part of the specified role.
     
    .PARAMETER Children
    The content to hide.
     
    .PARAMETER Role
    The role the user must be a part of.
     
    .EXAMPLE
    PS > Protect-UDSection -Content {
    PS > New-UDTypography 'Admins Only'
    PS > } -Role @("Administrator")
 
    Protect Section|Hides the section of the dashboard if the user is not an administrator.
    #>

    param(
        [Parameter(Mandatory)]
        [Alias('Content')]
        [ScriptBlock]$Children,
        [Parameter(Mandatory)]
        [string[]]$Role
    )

    $MatchingRoles = $Roles | Where-Object { $_ -in $Role }
    if ($MatchingRoles.Length -gt 0) {
        & $Children
    }
}
function New-UDRadioGroup {
    <#
    .SYNOPSIS
    Creates a group of radio buttons.
     
    .DESCRIPTION
    Creates a group of radio buttons. Within a group, only a single radio will be able to be selected.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label to show for this radio group.
     
    .PARAMETER Children
    The radio buttons to include within this group.
     
    .PARAMETER OnChange
    A script block to call when the radio group selection changes. The $EventData variable will include the value of the radio that is selected.
     
    .PARAMETER Value
    The selected value for this radio group.
 
    .PARAMETER ClassName
    A CSS class to apply to the radio group.
 
    .PARAMETER DefaultValue
    The default value for this radio group.
 
    .PARAMETER Disabled
    Whether the radio group is disabled.
 
    .EXAMPLE
    PS > New-UDRadioGroup -Label 'group' -Id 'radio1' -Children {
    PS > New-UDRadio -Value 'Adam' -Label 'Adam' -Id 'adam'
    PS > New-UDRadio -Value 'Sarah' -Label 'Sarah' -Id 'sarah'
    PS > New-UDRadio -Value 'Austin' -Label 'Austin' -Id 'austin'
    PS > }
 
    Basic radio group|Creates a basic radio group with three radio buttons.
     
    .EXAMPLE
    PS > New-UDRadioGroup -Label 'group' -Id 'radio2' -Children {
    PS > New-UDRadio -Value 'Adam' -Label 'Adam' -Id 'adam'
    PS > New-UDRadio -Value 'Sarah' -Label 'Sarah' -Id 'sarah'
    PS > New-UDRadio -Value 'Austin' -Label 'Austin' -Id 'austin'
    PS > } -OnChange {
    PS > Show-UDToast -Message "Radio selected: $EventData"
    PS > }
 
    OnChange|Creates a basic radio group with three radio buttons and an OnChange script block.
 
    .EXAMPLE
    PS > New-UDRadioGroup -Label 'group' -Id 'radio3' -Children {
    PS > New-UDRadio -Value 'Adam' -Label 'Adam' -Id 'adam'
    PS > New-UDRadio -Value 'Sarah' -Label 'Sarah' -Id 'sarah'
    PS > New-UDRadio -Value 'Austin' -Label 'Austin' -Id 'austin'
    PS > } -Disabled
 
    Disabled|Creates a basic radio group with three radio buttons and disables the group.
 
    .EXAMPLE
    PS > New-UDRadioGroup -Label 'group' -Id 'radio4' -Children {
    PS > New-UDRadio -Value 'Adam' -Label 'Adam' -Id 'adam'
    PS > New-UDRadio -Value 'Sarah' -Label 'Sarah' -Id 'sarah'
    PS > New-UDRadio -Value 'Austin' -Label 'Austin' -Id 'austin'
    PS > } -DefaultValue 'Sarah'
 
    Default Value|Creates a basic radio group with three radio buttons and sets the default value to 'Sarah'.
 
    .EXAMPLE
    PS > New-UDRadioGroup -Label 'group' -Id 'radio4' -Children {
    PS > New-UDRadio -Value 'Adam' -Label 'Adam' -Id 'adam' -LabelPlacement 'start'
    PS > New-UDRadio -Value 'Sarah' -Label 'Sarah' -Id 'sarah' -LabelPlacement 'start'
    PS > New-UDRadio -Value 'Austin' -Label 'Austin' -Id 'austin' -LabelPlacement 'start'
    PS > } -DefaultValue 'Sarah'
 
    Label Placement|Creates a basic radio group with three radio buttons and sets the label placement to 'start'.
 
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [String]$Label,
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children = { 
            New-UDRadio -Label "Option 1" -Value 1
            New-UDRadio -Label "Option 2" -Value 2
        },
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [string]$Value,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [string]$DefaultValue,
        [Parameter()]
        [Switch]$Disabled
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    @{
        assetId      = $MUAssetId 
        id           = $Id 
        isPlugin     = $true 
        type         = 'mu-radiogroup'

        onChange     = $OnChange 
        value        = $Value 
        label        = $Label 
        children     = & $Children
        className    = $ClassName
        defaultValue = $DefaultValue
        disabled     = $Disabled.IsPresent 
    }
}

function New-UDRadio {
    <#
    .SYNOPSIS
    Creates a radio.
     
    .DESCRIPTION
    Creates a radio. Radios should be included within a New-UDRadioGroup.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label to show next to the radio.
     
    .PARAMETER Disabled
    Whether the radio is disabled.
     
    .PARAMETER Value
    The value of the radio.
     
    .PARAMETER LabelPlacement
    The position to place the label in relation to the radio.
     
    .PARAMETER Color
    The theme color to apply to this radio
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [String]$Label,
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [string]$Value,
        [Parameter()]
        [ValidateSet('start', 'end')]
        [string]$LabelPlacement = 'end',
        [Parameter ()]
        [ValidateSet('default', 'primary', 'secondary')]
        [string]$Color = 'default'
    )

    @{
        assetId        = $MUAssetId 
        id             = $Id 
        isPlugin       = $true 
        type           = 'mu-radio'

        label          = $Label 
        disabled       = $Disabled.IsPresent 
        value          = $value 
        labelPlacement = $LabelPlacement
        color          = $Color.ToLower()
    }
}
function New-UDRating {
    <#
    .SYNOPSIS
    Ratings provide insight regarding others' opinions and experiences, and can allow the user to submit a rating of their own.
     
    .DESCRIPTION
    Ratings provide insight regarding others' opinions and experiences, and can allow the user to submit a rating of their own.
     
    .PARAMETER Id
    An ID for this component. If not specified, a random GUID will be used.
     
    .PARAMETER DefaultValue
    The default value for this rating.
     
    .PARAMETER Disabled
    Whether this rating is disabled.
     
    .PARAMETER EmptyLabelText
    The text to show when the rating is empty.
     
    .PARAMETER HighlightSelectedOnly
    Whether to highlight the selected rating only.
     
    .PARAMETER Max
    The maximum value for this rating.
     
    .PARAMETER OnChange
    A script block to call when the rating is changed. The $EventData variable will be the new value.
     
    .PARAMETER Precision
    The precision of the rating. Defaults to 1.
     
    .PARAMETER Size
    The size of the rating. Can be small, medium, or large.
     
    .PARAMETER Value
    The current value of the rating.
     
    .EXAMPLE
    PS > New-UDRating -Value 3 -Id 'rating1'
 
    Basic Rating|Creates a basic rating component.
 
    .EXAMPLE
    PS > New-UDRating -Value 3 -OnChange {
    PS > Show-UDToast -Message "Rating changed to $EventData"
    PS > } -Id 'rating2'
 
    OnChange|Creates a rating component with an OnChange script block.
 
    .EXAMPLE
    PS > New-UDRating -Value 3 -Size large -Id 'rating3'
 
    Size|Creates a large rating component.
 
    .EXAMPLE
    PS > New-UDRating -Value 3 -Disabled -Id 'rating4'
 
    Disabled|Creates a disabled rating component.
 
    .EXAMPLE
    PS > New-UDRating -Value 3 -HighlightSelectedOnly -Id 'rating5'
 
    Highlight Selected Only|Creates a rating component that only highlights the selected rating.
    #>

    [Category("app/component")]
    [Description("Allow the user to select a rating.")]
    [DisplayName("Rating")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [double]$DefaultValue,
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [string]$EmptyLabelText = "Empty",
        [Parameter()]
        [Switch]$HighlightSelectedOnly,
        [Parameter()]
        [double]$Max = 5,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [double]$Precision = 1,
        [Parameter()]
        [ValidateSet("small", "medium", "large")]
        [string]$Size = 'medium',
        [Parameter()]
        [double]$Value
    )

    if ($OnChange) {
        $OnChange.Register($id, $PSCmdlet)
    }

    @{
        id                    = $Id
        isPlugin              = $true 
        type                  = "mu-rating"

        defaultValue          = $DefaultValue
        disabled              = $Disabled.IsPresent
        emptyLabelText        = $EmptyLabelText
        highlightSelectedOnly = $HighlightSelectedOnly.IsPresent
        max                   = $Max 
        onChange              = $OnChange 
        precision             = $Precision
        size                  = $Size.ToLower()
        value                 = $Value
    }
}
function New-UDSelect {
    <#
    .SYNOPSIS
    Creates a new select.
     
    .DESCRIPTION
    Creates a new select. Selects can have multiple options and option groups. Selects can also be multi-select.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Option
    Options to include in this select. This can be either New-UDSelectOption or New-UDSelectGroup.
     
    .PARAMETER Label
    The label to show with the select.
     
    .PARAMETER OnChange
    A script block that is executed when the script changes. $EventData will be an array of the selected values.
     
    .PARAMETER DefaultValue
    The default selected value.
     
    .PARAMETER Disabled
    Whether this select is disabled.
 
    .PARAMETER Icon
    The icon to show next to the textbox. Use New-UDIcon to create an icon.
     
    .PARAMETER Multiple
    Whether you can select multiple values.
 
    .PARAMETER FullWidth
    Stretch the select to the full width of the parent component.
 
    .PARAMETER Variant
    The variant of the select. Can be filled, outlined, or standard.
 
    .PARAMETER MaxWidth
    The maximum width of the select.
 
    .PARAMETER ClassName
    A CSS class to apply to the select.
 
    .PARAMETER Sx
    A hashtable of styles to apply to the select.
     
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select1' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -FullWidth
 
    Basic Select|A basic select with three options
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select2' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -DefaultValue 2 -OnChange {
    PS > Show-UDToast -Message $EventData
    PS > } -FullWidth
 
    OnChange|A select with an OnChange script block that shows a toast when the value changes.
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select3' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -Disabled -FullWidth
 
    Disabled|A disabled select.
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select4' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -Multiple -FullWidth
 
    Multiple|A select that allows multiple selections.
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select5' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -Variant outlined -FullWidth
 
    Outlined|A select with an outlined variant.
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select6' -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > } -FullWidth -Icon (New-UDIcon -Icon Home)
 
    Icon|A select with an icon.
 
    .EXAMPLE
    PS > New-UDSelect -Label 'Select' -Id 'select7' -Option {
    PS > New-UDSelectGroup -Name "Category 1" -Option {
    PS > New-UDSelectOption -Name "One" -Value 1
    PS > New-UDSelectOption -Name "Two" -Value 2
    PS > }
    PS > New-UDSelectGroup -Name "Category 2" -Option {
    PS > New-UDSelectOption -Name "Three" -Value 3
    PS > New-UDSelectOption -Name "Four" -Value 4
    PS > }
    PS > } -FullWidth
 
    Grouped|A select with option groups.
    #>

    # [Component("Select", "ListDropdown", "Creates a new card.")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ScriptBlock]$Option = {
            New-UDSelectOption -Name 'Option 1' -Value 1
            New-UDSelectOption -Name 'Option 2' -Value 2
        },
        [Parameter()]
        [String]$Label,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        $DefaultValue,
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [Switch]$Multiple,
        [Parameter()]
        [string]$MaxWidth,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [switch]$FullWidth,
        [Parameter()]
        [ValidateSet('filled', 'outlined', 'standard')]
        [string]$Variant = 'standard',
        [Parameter()]
        $Icon,
        [Parameter()]
        [Hashtable]$Sx
    )

    if ($OnChange) {
        $OnChange.Register($Id + "onChange", $PSCmdlet)
    }

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    if ($PSBoundParameters.ContainsKey("Multiple") -and $PSBoundParameters.ContainsKey("DefaultValue") -and $DefaultValue -isnot [array]) {
        $DefaultValue = @($DefaultValue)
    }

    @{
        type         = 'mu-select'
        assetId      = $MUAssetId
        isPlugin     = $true 

        id           = $id 
        options      = $Option.Invoke()
        label        = $Label
        onChange     = $OnChange
        defaultValue = $DefaultValue
        disabled     = $Disabled.IsPresent
        icon         = $icon
        multiple     = $Multiple.IsPresent
        maxWidth     = $MaxWidth
        className    = $ClassName
        fullWidth    = $FullWidth.IsPresent
        variant      = $Variant.ToLower()
        value        = $DefaultValue
        sx           = $Sx
    }
}

function New-UDSelectGroup {
    <#
    .SYNOPSIS
    Creates a new select group.
     
    .DESCRIPTION
    Creates a new select group. This cmdlet is to be used with New-UDSelect. Pass the result of this cmdlet to the -Option parameter to create a new select group.
     
    .PARAMETER Option
    Options to include in this group.
     
    .PARAMETER Name
    The name of the group. This will be displayed in the select.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [ScriptBlock]$Option,
        [Parameter(Mandatory = $true)]
        [String]$Name
    )

    @{
        type    = 'mu-select-group'
        name    = $Name 
        options = $Option.Invoke()
    }

}

function New-UDSelectOption {
    <#
    .SYNOPSIS
    Creates a new select option.
     
    .DESCRIPTION
    Creates a new select option. This cmdlet is to be used with New-UDSelect. Pass the result of this cmdlet to the -Option parameter to create a new select group.
     
    .PARAMETER Name
    The name of the select option. This will be shown in the select.
     
    .PARAMETER Value
    Thevalue of the select option. This will be passed back to New-UDForm -OnSubmit or the $EventData for -OnChange on New-UDSelect.
     
    .PARAMETER Icon
    The icon to show next to the textbox. Use New-UDIcon to create an icon.
 
    .PARAMETER Disabled
    Whether this option is disabled.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [String]$Name,
        [Parameter()]
        [String]$Value,
        [Parameter()]
        $Icon,
        [Parameter()]
        [Switch]$Disabled
    )

    if (-not $Value) {
        $Value = $Name
    }

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    @{
        type     = 'mu-select-option'
        name     = $Name 
        value    = $Value 
        icon     = $icon
        disabled = $Disabled.IsPresent
    }
}

function New-UDSkeleton {
    <#
    .SYNOPSIS
    Display a placeholder preview of your content before the data gets loaded to reduce load-time frustration.
     
    .DESCRIPTION
    Creates a loading skeleton
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Variant
    The type of skeleton to display. Valid values are "text", "rect", "circle"
     
    .PARAMETER Height
    The static height of the skeleton.
     
    .PARAMETER Width
    The static width of the skeleton.
     
    .PARAMETER Animation
    The type of animation to use for the skeleton. Valid values are: "wave", "disabled", "pulsate"
 
    .PARAMETER ClassName
    A CSS class to apply to the skeleton.
 
    .EXAMPLE
    PS > New-UDSkeleton -Id 'skeleton1' -Variant text -Animation wave -Width 210
 
    Text skeleton|Creates a new text skeleton with a wave animation.
 
    .EXAMPLE
    PS > New-UDSkeleton -Id 'skeleton2' -Variant rect -Width 210 -Height 118
 
    Rect skeleton|Creates a new rect skeleton with a width of 210 and a height of 118.
 
    .EXAMPLE
    PS > New-UDSkeleton -Id 'skeleton3' -Variant circle -Width 40 -Height 40
 
    Circle skeleton|Creates a new circle skeleton with a width of 40 and a height of 40.
 
    .EXAMPLE
    PS > New-UDSkeleton -Id 'skeleton4' -Animation disabled -Width 210 -Height 118
 
    Disabled animation|Creates a new skeleton with a disabled animation.
 
    #>

    [Category("app/component")]
    [Description("Creates a loading skeleton")]
    [DisplayName("Skeleton")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ValidateSet("text", "rect", "circle")]
        [string]$Variant = 'text',
        [Parameter()]
        [int]$Height,
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [ValidateSet("wave", "disabled", "pulsate")]
        [string]$Animation = "pulsate",
        [Parameter()]
        [string]$ClassName
    )

    @{
        type      = "mu-skeleton"
        id        = $Id 
        isPlugin  = $true
        assetId   = $MUAssetId

        variant   = $Variant.ToLower()
        height    = $Height
        width     = $Width 
        animation = $Animation.ToLower()
        className = $ClassName
    }
}
function New-UDSlider {
    <#
    .SYNOPSIS
    A slider component.
     
    .DESCRIPTION
    A slider component. Sliders can be used to define values within a range or selecting a range of values. You can use this component with New-UDForm.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Value
    The value of the slider.
     
    .PARAMETER Minimum
    The minimum value of the slider.
     
    .PARAMETER Maximum
    The maximum value of the slider.
     
    .PARAMETER Disabled
    Whether the slider is disabled.
     
    .PARAMETER Marks
    Whether to display marks on the slider.
     
    .PARAMETER OnChange
    A script block that is invoked when the slider value changes. You can access the slider value within the script block by referencing the $EventData variable.
     
    .PARAMETER Orientation
    The orientation of the slider.
     
    .PARAMETER Step
    Step size of the slider.
     
    .PARAMETER ValueLabelDisplay
    Whether to display value labels.
     
    .PARAMETER ClassName
    A CSS class to apply to the slider.
     
    .PARAMETER Color
    The color of the slider. Defaults to 'primary'. Valid values: "primary", "secondary"
 
    .EXAMPLE
    PS > New-UDSlider -Value 50 -Minimum 0 -Maximum 100 -OnChange {
    PS > Show-UDToast -Message "Slider value changed to $($EventData.value)"
    PS > } -Id 'slider1'
 
    Basic Slider|Creates a basic slider.
 
    .EXAMPLE
    PS > New-UDSlider -Value 50 -Minimum 0 -Maximum 100 -Marks -OnChange {
    PS > Show-UDToast -Message "Slider value changed to $($EventData.value)"
    PS > } -Id 'slider2'
 
    Slider with Marks|Creates a slider with marks.
 
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Content {
    PS > New-UDSlider -Orientation vertical -Id 'slider3'
    PS > } -Attributes @{
    PS > style = @{
    PS > height = '200px'
    PS > }
    PS > }
     
    Vertical Slider|Creates a vertical slider.
 
    .EXAMPLE
    PS > New-UDSlider -Value 50 -Minimum 0 -Maximum 100 -Disabled -Id 'slider4'
 
    Disabled Slider|Creates a disabled slider.
 
    .EXAMPLE
    PS > New-UDSlider -Value 50 -Minimum 0 -Maximum 100 -ValueLabelDisplay on -Id 'slider5'
 
    Slider with Value Labels|Creates a slider with value labels.
 
    .EXAMPLE
    PS > New-UDSlider -Value 50 -Minimum 0 -Maximum 100 -Step 10 -Id 'slider6'
 
    Slider with Step|Creates a slider with a step size of 10.
    #>

    [Category("app/component")]
    [Description("Allow the user to select a value within a range.")]
    [DisplayName("Slider")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [int[]]$Value = 0, 
        [Parameter()]
        [int]$Minimum = 0, 
        [Parameter()]
        [int]$Maximum = 100,
        [Parameter()]
        [Switch]$Disabled, 
        [Parameter()]
        [Switch]$Marks, 
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [ValidateSet('horizontal', 'vertical')]
        [string]$Orientation = 'horizontal',
        [Parameter()]
        [int]$Step = 1,
        [Parameter()]
        [ValidateSet('on', 'auto', 'off')]
        [string]$ValueLabelDisplay = 'auto',
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [ValidateSet("primary", 'secondary')]
        [string]$Color = 'primary'
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    if (-not $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Value")) {
        $Value = $Minimum
    }

    if ($Value -lt $Minimum) {
        throw "Value cannot be less than minimum"
    }

    if ($Value -gt $Maximum) {
        throw "Value cannot be more than maximum"
    }

    $Val = $Value 
    if ($Value.Length -eq 1) {
        $Val = $Value | Select-Object -First 1
    }

    @{
        type              = 'mu-slider'
        isPlugin          = $true 
        assetId           = $MUAssetId 
        id                = $Id 

        value             = $val 
        min               = $Minimum
        max               = $Maximum
        disabled          = $Disabled.IsPresent
        marks             = $Marks.IsPresent
        onChange          = $OnChange 
        orientation       = $Orientation 
        step              = $Step
        valueLabelDisplay = $ValueLabelDisplay
        className         = $ClassName
        color             = $Color.ToLower()
    }
}
function New-UDSpan {
    <#
    .SYNOPSIS
    A span component.
     
    .DESCRIPTION
    A span component. Defines a span HTML tag.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Content
    The content of the span.
     
    .EXAMPLE
    PS > New-UDSpan -Content {
    PS > New-UDTypography -Text 'Text'
    PS > }
 
    Basic Span|A basic span.
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ScriptBlock]$Content
    )

    New-UDElement -Id $Id -Tag "span" -Content {
        $Content
    }
}
function New-UDSparkline {
    <#
    .SYNOPSIS
    Small charts for displaying data.
     
    .DESCRIPTION
    Small charts for displaying data.
     
    .PARAMETER Id
    The ID of this component. The default value is a random GUID.
     
    .PARAMETER Data
    The data to display in the sparkline. Usually, a list of numbers.
     
    .PARAMETER Limit
    The number of data points to display.
     
    .PARAMETER Width
    The width of the sparkline.
     
    .PARAMETER Height
    The height of the sparkline.
     
    .PARAMETER Margin
    The margin of the sparkline.
     
    .PARAMETER Color
    The color of the sparkline.
     
    .PARAMETER Type
    The type of sparkline to display. Valid values are bars, lines, and both.
     
    .PARAMETER Min
    The minimum value.
     
    .PARAMETER Max
    The maximum value.
     
    .EXAMPLE
    PS > $Data = 1..10 | ForEach-Object { Get-Random -Max 1000 }
    PS > New-UDSparkline -Data $Data -Max 1000 -Height 100 -Width 500
 
    Basic sparkline|A basic sparkline.
 
    .EXAMPLE
    PS > $Data = 1..10 | ForEach-Object { Get-Random -Max 1000 }
    PS > New-UDSparkline -Data $Data -Type 'bars' -Max 1000 -Height 100 -Width 500
 
    Lines|A sparkline with lines.
 
    .EXAMPLE
    PS > $Data = 1..10 | ForEach-Object { Get-Random -Max 1000 }
    PS > New-UDSparkline -Data $Data -Type 'both' -Max 1000 -Height 100 -Width 500
 
    Both|A sparkline with both lines and bars.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter(Mandatory)]
        [int[]]$Data, 
        [Parameter()]
        [int]$Limit, 
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [int]$Height, 
        [Parameter()]
        [int]$Margin,
        [Parameter()]
        [DashboardColor]$Color,
        [Parameter()]
        [ValidateSet("bars", "lines", "both")]
        $Type = 'bars',
        [Parameter()]
        [int]$Min,
        [Parameter()]
        [int]$Max
    )

    @{
        type      = 'sparklines'
        isPlugin  = $true
        assetId   = $AssetId
        id        = $Id 
        data      = $data 
        limit     = $Limit 
        width     = $Width 
        height    = $Height 
        marin     = $Margin 
        color     = $Color.HtmlColor 
        sparkType = $Type
        min       = $Min 
        max       = $Max
    }
}
function New-UDSpeedDial {
    <#
    .SYNOPSIS
    When pressed, a floating action button can display three to six related actions in the form of a Speed Dial.
     
    .DESCRIPTION
    When pressed, a floating action button can display three to six related actions in the form of a Speed Dial.
     
    .PARAMETER Id
    The ID of this component. It defaults to a random GUID.
     
    .PARAMETER Children
    Actions for this speed dial. Use New-UDSpeedDialAction to create actions.
     
    .PARAMETER Direction
    The direction the speed dial opens. Valid values are up, down, left, and right.
     
    .PARAMETER Icon
    The icon for the speed dial. Use New-UDIcon to create an Icon.
     
    .PARAMETER OnClose
    A scriptblock to call when the speed dial closes.
     
    .PARAMETER OnOpen
    A scriptblock to call when the speed dial opens.
 
    .PARAMETER OnActionClick
    A scriptblock to call when an action is clicked.
     
    .EXAMPLE
    PS > New-UDSpeedDial -Content {
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'User') -TooltipTitle 'My Account'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'Users') -TooltipTitle 'Groups'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'Save') -TooltipTitle 'Save'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'File') -TooltipTitle 'Open'
    PS > } -Icon (New-UDIcon -Icon 'Plus') -Id 'speedDial1'
 
    Basic Speed Dial|Creates a basic speed dial.
 
    .EXAMPLE
    PS > New-UDSpeedDial -Content {
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'User') -TooltipTitle 'My Account' -Id 'myAccount'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'Users') -TooltipTitle 'Groups'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'Save') -TooltipTitle 'Save'
    PS > New-UDSpeedDialAction -Icon (New-UDIcon -Icon 'File') -TooltipTitle 'Open'
    PS > } -Icon (New-UDIcon -Icon 'Plus') -Id 'speedDial2' -OnActionClick {
    PS > Show-UDToast "Ouch! You clicked $EventData"
    PS > }
 
    OnClick|Creates a speed dial with an OnClick action.
 
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid().ToString()),
        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children,
        [Parameter()]
        [ValidateSet("up", "down", "left", "right")]
        [string]$Direction = "up",
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter()]
        [Endpoint]$OnClose, 
        [Parameter()]
        [Endpoint]$OnOpen,
        [Parameter()]
        [Endpoint]$OnActionClick
    )

    if ($OnClose) {
        $OnClose.Register($Id + "onClose", $PSCmdlet)
    }

    if ($OnOpen) {
        $OnOpen.Register($Id + "onOpen", $PSCmdlet)
    }

    if ($OnActionClick) {
        $OnActionClick.Register($Id + "onActionClick", $PSCmdlet)
    }

    @{
        type          = "mu-speeddial"
        id            = $Id
        children      = & $Children
        direction     = $Direction.ToLower()
        icon          = $Icon
        onClose       = $OnClose
        onOpen        = $OnOpen
        onActionClick = $OnActionClick
    }
}

function New-UDSpeedDialAction {
    <#
    .SYNOPSIS
    Creates an action for a speed dial.
     
    .DESCRIPTION
    Creates an action for a speed dial.
     
    .PARAMETER Id
    The ID of this component. It defaults to a random GUID.
     
    .PARAMETER Icon
    The icon for the action. Use New-UDIcon to create an Icon.
     
    .PARAMETER TooltipTitle
    The title to show when the user hovers over the action.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid().ToString()),
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter()]
        [string]$TooltipTitle,
        [Parameter()]
        [Endpoint]$OnClick
    )

    if ($OnClick) {
        $OnClick.Register($Id + "onClick", $PSCmdlet)
    }

    @{
        type         = "mu-speeddial-action"
        id           = $Id
        icon         = $Icon
        tooltipTitle = $TooltipTitle
        onClick      = $OnClick
    }
}
function New-UDSplitPane {
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter(Mandatory)]
        [ScriptBlock]$Content,
        [Parameter()]
        [ValidateSet("vertical", "horizontal")]
        [string]$Direction = "vertical",
        [Parameter()]
        [int]$MinimumSize,
        [Parameter()]
        [int]$DefaultSize
    )

    try {
        $Children = & $Content
    }
    catch {
        $Children = New-UDError -Message $_
    }

    if ($Children.Length -ne 2) {
        Write-Error "Split pane requires exactly two components in Content"
        return
    }

    $Options = @{
        content = $Children
        id = $Id
        split = $Direction.ToLower()
        type = "ud-splitpane"
    }

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("MinimumSize")) {
        $Options["minSize"] = $MinimumSize
    }

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("DefaultSize")) {
        $Options["defaultSize"] = $DefaultSize
    }

    $Options
}
function New-UDStack {
    <#
    .SYNOPSIS
    The Stack component manages layout of immediate children along the vertical or horizontal axis with optional spacing and/or dividers between each child.
     
    .DESCRIPTION
    Stack is concerned with one-dimensional layouts, while Grid handles two-dimensional layouts. The default direction is column which stacks children vertically.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Children
    The children to stack.
     
    .PARAMETER Divider
    An optional divider component.
     
    .PARAMETER Spacing
    The spacing between the items.
     
    .PARAMETER Direction
    The stack direction. The default direction is column which stacks children vertically. Valid values are row, column, row-reverse, and column-reverse.
 
    .PARAMETER FullWidth
    If set, the stack will take up the full width of its container.
 
    .PARAMETER AlignItems
    Defines the align-items style property. It's applied for all screen sizes. Valid values are flex-start, flex-end, center, stretch, and baseline.
 
    .PARAMETER JustifyContent
    Defines the justify-content style property. It's applied for all screen sizes. Valid values are flex-start, flex-end, center, space-between, space-around, and space-evenly.
     
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack1'
     
    Basic Stack|A basic stack.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack2' -Direction 'column'
 
    Direction|A stack in a column direction.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack3' -Direction 'row-reverse'
 
    Row Reverse|A row reverse stack.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack4' -AlignItems 'center'
 
    Alignment|A center stack.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack5' -Divider {
    PS > New-UDDivider
    PS > }
 
    Divider|A stack with a divider.
 
    .EXAMPLE
    PS > New-UDStack -Content {
    PS > New-UDCard -Text "Card 1"
    PS > New-UDCard -Text "Card 2"
    PS > New-UDCard -Text "Card 3"
    PS > } -Spacing 2 -Id 'stack6' -AlignItems 'center' -JustifyContent 'space-between'
 
    Justify Content|A stack with justify content.
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter(Mandatory)]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter()]
        [scriptblock]$Divider = {},
        [Parameter()]
        [int]$Spacing,
        [Parameter()]
        [ValidateSet('row', 'column', "row-reverse", "column-reverse")]
        [string]$Direction = 'row',
        [Parameter()]
        [switch]$FullWidth,
        [Parameter()]
        [ValidateSet('flex-start', 'flex-end', 'center', 'stretch', 'baseline')]
        [string]$AlignItems,
        [Parameter()]
        [ValidateSet('flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly')]
        [string]$JustifyContent
    )

    @{
        id             = $id 
        assetId        = $MUAssetId 
        isPlugin       = $true 
        type           = "mu-stack"

        children       = & $Children
        divider        = & $Divider
        spacing        = $Spacing
        alignItems     = if ($AlignItems) { $AlignItems.ToLower() } else { $null }
        direction      = $Direction.ToLower()
        fullWidth      = $FullWidth.IsPresent
        justifyContent = if ($JustifyContent) { $JustifyContent.ToLower() } else { $null }
    }
}

function New-UDStepper {
    <#
    .SYNOPSIS
    Creates a new stepper component.
     
    .DESCRIPTION
    Creates a new stepper component. Steppers can be used as multi-step forms or to display information in a stepped manner.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER ActiveStep
    Sets the active step. This should be the index of the step.
     
    .PARAMETER Children
    The steps for this stepper. Use New-UDStep to create new steps.
     
    .PARAMETER NonLinear
    Allows the user to progress to steps out of order.
     
    .PARAMETER AlternativeLabel
    Places the step label under the step number.
     
    .PARAMETER OnFinish
    A script block that is executed when the stepper is finished.
 
    .PARAMETER Orientation
    Sets the orientation of the stepper.
 
    .PARAMETER NextButtonText
    The text to display in the next button.
 
    .PARAMETER BackButtonText
    The text to display in the back button.
 
    .PARAMETER FinsihButtonText
    The text to display in the finish button.
 
    .PARAMETER CancelButtonText
    The text to display in the cancel button.
 
    .PARAMETER ClassName
    A CSS class to apply to the stepper.
 
    .PARAMETER Style
    A hashtable of styles to apply to the stepper.
 
    .PARAMETER Sx
    A hashtable of styles to apply to the stepper.
     
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper1' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep1'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep2'
    PS > } -Label "Step 2"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep3'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > }
 
    Basic stepper|Creates a new stepper with three steps.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper2' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep23'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep24'
    PS > } -Label "Step 2"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep25'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > } -NonLinear -AlternativeLabel
 
    NonLinear and AlternativeLabel|Creates a new stepper with three steps and non-linear and alternative label options.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper3' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep5'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep6'
    PS > } -Label "Step 2"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep7'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > } -Orientation 'vertical'
 
    Vertical orientation|Creates a new stepper with three steps and vertical orientation.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper4' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep8'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep9'
    PS > } -Label "Step 2"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep10'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > } -NextButtonText 'Forward' -BackButtonText 'Reverse'
 
    Custom button text|Creates a new stepper with three steps and custom button text.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper5' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep11'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep12'
    PS > } -Label "Step 2" -Optional
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep13'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > }
 
    Optional step|Creates a new stepper with three steps and an optional step.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper6' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep14'
    PS > } -Label "Step 1" -Icon (New-UDIcon -Icon 'Check')
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep15'
    PS > } -Label "Step 2" -Icon (New-UDIcon -Icon 'Check')
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep16'
    PS > } -Label "Step 3" -Icon (New-UDIcon -Icon 'Check')
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > }
 
    Custom icons|Creates a new stepper with three steps and custom icons.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper7' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep17'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep18'
    PS > } -Label "Step 2"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep19'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > } -OnValidateStep {
    PS > if ($EventData.CurrentStep -eq 0 -and [string]::IsNullOrEmpty($EventData.txtStep17)) {
    PS > New-UDValidationResult -ValidationError 'Step 1 is required'
    PS > }
    PS > else {
    PS > New-UDValidationResult -Valid
    PS > }
    PS > }
 
    Validation|Creates a new stepper with three steps and validation.
 
    .EXAMPLE
    PS > New-UDStepper -Id 'stepper8' -Steps {
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 1" }
    PS > New-UDTextbox -Id 'txtStep20'
    PS > } -Label "Step 1"
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 2" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep21'
    PS > } -Label "Step 2" -DisablePrevious
    PS > New-UDStep -OnLoad {
    PS > New-UDElement -tag 'div' -Content { "Step 3" }
    PS > New-UDElement -tag 'div' -Content { "Previous data: $Body" }
    PS > New-UDTextbox -Id 'txtStep22'
    PS > } -Label "Step 3"
    PS > } -OnFinish {
    PS > New-UDTypography -Text 'Nice! You did it!' -Variant h3
    PS > New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    PS > }
 
    Disable previous|Creates a new stepper with three steps and disables the back button on the second step.
    #>

    # [Component("Stepper", "WandMagicSparkles", "Creates a new card.")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [int]$ActiveStep = 0,
        [Alias("Steps")]
        [Parameter(Mandatory)]
        [ScriptBlock]$Children,
        [Parameter()]
        [Switch]$NonLinear,
        [Parameter()]
        [Switch]$AlternativeLabel,
        [Parameter(Mandatory)]
        [Endpoint]$OnFinish,
        [Parameter()]
        [Endpoint]$OnCancel,
        # [Parameter()]
        # [Endpoint]$OnCompleteStep,
        [Parameter()]
        [Endpoint]$OnValidateStep,
        [Parameter()]
        [ValidateSet("vertical", "horizontal")]
        [string]$Orientation = "horizontal",
        [Parameter()]
        [string]$NextButtonText = "Next",
        [Parameter()]
        [string]$BackButtonText = "Back",
        [Parameter()]
        [string]$FinishButtonText = "Finish",
        [Parameter()]
        [string]$CancelButtonText = "Cancel",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Hashtable]$Style, 
        [Parameter()]
        [Hashtable]$Sx
    )

    $OnFinish.Register($Id + "onFinish", $PSCmdlet)

    if ($OnCancel) {
        $OnCancel.Register($Id + "OnCancel", $PSCmdlet)
    }

    if ($OnCompleteStep) {
        $OnCompleteStep.Register($Id + "onComplete", $PSCmdlet)
    }

    if ($OnValidateStep) {
        $OnValidateStep.Register($Id + "onValidate", $PSCmdlet)
    }

    $c = New-UDErrorBoundary -Content $Children

    @{
        id               = $id 
        isPlugin         = $true 
        type             = 'mu-stepper'
        assetId          = $MUAssetId 

        children         = $c
        nonLinear        = $NonLinear.IsPresent 
        alternativeLabel = $AlternativeLabel.IsPresent
        onFinish         = $OnFinish
        activeStep       = $ActiveStep
        onValidateStep   = $OnValidateStep 
        onCompleteStep   = $OnCompleteStep
        orientation      = $Orientation.ToLower()
        nextButtonText   = $NextButtonText
        finishButtonText = $FinishButtonText
        backButtonText   = $BackButtonText
        className        = $ClassName
        onCancel         = $OnCancel
        cancelButtonText = $CancelButtonText
        style            = $Style
        sx               = $Sx
    }
}

function New-UDStep {
    <#
    .SYNOPSIS
    Creates a new step for a stepper.
     
    .DESCRIPTION
    Creates a new step for a stepper. Add to the Children (alias Steps) parameter for New-UDStepper.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER OnLoad
    The script block that is executed when the step is loaded. The script block will receive the $Body parameter which contains JSON for the current state of the stepper. If you are using form controls, their data will be availalble in the $Body.Context property.
     
    .PARAMETER Label
    A label for this step.
     
    .PARAMETER Optional
    Whether this step is optional.
 
    .PARAMETER Icon
    The icon to put with the step
 
    .PARAMETER RemoveIconStyle
    Removes circle style around icon
 
    .PARAMETER DisablePrevious
    Disables the back button on the step
     
    .EXAMPLE
    Creates a stepper that reports the stepper context with each step.
 
    New-UDStepper -Id 'stepper' -Steps {
        New-UDStep -OnLoad {
            New-UDElement -tag 'div' -Content { "Step 1" }
            New-UDTextbox -Id 'txtStep1'
        } -Label "Step 1"
        New-UDStep -OnLoad {
            New-UDElement -tag 'div' -Content { "Step 2" }
            New-UDElement -tag 'div' -Content { "Previous data: $Body" }
            New-UDTextbox -Id 'txtStep2'
        } -Label "Step 2"
        New-UDStep -OnLoad {
            New-UDElement -tag 'div' -Content { "Step 3" }
            New-UDElement -tag 'div' -Content { "Previous data: $Body" }
            New-UDTextbox -Id 'txtStep3'
        } -Label "Step 3"
    } -OnFinish {
        New-UDTypography -Text 'Nice! You did it!' -Variant h3
        New-UDElement -Tag 'div' -Id 'result' -Content {$Body}
    }
     
    #>

    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Alias("Content")]
        [Parameter(Mandatory)]
        [Endpoint]$OnLoad,
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [Switch]$Optional,
        [Parameter()]
        [Switch]$DisablePrevious,
        [Parameter ()]
        $Icon,
        [Parameter()]
        [Switch]$RemoveIconStyle,
        [Parameter()]        
        [Hashtable]$IconStyle,
        [Parameter()]        
        [Hashtable]$DarkIconStyle
    )

    $OnLoad.Register($Id + "onLoad", $PSCmdlet)

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    @{
        id              = $id 
        isPlugin        = $true 
        type            = 'mu-stepper-step'
        assetId         = $MUAssetId 

        onLoad          = $OnLoad
        label           = $Label
        optional        = $Optional.IsPresent 
        icon            = $Icon
        disablePrevious = $DisablePrevious.IsPresent 
        removeIconStyle = $RemoveIconStyle.IsPresent 
        iconStyle       = $IconStyle
        darkIconStyle   = $DarkIconStyle
    }
}


function New-UDStyle {
    <#
    .SYNOPSIS
    Style a component with CSS styles.
     
    .DESCRIPTION
    Style a component with CSS styles.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Style
    The CSS style to apply
     
    .PARAMETER Tag
    The outer HTML tag to use.
     
    .PARAMETER Content
    The content of this style.
 
    .PARAMETER Sx
    A hashtable of theme-aware CSS properties.
 
    .PARAMETER Component
    The root component to apply the Sx component to.
     
    .EXAMPLE
 
    PS > New-UDStyle -Style 'color: red; background-color: black' -Content {
    PS > New-UDTypography -Text "This is red"
    PS > }
 
    Style|Apply a CSS style to a component.
    #>

    # [Component("Style", "Css3", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter(Mandatory = $true, ParameterSetName = 'Style')]
        [string]$Style,
        [Parameter(Mandatory = $true, ParameterSetName = 'Sx')]
        [Hashtable]$Sx,
        [Parameter(ParameterSetName = 'Sx')]
        [string]$Component,
        [Parameter()]
        [string]$Tag = 'div',
        [Parameter()]
        [ScriptBlock]$Content
    )

    End {

        $Children = $null
        if ($null -ne $Content) {
            $Children = & $Content
        }

        @{
            assetId   = $AssetId 
            isPlugin  = $true 
            type      = "ud-style"
            id        = $Id
            style     = $Style
            tag       = $Tag
            content   = $Children
            sx        = $Sx
            component = $Component
        }
    }
}
function New-UDSwitch {
    <#
    .SYNOPSIS
    Creates a new switch.
     
    .DESCRIPTION
    Creates a new switch. A switch behaves like a checkbox but looks a little different.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Disabled
    Whether this switch is disabled.
     
    .PARAMETER OnChange
    A script block that is called when this switch changes. The $EventData variable will contain the checked value ($true\$false).
     
    .PARAMETER Checked
    Whether this switch is checked.
 
    .PARAMETER Color
    The theme color to apply to this switch.
 
    .PARAMETER Label
    The label to display next to the switch
 
    .PARAMETER CheckedLabel
    The label to display for when the switch is checked
 
    .PARAMETER UncheckedLabel
    The label to display for when the switch is unchecked
     
    .PARAMETER ClassName
    A CSS class to apply to the switch.
 
    .PARAMETER Size
    The size of the switch. Valid values are: "small", "medium"
 
    .PARAMETER CheckStyle
    Style switch with check and unchecked icons for the colorblind.
 
    .PARAMETER LabelPlacement
    The placement of the label. Valid values are: "top", "start", "right", "bottom"
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch1' -Checked $true
 
    Basic switch|Creates a basic switch.
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch2' -Checked $true -OnChange {
    PS > Show-UDToast -Message "Switch changed to $($EventData)"
    PS > }
 
    OnChange|Creates a switch with an OnChange script block.
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch3' -Disabled
 
    Disabled|Creates a disabled switch.
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch4' -Checked $true -Label 'On' -UncheckedLabel 'Off'
 
    Label|Creates a switch with a label.
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch5' -Checked $true -Label 'On' -UncheckedLabel 'Off' -LabelPlacement 'bottom'
 
    Label Placement|Creates a switch with a label and a label placement.
 
    .EXAMPLE
    PS > New-UDSwitch -Id 'switch6' -Checked $true -Label 'On' -UncheckedLabel 'Off' -LabelPlacement 'bottom' -CheckStyle
 
    Check Style|Creates a switch with a label, a label placement, and a check style.
    #>

    [Category("app/component")]
    [Description("Creates a new switch.")]
    [DisplayName("Switch")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [bool]$Checked,
        [Parameter()]
        [string]$ClassName,
        [Parameter ()]
        [ValidateSet('default', 'primary', 'secondary')]
        [string]$Color = 'default',
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [string]$CheckedLabel,
        [Parameter()]
        [string]$UncheckedLabel,
        [Parameter()]
        [ValidateSet("small", "medium")]
        [string]$Size = 'medium',
        [Parameter()]
        [ValidateSet("top", "start", "right", "bottom")]
        [string]$LabelPlacement,
        [Parameter()]
        [Switch]$CheckStyle
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    @{
        type           = 'mu-switch'
        id             = $Id 
        assetId        = $MUAssetId 
        isPlugin       = $true 

        disabled       = $Disabled.IsPresent 
        checked        = $Checked 
        onChange       = $onChange
        className      = $ClassName
        color          = $Color.ToLower()
        label          = $Label
        checkedLabel   = $CheckedLabel
        uncheckedLabel = $UncheckedLabel
        size           = $Size.ToLower()
        labelPlacement = if ($LabelPlacement) { $LabelPlacement.ToLower() } else { $null }
        checkStyle     = $CheckStyle.IsPresent 
    }
}

function New-UDSyntaxHighlighter {
    <#
    .SYNOPSIS
    Syntax highlighting for text.
     
    .DESCRIPTION
    Syntax highlighting for text.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Code
    The code to syntax highlight.
     
    .PARAMETER Language
    The language to highlight the code in.
     
    .PARAMETER Style
    The style to apply to the code.
     
    .PARAMETER ShowLineNumbers
    Whether to show line numbers.
 
    .EXAMPLE
    PS > New-UDSyntaxHighlighter -Code 'Write-Host "Hello World"' -Language powershell
 
    Basic Syntax Highlighter|A basic syntax highlighter.
 
    .EXAMPLE
    PS > New-UDSyntaxHighlighter -Code 'Write-Host "Hello World"' -Language powershell -Style 'vs'
 
    Style|A syntax highlighter with a style.
 
    .EXAMPLE
    PS > New-UDSyntaxHighlighter -Code 'Write-Host "Hello World"' -Language powershell -ShowLineNumbers
 
    Line Numbers|A syntax highlighter with line numbers.
    #>

    [Category("app/component")]
    [Description("Syntax highlighting for text")]
    [DisplayName("Syntax Highlighter")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [string]$Code,
        [Parameter()]
        [ValidateSet('abap',
            'abnf',
            'actionscript',
            'ada',
            'agda',
            'al',
            'antlr4',
            'apacheconf',
            'apex',
            'apl',
            'applescript',
            'aql',
            'arduino',
            'arff',
            'asciidoc',
            'asm6502',
            'asmatmel',
            'aspnet',
            'autohotkey',
            'autoit',
            'avisynth',
            'avroIdl',
            'bash',
            'basic',
            'batch',
            'bbcode',
            'bicep',
            'birb',
            'bison',
            'bnf',
            'brainfuck',
            'brightscript',
            'bro',
            'bsl',
            'c',
            'cfscript',
            'chaiscript',
            'cil',
            'clike',
            'clojure',
            'cmake',
            'cobol',
            'coffeescript',
            'concurnas',
            'coq',
            'cpp',
            'crystal',
            'csharp',
            'cshtml',
            'csp',
            'cssExtras',
            'css',
            'csv',
            'cypher',
            'd',
            'dart',
            'dataweave',
            'dax',
            'dhall',
            'diff',
            'django',
            'dnsZoneFile',
            'docker',
            'dot',
            'ebnf',
            'editorconfig',
            'eiffel',
            'ejs',
            'elixir',
            'elm',
            'erb',
            'erlang',
            'etlua',
            'excelFormula',
            'factor',
            'falselang',
            'firestoreSecurityRules',
            'flow',
            'fortran',
            'fsharp',
            'ftl',
            'gap',
            'gcode',
            'gdscript',
            'gedcom',
            'gherkin',
            'git',
            'glsl',
            'gml',
            'gn',
            'goModule',
            'go',
            'graphql',
            'groovy',
            'haml',
            'handlebars',
            'haskell',
            'haxe',
            'hcl',
            'hlsl',
            'hoon',
            'hpkp',
            'hsts',
            'http',
            'ichigojam',
            'icon',
            'icuMessageFormat',
            'idris',
            'iecst',
            'ignore',
            'inform7',
            'ini',
            'io',
            'j',
            'java',
            'javadoc',
            'javadoclike',
            'javascript',
            'javastacktrace',
            'jexl',
            'jolie',
            'jq',
            'jsExtras',
            'jsTemplates',
            'jsdoc',
            'json',
            'json5',
            'jsonp',
            'jsstacktrace',
            'jsx',
            'julia',
            'keepalived',
            'keyman',
            'kotlin',
            'kumir',
            'kusto',
            'latex',
            'latte',
            'less',
            'lilypond',
            'liquid',
            'lisp',
            'livescript',
            'llvm',
            'log',
            'lolcode',
            'lua',
            'magma',
            'makefile',
            'markdown',
            'markupTemplating',
            'markup',
            'matlab',
            'maxscript',
            'mel',
            'mermaid',
            'mizar',
            'mongodb',
            'monkey',
            'moonscript',
            'n1ql',
            'n4js',
            'nand2tetrisHdl',
            'naniscript',
            'nasm',
            'neon',
            'nevod',
            'nginx',
            'nim',
            'nix',
            'nsis',
            'objectivec',
            'ocaml',
            'opencl',
            'openqasm',
            'oz',
            'parigp',
            'parser',
            'pascal',
            'pascaligo',
            'pcaxis',
            'peoplecode',
            'perl',
            'phpExtras',
            'php',
            'phpdoc',
            'plsql',
            'powerquery',
            'powershell',
            'processing',
            'prolog',
            'promql',
            'properties',
            'protobuf',
            'psl',
            'pug',
            'puppet',
            'pure',
            'purebasic',
            'purescript',
            'python',
            'q',
            'qml',
            'qore',
            'qsharp',
            'r',
            'racket',
            'reason',
            'regex',
            'rego',
            'renpy',
            'rest',
            'rip',
            'roboconf',
            'robotframework',
            'ruby',
            'rust',
            'sas',
            'sass',
            'scala',
            'scheme',
            'scss',
            'shellSession',
            'smali',
            'smalltalk',
            'smarty',
            'sml',
            'solidity',
            'solutionFile',
            'soy',
            'sparql',
            'splunkSpl',
            'sqf',
            'sql',
            'squirrel',
            'stan',
            'stylus',
            'swift',
            'systemd',
            't4Cs',
            't4Templating',
            't4Vb',
            'tap',
            'tcl',
            'textile',
            'toml',
            'tremor',
            'tsx',
            'tt2',
            'turtle',
            'twig',
            'typescript',
            'typoscript',
            'unrealscript',
            'uorazor',
            'uri',
            'v',
            'vala',
            'vbnet',
            'velocity',
            'verilog',
            'vhdl',
            'vim',
            'visualBasic',
            'warpscript',
            'wasm',
            'webIdl',
            'wiki',
            'wolfram',
            'wren',
            'xeora',
            'xmlDoc',
            'xojo',
            'xquery',
            'yaml',
            'yang',
            'zig')]
        [string]$Language = "powershell",
        [Parameter()]
        [ValidateSet('coy',
            'dark',
            'funky',
            'okaidia',
            'solarizedlight',
            'tomorrow',
            'twilight',
            'prism',
            'a11yDark',
            'atomDark',
            'base16AteliersulphurpoolLight',
            'cb',
            'coldarkCold',
            'coldarkDark',
            'coyWithoutShadows',
            'darcula',
            'dracula',
            'duotoneDark',
            'duotoneEarth',
            'duotoneForest',
            'duotoneLight',
            'duotoneSea',
            'duotoneSpace',
            'ghcolors',
            'gruvboxDark',
            'gruvboxLight',
            'holiTheme',
            'hopscotch',
            'lucario',
            'materialDark',
            'materialLight',
            'materialOceanic',
            'nightOwl',
            'nord',
            'oneDark',
            'oneLight',
            'pojoaque',
            'shadesOfPurple',
            'solarizedDarkAtom',
            'synthwave84',
            'vs',
            'vscDarkPlus',
            'xonokai',
            'zTouch')]
        [string]$Style = "oneDark",
        [Parameter()]
        [Switch]$ShowLineNumbers
    )
    $Styles = @('coy',
        'dark',
        'funky',
        'okaidia',
        'solarizedlight',
        'tomorrow',
        'twilight',
        'prism',
        'a11yDark',
        'atomDark',
        'base16AteliersulphurpoolLight',
        'cb',
        'coldarkCold',
        'coldarkDark',
        'coyWithoutShadows',
        'darcula',
        'dracula',
        'duotoneDark',
        'duotoneEarth',
        'duotoneForest',
        'duotoneLight',
        'duotoneSea',
        'duotoneSpace',
        'ghcolors',
        'gruvboxDark',
        'gruvboxLight',
        'holiTheme',
        'hopscotch',
        'lucario',
        'materialDark',
        'materialLight',
        'materialOceanic',
        'nightOwl',
        'nord',
        'oneDark',
        'oneLight',
        'pojoaque',
        'shadesOfPurple',
        'solarizedDarkAtom',
        'synthwave84',
        'vs',
        'vscDarkPlus',
        'xonokai',
        'zTouch')
    
    

    $Languages = @('abap',
        'abnf',
        'actionscript',
        'ada',
        'agda',
        'al',
        'antlr4',
        'apacheconf',
        'apex',
        'apl',
        'applescript',
        'aql',
        'arduino',
        'arff',
        'asciidoc',
        'asm6502',
        'asmatmel',
        'aspnet',
        'autohotkey',
        'autoit',
        'avisynth',
        'avroIdl',
        'bash',
        'basic',
        'batch',
        'bbcode',
        'bicep',
        'birb',
        'bison',
        'bnf',
        'brainfuck',
        'brightscript',
        'bro',
        'bsl',
        'c',
        'cfscript',
        'chaiscript',
        'cil',
        'clike',
        'clojure',
        'cmake',
        'cobol',
        'coffeescript',
        'concurnas',
        'coq',
        'cpp',
        'crystal',
        'csharp',
        'cshtml',
        'csp',
        'cssExtras',
        'css',
        'csv',
        'cypher',
        'd',
        'dart',
        'dataweave',
        'dax',
        'dhall',
        'diff',
        'django',
        'dnsZoneFile',
        'docker',
        'dot',
        'ebnf',
        'editorconfig',
        'eiffel',
        'ejs',
        'elixir',
        'elm',
        'erb',
        'erlang',
        'etlua',
        'excelFormula',
        'factor',
        'falselang',
        'firestoreSecurityRules',
        'flow',
        'fortran',
        'fsharp',
        'ftl',
        'gap',
        'gcode',
        'gdscript',
        'gedcom',
        'gherkin',
        'git',
        'glsl',
        'gml',
        'gn',
        'goModule',
        'go',
        'graphql',
        'groovy',
        'haml',
        'handlebars',
        'haskell',
        'haxe',
        'hcl',
        'hlsl',
        'hoon',
        'hpkp',
        'hsts',
        'http',
        'ichigojam',
        'icon',
        'icuMessageFormat',
        'idris',
        'iecst',
        'ignore',
        'inform7',
        'ini',
        'io',
        'j',
        'java',
        'javadoc',
        'javadoclike',
        'javascript',
        'javastacktrace',
        'jexl',
        'jolie',
        'jq',
        'jsExtras',
        'jsTemplates',
        'jsdoc',
        'json',
        'json5',
        'jsonp',
        'jsstacktrace',
        'jsx',
        'julia',
        'keepalived',
        'keyman',
        'kotlin',
        'kumir',
        'kusto',
        'latex',
        'latte',
        'less',
        'lilypond',
        'liquid',
        'lisp',
        'livescript',
        'llvm',
        'log',
        'lolcode',
        'lua',
        'magma',
        'makefile',
        'markdown',
        'markupTemplating',
        'markup',
        'matlab',
        'maxscript',
        'mel',
        'mermaid',
        'mizar',
        'mongodb',
        'monkey',
        'moonscript',
        'n1ql',
        'n4js',
        'nand2tetrisHdl',
        'naniscript',
        'nasm',
        'neon',
        'nevod',
        'nginx',
        'nim',
        'nix',
        'nsis',
        'objectivec',
        'ocaml',
        'opencl',
        'openqasm',
        'oz',
        'parigp',
        'parser',
        'pascal',
        'pascaligo',
        'pcaxis',
        'peoplecode',
        'perl',
        'phpExtras',
        'php',
        'phpdoc',
        'plsql',
        'powerquery',
        'powershell',
        'processing',
        'prolog',
        'promql',
        'properties',
        'protobuf',
        'psl',
        'pug',
        'puppet',
        'pure',
        'purebasic',
        'purescript',
        'python',
        'q',
        'qml',
        'qore',
        'qsharp',
        'r',
        'racket',
        'reason',
        'regex',
        'rego',
        'renpy',
        'rest',
        'rip',
        'roboconf',
        'robotframework',
        'ruby',
        'rust',
        'sas',
        'sass',
        'scala',
        'scheme',
        'scss',
        'shellSession',
        'smali',
        'smalltalk',
        'smarty',
        'sml',
        'solidity',
        'solutionFile',
        'soy',
        'sparql',
        'splunkSpl',
        'sqf',
        'sql',
        'squirrel',
        'stan',
        'stylus',
        'swift',
        'systemd',
        't4Cs',
        't4Templating',
        't4Vb',
        'tap',
        'tcl',
        'textile',
        'toml',
        'tremor',
        'tsx',
        'tt2',
        'turtle',
        'twig',
        'typescript',
        'typoscript',
        'unrealscript',
        'uorazor',
        'uri',
        'v',
        'vala',
        'vbnet',
        'velocity',
        'verilog',
        'vhdl',
        'vim',
        'visualBasic',
        'warpscript',
        'wasm',
        'webIdl',
        'wiki',
        'wolfram',
        'wren',
        'xeora',
        'xmlDoc',
        'xojo',
        'xquery',
        'yaml',
        'yang',
        'zig')



    $Style = $styles | Where-Object { $_ -eq $Style }
    $Language = $Languages | Where-Object { $_ -eq $Language }

    @{
        isPlugin        = $true 
        type            = "mu-syntax-highlighter"
        id              = $Id
        code            = $code 
        language        = $Language
        style           = $style 
        showLineNumbers = $ShowLineNumbers.IsPresent
    }
}
function ConvertTo-FlatObject {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $InputObject
    )

    Process {
        $OutputObject = @{ }

        if ($null -eq $InputObject) {
            return
        }

        if ($InputObject -is [Hashtable]) {
            foreach ($key in $InputObject.Keys) {
                if ($key -and ($key.StartsWith('rendered') -or $key -eq 'rowexpanded' -or $key -eq 'udrowstyle')) { 
                    $OutputObject[$key] = $InputObject[$key]
                }
                else {
                    $Value = $InputObject[$key]
                    if ($Value -is [DateTime]) {
                        $OutputObject[$key] = $Value
                    }
                    else {
                        $OutputObject[$key] = if ($null -ne $Value) { $Value.ToString() } else { "" } 
                    }
                    
                } 
            }
            [PSCustomObject]$OutputObject
        }
        else {
            $InputObject | Get-Member -MemberType Properties | ForEach-Object {
                if ($_.Name -and ($_.Name.StartsWith('rendered') -or $_.Name -eq 'rowexpanded' -or $_.Name -eq 'udrowstyle') ) { 
                    $OutputObject[$_.Name] = $InputObject."$($_.Name)"
                }
                else {
                    $Value = $InputObject."$($_.Name)"
                    if ($Value -is [DateTime]) {
                        $OutputObject[$_.Name] = $Value
                    }
                    else {
                        $OutputObject[$_.Name] = if ($null -ne $Value) { $Value.ToString() } else { "" } 
                    }
                } 
            }
            [PSCustomObject]$OutputObject
        }
    }
}

function New-UDTable {
    <#
    .SYNOPSIS
    Creates a table.
     
    .DESCRIPTION
    Creates a table. Tables are used to show both static and dynamic data. You can define columns and data to show within the table. The columns can be used to render custom components based on row data. You can also enable paging, filtering, sorting and even server-side processing.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Title
    The title to show at the top of the table's card.
     
    .PARAMETER Data
    The data to put into the table.
     
    .PARAMETER LoadRows
    When using dynamic tables, this script block is called. The $Body parameter will contain a hashtable the following options:
  
    filters: @()
    orderBy: string
    orderDirection: string
    page: int
    pageSize: int
    properties: @()
    search: string
    totalCount: int
 
    You can use these values to perform server-side processing, like SQL queries, to improve the performance of large grids.
 
    After processing the data with these values, output the data via Out-UDTableData.
             
    .PARAMETER Columns
    Defines the columns to show within the table. Use New-UDTableColumn to define these columns. If this parameter isn't specified, the properties of the data that you pass in will become the columns.
     
    .PARAMETER Sort
    Whether sorting is enabled in the table.
     
    .PARAMETER Filter
    Whether filtering is enabled in the table.
     
    .PARAMETER Search
    Whether search is enabled in the table.
     
    .PARAMETER Export
    Whether exporting is enabled within the table.
 
    .PARAMETER Icon
    Sets an icon next to the title. Use New-UDIcon to create the icon.
 
    .PARAMETER OnRowSelection
    A script block to call when a row is selected. $EventData will contain the selected rows.
 
    .PARAMETER ShowSort
    Whether to show sort controls on columns
 
    .PARAMETER ShowFilter
    Whether to show filter controls on columns
 
    .PARAMETER ShowSearch
    Whether to show full table search
 
    .PARAMETER Dense
    Reduces the white-space used within the table.
 
    .PARAMETER StickyHeader
    Makes the header sticky.
 
    .PARAMETER PageSize
    The default page size.
 
    .PARAMETER PageSizeOptions
    An array of available page size options.
 
    .PARAMETER ShowSelection
    Whether to allow selection within the table.
 
    .PARAMETER ShowPagination
    Whether to show pagination controls.
 
    .PARAMETER Size
    The size of the table. Defaults to medium. Valid values are medium and small.
 
    .PARAMETER TextOption
    Customizations to standard text within the table. Use New-UDTextOption to create the text options.
 
    .PARAMETER ExportOption
    An array of export options.
 
    .PARAMETER OnExport
    A script block used to customize how the export is performed.
 
    .PARAMETER DisablePageSizeAll
    Removes the All option from page size options.
 
    .PARAMETER DefaultSortDirection
    The default sort direction.
 
    .PARAMETER HideToggleAllRowsSelected
    Hides the toggle all rows selected button.
 
    .PARAMETER DisableMultiSelect
    Disables multi-select.
 
    .PARAMETER DisableSortRemove
    Removes the sort option for unsorted columns. Columns will always be ascending or descending.
 
    .PARAMETER ClassName
    A CSS class to apply to the table.
 
    .PARAMETER PaginationLocation
    Where to show the pagination controls. Valid values are top, bottom, or both. Defaults to bottom.
 
    .PARAMETER AutoRefresh
    Reloads the table on an interval when -LoadRows is being used.
 
    .PARAMETER AutoRefreshInterval
    The interval to reload data when AutoRefresh is specified.
 
    .PARAMETER Language
    The language. Primarily used for Date and Time filters.
 
    .PARAMETER ShowRefresh
    Whether to show the refresh button.
 
    .PARAMETER ShowExport
    Whether to show the export button.
 
    .PARAMETER ToolbarContent
    Custom content to show in the toolbar.
 
    .PARAMETER OnRowStyle
    A script block to call to style rows. The $EventData variable will contain the row data.
 
    .PARAMETER OnRowExpand
    A script block to call when a row is expanded. The $EventData variable will contain the row data.
 
    .PARAMETER HeaderStyle
    A hashtable of styles to apply to the header.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS > New-UDTable -Data $Data -Id 'table1'
 
    Basic Table|Creates a static table whether the columns of the table are the properties of the data specified.
 
    .EXAMPLE
     
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title "A Dessert"
    PS > New-UDTableColumn -Property Calories -Title Calories
    PS > New-UDTableColumn -Property Fat -Title Fat
    PS > New-UDTableColumn -Property Carbs -Title Carbs
    PS > New-UDTableColumn -Property Protein -Title Protein
    PS > )
    PS > New-UDTable -Data $Data -Columns $Columns -Id 'table2'
 
    Columns|Creates a table where there are custom columns defined for that table.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title Dessert -Render {
    PS > New-UDButton -Id "btn$($EventData.Dessert)" -Text "Click for Dessert!" -OnClick { Show-UDToast -Message $EventData.Dessert }
    PS > }
    PS > New-UDTableColumn -Property Calories -Title Calories
    PS > New-UDTableColumn -Property Fat -Title Fat
    PS > New-UDTableColumn -Property Carbs -Title Carbs
    PS > New-UDTableColumn -Property Protein -Title Protein
    PS > )
    PS > New-UDTable -Data $Data -Columns $Columns -Id 'table3'
 
    Render|Creates a table where the columns are rendered with custom content.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title Dessert -Render {
    PS > New-UDButton -Id "btn$($EventData.Dessert)" -Text "Click for Dessert!" -OnClick { Show-UDToast -Message $EventData.Dessert }
    PS > }
    PS > New-UDTableColumn -Property Calories -Title Calories -Width 5 -Truncate
    PS > New-UDTableColumn -Property Fat -Title Fat
    PS > New-UDTableColumn -Property Carbs -Title Carbs
    PS > New-UDTableColumn -Property Protein -Title Protein
    PS > )
    PS >
    PS > New-UDTable -Data $Data -Columns $Columns -Id 'table4'
 
    Column Width|Set custom column width
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title "A Dessert" -Filter -FilterType AutoComplete
    PS > New-UDTableColumn -Property Calories -Title Calories -Filter -FilterType Range
    PS > New-UDTableColumn -Property Fat -Title Fat -Filter -FilterType Range
    PS > New-UDTableColumn -Property Carbs -Title Carbs -Filter -FilterType Range
    PS > New-UDTableColumn -Property Protein -Title Protein -Filter -FilterType Range
    PS > )
    PS >
    PS > New-UDTable -Id 'table5' -Data $Data -Columns $Columns -ShowFilter
 
    Column Filter|Columns filters for a table
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title "A Dessert" -IncludeInSearch
    PS > New-UDTableColumn -Property Calories -Title Calories
    PS > New-UDTableColumn -Property Fat -Title Fat
    PS > New-UDTableColumn -Property Carbs -Title Carbs
    PS > New-UDTableColumn -Property Protein -Title Protein
    PS > )
    PS >
    PS > New-UDTable -Id 'table6' -Data $Data -Columns $Columns -ShowSearch
 
    Column Search|Columns can be included in the search
 
    .EXAMPLE
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Name -Title "Name" -ShowFilter
    PS > New-UDTableColumn -Property Value -Title "Value" -ShowFilter
    PS > )
    PS >
    PS > $Data = 1..1000 | ForEach-Object {
    PS > [PSCustomObject]@{
    PS > Name = "Record-$_"
    PS > Value = $_
    PS > }
    PS > }
    PS >
    PS > New-UDTable -Columns $Columns -LoadData {
    PS > foreach($Filter in $EventData.Filters)
    PS > {
    PS > $Data = $Data | Where-Object -Property $Filter.Id -Match -Value $Filter.Value
    PS > }
    PS >
    PS > $TotalCount = $Data.Count
    PS >
    PS > if (-not [string]::IsNullOrEmpty($EventData.OrderBy.Field))
    PS > {
    PS > $Descending = $EventData.OrderDirection -ne 'asc'
    PS > $Data = $Data | Sort-Object -Property ($EventData.orderBy.Field) -Descending:$Descending
    PS > }
    PS >
    PS > $Data = $Data | Select-Object -First $EventData.PageSize -Skip ($EventData.Page * $EventData.PageSize)
    PS >
    PS > $Data | Out-UDTableData -Page $EventData.Page -TotalCount $TotalCount -Properties $EventData.Properties
    PS > } -ShowFilter -ShowSort -ShowPagination -Id 'table7'
 
    Server-Side Processing|Server-side processing is useful for large data sets that should not be loaded into the browser.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > New-UDTable -Data $Data -Paging -PageSize 2 -Id 'table8'
 
    Paging|Paging can be enabled by setting the -Paging parameter to $true. The default page size is 10.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > New-UDTable -Data $Data -OnRowExpand {
    PS > New-UDAlert -Text $EventData.Calories
    PS > } -Columns @(
    PS > New-UDTableColumn -Title 'Dessert' -Property 'Dessert'
    PS > ) -Id 'table9'
     
    Row Expansion|Row expansion can be enabled by setting the -OnRowExpand parameter to a scriptblock. The scriptblock will be passed the row data as a hashtable in the $EventData variable.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title "A Dessert" -IncludeInExport
    PS > New-UDTableColumn -Property Calories -Title Calories -IncludeInExport
    PS > New-UDTableColumn -Property Fat -Title Fat -IncludeInExport
    PS > New-UDTableColumn -Property Carbs -Title Carbs -IncludeInExport
    PS > New-UDTableColumn -Property Protein -Title Protein -IncludeInExport -Hidden
    PS > )
    PS >
    PS > New-UDTable -Id 'table10' -Data $Data -Columns $Columns -Export
 
    Export|Export table data with the -Export parameter. You can configure which columns are included in the export by setting the -IncludeInExport parameter on the column. Use -Hidden to hide a column from the table but include it in the export.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0}
    PS > )
    PS >
    PS > $Option = New-UDTableTextOption -Search "Search all these records"
    PS >
    PS > New-UDTable -Data $Data -TextOption $Option -ShowSearch -Id 'table11'
 
    Text Options|Text options can be used to customize the text in the table.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0 }
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0 }
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0 }
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0 }
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 24; Protein = 4.0 }
    PS > )
    PS >
    PS > New-UDTable -Data $Data -OnRowStyle {
    PS > if ($EventData.Dessert -eq 'Frozen yoghurt')
    PS > {
    PS > @{
    PS > backgroundColor = "red"
    PS > }
    PS > } else {
    PS > @{}
    PS > }
    PS > } -Id 'table12'
 
    Row Styling|You can style rows by using the -OnRowStyle parameter. The script block will be passed the row data as a hashtable in the $EventData variable. The script block should return a hashtable of CSS styles to apply to the row.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{Dessert = 'Frozen yoghurt'; Calories = 159; Fat = 6.0; Carbs = 1; Protein = 4.0 }
    PS > @{Dessert = 'Ice cream sandwich'; Calories = 159; Fat = 6.0; Carbs = 34; Protein = 4.0 }
    PS > @{Dessert = 'Eclair'; Calories = 159; Fat = 6.0; Carbs = 73; Protein = 4.0 }
    PS > @{Dessert = 'Cupcake'; Calories = 159; Fat = 6.0; Carbs = 25; Protein = 4.0 }
    PS > @{Dessert = 'Gingerbread'; Calories = 159; Fat = 6.0; Carbs = 99; Protein = 4.0 }
    PS > )
    PS > $Columns = @(
    PS > New-UDTableColumn -Property Dessert -Title "Dessert"
    PS > New-UDTableColumn -Property Calories -Title "Calories"
    PS > New-UDTableColumn -Property Fat -Title "Fat"
    PS > New-UDTableColumn -Property Carbs -Title "Carbs" -DefaultSortColumn
    PS > New-UDTableColumn -Property Protein -Title "Protein"
    PS > )
    PS > New-UDTable -Data $Data -DefaultSortDirection descending -Id 'table13' -ShowSort -Columns $Columns
 
    Default Sort|Set the default sort direction and column.
    #>

    [CmdletBinding(DefaultParameterSetName = "Static")]
    [Category("app/component")]
    [Description("A table component for displaying data.")]
    [DisplayName("Table")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [string]$Title = "",
        [Parameter(ParameterSetName = "Static")]
        [AllowEmptyCollection()]
        [AllowNull()]
        [System.ComponentModel.BrowsableAttribute(0)]
        [object[]]$Data,
        [Parameter(Mandatory, ParameterSetName = "Dynamic")]
        [Alias("LoadData")]
        [Endpoint]$LoadRows,
        [Parameter(ParameterSetName = "Static")]
        [Parameter(ParameterSetName = "Dynamic")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Hashtable[]]$Columns,
        [Parameter()]
        [Endpoint]$OnRowSelection,
        [Parameter()]
        [Alias("Sort")]
        [Switch]$ShowSort,
        [Parameter()]
        [Alias("Filter")]
        [Switch]$ShowFilter,
        [Parameter()]
        [Alias("Search")]
        [Switch]$ShowSearch,
        [Parameter()]
        [Switch]$Dense,
        [Parameter()]
        [Alias("Export")]
        [Switch]$ShowExport,
        [Parameter()]
        [Switch]$StickyHeader,
        [Parameter()]
        [int]$PageSize = 5,
        [Parameter()]
        [int[]]$PageSizeOptions = @(),
        [Parameter()]
        [ValidateSet("top", "bottom", "both")]
        [string]$PaginationLocation = "bottom",
        [Parameter()]
        [Alias("Select")]
        [Switch]$ShowSelection,
        [Parameter()]
        [Alias("Paging")]
        [Switch]$ShowPagination,
        [Parameter()]
        [ValidateSet("default", "checkbox", "none")]
        [string]$Padding = "default",
        [Parameter()]
        [ValidateSet("small", "medium")]
        [string]$Size = "medium",
        [Parameter()]
        [Hashtable]$TextOption = (New-UDTableTextOption),
        [Parameter()]
        [string[]]$ExportOption = @("XLSX", "PDF", "JSON", "CSV"),
        [Parameter()]
        [Endpoint]$OnExport,
        [Parameter()]
        [Switch]$DisablePageSizeAll,
        [Parameter()]
        [ValidateSet('ascending', 'descending')]
        [string]$DefaultSortDirection = 'ascending',
        [Parameter()]
        [Switch]$HideToggleAllRowsSelected,
        [Parameter()]
        [Switch]$DisableMultiSelect,
        [Parameter()]
        [Switch]$DisableSortRemove,
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$ShowRefresh,
        [Parameter()]
        [ScriptBlock]$ToolbarContent,
        [Parameter()]
        [ScriptBlock]$OnRowExpand,
        [int]$MaxHeight,
        [Parameter(ParameterSetName = "Dynamic")]
        [Switch]$AutoRefresh,
        [Parameter(ParameterSetName = "Dynamic")]
        [int]$AutoRefreshInterval = 10,
        [Parameter()]
        [Alias("Locale")]
        [ValidateSet("en", "de", 'ru', 'fr', 'nl')]
        [string]$Language = "en",
        [Parameter()]
        [Switch]$RemoveCard,
        [Parameter()]
        [ScriptBlock]$OnRowStyle,
        [Parameter()]
        [Hashtable]$HeaderStyle
    )

    Begin {
        function getDefaultSortColumn {
            param(
                [Parameter()]    
                [object[]]$Columns
            )
            $DefaultSortColumn = $Columns.Where( { $_.DefaultSortColumn })
            $DefaultSortColumn.field
        }
    }
    Process {

        if ($OnExport) {
            $OnExport.Register($Id + 'Export', $PSCmdlet)
        }

        if (($null -eq $Columns) -and ($null -ne $Data)) {
            $item = $Data | Select-Object -First 1 | ConvertTo-FlatObject
    
            if ($item -is [Hashtable]) {
                $Columns = foreach ($member in $item.Keys) {
                    if ($ShowSearch) {
                        New-UDTableColumn -Property $member -IncludeInSearch
                    }
                    elseif ($ShowExport) {
                        New-UDTableColumn -Property $member -IncludeInExport
                    }
                    elseif ($ShowSearch -and $ShowExport) {
                        New-UDTableColumn -Property $member -IncludeInExport -IncludeInSearch
                    }
                    else {
                        New-UDTableColumn -Property $member
                    }
                }
            }
            else {
                $Columns = foreach ($member in $item.PSObject.Properties) {
                    if ($ShowSearch) {
                        New-UDTableColumn -Property $member.Name -IncludeInSearch
                    }
                    elseif ($ShowExport) {
                        New-UDTableColumn -Property $member.Name -IncludeInExport
                    }
                    elseif ($ShowSearch -and $ShowExport) {
                        New-UDTableColumn -Property $member.Name -IncludeInExport -IncludeInSearch
                    }
                    else {
                        New-UDTableColumn -Property $member.Name
                    }
                }
            }
        }
        
        if ($LoadRows) {
            $LoadRows.Register($Id, $PSCmdlet, @{ "TableColumns" = $Columns; "OnRowExpand" = $OnRowExpand; "OnRowStyle" = $OnRowStyle })            
        }

        if ($Columns -eq $null) {
            $Rows = $LoadRows
            $LoadRows = $null
        }
            
        if ($OnRowSelection) {
            $OnRowSelection.Register($Id + 'OnRowSelection', $PSCmdlet)
        }        
        
        if ($Columns) {
            $RenderedColumns = $Columns.Where( { $null -ne $_.Render })
            if ($Data.Count -ge 1) {
                foreach ($Item in $Data) {
                    $vars = [System.Collections.Generic.List[PSVariable]]::new()
                    $null = $vars.Add((New-Variable -Name EventData -Value $Item -Force -PassThru))

                    if ($OnRowStyle) {
                        [Hashtable]$RowStyle = $OnRowStyle.GetNewClosure().InvokeWithContext(@{}, $vars) | Select-Object -First 1
                        if ($Item -isnot [hashtable]) {
                            Add-Member -InputObject $Item -MemberType NoteProperty -Name "udrowstyle" -Value $RowStyle -Force
                        }
                        else {
                            $Item["udrowstyle"] = $RowStyle
                        }
                    }

                    foreach ($Column in $RenderedColumns) {
                        $RenderedData = $Column.Render.GetNewClosure().InvokeWithContext(@{}, $vars)
                        if (-not $RenderedData) {
                            $RenderedData = ""
                        }

                        if ($Item -isnot [hashtable]) {
                            Add-Member -InputObject $Item -MemberType NoteProperty -Name "rendered$($Column.field)" -Value $RenderedData -Force
                        }
                        else {
                            $Item["rendered$($Column.field)"] = $RenderedData
                        }
                    }
                }
            }
        }

        if ($OnRowExpand -and $Data.Count -ge 1) {
            foreach ($Item in $Data) {
                $vars = [System.Collections.Generic.List[PSVariable]]::new()
                $null = $vars.Add((New-Variable -Name EventData -Value $Item -Force -PassThru))

                $RenderedData = $OnRowExpand.GetNewClosure().InvokeWithContext(@{}, $vars)
                if (-not $RenderedData) {
                    $RenderedData = ""
                }

                if ($Item -isnot [hashtable]) {
                    Add-Member -InputObject $Item -MemberType NoteProperty -Name "rowexpanded" -Value $RenderedData -Force
                }
                else {
                    $Item["rowexpanded"] = $RenderedData
                }
            }
        }
    }

    End {

        $defaultSortColumn = getDefaultSortColumn($Columns)
        if ($defaultSortColumn -and -not $DefaultSortDirection) {
            $DefaultSortDirection = 'ascending'
        }

        if ($Data) { 
            $Data = [Array]($Data | ConvertTo-FlatObject) 
            if ($Data -isnot [Array]) {
                $Data = @($Data)
            }
        }
        else {
            $Data = @()
        }

        @{
            id                        = $Id 
            assetId                   = $MUAssetId 
            isPlugin                  = $true 
            type                      = "mu-table"
    
            title                     = $Title
            columns                   = $Columns
            defaultSortColumn         = $defaultSortColumn
            data                      = $Data
            showSort                  = $ShowSort.IsPresent
            showFilter                = $ShowFilter.IsPresent
            showSearch                = $ShowSearch.IsPresent
            showExport                = $ShowExport.IsPresent 
            showSelection             = $ShowSelection.IsPresent 
            showPagination            = $ShowPagination.IsPresent
            stickyHeader              = $StickyHeader.IsPresent
            isDense                   = $Dense.IsPresent
            loadData                  = $LoadRows
            onRowSelection            = $OnRowSelection
            userPageSize              = $PageSize
            userPageSizeOptions       = if ($PageSizeOptions.Count -gt 0) { $PageSizeOptions }else { @(5, 10, 20, 50) }
            padding                   = $Padding.ToLower()
            size                      = $Size
            textOption                = $TextOption
            exportOption              = $ExportOption | ForEach-Object { $_.ToUpper() }
            onExport                  = $OnExport
            disablePageSizeAll        = $DisablePageSizeAll.IsPresent
            defaultSortDirection      = $DefaultSortDirection.ToLower()
            hideToggleAllRowsSelected = $HideToggleAllRowsSelected.IsPresent
            disableMultiSelect        = $DisableMultiSelect.IsPresent
            disableSortRemove         = $DisableSortRemove.IsPresent
            icon                      = $Icon
            className                 = $ClassName
            paginationLocation        = $PaginationLocation.ToLower()
            showRefresh               = $ShowRefresh.IsPresent
            toolbarContent            = if ($ToolbarContent) { & $ToolbarContent } else { $null }
            maxHeight                 = if ($MaxHeight -eq 0) { $null } else { $MaxHeight }
            autoRefresh               = $AutoRefresh.IsPresent
            autoRefreshInterval       = $AutoRefreshInterval
            locale                    = $Language.ToLower()
            removeCard                = $RemoveCard.IsPresent
            noData                    = $null -eq $LoadRows -and ($null -eq $Data -or $Data.Count -eq 0)
            rows                      = $Rows
            headerStyle               = $HeaderStyle
        }
    }
}

function New-UDTableTextOption {
    <#
    .SYNOPSIS
    Creates a hashtable to set the text options of a table.
     
    .DESCRIPTION
    Creates a hashtable to set the text options of a table.
     
    .PARAMETER ExportAllCsv
    Overrides the Export All to CSV text.
     
    .PARAMETER ExportCurrentViewCsv
    Overrides the Export Current View as CSV text.
     
    .PARAMETER ExportAllXLSX
    Overrides the Export All to XLSX text.
     
    .PARAMETER ExportCurrentViewXLSX
    Overrides the Export Current View as XLSX text.
     
    .PARAMETER ExportAllPDF
    Overrides the Export All to PDF text.
     
    .PARAMETER ExportCurrentViewPDF
    Overrides the Export Current View as PDF text.
     
    .PARAMETER ExportAllJson
    Overrides the Export All to JSON text.
     
    .PARAMETER ExportCurrentViewJson
    Overrides the Export Current View as JSON text.
     
    .PARAMETER Search
    Overrides the Search text. You can use {0} to use as a place holder for the number of rows.
     
    .PARAMETER FilterSearch
    Overrides the column filter text. You can use {0} to use as a place holder for the number of rows.
 
    .PARAMETER NoData
    Overrides the No Data text.
     
    .EXAMPLE
    $Options = New-UDTableTextOption -Search "Filter all the rows"
    New-UDTable -Data $Data -TextOption $Ootions
     
    .NOTES
    General notes
    #>

    param(
        [Parameter()]
        [string]$ExportAllCsv = "Export all as CSV",
        [Parameter()]
        [string]$ExportCurrentViewCsv = "Export Current View as CSV",
        [Parameter()]
        [string]$ExportAllXLSX = "Export all as XLSX",
        [Parameter()]
        [string]$ExportCurrentViewXLSX = "Export Current View as XLSX",
        [Parameter()]
        [string]$ExportAllPDF = "Export all as PDF",
        [Parameter()]
        [string]$ExportCurrentViewPDF = "Export Current View as PDF",
        [Parameter()]
        [string]$ExportAllJson = "Export all as JSON",
        [Parameter()]
        [string]$ExportCurrentViewJson = "Export Current View as JSON",
        [Parameter()]
        [string]$ExportFileName = "File Name",
        [Parameter()]
        [string]$Search = "Search {0} records...",
        [Parameter()]
        [string]$FilterSearch = "Search {0} records...",
        [Parameter()]
        [string]$Export = "",
        [Parameter()]
        [string]$FilterDate = "Filter Date",
        [Parameter()]
        [string]$NoData = "No Data",
        [Parameter()]
        [string]$Loading = "Loading...",
        [Parameter()]
        [string]$RowsPerPage = "Rows per page:",
        [Parameter()]
        [string]$CountDescription
    )

    @{
        exportAllCsv          = $ExportAllCsv
        exportCurrentViewCsv  = $ExportCurrentViewCsv
        exportAllXlsx         = $ExportAllXLSX
        exportCurrentViewXlsx = $ExportCurrentViewXLSX
        exportAllPdf          = $ExportAllPDF
        exportCurrentViewPdf  = $ExportCurrentViewPDF
        exportAllJson         = $ExportAllJson
        exportCurrentViewJson = $ExportCurrentViewJson
        exportFileName        = $ExportFileName
        search                = $Search
        filterSearch          = $FilterSearch
        export                = $Export
        filterDate            = $FilterDate
        noData                = $NoData
        loading               = $Loading
        rowsPerPage           = $RowsPerPage
        countDescription      = $CountDescription
    }
}

function New-UDTableColumn {
    <#
    .SYNOPSIS
    Defines a table column.
     
    .DESCRIPTION
    Defines a table column. Use this cmdlet in conjunction with New-UDTable's -Column property. Table columns can be used to control many aspects of the columns within a table.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Property
    The property to select from the data.
     
    .PARAMETER Title
    The title of the column to show at the top of the table.
     
    .PARAMETER Render
    How to render this table. Use this parameter instead of property to render custom content within a column. The $Body variable will contain the current row being rendered.
     
    .PARAMETER ShowSort
    Whether this column supports sorting.
     
    .PARAMETER ShowFilter
    Whether this column supports filtering.
     
    .PARAMETER Search
    Whether this column supports searching.
 
    .PARAMETER Hidden
    Includes a column in the table but does not show it. This is useful for columns that are used for filtering and exporting but are not meant to be displayed in the table.
     
    .PARAMETER FilterType
    The type of filter to use with this column. Valid values are "text", "select", "fuzzy", "slider", "range", "date", "number", 'autocomplete'
 
    .PARAMETER Style
    A hashtable of style attributes to apply to the column.
 
    .PARAMETER Width
    The width of this column in pixels.
 
    .PARAMETER IncludeInSearch
    Whether to include this column in the search.
 
    .PARAMETER IncludeInExport
    Whether to include this column in the export.
 
    .PARAMETER DefaultSortColumn
    Sets this column as the default sort column.
 
    .PARAMETER Align
    The alignment of the column. Supported values are 'center', 'inherit', 'justify', 'left', 'right'.
     
    .PARAMETER Truncate
    Whether to truncate the text in this column. A -Width is required to use truncate.
 
    .PARAMETER SortType
    Whether to sort this column as a string or datetime.
 
    .PARAMETER Options
    The options to use for a select filter.
 
 
    .EXAMPLE
    See New-UDTable for examples.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter(Mandatory)]
        [string]$Property, 
        [Parameter()]
        [string]$Title,
        [Parameter()]
        [Alias("Render")]
        [ScriptBlock]$OnRender,
        [Parameter()]
        [Alias("Sort")]
        [switch]$ShowSort,
        [Parameter()]
        [Alias("Filter")]
        [switch]$ShowFilter,
        [Parameter()]
        [ValidateSet("text", "select", "fuzzy", "slider", "range", "date", "number", 'autocomplete')]
        [string]$FilterType = "text",
        [Parameter()]
        [hashtable]$Style = @{ },
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [int]$MinWidth,
        [Parameter()]
        [Alias("Search")]
        [switch]$IncludeInSearch,
        [Parameter()]
        [Alias("Export")]
        [switch]$IncludeInExport,
        [Parameter()]
        [switch]$DefaultSortColumn,
        [Parameter()]
        [ValidateSet('center', 'inherit', 'justify', 'left', 'right')]
        [string]$Align = 'inherit',
        [Parameter()]
        [Switch]$Truncate,
        [Parameter()]
        [ValidateSet('basic', 'datetime', 'alphanumeric')]
        [string]$SortType = 'alphanumeric',
        [Parameter()]
        [Switch]$Hidden,
        [Parameter()]
        [string[]]$Options
    )

    if ($null -eq $Title -or $Title -eq '') {
        $Title = $Property
    }

    if ($Width -gt 0) {
        $style["maxWidth"] = $width
        $style["width"] = $width
    }

    if ($MinWidth -gt 0) {
        $style["minWidth"] = $width
    }

    if ($Truncate) {
        $style["whiteSpace"] = "nowrap"
        $style["overflow"] = "hidden"
        $style["textOverflow"] = "ellipsis"
    }

    @{
        id                  = $Id 
        field               = $Property.ToLower()
        title               = $Title 
        showSort            = $ShowSort.IsPresent 
        showFilter          = $ShowFilter.IsPresent
        filterType          = $FilterType.ToLower()
        includeInSearch     = $IncludeInSearch.IsPresent
        includeInExport     = $IncludeInExport.IsPresent
        isDefaultSortColumn = $DefaultSortColumn.IsPresent
        render              = $OnRender
        width               = $Width
        align               = $Align
        style               = $Style
        sortType            = $SortType
        hidden              = $Hidden.IsPresent
        options             = $Options
    }
}
function Out-UDTableData {
    <#
    .SYNOPSIS
    Formats data to be output from New-UDTable's -LoadRows script block.
     
    .DESCRIPTION
    Formats data to be output from New-UDTable's -LoadRows script block.
     
    .PARAMETER Data
    The data to return from LoadRows.
     
    .PARAMETER Page
    The current page we are on within the table.
     
    .PARAMETER TotalCount
    The total count of items within the data set.
     
    .PARAMETER Properties
    The properties that are currently passed from the table. You can return the array from the $EventData.Properties array.
     
    .EXAMPLE
    See New-UDTable for examples.
    #>

    param(
        [Parameter(ValueFromPipeline = $true, Mandatory)]
        [object]$Data,
        [Parameter(Mandatory)]
        [int]$Page,
        [Parameter(Mandatory)]
        [int]$TotalCount,
        [Parameter(Mandatory)]
        [Alias("Property")]
        [string[]]$Properties
    )

    Begin {
        $DataPage = @{
            data       = @() 
            page       = $Page 
            totalCount = $TotalCount
        }
    }

    Process {
        $item = @{ }
        foreach ($property in $Properties) {
            $RenderedColumn = $TableColumns.Where( { $_.field -eq $property -and $_.Render })
            if ($RenderedColumn) {
                Set-Variable -Name 'EventData' -Value $Data
                $Render = $RenderedColumn.Render.GetNewClosure()
                $item["rendered" + $property] = $Render.Invoke()
            }

            $item[$property] = $Data.$property
        }

        if ($OnRowStyle) {
            Set-Variable -Name 'EventData' -Value $Item

            $Render = $OnRowStyle.GetNewClosure()
            $RenderedData = $Render.Invoke()
            if (-not $RenderedData) {
                $RenderedData = ""
            }

            if ($Item -isnot [hashtable]) {
                Add-Member -InputObject $Item -MemberType NoteProperty -Name "udrowstyle" -Value $RenderedData -Force
            }
            else {
                $Item["udrowstyle"] = $RenderedData
            }
        }

        if ($OnRowExpand) {
            Set-Variable -Name 'EventData' -Value $Item

            $Render = $OnRowExpand.GetNewClosure()
            $RenderedData = $Render.Invoke()
            if (-not $RenderedData) {
                $RenderedData = ""
            }

            if ($Item -isnot [hashtable]) {
                Add-Member -InputObject $Item -MemberType NoteProperty -Name "rowexpanded" -Value $RenderedData -Force
            }
            else {
                $Item["rowexpanded"] = $RenderedData
            }
        }

        $DataPage.data += $item
    }

    End {
        if ($DataPage.data) {
            $DataPage.data = [Array]($DataPage.data | ConvertTo-FlatObject)

            $DataPage
        }
    }
}
function New-UDTabs {
    <#
    .SYNOPSIS
    Creates a new set of tabs.
     
    .DESCRIPTION
    Creates a new set of tabs. Tabs can be used to show lots of content on a single page.
     
    .PARAMETER Tabs
    The tabs to put within this container.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER RenderOnActive
    Deprecated
     
    .PARAMETER Orientation
    The orientation of the tabs. Valid values are horizontal and vertical.
 
    .PARAMETER Variant
    The variantion of tabs. Valid values are standard, fullWidth and scrollable.
 
    .PARAMETER ScrollButtons
    The behavior of the scrollbuttons. Valid values are on, off, auto and desktop. On will enable scroll buttons no matter what. off will disable all scroll buttons. Auto will show scrollbuttons when necessary. Desktop will show scrollbuttons on medium and large screens.
 
    .PARAMETER Centered
    Whether the tabs should be centered.
 
    .PARAMETER ClassName
    The CSS class to apply to the tabs.
     
    .EXAMPLE
    PS > New-UDTabs -Tabs {
    PS > New-UDTab -Text "Tab1" -Id 'Tab1' -Content {
    PS > New-UDElement -Tag div -Id 'tab1Content' -Content { "Tab1Content"}
    PS > }
    PS > New-UDTab -Text "Tab2" -Id 'Tab2' -Content {
    PS > New-UDElement -Tag div -Id 'tab2Content' -Content { "Tab2Content"}
    PS > }
    PS > New-UDTab -Text "Tab3" -Id 'Tab3' -Content {
    PS > New-UDElement -Tag div -Id 'tab3Content' -Content { "Tab3Content"}
    PS > }
    PS > } -Id 'tabs1'
 
    Basic Tabs|A basic set of tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs2' -Tabs {
    PS > New-UDTab -Text "Tab1" -Id 'DynamicTab1' -Dynamic -Content {
    PS > New-UDElement -Tag div -Id 'DynamicTab1Content' -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Id 'DynamicTab2' -Dynamic -Content {
    PS > New-UDElement -Tag div -Id 'DynamicTab2Content' -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Id 'DynamicTab3' -Dynamic -Content {
    PS > New-UDElement -Tag div -Id 'DynamicTab3Content' -Content { Get-Date }
    PS > }
    PS > }
 
    Dynamic Tabs|A set of dynamic tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs3' -Orientation 'vertical' -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Vertical Tabs|A set of vertical tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs4' -Variant 'fullWidth' -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Full Width Tabs|A set of full width tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs5' -Variant 'scrollable' -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Scrollable Tabs|A set of scrollable tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs6' -ScrollButtons 'on' -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Scroll Buttons On|A set of tabs with scroll buttons always on.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs7' -Centered -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Centered Tabs|A set of centered tabs.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs8' -Tabs {
    PS > New-UDTab -Text "Tab1" -Icon (New-UDIcon -Icon Eye) -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Icon (New-UDIcon -Icon Home) -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Icon (New-UDIcon -Icon User) -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Tabs with Icons|A set of tabs with icons.
 
    .EXAMPLE
    PS > New-UDTabs -Id 'tabs7' -Tabs {
    PS > New-UDTab -Text "Tab1" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab2" -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > New-UDTab -Text "Tab3" -Disabled -Content {
    PS > New-UDElement -Tag div -Content { Get-Date }
    PS > }
    PS > }
 
    Disabled Tab|A set of tabs with a disabled tab.
 
 
    #>

    [CmdletBinding()]
    # [Component("Tabs", "TableColumns", "Creates a new card.")]
    param(
        [Parameter(Mandatory)]
        [ScriptBlock]$Tabs,
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [Switch]$RenderOnActive,
        [Parameter()]
        [ValidateSet('horizontal', 'vertical')]
        [string]$Orientation = "horizontal",
        [Parameter()]
        [ValidateSet('fullWidth', 'scrollable', 'standard')]
        [string]$Variant = 'standard',
        [Parameter()]
        [ValidateSet('on', 'off', 'auto')]
        [string]$ScrollButtons = 'auto',
        [Parameter()]
        [switch]$Centered,
        [Parameter()]
        [string]$ClassName
    )

    End {

        $ScrollButtonsValue = "auto"
        if ($ScrollButtons -eq 'on') {
            $ScrollButtonsValue = $true
            $Variant = 'scrollable'
        }
        else {
            $ScrollButtonsValue = $false
        }

        if ($RenderOnActive) {
            Write-Warning "RenderOnActive is deprecated and will be removed in 4.0"
        }

        $c = New-UDErrorBoundary -Content $Tabs

        if ($Variant -eq 'fullWidth') {
            $Variant = 'fullWidth'
        }
        else {
            $Variant = $Variant.ToLower()
        }

        @{
            isPlugin                 = $true
            assetId                  = $MUAssetId
            type                     = "mu-tabs"
            tabs                     = $c
            id                       = $id
            orientation              = $Orientation
            variant                  = $Variant
            scrollButtons            = $ScrollButtonsValue
            allowScrollButtonsMobile = $true
            centered                 = $Centered.IsPresent
            className                = $ClassName
        }
    }
}

function New-UDTab {
    <#
    .SYNOPSIS
    Creates a new tab.
     
    .DESCRIPTION
    Creates a new tab. Use New-UDTabs as a container for tabs.
     
    .PARAMETER Text
    The text to display for this tab.
     
    .PARAMETER Content
    The content to display when the tab is selected.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Dynamic
    Whether this tab is dynamic. Dynamic tabs won't render until they are displayed.
     
    .PARAMETER Icon
    The Icon to display within the tab header.
     
    .PARAMETER Disabled
    Whether this tab is disabled.
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Text,
        [Parameter(Mandatory)]
        [Endpoint]$Content,
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [switch]$Dynamic,
        [Parameter()]
        [object]$Icon,
        [Parameter()]
        [switch]$Disabled
    )

    End {
        if ($null -ne $Content -and $Dynamic) {
            $Content.Register($Id, $PSCmdlet)
        }
        else {
            $c = New-UDErrorBoundary -Content $Content.ScriptBlock
        }

        @{
            isPlugin = $true
            assetId  = $MUAssetId
            type     = "mu-tab"
            label    = $Text
            icon     = $Icon
            content  = $c
            id       = $Id
            dynamic  = $Dynamic.IsPresent
            disabled = $Disabled.IsPresent
            render   = if ($Dynamic.IsPresent) { $Content } else { $null }
        }
    }
}
function New-UDTextbox {
    <#
    .SYNOPSIS
    Textboxes let users enter and edit text.
     
    .DESCRIPTION
    Creates a textbox. Textboxes can be used by themselves or within a New-UDForm.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
 
    .PARAMETER Label
    A label to show above this textbox.
     
    .PARAMETER Placeholder
    A placeholder to place within the text box.
     
    .PARAMETER Value
    The current value of the textbox.
     
    .PARAMETER Type
    The type of textbox. This can be values such as text, password or email.
     
    .PARAMETER Disabled
    Whether this textbox is disabled.
     
    .PARAMETER Icon
    The icon to show next to the textbox. Use New-UDIcon to create an icon.
     
    .PARAMETER Autofocus
    Whether to autofocus this textbox.
 
    .PARAMETER Multiline
    Creates a multiline textbox
 
    .PARAMETER Shrink
    Whether to shrink label by default.
 
    .PARAMETER Rows
    The number of rows in a multiline textbox.
 
    .PARAMETER RowsMax
    The maximum number of rows in a multiline textbox.
 
    .PARAMETER FullWidth
    Whether to make this textbox take up the full width of the parent control.
 
    .PARAMETER Mask
    The mask to apply over a textbox.
 
    .PARAMETER Variant
    The variant of textbox. Valid values are "filled", "outlined", "standard"
     
    .PARAMETER ClassName
    A CSS class to apply to the textbox.
 
    .PARAMETER OnEnter
    A script block that is called when the user presses the enter key. The $EventData variable will contain the current value of the textbox.
 
    .PARAMETER OnBlur
    A script block that is called when the user leaves the textbox. The $EventData variable will contain the current value of the textbox.
 
    .PARAMETER OnValidate
    A script block that is called when the user leaves the textbox. The $EventData variable will contain the current value of the textbox. If the script block returns an validation result that is invalid, it will be displayed below the textbox.
 
    .PARAMETER HelperText
    A helper text to show below the textbox.
 
    .PARAMETER OnChange
    A script block that is called when the user changes the value of the textbox. The $EventData variable will contain the current value of the textbox.
 
    .PARAMETER Minimum
    The minimum value for a number textbox.
 
    .PARAMETER Maximum
    The maximum value for a number textbox.
     
    .PARAMETER MaximumLength
    The maximum length of the text in the textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox1' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    Basic textbox|Creates a basic textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox2' -Label 'Password' -Placeholder 'Enter your password' -Type 'password'
 
    Password textbox|Creates a password textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox3' -Label 'Email' -Placeholder 'Enter your email' -Type 'email'
 
    Email textbox|Creates an email textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox4' -Disabled -Label 'Disabled' -Placeholder 'Enter your name' -Value 'John Doe'
 
    Disabled textbox|Creates a disabled textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox5' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe' -Icon (New-UDIcon -Icon 'User')
 
    Textbox with icon|Creates a textbox with an icon.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox6' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe' -Autofocus
 
    Autofocus textbox|Creates a textbox that will autofocus when the page loads.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox7' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe' -Multiline
 
    Multiline textbox|Creates a multiline textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox8' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe' -Multiline -Rows 5 -RowsMax 10
 
    Multiline textbox with 5 rows|Creates a multiline textbox with 5 rows.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox9' -Variant 'filled' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    Variant|Creates a filled textbox.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox10' -OnEnter {
    PS > Show-UDToast -Message "You pressed enter!"
    PS > } -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    OnEnter|Creates a textbox with an OnEnter event.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox11' -OnBlur {
    PS > Show-UDToast -Message "You left the textbox!"
    PS > } -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    OnBlur|Creates a textbox with an OnBlur event.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox12' -OnValidate {
    PS > if ($EventData -eq 'John Doe') {
    PS > return New-UDValidationResult -Valid
    PS > }
    PS > else {
    PS > return New-UDValidationResult -ValidationError "You must enter John Doe"
    PS > }
    PS > } -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    OnValidate|Creates a textbox with an OnValidate event.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox13' -HelperText 'This is a helper text' -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    Helper Text|Creates a textbox with helper text.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox14' -OnChange {
    PS > Show-UDToast -Message $EventData
    PS > } -Label 'Name' -Placeholder 'Enter your name' -Value 'John Doe'
 
    OnChange|Creates a textbox with an OnChange event.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'textbox15' -Type 'number' -Minimum 1 -Maximum 100
 
    Minimum and Maximum|Creates a number textbox with a minimum and maximum value.
 
    #>

    [Category("app/component")]
    [Description("Creates a textbox")]
    [DisplayName("Textbox")]
    param(
        [Parameter()]
        [String]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [string]$Placeholder,
        [Parameter()]
        [string]$Value,
        [Parameter()]
        [ValidateSet('text', 'password', 'email', 'number', 'time', 'datetime-local', 'date', 'color', 'month', 'week')]
        [String]$Type = 'text',
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        $Icon,
        [Parameter()]
        [Switch]$Autofocus,
        [Parameter()]
        [Switch]$Multiline,
        [Parameter()]
        [int]$Rows = -1,
        [Parameter()]
        [int]$RowsMax = 9999,
        [Parameter()]
        [Switch]$FullWidth,
        [Parameter()]
        [string]$Mask,
        [Parameter()]
        [switch]$Unmask,
        [Parameter()]
        [ValidateSet("filled", "outlined", "standard")]
        [string]$Variant = "standard",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Endpoint]$OnEnter,
        [Parameter()]
        [Endpoint]$OnBlur,
        [Parameter()]
        [Switch]$Shrink,
        [Parameter()]
        [Endpoint]$OnValidate,
        [Parameter()]
        [string]$HelperText,
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [int]$Minimum,
        [Parameter()]
        [int]$Maximum,
        [Parameter()]
        [int]$MaximumLength
    )

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    if ($OnValidate) {
        $OnValidate.Register($Id + "onBlur", $PSCmdlet)
    }

    if ($OnEnter) {
        $OnEnter.Register($Id + "onEnter", $PSCmdlet)
    }

    if ($OnBlur) {
        $OnBlur.Register($Id + 'onBlur', $PSCmdlet)
    }

    if ($OnChange) {
        $OnChange.Register($Id + 'onChange', $PSCmdlet)
    }

    @{
        id          = $id 
        assetId     = $MUAssetId 
        isPlugin    = $true 
        type        = "mu-textbox"

        label       = $Label
        placeholder = $placeholder
        value       = $value 
        textType    = $type.ToLower()
        disabled    = $Disabled.IsPresent 
        autoFocus   = $AutoFocus.IsPresent
        icon        = $icon
        multiline   = $Multiline.IsPresent
        rows        = if ($Rows -eq -1) { $null } else { $Rows }
        maxRows     = $RowsMax
        fullWidth   = $FullWidth.IsPresent
        mask        = $Mask
        unmask      = $Unmask.IsPresent
        variant     = $Variant.ToLower()
        className   = $ClassName
        onEnter     = $OnEnter
        onBlur      = $OnBlur
        shrink      = $Shrink.IsPresent
        onValidate  = $OnValidate
        valid       = $true
        helperText  = $HelperText
        onChange    = $OnChange
        minimum     = if ($MyInvocation.BoundParameters.ContainsKey("Minimum")) { $Minimum } else { $null } 
        maximum     = if ($MyInvocation.BoundParameters.ContainsKey("Maximum")) { $Maximum } else { $null }
        maxLength   = if ($MyInvocation.BoundParameters.ContainsKey("MaximumLength")) { $MaximumLength } else { $null }
    }
}


$AntDesign = @{
    light = @{
        palette     = @{
            text       = @{
                disabled = "rgba(0, 0, 0, 0.50)"
            }
            primary    = @{
                light = '#69696a'
                main  = '#1890ff'
                dark  = '#1e1e1f'
            }
            secondary  = @{
                light = '#1890ff'
                main  = '#1890ff'
                dark  = '#e62958'
            }
            warning    = @{
                main = '#ffc071'
                dark = '#ffb25e'
            }
            error      = @{
                xLight = '#ffebee'
                main   = '#f44336'
                dark   = '#d32f2f'
            }
            success    = @{
                xLight = '#e8f5e9'
                main   = '#4caf50'
                dark   = '#388e3c'
            }
            background = @{
                default = "#f0f2f5"
            }
        }
        typography  = @{
            fontFamily = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji'"
            body1      = @{
                fontSize = 14
            }
            h6         = @{
                fontSize   = 14
                fontWeight = 400
            }
        }
        globalStyle = "
            ::-webkit-scrollbar {
                width: 10px;
            }
            ::-webkit-scrollbar-track {
                background: #f1f1f1;
            }
            ::-webkit-scrollbar-thumb {
                background: #cccccc;
            }
            ::-webkit-scrollbar-thumb:hover {
                background: #888;
            }
        "

        overrides   = @{
            MuiAlert          = @{
                root            = @{
                    borderRadius = '2px'
                    padding      = '8px 15px'
                    marginBottom = '16px'
                }
                standardError   = @{
                    border = '1px #ffccc7 solid'
                }
                standardInfo    = @{
                    border = '1px #91d5ff solid'
                }
                standardSuccess = @{
                    border = '1px #b7eb8f solid'
                }
                standardWarning = @{
                    border = '1px #ffe58f solid'
                }

            }
            MuiAppBar         = @{
                root         = @{
                    boxShadow = 'none'
                }
                colorPrimary = @{
                    color           = '#000'
                    backgroundColor = '#fff'
                }
            }
            MuiAutocomplete   = @{
                inputRoot = @{
                    padding = '4px'
                }
            }
            MuiButton         = @{
                root      = @{
                    borderRadius  = 0
                    boxShadow     = 'none'
                    textTransform = 'none'
                    '&:hover'     = @{
                        backgroundColor = '#40a9ff'
                        borderColor     = '#40a9ff'
                        color           = '#fff'
                        boxShadow       = 'none'
                    }
                }
                contained = @{
                    color           = "#fff"
                    lineHeight      = 1.5715
                    fontWeight      = 400
                    backgroundColor = "#1890ff"
                    borderColor     = "#1890ff"
                    transition      = "all .3s cubic-bezier(.645,.045,.355,1)"
                }
                
            }
            MuiCheckbox       = @{
                root = @{
                    color      = 'rgb(24, 144, 255)'
                    fontWeight = 100
                    fontSize   = '1.1rem'
                }
            }
            MuiCollapse       = @{
                wrapperInner = @{
                    backgroundColor = "rgb(250, 250, 250)"
                }
            }
            MuiDataGrid       = @{
                cell          = @{
                    borderBottom = '1px solid rgb(250, 250, 250)'
                }
                columnHeaders = @{
                    backgroundColor = 'rgb(250, 250, 250)'
                }
                root          = @{
                    border       = '0px'
                    borderRadius = '0'
                }
            }
            MuiDrawer         = @{
                paper                 = @{
                    border = 'none'
                }
                paperAnchorDockedLeft = @{
                    borderRight = $null
                }
            }
            MuiFormControl    = @{
                root = @{
                    #marginTop = '10px'
                }
            }
            MuiExpansionPanel = @{
                rounded = @{
                    "&:first-child" = @{
                        borderTopLeftRadius  = 0
                        borderTopRightRadius = 0
                    }
                    "&:last-child"  = @{
                        borderBottomLeftRadius  = 0
                        borderBottomRightRadius = 0
                    }
                }
            }
            MuiIconButton     = @{
                root = @{
                    borderRadius = 0
                    fontSize     = 14
                    padding      = "4px 12px"
                }
            }
            MuiInput          = @{
                root = @{
                    lineHeight  = 1.5715
                    "&::before" = @{
                        border = '0px !important'
                    }
                    "&::after"  = @{
                        border = '0px !important'
                    }
                }
            }
            MuiInputBase      = @{
                input = @{
                    border       = '1px solid #d9d9d9'
                    borderRadius = '2px'
                    padding      = '4px 11px'
                    color        = "rgba(0,0,0,.85)"
                    lineHeight   = 1.5715
                    fontSize     = 14
                    "&:hover"    = @{
                        borderColor = "#40a9ff"
                    }
                    "&:focus"    = @{
                        borderColor = '#40a9ff'
                        boxShadow   = "0 0 0 2px rgb(24 144 255 / 20%)"
                    }
                }
            }
            MuiInputLabel     = @{
                root     = @{
                    paddingLeft = '14px'
                }
                shrink   = @{
                    transform = 'translate(-10px, -1.5px) scale(0.75)'
                }
                outlined = @{
                    paddingLeft = '0px'
                    transform   = 'translate(0, -20px) scale(0.75)'
                }
            }

            MuiListItem       = @{
                root   = @{
                    transition       = "opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1),color .3s"
                    cursor           = 'pointer'
                    "&:hover"        = @{
                        color = '#1890ff !important'
                    }
                    "&.Mui-selected" = @{
                        backgroundColor = '#e6f7ff'
                        color           = '#1890ff'
                        borderRight     = '2px solid #1890ff'
                    }
                }
                button = @{
                    "&:hover" = @{
                        color           = '#1890ff'
                        backgroundColor = "#fff"
                    }
                }
            
            }
            MuiListItemIcon   = @{
                root = @{
                    minWidth  = '25px'
                    "&:hover" = @{
                        color = '#1890ff'
                    }
                }
            }
            MuiListItemText   = @{
                root      = @{
                    marginLeft = '10px'
                }
                multiline = @{
                    marginTop    = 0
                    marginBottom = 0
               
                }

            }
            MuiLoadingButton  = @{
                loadingIndicator = @{
                    color = 'black'
                }
            }
            MuiOutlinedInput  = @{
                root      = @{
                    borderRadius = 0
                }
                input     = @{
                    paddingTop    = '4px'
                    paddingBottom = '4px'
                    border        = '0px'
                }
                "&:focus" = @{
                    border = '0px'
                }
            }
            MuiPaper          = @{
                root       = @{
                    boxShadow = 'none'
                }
                rounded    = @{
                    borderRadius = 0
                }
                elevation1 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation2 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation3 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation4 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation5 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
            }
            MuiSvgIcon        = @{
                colorPrimary = @{
                    color = "#000"
                }
            }
            MuiSwitch         = @{
                root       = @{
                    height = "40px !important"
                }
                thumb      = @{
                    marginTop  = '5px !important'
                    marginLeft = '5px !important'
                    width      = "12px !important"
                    height     = "12px !important"
                }
                track      = @{
                    height       = '16px'
                    borderRadius = "9px !important"
                }
                sizeSmall  = @{
                    paddingTop = '12px'
                }
                switchBase = @{
                    padding                          = '9px !important'
                    '&.Mui-checked'                  = @{
                        transform = 'translateX(50%) !important'
                    }
                    '&.Mui-checked+.MuiSwitch-track' = @{
                        backgroundColor = '#177ddc'
                        opacity         = '1'
                    }
                    '&:hover'                        = @{
                        backgroundColor = 'transparent'
                    }
                }

            }
            MuiTab            = @{
                root      = @{
                    minHeight     = 0
                    textTransform = 'none'
                }
                labelIcon = @{
                    minHeight = 0
                }
            } 
        }
    }
    dark  = @{
        palette     = @{
            text       = @{
                primary   = 'rgba(255, 255, 255, 0.85)'
                disabled  = "rgba(255, 255, 255, 0.38)"
                secondary = "rgba(255, 255, 255, 0.38)"
            }
            primary    = @{
                main = 'rgb(31, 31, 31)'
            }
            warning    = @{
                main = '#ffc071'
                dark = '#ffb25e'
            }
            error      = @{
                xLight = '#ffebee'
                main   = '#f44336'
                dark   = '#d32f2f'
            }
            success    = @{
                xLight = '#e8f5e9'
                main   = '#4caf50'
                dark   = '#388e3c'
            }
            background = @{
                default = "#000"
            }
        }
        typography  = @{
            fontFamily = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji'"
            body1      = @{
                fontSize = 14
            }
            h6         = @{
                fontSize   = 14
                fontWeight = 400
            }
        }
        globalStyle = "
            ::-webkit-scrollbar {
                width: 10px;
            }
            ::-webkit-scrollbar-track {
                background: rgb(31, 31, 31);
            }
            ::-webkit-scrollbar-thumb {
                background: #c4c4c4;
            }
            ::-webkit-scrollbar-thumb:hover {
                background: #888;
            }
            option {
                background: rgb(31, 31, 31) !important;
            }
        "

        overrides   = @{
            MuiAccordionSummary = @{
                expandIconWrapper = @{
                    color = "#fff"
                }
            }
            MuiAlert            = @{
                root            = @{
                    color        = 'rgba(255, 255, 255, 0.85)'
                    borderRadius = '2px'
                    padding      = '8px 15px'
                    marginBottom = '16px'
                }
                standardError   = @{
                    border          = '1px solid #58181c'
                    backgroundColor = '#2a1215'
                }
                standardInfo    = @{
                    border          = '1px solid #153450'
                    backgroundColor = '#111b26'
                }
                standardSuccess = @{
                    border          = '1px solid #274916'
                    backgroundColor = '#162312'
                }
                standardWarning = @{
                    border          = '1px #594214 solid'
                    backgroundColor = '#2b2111'
                }
            }
            MuiAppBar           = @{
                root = @{
                    boxShadow = 'none'
                }
            }
            MuiButton           = @{
                root      = @{
                    color            = "#fff"
                    textTransform    = 'none'
                    '&.Mui-disabled' = @{
                        color = 'rgba(255, 255, 255, 0.50);'
                    }
                }
                contained = @{
                    color           = "#fff"
                    lineHeight      = 1.5715
                    fontWeight      = 400
                    backgroundColor = "#1890ff"
                    borderColor     = "#1890ff"
                    borderRadius    = 0
                    boxShadow       = $null
                    transition      = "all .3s cubic-bezier(.645,.045,.355,1)"
                    '&:hover'       = @{
                        backgroundColor = '#40a9ff'
                        borderColor     = '#40a9ff'
                        color           = '#fff'
                        boxShadow       = $null
                    }
                }
                outlined  = @{
                    borderRadius = 0
                    border       = '1px solid #434343'
                    '&:hover'    = @{
                        borderColor = '#165996'
                        color       = '#165996'
                    }
                }
            }
            MuiCheckbox         = @{
                root = @{
                    color           = 'rgb(24, 144, 255)'
                    fontWeight      = 100
                    '&.Mui-checked' = @{
                        color = 'rgb(24, 144, 255)'
                    }
                }
                svg  = @{
                    fontSize = '1.1rem !important'
                }
            }
            MuiChip             = @{
                root = @{
                    color        = '#3c9ae8'
                    borderColor  = '#153956'
                    background   = '#111d2c'
                    borderRadius = '2px'
                }
            }
            MuiCollapse         = @{
                wrapperInner = @{
                    backgroundColor = "rgb(20,20,20)"
                }
            }
            MuiDataGrid         = @{
                cell          = @{
                    borderBottom = '1px solid #1d1d1d'
                }
                columnHeaders = @{
                    backgroundColor = '#1d1d1d'
                }
                root          = @{
                    border          = '0px'
                    borderRadius    = '0'
                    backgroundColor = '#141414'
                }
                paper         = @{
                    backgroundColor = "rgb(31, 31, 31)"
                }
            }
            MuiDrawer           = @{
                paper                 = @{
                    color = "rgba(255, 255, 255, 0.65);"
                }
                paperAnchorDockedLeft = @{
                    borderRight = 'none'
                }
            }
            MuiExpansionPanel   = @{
                rounded = @{
                    "&:first-child" = @{
                        borderTopLeftRadius  = 0
                        borderTopRightRadius = 0
                    }
                    "&:last-child"  = @{
                        borderBottomLeftRadius  = 0
                        borderBottomRightRadius = 0
                    }
                }
            }
            MuiFormControl      = @{
                root = @{
                    #marginTop = '10px'
                }
            }
            MuiLink             = @{
                root = @{
                    color = 'rgba(255, 255, 255, 0.85)'
                }
            }
            MuiIconButton       = @{
                root = @{
                    borderRadius     = 0
                    fontSize         = 14
                    padding          = "4px 12px"
                    color            = 'rgba(255, 255, 255, 0.85)'
                    '&.Mui-disabled' = @{
                        color = 'rgba(255, 255, 255, 0.25)'
                    }
                }
            }
            MuiInput            = @{
                root  = @{
                    '&::before' = @{
                        borderBottom = '0px'
                    }
                    "&::after"  = @{
                        border = '0px !important'
                    }
                    border      = '1px solid #d9d9d9'
                    lineHeight  = 1.5715
                }

                input = @{
                    border = '0px'
                }
            }
            MuiInputAdornment   = @{
                root = @{
                    marginLeft = '10px'
                    color      = '#d9d9d9'
                }
            }
            MuiInputBase        = @{
                input = @{
                    border       = '1px solid #d9d9d9'
                    borderRadius = '2px'
                    padding      = '4px 11px'
                    color        = "#fff"
                    lineHeight   = 1.5715
                    fontSize     = 14
 
                    "&:hover"    = @{
                        borderColor = "#40a9ff"
                    }
                    "&:focus"    = @{
                        borderColor = '#40a9ff'
                        boxShadow   = "0 0 0 2px rgb(24 144 255 / 20%)"
                    }
                }
            }
            MuiInputLabel       = @{
                root     = @{
                    color           = "#fff"
                    paddingLeft     = '14px'
                    "&.Mui-focused" = @{
                        color = "#d9d9d9"
                    }
                }
                shrink   = @{
                    color     = "#fff"
                    transform = 'translate(-10px, -1.5px) scale(0.75)'
                }
                outlined = @{
                    paddingLeft = '0px'
                    transform   = 'translate(0, -20px) scale(0.75)'
                }
            }
            MuiListItem         = @{
                root = @{
                    cursor           = 'pointer'
                    "&.Mui-selected" = @{
                        backgroundColor = '#177ddc'
                        color           = '#fff'
                    }
                }

            }
            MuiListItemIcon     = @{
                root = @{
                    minWidth  = '25px'
                    color     = "#fff"
                    "&:hover" = @{
                        color = '#fff'
                    }
                }
            }
            MuiListItemText     = @{
                root      = @{
                    marginLeft = '10px'
                }
                secondary = @{
                    #color = "rgba(255, 255, 255, 0.3)"
                }
                multiline = @{
                    marginTop    = 0
                    marginBottom = 0
                }

            }
            MuiLoadingButton    = @{
                loadingIndicator = @{
                    color = 'white'
                }
            }
            MuiMenuItem         = @{
                root = @{
                    '&.Mui-selected' = @{
                        backgroundColor = '#1890ff !important'
                    }
                }
            }
            MuiNativeSelect     = @{
                icon = @{
                    color = 'white'
                }
            }
            MuiOutlinedInput    = @{
                root      = @{
                    borderRadius = 0
                    border       = '1px solid #d9d9d9'
                }
                input     = @{
                    paddingTop    = '4px'
                    paddingBottom = '4px'
                    border        = '0px'
                }
                "&:focus" = @{
                    border = '0px'
                }
            }
            MuiPaper            = @{
                root       = @{
                    color           = '#fff'
                    backgroundColor = 'rgb(31, 31, 31)'
                    boxShadow       = 'none'
                }
                rounded    = @{
                    borderRadius = 0
                }
                elevation1 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation2 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation3 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation4 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
                elevation5 = @{
                    boxShadow = '0px 3px 3px -2px rgb(0 0 0 / 20%), 0px 3px 4px 0px rgb(0 0 0 / 14%), 0px 1px 8px 0px rgb(0 0 0 / 12%)'
                }
            }
            MuiPickersLayout    = @{
                root = @{
                    backgroundColor = 'transparent !important'
                }
            }
            MuiRadio            = @{
                root = @{
                    '&.Mui-checked' = @{
                        color = '#177ddc'
                    }
                }
            }
            MuiPickersDay       = @{
                root = @{
                    '&.Mui-selected' = @{
                        backgroundColor = '#1890ff !important'
                    }
                    '&:hover'        = @{
                        backgroundColor = '#1890ff'
                    }
                }
            }
            MuiSelect           = @{
                root         = @{
                    border       = '1px solid #434343'
                    borderRadius = '1px'
                }
                iconStandard = @{
                    color = "#fff"
                }
            }
            MuiSlider           = @{
                root = @{
                    color = "#40a9ff"
                }
            }
            MuiStepIcon         = @{
                root = @{
                    '&.Mui-active' = @{
                        color = "#1890ff"
                    }
                }
            }
            MuiSwitch           = @{
                root       = @{
                    height = "40px !important"
                }
                thumb      = @{
                    marginTop  = '5px !important'
                    marginLeft = '5px !important'
                    width      = "12px !important"
                    height     = "12px !important"
                }
                track      = @{
                    height          = '16px'
                    borderRadius    = "9px !important"
                    backgroundColor = '#177ddc !important'
                }
                sizeSmall  = @{
                    paddingTop = '12px'
                }
                switchBase = @{
                    padding                          = '9px !important'
                    '&.Mui-checked'                  = @{
                        transform = 'translateX(50%) !important'
                    }
                    '&.Mui-checked+.MuiSwitch-track' = @{
                        backgroundColor = '#177ddc'
                        opacity         = '1'
                    }
                    '&:hover'                        = @{
                        backgroundColor = 'transparent'
                    }
                }

            }
            MuiRating           = @{
                icon = @{
                    color = "#faaf00"
                }
            }
            MuiSvgIcon          = @{
                colorPrimary = @{
                    color = "rgba(255, 255, 255, 0.65)"
                }
            }
            MuiTab              = @{
                root      = @{
                    minHeight        = 0
                    textTransform    = 'none'
                    color            = '#fff'
                    '&.Mui-selected' = @{
                        "color " = "#177ddc"
                    }
                }
                labelIcon = @{
                    minHeight = 0
                }
            }
            MuiTreeItem         = @{
                content = @{
                    '&.Mui-selected' = @{
                        backgroundColor = "rgb(80,80,80) !important"
                    }
                }

            }
        }
    }
}


$Paperbase = @{
    palette    = @{
        primary = @{
            light = '#63ccff'
            main  = '#009be5'
            dark  = '#006db3'
        }
    }
    typography = @{
        h5 = @{
            fontWeight    = 500
            fontSize      = 26
            letterSpacing = 0.5
        }
    }
    shape      = @{
        borderRadius = 8
    }
    mixins     = @{
        toolbar = @{
            minHeight = 48
        }
    }
    overrides  = @{
        MuiDrawer         = @{
            paper = @{
                backgroundColor = '#081627'
            }
        }
        MuiButton         = @{
            label     = @{
                textTransform = 'none'
            }
            contained = @{
                boxShadow  = 'none'
                '&:active' = @{
                    boxShadow = 'none'
                }
            }
        }
        MuiTabs           = @{
            root      = @{
                marginLeft = 1
            }
            indicator = @{
                height               = 3
                borderTopLeftRadius  = 3
                borderTopRightRadius = 3
                backgroundColor      = '#000'
            }
        }
        MuiTab            = @{
            root = @{
                textTransform = 'none'
                margin        = '0 16px'
                minWidth      = 0
                padding       = 0
            }
        }
        MuiIconButton     = @{
            root = @{
                padding = 1
            }
        }
        MuiTooltip        = @{
            tooltip = @{
                borderRadius = 4
            }
        }
        MuiDivider        = @{
            root = @{
                backgroundColor = 'rgb(255,255,255,0.15)'
            }
        }
        MuiListItemButton = @{
            root = @{
                '&.Mui-selected' = @{
                    color = '#4fc3f7'
                }
            }
        }
        MuiListItemText   = @{
            primary = @{
                color      = 'rgba(255, 255, 255, 0.7) '
                fontSize   = 14
                fontWeight = 500
            }
        }
        MuiListItemIcon   = @{
            root = @{
                color       = 'rgba(255, 255, 255, 0.7) '
                minWidth    = 'auto'
                marginRight = 2
                '& svg'     = @{
                    fontSize = 20
                }
            }
        }
        MuiAvatar         = @{
            root = @{
                width  = 32
                height = 32
            }
        }
    }
}

$Sand = @{
    palette = @{
        primary   = @{
            light = '#ffe8d6'
            main  = '#ddbea9'
            dark  = '#cb997e'
        }
        secondary = @{
            light = '#b7b7a4'
            main  = '#a5a58d'
            dark  = '#6b705c'
        }
    }
}

$Compliment = @{
    palette = @{
        primary   = @{
            light = '#e9c46a'
            main  = '#2a9d8f'
            dark  = '#264653'
        }
        secondary = @{
            light = '#e9c46a'
            main  = '#f4a261'
            dark  = '#e76f51'
        }
    }
}

function ConvertTo-UDTheme {
    param($Name, $NavigationStyle)

    [string]$CSS = Get-ThemeCss

    $colors = Get-UDThemeColors -Theme $Name | Group-Object -Property "mode" | ForEach-Object { $_.Group | Select-Object -First 1 }
    $common = $colors | Where-Object Mode -eq common  
    $dark = $colors | Where-Object Mode -eq dark 

    # add some defaults
    $light = $colors | Where-Object Mode -eq light
    if (-not $light) {
        $colors += [pscustomobject]@{
            "Mode"              = "light"
            "Enabled"           = "true"
            "Main"              = "#f6f8fa"
            "MainSecondary"     = "#DEDEDE"
            "MainGamma"         = "#B8B8C2"
            "MainDelta"         = "#c6c8ca"
            "Opposite"          = "#24292f"
            "OppositeSecondary" = "#57606a"
            "HighContrast"      = "#000000"
        }
    }
    if (-not $dark) {
        $colors += [pscustomobject]@{
            "Mode"              = "dark"
            "Enabled"           = "true"
            "Main"              = "#0D0F31"
            "MainSecondary"     = "#070825"
            "MainGamma"         = "#454761"
            "MainDelta"         = "#A2A2AA"
            "Opposite"          = "#c9d1d9"
            "OppositeSecondary" = "#8b949e"
            "HighContrast"      = "#ffffff"
        }
        $dark = $colors | Where-Object Mode -eq dark
    }

    $shadows = @()
    1..25 | ForEach-Object { $shadows += "none" }
    $themes = @{}

    ForEach ($theme in $colors) {
        switch ($NavigationStyle) {
            "theme" { $Navigation = $theme }
            "dark" { $Navigation = $dark }
            "light" { $Navigation = $light }
            "opposite" {
                if ($theme.mode -eq 'dark') {
                    $navigation = $Light
                } 
                if ($theme.mode -eq 'light') {
                    $navigation = $Dark
                } 
            }
        }

        $themes += @{
            $theme.Mode = @{
                globalStyle = $CSS
                palette     = @{
                    mode       = $theme.Mode
                    background = @{
                        default = $theme.Main
                    }
                    text       = @{
                        primary   = $theme.Opposite
                        secondary = $theme.OppositeSecondary
                        disabled  = $theme.OppositeSecondary
                    }
                    primary    = @{
                        main         = $common.Blue
                        dark         = $theme.BlueRGBA15
                        light        = $common.Blue
                        contrastText = $common.Black
                    }
                    secondary  = @{
                        main         = $common.Cyan
                        dark         = $theme.CyanRGBA15
                        light        = $common.CyanRGBA15
                        contrastText = $common.Black
                    }
                    warning    = @{
                        main         = $common.Yellow
                        dark         = $theme.YellowRGBA15
                        light        = $common.Yellow
                        contrastText = $common.Black
                    }
                    error      = @{
                        main         = $common.Red
                        dark         = $theme.RedRGBA15
                        light        = $common.Red
                        contrastText = $common.Black
                    }
                    success    = @{
                        main         = $common.Green
                        dark         = $theme.GreenRGBA15
                        light        = $common.Green
                        contrastText = $common.Black
                    }
                    info       = @{
                        main         = $common.Purple
                        dark         = $theme.PurpleRGBA15
                        light        = $common.Purple
                        contrastText = $common.Black
                    }
                }
                shape       = @{
                    borderRadius = $common.BorderRadius
                }
                shadows     = $shadows
                typography  = @{
                    fontFamily = $common.FontFamily
                    # Additional styles for fonts can be found in theme.css
                }
                overrides   = @{
                    MuiAppBar          = @{
                        root = @{
                            backgroundColor = $theme.MainSecondary
                            backgroundImage = 'none'
                            borderBottom    = '1px solid ' + $theme.MainGamma
                            paddingRight    = '18px'
                            paddingLeft     = '18px'
                        }
                    }
                    MuiDrawer          = @{
                        paper = @{
                            backgroundColor                           = $Navigation.MainSecondary
                            color                                     = $Navigation.Opposite
                            #zIndex = 1202 # Comment out this line in order to show the default header with logo
                            flex                                      = '0 0 250px'
                            maxWidth                                  = '250px'
                            minWidth                                  = '250px'
                            width                                     = '250px !important'
                            borderRight                               = '1px solid ' + $Navigation.MainGamma
                            '.MuiList-subheader'                      = @{
                                paddingLeft  = '.5rem'
                                paddingRight = '.5rem'
                            }
                            '.MuiList-root'                           = @{
                                paddingTop = 0
                            }
                            '.MuiListItem-root'                       = @{
                                paddingLeft = '18px !important'
                                cursor      = "pointer"
                                '&:hover'   = @{
                                    background   = $Navigation.MainGamma
                                    borderRadius = $common.BorderRadius
                                }
                            }
                            '#drawerSettings + .MuiCollapse-root'     = @{
                                paddingLeft = '18px'
                            }
                            '.MuiListItemIcon-root, .MuiSvgIcon-root' = @{
                                color = $Navigation.Opposite
                            }
                            '.MuiToolbar-root'                        = @{
                                #backgroundImage = 'url(/assets/logo.png)'
                                backgroundSize     = '80%'
                                backgroundRepeat   = 'no-repeat'
                                marginLeft         = '18px'
                                marginTop          = '1px'
                                backgroundPosition = 'left center'
                            }
                        }
                    }
                    MuiPaper           = @{
                        root = @{
                            backgroundColor = $theme.MainSecondary
                            backgroundImage = 'none'
                        }
                    }
                    MuiToolbar         = @{
                        root = @{
                            paddingLeft  = '0 !important'
                            paddingRight = '0 !important'
                        }
                    }
                    MuiListItem        = @{
                        root = @{
                            paddingLeft = 0
                        }
                    }
                    MuiListItemIcon    = @{
                        root = @{
                            color       = $theme.Opposite
                            minWidth    = 'auto'
                            marginRight = '1rem'
                        }
                    }
                    MuiCard            = @{
                        root = @{
                            border = '1px solid ' + $theme.MainGamma
                        }
                    }
                    MuiCardHeader      = @{
                        root = @{
                            paddingBottom = 0
                        }
                    }
                    MuiFormControl     = @{
                        root = @{
                            #width = '100%'
                        }
                    }
                    MuiInput           = @{
                        root = @{
                            '&:before' = @{
                                borderBottom = '2px solid ' + $theme.MainGamma
                            }
                        }
                    }
                    MuiFormLabel       = @{
                        root = @{
                            color = $theme.Opposite
                        }
                    }
                    MuiSwitch          = @{
                        switchBase = @{
                            color = $theme.MainGamma
                        }
                    }
                    MuiGrid            = @{
                        root = @{
                            '&.transfer-list' = @{
                                '.MuiPaper-root'  = @{
                                    border = '1px solid ' + $theme.MainGamma
                                }
                                'button + button' = @{
                                    marginLeft = '0 !important'
                                }
                            }
                        }
                    }
                    MuiStepIcon        = @{
                        text = @{
                            fill = $dark.Main
                        }
                        root = @{
                            color = $dark.Opposite
                        }
                    }
                    MuiAlert           = @{
                        icon            = @{
                            opacity = 1
                        }
                        standardWarning = @{
                            backgroundColor = $common.YellowRGBA15
                            borderColor     = $common.Yellow
                            color           = $theme.HighContrast
                        }
                        standardError   = @{
                            backgroundColor = $common.RedRGBA15
                            borderColor     = $common.Red
                            color           = $theme.HighContrast
                        }
                        standardSuccess = @{
                            backgroundColor = $common.GreenRGBA15
                            borderColor     = $common.Green
                            color           = $theme.HighContrast
                        }
                        standardInfo    = @{
                            backgroundColor = $common.PurpleRGBA15
                            borderColor     = $common.Purple
                            color           = $theme.HighContrast
                        }
                        # Additional styles for alerts can be found in theme.css
                    }
                    MuiTableContainer  = @{
                        root = @{
                            backgroundColor                    = "unset"
                            borderRadius                       = 0;
                            backgroundImage                    = 'none'
                            margin                             = '0 !important'
                            'div[class*="makeStyles-search-"]' = @{
                                background  = $theme.MainGamma
                                marginRight = 0
                            }
                            'h5, .MuiTypography-h5'            = @{
                                marginBottom = '0 !important'
                            }
                        }
                    }
                    MuiTable           = @{
                        root = @{
                            borderCollapse = 'separate'
                        }
                    }
                    MuiTablePagination = @{
                        toolbar       = @{
                            paddingLeft  = '0 !important'
                            paddingRight = '0 !important'
                        }
                        spacer        = @{
                            display = 'none'
                        }
                        displayedRows = @{
                            marginRight = 'auto'
                        }
                    }
                    MuiTableCell       = @{
                        head   = @{
                            backgroundColor = $theme.MainGamma
                        }
                        root   = @{
                            borderBottom = '1px solid ' + $theme.MainGamma
                        }
                        footer = @{
                            borderTop = '1px solid ' + $theme.MainGamma
                        }
                    }
                    MuiTableBody       = @{
                        root = @{
                            'tr:nth-child(even)' = @{
                                backgroundColor = $theme.Main
                            }
                            'tr:nth-child(odd)'  = @{
                                backgroundColor = $theme.MainSecondary
                            }
                        }
                    }
                    MuiTableRow        = @{
                        root = @{
                            '&:last-child td' = @{
                                borderBottom = 0
                            }
                        }
                    }
                    MuiButton          = @{
                        root               = @{
                            fontWeight          = 600
                            margin              = '0 0 .25rem !important'
                            '+ .MuiButton-root' = @{
                                marginLeft = '0.5rem !important'
                            }
                        }
                        contained          = @{
                            color = $common.Black
                        }
                        outlined           = @{
                            '&:hover' = @{
                                color = $common.Black
                            }
                        }
                        containedInherit   = @{
                            backgroundColor = $theme.MainGamma
                            color           = $theme.Opposite
                            '&:hover'       = @{
                                backgroundColor = $theme.MainDelta
                            }
                        }
                        containedPrimary   = @{
                            '&:hover' = @{
                                backgroundColor = $common.BlueHover
                            }
                        }
                        containedSecondary = @{
                            '&:hover' = @{
                                backgroundColor = $common.CyanHover
                            }
                        }
                        containedInfo      = @{
                            '&:hover' = @{
                                backgroundColor = $common.PurpleHover
                            }
                        }
                        containedWarning   = @{
                            '&:hover' = @{
                                backgroundColor = $common.YellowHover
                            }
                        }
                        containedError     = @{
                            '&:hover' = @{
                                backgroundColor = $common.RedHover
                            }
                        }
                        containedSuccess   = @{
                            '&:hover' = @{
                                backgroundColor = $common.GreenHover
                            }
                        }
                        outlinedInherit    = @{
                            borderColor = $theme.MainGamma
                            '&:hover'   = @{
                                backgroundColor = $theme.MainGamma
                                color           = $theme.Opposite
                            }
                        }
                        outlinedPrimary    = @{
                            borderColor = $common.Blue
                            '&:hover'   = @{
                                backgroundColor = $common.Blue
                            }
                        }
                        outlinedSecondary  = @{
                            borderColor = $common.Cyan
                            '&:hover'   = @{
                                backgroundColor = $common.Cyan
                            }
                        }
                        outlinedInfo       = @{
                            borderColor = $common.Purple
                            '&:hover'   = @{
                                backgroundColor = $common.Purple
                            }
                        }
                        outlinedWarning    = @{
                            borderColor = $common.Yellow
                            '&:hover'   = @{
                                backgroundColor = $common.Yellow
                            }
                        }
                        outlinedError      = @{
                            borderColor = $common.Red
                            '&:hover'   = @{
                                backgroundColor = $common.Red
                            }
                        }
                        outlinedSuccess    = @{
                            borderColor = $common.Green
                            '&:hover'   = @{
                                backgroundColor = $common.Green
                            }
                        }
                    }
                }
            }
        }
    }
    $themes
}


function Get-UDThemeColors {
    param(
        [Parameter(Mandatory)]
        $Theme
    )

    Get-Content -Path (Join-Path -Path (Get-ThemeFolder) -ChildPath "$theme.json") | ConvertFrom-Json
}

function Get-ThemeFolder {
    Join-Path -Path $PSScriptRoot -ChildPath themes
}

function Get-ThemeCss {
    $ThemeFolder = Get-ThemeFolder
    $CssPath = Join-Path -Path $ThemeFolder -ChildPath theme.css
    Get-Content $CssPath -Raw
}

function Get-AllThemes {
    $ThemeFolder = Get-ThemeFolder
    (Get-ChildItem -Path $ThemeFolder -Filter *.json).BaseName
}

function Get-Rgb {
    [cmdletbinding()]
    param(
        [string[]]$Color
    )
    # Clean up
    if ($first = $Color[1]) {
        $cleanedcolor = $Color -join ","
        if ($first -notmatch "rgb" -and $first -notmatch "\(") {
            $cleanedcolor = "rgb($cleanedcolor)"
        }
    }
    else {
        $cleanedcolor = "$Color"
    }
    $cleanedcolor = $cleanedcolor.Replace('#', '')
    $cleanedcolor = $cleanedcolor.Replace(' ', '')

    if ($cleanedcolor -match '^rgb') {
        try {
            # If RGB --> store the red, green, blue values in separate variables
            $rgb = $cleanedcolor -match '^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$'
            if (-not $rgb) {
                $rgb = $cleanedcolor -match '^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$'
            }
            
            $r = [convert]::ToInt32($matches[1])
            $g = [convert]::ToInt32($matches[2])
            $b = [convert]::ToInt32($matches[3])
        }
        catch {
            Write-Warning "$cleanedcolor isnt a valid rgb color for the purposes of the script: $PSItem"
            return
        }
    }
    else {
        try {
            $null = [int]::Parse($cleanedcolor, [System.Globalization.NumberStyles]::HexNumber)
            if ($cleanedcolor.Length -eq 3) {
                # if someone passed in a shortcut html, expand it
                $cleanedcolor = $cleanedcolor[0] + $cleanedcolor[0] + $cleanedcolorex[1] + $cleanedcolor[1] + $cleanedcolor[2] + $cleanedcolor[2]
            }
            $r = $cleanedcolor.Remove(2, 4)
            $g = $cleanedcolor.Remove(4, 2)
            $g = $g.remove(0, 2)
            $b = $cleanedcolor.Remove(0, 4)
            $r = [convert]::ToInt32($r, 16)
            $g = [convert]::ToInt32($g, 16)
            $b = [convert]::ToInt32($b, 16)
        }
        catch {
            Write-Warning "$cleanedcolor is not a valid hex for the purposes of the script"
            return
        }
    }
    
    $r, $g, $b
}

function Get-Hsp {
    [cmdletbinding()]
    param(
        [string]$Color
    )
    $r, $g, $b = Get-Rgb $Color
    
    # HSP equation from http://alienryderflex.com/hsp.html
    [math]::Sqrt(
        0.299 * ($r * $r) +
        0.587 * ($g * $g) +
        0.114 * ($b * $b)
    )
}


function Get-Contrast {
    [cmdletbinding()]
    param(
        [string]$Color1,
        [string]$Color2
    )
    $one = Get-Hsp $Color1
    $two = Get-Hsp $Color2
    
    if ($one -ge $two) {
        $first = $one
        $second = $two
    }
    else {
        $first = $two
        $second = $one
    }

    $first - $second
}

function Test-Contrast {
    [cmdletbinding()]
    param(
        [string]$Color1,
        [string]$Color2
    )

    $diff = Get-Contrast $Color1 $Color2

    if ($diff -gt 50) {
        $true
    }
    else {
        $false
    }
}

    

function Test-DarkColor {
    <#
    .SYNOPSIS
    Tests an rgb or hex value to see if it's considered light or dark
 
    .PARAMETER Color
    The color to test in Hex or RGB
     
    .NOTES
    Thanks to:
    https://github.com/EvotecIT/PSSharedGoods/blob/master/Public/Converts/Convert-Color.ps1
    https://awik.io/determine-color-bright-dark-using-javascript/
 
    .EXAMPLE
    Test-DarkColor ffffff
    False
     
    .EXAMPLE
    Test-DarkColor 000000
    True
 
    .EXAMPLE
    Test-DarkColor "rgb(255,255,255)"
    False
 
    .EXAMPLE
    Test-DarkColor "rgb(255,255,255)"
    VERBOSE: 255
    VERBOSE: 255
    VERBOSE: 255
    VERBOSE: 255
    False
    
    #>

    [cmdletbinding()]
    param(
        [string[]]$Color
    )
    
    # Clean up
    if ($first = $Color[1]) {
        $cleanedcolor = $Color -join ","
        if ($first -notmatch "rgb" -and $first -notmatch "\(") {
            $cleanedcolor = "rgb($cleanedcolor)"
        }
    }
    else {
        $cleanedcolor = "$Color"
    }
    $cleanedcolor = $cleanedcolor.Replace('#', '')
    $cleanedcolor = $cleanedcolor.Replace(' ', '')

    $r, $g, $b = Get-Rgb $cleanedcolor
    $hsp = Get-Hsp $cleanedcolor

    # Using the HSP value, determine whether the color is light or dark
    if ($hsp -gt 127.5) {
        $hsp | Write-Verbose
        return $false
    }
    else {
        $hsp | Write-Verbose
        return $true
    }
}

function Get-InvertedColor {
    [cmdletbinding()]
    param(
        [Alias("Color")]
        $Hex
    )
    
    $hex = $hex -replace "#"
    if ($hex.Length -eq 3) {
        # if someone passed in a shortcut html, expand it
        $hex = $hex[0] + $hex[0] + $hex[1] + $hex[1] + $hex[2] + $hex[2]
    }

    $rgb = "#"
    for ($i = 0; $i -lt 3; $i++) {
        $number = $i * 2
        $what = $hex.substring($number, 2)
        $c = [convert]::ToInt32($what, 16)
        $opposite = (255 - $c)
        $c = '{0:X4}' -f $opposite
        $rgb += ("00" + $c).SubString($c.length)
    }
    -join $rgb
}

$Themes = @{
    AntDesign  = $AntDesign
    Paperbase  = $Paperbase 
    Sand       = $Sand
    Compliment = $Compliment
} 

function Get-UDTheme {
    <#
    .SYNOPSIS
    Returns predefined themes.
     
    .DESCRIPTION
    Returns predefined themes.
     
    .PARAMETER Name
    The name of the theme.
 
    .PARAMETER NavigationStyle
    The style of the navigation.
     
    .EXAMPLE
    $Theme = Get-UDTheme -Name 'AntDesign'
     
    .NOTES
    General notes
    #>

    param(
        [Parameter(ParameterSetName = 'All')]
        $Name,
        [Parameter(ParameterSetName = 'All')]
        [ValidateSet("theme", "dark", "light", "opposite")]
        [string]$NavigationStyle = "theme",
        [Parameter(ParameterSetName = 'Current')]
        [switch]$Current
    )

    if ($Current) {
        $DashboardHub.SendWebSocketMessageWithResult($ConnectionId, "getTheme", $null)
        return
    }

    if ($Name -eq 'MaterialDesign') {
        return @{}
    }

    if ($Name) {
        if ($Themes.Keys -contains $Name) {
            return $Themes[$Name]
        }
        ConvertTo-UDTheme -Name $Name -NavigationStyle $NavigationStyle
    }
    else {
        Get-AllThemes
    }
}

function New-UDTheme {
    <#
    .SYNOPSIS
    Creates a new theme.
     
    .DESCRIPTION
    Creates a new theme.
     
    .PARAMETER PrimaryColor
    The primary color for the light theme.
     
    .PARAMETER SecondaryColor
    The secondary color for the light theme.
     
    .PARAMETER WarningColor
    The warning color for the light theme.
     
    .PARAMETER ErrorColor
    The error color for the light theme.
     
    .PARAMETER SuccessColor
    The success color for the light theme.
     
    .PARAMETER Background
    The background color for the light theme.
     
    .PARAMETER DarkPrimaryColor
    The primary color for the dark theme.
     
    .PARAMETER DarkSecondaryColor
    The secondary color for the dark theme.
     
    .PARAMETER DarkWarningColor
    The warning color for the dark theme.
     
    .PARAMETER DarkErrorColor
    The error color for the dark theme.
     
    .PARAMETER DarkSuccessColor
    The success color for the dark theme.
     
    .PARAMETER DarkBackground
    The background color for the dark theme.
    #>

    param(
        [Parameter()]
        [DashboardColor]$PrimaryColor, 
        [Parameter()]
        [DashboardColor]$SecondaryColor, 
        [Parameter()]
        [DashboardColor]$WarningColor, 
        [Parameter()]
        [DashboardColor]$ErrorColor, 
        [Parameter()]
        [DashboardColor]$SuccessColor, 
        [Parameter()]
        [DashboardColor]$Background, 
        [Parameter()]
        [DashboardColor]$DarkPrimaryColor, 
        [Parameter()]
        [DashboardColor]$DarkSecondaryColor, 
        [Parameter()]
        [DashboardColor]$DarkWarningColor, 
        [Parameter()]
        [DashboardColor]$DarkErrorColor, 
        [Parameter()]
        [DashboardColor]$DarkSuccessColor, 
        [Parameter()]
        [DashboardColor]$DarkBackground
    )

    $Theme = @{
        light = @{
            palette = @{
            }
        }
        dark  = @{
            palette = @{
            }
        }
    }

    if ($PrimaryColor) {
        $Theme.light.palette.primary = @{}
        $Theme.light.palette.primary.light = $PrimaryColor.Lighten().HtmlColor
        $Theme.light.palette.primary.main = $PrimaryColor.HtmlColor
        $Theme.light.palette.primary.dark = $PrimaryColor.Darken().HtmlColor
    }

    if ($SecondaryColor) {
        $Theme.light.palette.secondary = @{}
        $Theme.light.palette.secondary.light = $SecondaryColor.Lighten().HtmlColor
        $Theme.light.palette.secondary.main = $SecondaryColor.HtmlColor
        $Theme.light.palette.secondary.dark = $SecondaryColor.Darken().HtmlColor
    }

    if ($DarkPrimaryColor) {
        $Theme.dark.palette.primary = @{}
        $Theme.dark.palette.primary.light = $DarkPrimaryColor.Lighten().HtmlColor
        $Theme.dark.palette.primary.main = $DarkPrimaryColor.HtmlColor
        $Theme.dark.palette.primary.dark = $DarkPrimaryColor.Darken().HtmlColor
    }

    if ($DarkSecondaryColor) {
        $Theme.dark.palette.secondary = @{}
        $Theme.dark.palette.secondary.light = $DarkSecondaryColor.Lighten().HtmlColor
        $Theme.dark.palette.secondary.main = $DarkSecondaryColor.HtmlColor
        $Theme.dark.palette.secondary.dark = $DarkSecondaryColor.Darken().HtmlColor
    }

    if ($WarningColor) {
        $Theme.light.palette.warning = @{}
        $Theme.light.palette.warning.light = $WarningColor.Lighten().HtmlColor
        $Theme.light.palette.warning.main = $WarningColor.HtmlColor
        $Theme.light.palette.warning.dark = $WarningColor.Darken().HtmlColor
    }

    if ($DarkWarningColor) {
        $Theme.dark.palette.warning = @{}
        $Theme.dark.palette.warning.light = $DarkWarningColor.Lighten().HtmlColor
        $Theme.dark.palette.warning.main = $DarkWarningColor.HtmlColor
        $Theme.dark.palette.warning.dark = $DarkWarningColor.Darken().HtmlColor
    }

    if ($ErrorColor) {
        $Theme.light.palette.error = @{}
        $Theme.light.palette.error.light = $ErrorColor.Lighten().HtmlColor
        $Theme.light.palette.error.main = $ErrorColor.HtmlColor
        $Theme.light.palette.error.dark = $ErrorColor.Darken().HtmlColor
    }

    if ($DarkErrorColor) {
        $Theme.dark.palette.error = @{}
        $Theme.dark.palette.error.light = $DarkErrorColor.Lighten().HtmlColor
        $Theme.dark.palette.error.main = $DarkErrorColor.HtmlColor
        $Theme.dark.palette.error.dark = $DarkErrorColor.Darken().HtmlColor
    }

    if ($SuccessColor) {
        $Theme.light.palette.success = @{}
        $Theme.light.palette.success.light = $SuccessColor.Lighten().HtmlColor
        $Theme.light.palette.success.main = $SuccessColor.HtmlColor
        $Theme.light.palette.success.dark = $SuccessColor.Darken().HtmlColor
    }

    if ($DarkSuccessColor) {
        $Theme.dark.palette.success = @{}
        $Theme.dark.palette.success.light = $DarkSuccessColor.Lighten().HtmlColor
        $Theme.dark.palette.success.main = $DarkSuccessColor.HtmlColor
        $Theme.dark.palette.success.dark = $DarkSuccessColor.Darken().HtmlColor
    }

    if ($Background) {
        $Theme.light.palette.background = @{}
        $Theme.light.palette.background.default = $Background.HtmlColor
    }

    if ($DarkBackground) {
        $Theme.dark.palette.background = @{}
        $Theme.dark.palette.background.default = $DarkBackground.HtmlColor
    }


    $Theme
}
function New-UDTimeline {
    <#
    .SYNOPSIS
    The timeline displays a list of events in chronological order.
     
    .DESCRIPTION
    The timeline displays a list of events in chronological order.
     
    .PARAMETER Id
    The ID of this component. The default value is a random GUID.
     
    .PARAMETER Children
    The content of this timeline.
     
    .PARAMETER Position
    The position of the timeline. Valid values are right, left, and alternate.
     
    .EXAMPLE
    PS > New-UDTimeline -Children {
    PS > New-UDTimelineItem -Content {
    PS > 'Breakfast'
    PS > } -OppositeContent {
    PS > '7:45 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'Welcome Message'
    PS > } -OppositeContent {
    PS > '9:00 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'State of the Shell'
    PS > } -OppositeContent {
    PS > '9:30 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'General Session'
    PS > } -OppositeContent {
    PS > '11:00 AM'
    PS > }
    PS > } -Id 'timeline1'
 
    Basic Timeline|A basic timeline.
 
    .EXAMPLE
    PS > New-UDTimeline -Children {
    PS > New-UDTimelineItem -Content {
    PS > 'Breakfast'
    PS > } -OppositeContent {
    PS > '7:45 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'Welcome Message'
    PS > } -OppositeContent {
    PS > '9:00 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'State of the Shell'
    PS > } -OppositeContent {
    PS > '9:30 AM'
    PS > }
    PS > New-UDTimelineItem -Content {
    PS > 'General Session'
    PS > } -OppositeContent {
    PS > '11:00 AM'
    PS > }
    PS > } -Position alternate -Id 'timeline2'
 
    Alternate Timeline|An alternating timeline.
 
    .EXAMPLE
    PS > New-UDTimeline -Children {
    PS > New-UDTimelineItem -Content {
    PS > 'Breakfast'
    PS > } -OppositeContent {
    PS > '7:45 AM'
    PS > } -Icon (New-UDIcon -Icon 'CheckCircle' -Color 'success')
    PS > New-UDTimelineItem -Content {
    PS > 'Welcome Message'
    PS > } -OppositeContent {
    PS > '9:00 AM'
    PS > } -Icon (New-UDIcon -Icon 'CheckCircle' -Color 'success')
    PS > New-UDTimelineItem -Content {
    PS > 'State of the Shell'
    PS > } -OppositeContent {
    PS > '9:30 AM'
    PS > } -Icon (New-UDIcon -Icon 'CheckCircle' -Color 'success')
    PS > New-UDTimelineItem -Content {
    PS > 'General Session'
    PS > } -OppositeContent {
    PS > '11:00 AM'
    PS > } -Icon (New-UDIcon -Icon 'CheckCircle' -Color 'success')
    PS > } -Id 'timeline3'
 
    Timeline with Icons|A timeline with icons.
    #>

    # [Component("Timeline", "Timeline", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),
        [Parameter()]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter()]
        [ValidateSet("right", 'left', 'alternate')]
        [string]$Position = "right"
    )

    @{
        isPlugin = $true
        assetId  = $MUAssetId
        type     = "mu-timeline"
        id       = $id

        children = & $Children
        position = $Position.ToLower()
    }
}

function New-UDTimelineItem {
    <#
    .SYNOPSIS
    A timeline item.
 
    .DESCRIPTION
    A timeline item.
     
    .PARAMETER Content
    Content for this timeline item.
     
    .PARAMETER OppositeContent
    Opposite content for this timeline item.
     
    .PARAMETER Icon
    An icon for this timeline item. Use the New-UDIcon cmdlet to create an icon.
     
    .PARAMETER Color
    The color of this timeline item. Valid values are error, grey, info, inherit, primary, secondary, success, and warning.
     
    .PARAMETER Variant
    The variant of this timeline item. Valid values are filled and outlined.
    #>

    param(
        [Parameter()]
        [ScriptBlock]$Content = {},
        [Parameter()]
        [ScriptBlock]$OppositeContent = {},
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter()]
        [ValidateSet("error", 'grey', 'info', 'inherit', 'primary', 'secondary', 'success', 'warning')]
        [string]$Color = 'grey',
        [Parameter()]
        [ValidateSet('filled', 'outlined')]
        [string]$Variant = 'filled'
    )

    @{
        content         = & $Content
        oppositeContent = & $OppositeContent
        icon            = $icon 
        color           = $Color.ToLower()
        variant         = $Variant.ToLower()
    }
}

<#
 
New-UDTimeline -Children {
            New-UDTimelineItem -Content {
                'Breakfast'
            } -OppositeContent {
                '7:45 AM'
            }
            New-UDTimelineItem -Content {
                'Welcome Message'
            } -OppositeContent {
                '9:00 AM'
            }
            New-UDTimelineItem -Content {
                'State of the Shell'
            } -OppositeContent {
                '9:30 AM'
            }
            New-UDTimelineItem -Content {
                'General Session'
            } -OppositeContent {
                '11:00 AM'
            }
        }
 
#>

function New-UDTimePicker {
    <#
    .SYNOPSIS
    Creates a time picker.
     
    .DESCRIPTION
    Creates a time picker. This component can be used stand alone or within New-UDForm.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Label
    The label to show with the time picker.
     
    .PARAMETER OnChange
    A script block to call when the time is changed. The $EventData variable contains the currently selected time.
     
    .PARAMETER Value
    The current value of the time picker.
 
    .PARAMETER Locale
    Change the language of the time picker.
     
    .PARAMETER ClassName
    A CSS class to apply to the time picker.
 
    .PARAMETER DisableAmPm
    Use 24-hour time instead of them AM/PM ante meridiem.
 
    .PARAMETER TimeZone
    The time zone to use for the time picker. This should be an IANA time zone string.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker1'
 
    Basic Time Picker|Creates a basic time picker.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker2' -OnChange {
    PS > Show-UDToast -Message "Time changed to $($EventData)"
    PS > }
 
    OnChange|Creates a time picker with an OnChange script block.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker3' -Value "12:00"
 
    Value|Creates a time picker with a value.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker4' -Locale "de"
 
    Locale|Creates a time picker with a German locale.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker5' -DisableAmPm
 
    DisableAmPm|Creates a time picker with 24-hour time.
 
    .EXAMPLE
    PS > New-UDTimePicker -Id 'timepicker6' -TimeZone (Get-TimeZone -Id "Central Standard Time")
 
    TimeZone|Creates a time picker with a specific time zone.
    #>

    [Category("app/component")]
    [Description("Creates a time picker. This component can be used stand alone or within New-UDForm.")]
    [DisplayName("Time Picker")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter()]
        [string]$Label,
        [Parameter()]
        [Endpoint]$OnChange, 
        [Parameter()]
        [DateTime]$Value,
        [Parameter()]
        [ValidateSet("en", "de", 'ru', 'fr', 'nl', 'it')]
        [string]$Locale = "en",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$DisableAmPm,
        [Parameter()]
        $TimeZone
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    if ($TimeZone -is [System.TimeZoneInfo]) {
        Write-Warning "TimezoneInfo is not supported in UDDatePicker. Please use an IANA string instead."
    }

    @{
        id        = $Id 
        type      = 'mu-timepicker'
        asset     = $MUAssetId
        isPlugin  = $true 

        onChange  = $OnChange 
        value     = $Value
        label     = $Label
        locale    = $Locale.ToLower()
        className = $ClassName
        ampm      = -not ($DisableAmPm.IsPresent)
        timezone  = $TimeZone
    }
}
function New-UDToggleButtonGroup {
    <#
    .SYNOPSIS
    A Toggle Button can be used to group related options.
     
    .DESCRIPTION
    A Toggle Button can be used to group related options.
     
    .PARAMETER Id
    The ID of the component. If not specified, a random GUID will be assigned.
     
    .PARAMETER Color
    The color of the component. Valid values are 'standard', 'primary', 'secondary', 'error', 'info', 'success', 'warning'.
     
    .PARAMETER Disabled
    If specified, the component will be disabled.
     
    .PARAMETER Exclusive
    If specified, the component will only allow a single selection.
     
    .PARAMETER FullWidth
    If specified, the component will take up the full width of its container.
     
    .PARAMETER OnChange
    The endpoint to call when the value of the component changes.
     
    .PARAMETER Size
    The size of the component. Valid values are 'small', 'medium', 'large'.
     
    .PARAMETER Orientation
    The orientation of the component. Valid values are 'horizontal', 'vertical'.
     
    .PARAMETER Sx
    The style of the component.
     
    .PARAMETER Value
    The value of the component.
     
    .PARAMETER Children
    The content of the component. Use New-UDToggleButton to create toggle buttons.
 
    .EXAMPLE
    PS > New-UDToggleButtonGroup -Content {
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > } -Id 'test'
    PS > } -Exclusive
 
    Exclusive selection|With exclusive selection, selecting one option deselects any other.
 
    .EXAMPLE
    PS > New-UDToggleButtonGroup -Content {
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > } -Id 'test'
    PS > }
 
    Multiple selection|With multiple selection, selecting one option does not deselect any other.
 
    .EXAMPLE
    PS > New-UDToggleButtonGroup -Content {
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > } -Id 'test'
    PS > } -Size 'small'
 
    Size|The small size is equivalent to the dense toggle button styling.
 
    .EXAMPLE
    PS > New-UDToggleButtonGroup -Content {
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > } -Id 'test'
    PS > } -Orientation 'vertical'
 
    Orientation|The vertical orientation can be used to display a column of toggle buttons.
 
    .EXAMPLE
    PS > New-UDToggleButtonGroup -Content {
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > }
    PS > New-UDToggleButton -Content {
    PS > New-UDIcon -Icon 'User'
    PS > } -Id 'test'
    PS > } -OnChange {
    PS > Show-UDToast -Message "You selected $EventData"
    PS > }
 
    OnChange|The OnChange endpoint is called when the value of the component changes. The event data is the value of the component.
 
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),

        [Parameter()]
        [ValidateSet('standard', 'primary', 'secondary', 'error', 'info', 'success', 'warning')]
        [string]$Color = 'standard',

        [Parameter()]
        [Switch]$Disabled,

        [Parameter()]
        [Switch]$Exclusive,

        [Parameter()]
        [Switch]$FullWidth,

        [Parameter()]
        [Endpoint]$OnChange, 

        [Parameter()]
        [ValidateSet('small', 'medium', 'large')]
        [string]$Size = 'medium',

        [Parameter()]
        [ValidateSet('horizontal', 'vertical')]
        [string]$Orientation = 'horizontal',

        [Parameter()]
        [Hashtable]$Sx,

        [Parameter()]
        [string[]]$Value,

        [Parameter(Mandatory)]
        [Alias("Content")]
        [ScriptBlock]$Children
    )

    if ($OnChange) {
        $OnChange.Register($Id, $PSCmdlet)
    }

    @{
        id          = $Id
        type        = 'mui-toggle-button-group'
        color       = $Color
        disabled    = $Disabled.IsPresent
        exclusive   = $Exclusive.IsPresent
        fullWidth   = $FullWidth.IsPresent
        onChange    = $OnChange
        size        = if ($Size) { $Size.ToLower() } else { $null }
        orientation = if ($Orientation) { $Orientation.ToLower() } else { $null }
        sx          = $Sx
        value       = $Value
        children    = & $Children
    }
}

function New-UDToggleButton {
    <#
    .SYNOPSIS
    A Toggle Button can be used to group related options.
     
    .DESCRIPTION
    A Toggle Button can be used to group related options.
     
    .PARAMETER Id
    The ID of the component. If not specified, a random GUID will be assigned.
     
    .PARAMETER Color
    The color of the component. Valid values are 'standard', 'primary', 'secondary', 'error', 'info', 'success', 'warning'.
     
    .PARAMETER Disabled
    If specified, the component will be disabled.
     
    .PARAMETER FullWidth
    If specified, the component will take up the full width of its container.
     
    .PARAMETER DisableFocusRipple
    If specified, the component will not show a focus ripple.
     
    .PARAMETER DisableRipple
    If specified, the component will not show a ripple.
     
    .PARAMETER Size
    The size of the component. Valid values are 'small', 'medium', 'large'.
     
    .PARAMETER Sx
    The style of the component.
     
    .PARAMETER Children
    The content of the component.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),

        [Parameter()]
        [ValidateSet('standard', 'primary', 'secondary', 'error', 'info', 'success', 'warning')]
        [string]$Color = 'standard',

        [Parameter()]
        [Switch]$Disabled,

        [Parameter()]
        [Switch]$FullWidth,

        [Parameter()]
        [Switch]$DisableFocusRipple,

        [Parameter()]
        [Switch]$DisableRipple,

        [Parameter()]
        [ValidateSet('small', 'medium', 'large')]
        [string]$Size = 'medium',

        [Parameter()]
        [Hashtable]$Sx,

        [Parameter()]
        [Alias("Content")]
        [ScriptBlock]$Children
    )

    @{
        id                 = $Id
        type               = 'mui-toggle-button'
        color              = $Color
        disabled           = $Disabled.IsPresent
        fullWidth          = $FullWidth.IsPresent
        disableFocusRipple = $DisableFocusRipple.IsPresent
        disableRipple      = $DisableRipple.IsPresent
        size               = if ($Size) { $Size.ToLower() } else { $null }
        sx                 = $Sx
        children           = & $Children
    }
}
function New-UDTooltip {
    <#
    .SYNOPSIS
    A tooltip component.
     
    .DESCRIPTION
    A tooltip component. Tooltips can be placed over an other component to display a popup when the user hovers over the nested component.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Place
    Where to place the tooltip.
     
    .PARAMETER Type
    The type of tooltip.
     
    .PARAMETER Effect
    An effect to apply to the tooltip.
     
    .PARAMETER TooltipContent
    Content to display within the tooltip.
     
    .PARAMETER Content
    Content that activates the tooltip when hovered.
 
    .PARAMETER Sx
    Theme-based styling hashtable.
 
    .PARAMETER Arrow
    Whether to display an arrow on the tooltip.
 
    .PARAMETER FollowCursor
    Whether the tooltip should follow the cursor.
 
    .PARAMETER EnterDelay
    The delay before the tooltip appears in milliseconds.
 
    .PARAMETER LeaveDelay
    The delay before the tooltip disappears in milliseconds.
 
    .EXAMPLE
    PS > New-UDTooltip -TooltipContent { New-UDTypography -Text "Hello World" -Variant "h1" } -Content { New-UDTypography -Text "Hover over me" -Variant "h1" } -Id "tooltip1"
 
    Basic Tooltip|A basic tooltip component that displays a header when hovered over.
 
    .EXAMPLE
    PS > New-UDTooltip -TooltipContent { New-UDTypography -Text "Hello World" -Variant "h1" } -Content { New-UDTypography -Text "Hover over me" -Variant "h1" } -Id "tooltip3" -Place "right"
 
    Placement|A tooltip component that displays a header when hovered over with a right placement.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [ValidateSet("top", "bottom", "left", "right")]
        [Alias("place")]
        [string]$Placement = "top",
        [Parameter(Mandatory)]
        [ScriptBlock]$TooltipContent,
        [Parameter(Mandatory)]
        [ScriptBlock]$Content,
        [Parameter()]
        [Hashtable]$Sx,
        [Parameter()]
        [Switch]$Arrow,
        [Parameter()]
        [Switch]$FollowCursor,
        [Parameter()]
        [ValidateSet("dark", "success", "warning", "error", "info", "light")]
        [string]$Type = "dark",
        [Parameter()]
        [int]$EnterDelay = 0,
        [Parameter()]
        [int]$LeaveDelay = 0
    )

    @{
        type           = "ud-tooltip"
        tooltipType    = $Type
        placement      = $Placement
        id             = $Id
        tooltipContent = New-UDErrorBoundary -Content $TooltipContent
        content        = New-UDErrorBoundary -Content $Content
        sx             = $Sx
        arrow          = $Arrow.IsPresent
        followCursor   = $FollowCursor.IsPresent
        enterDelay     = $EnterDelay
        leaveDelay     = $LeaveDelay
    }
}
function New-UDTransferList {
    <#
    .SYNOPSIS
    Creates a transfer list component.
     
    .DESCRIPTION
    A transfer list (or "shuttle") enables the user to move one or more list items between lists.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Item
    A list of items that can be transferred between lists. Use New-UDTransferListItem to create an item.
     
    .PARAMETER SelectedItem
    A list of selected items. Use the value of item to transfer items between lists.
     
    .PARAMETER OnChange
    A script block that is executed when the user changes the selected items.
 
    .PARAMETER ShowSearch
    Shows search box for left hand items
 
    .PARAMETER Alignment
    Alignment of the transfer list. Valid values are left, right, and center.
 
    .PARAMETER Dense
    Makes the transfer list dense.
 
    .PARAMETER LeftTitle
    Title for the left hand list.
 
    .PARAMETER RightTitle
    Title for the right hand list.
 
    .PARAMETER LeftSubTitle
    Subtitle for the left hand list.
 
    .PARAMETER RightSubTitle
    Subtitle for the right hand list.
     
     
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Id 'transferlist1'
 
    Basic Transfer List|Creates a basic transfer list.
     
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -OnChange {
    PS > Show-UDToast ($EventData | ConvertTo-Json)
    PS > } -Id 'transferlist2'
 
    OnChange|Creates a transfer list with an OnChange script block.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -SelectedItem @(1, 2) -Id 'transferlist3'
 
    Selected Items|Creates a transfer list with selected items.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Disabled -Id 'transferlist4'
 
    Disabled|Creates a disabled transfer list.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5 -Disabled
    PS > } -Id 'transferlist5'
 
    Disabled Item|Creates a transfer list with a disabled item.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Id 'transferlist6' -Width "50%" -Height '200'
 
    Width and Height|Creates a transfer list with a width and height.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Id 'transferlist7' -Dense
 
    Dense|Creates a transfer list with dense items.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Id 'transferlist8' -ShowSearch
 
    Show Search|Creates a transfer list with a search box.
 
    .EXAMPLE
    PS > New-UDTransferList -Item {
    PS > New-UDTransferListItem -Name 'test1' -Value 1
    PS > New-UDTransferListItem -Name 'test2' -Value 2
    PS > New-UDTransferListItem -Name 'test3' -Value 3
    PS > New-UDTransferListItem -Name 'test4' -Value 4
    PS > New-UDTransferListItem -Name 'test5' -Value 5
    PS > } -Id 'transferlist9' -LeftTitle 'Left Title' -RightTitle 'Right Title' -LeftSubTitle 'Left Subtitle' -RightSubTitle 'Right Subtitle'
 
    Titles|Creates a transfer list with titles.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [ScriptBlock]$Item,
        [Parameter()]
        [string[]]$SelectedItem = @(),
        [Parameter()]
        [Endpoint]$OnChange,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$Disabled,
        [Parameter()]
        [string]$Height,
        [Parameter()]
        [string]$Width = "100%",
        [Parameter()]
        [Switch]$ShowSearch,
        [ValidateSet('left', 'right', 'center')]
        [string]$Alignment = 'center',
        [Parameter()]
        [Switch]$Dense,
        [Parameter()]
        [string]$LeftTitle,
        [Parameter()]
        [string]$RightTitle,
        [Parameter()]
        [string]$LeftSubTitle,
        [Parameter()]
        [string]$RightSubTitle
    )

    if ($OnChange) {
        $OnChange.Register($Id + "onChange", $PSCmdlet)
    }

    @{
        type          = 'mu-transfer-list'
        assetId       = $MUAssetId
        isPlugin      = $true 

        id            = $id 
        item          = $Item.Invoke() | Sort-Object -Unique -Property Value
        selectedItem  = $SelectedItem
        onChange      = $OnChange
        className     = $ClassName
        disabled      = $Disabled.IsPresent
        height        = $Height
        width         = $Width
        showSearch    = $ShowSearch.IsPresent
        alignment     = $Alignment
        dense         = $Dense.IsPresent
        leftTitle     = $LeftTitle
        rightTitle    = $RightTitle
        leftSubTitle  = $LeftSubTitle
        rightSubTitle = $RightSubTitle
    }
}

function New-UDTransferListItem {
    <#
    .SYNOPSIS
    Creates an item for use in a transfer list.
     
    .DESCRIPTION
    Creates an item for use in a transfer list.
     
    .PARAMETER Name
    The display name of the item.
     
    .PARAMETER Value
    The value of the item.
 
    .PARAMETER Disabled
    Disables the item.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [String]$Name,
        [Parameter(Mandatory = $true)]
        [String]$Value,
        [Parameter()]
        [Switch]$Disabled
    )

    @{
        name     = $Name 
        value    = $Value 
        disabled = $Disabled.IsPresent 
    }
}

function New-UDTransition {
    <#
    .SYNOPSIS
    Creates a transition effect.
     
    .DESCRIPTION
    Creates a transition effect.
     
    .PARAMETER Id
    The ID of this component.
     
    .PARAMETER Collapse
    Creates a collapse transition.
     
    .PARAMETER CollapseHeight
    The height of the content when collapsed.
     
    .PARAMETER Fade
    Creates a fade transition.
     
    .PARAMETER Grow
    Creates a grow transition.
     
    .PARAMETER Slide
    Creates a slide transition.
     
    .PARAMETER SlideDirection
    The direction of the slide transition.
     
    .PARAMETER Zoom
    Creates a zoom transition.
     
    .PARAMETER Children
    The content or children to transition.
     
    .PARAMETER In
    Whether the content is transitioned in. You can use Set-UDElement to trigger a transition.
     
    .PARAMETER Timeout
    The number of milliseconds it takes to transition.
 
    .EXAMPLE
    PS > New-UDTransition -Fade -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button1'
    PS > } -Id 'transition1' -Timeout 1000
 
    Fade|A fade transition.
 
    .EXAMPLE
    PS > New-UDTransition -Slide -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button2'
    PS > } -Id 'transition2' -Timeout 1000
 
    Slide|A slide transition.
 
    .EXAMPLE
    PS > New-UDTransition -Zoom -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button3'
    PS > } -Id 'transition3' -Timeout 1000
 
    Zoom|A zoom transition.
 
    .EXAMPLE
    PS > New-UDTransition -Grow -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button4'
    PS > } -Id 'transition4' -Timeout 1000
 
    Grow|A grow transition.
 
    .EXAMPLE
    PS > New-UDTransition -Collapse -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button5'
    PS > } -Id 'transition5' -Timeout 1000
 
    Collapse|A collapse transition.
 
    .EXAMPLE
    PS > New-UDTransition -Collapse -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button6'
    PS > } -Id 'transition6' -CollapseHeight 100 -Timeout 1000
 
    Collapse Height|A collapse transition with 100 height.
 
    .EXAMPLE
    PS > New-UDTransition -Slide -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button7'
    PS > } -Id 'transition7' -SlideDirection 'Left' -Timeout 1000
 
    Slide Direction|A slide transition with left direction.
 
    .EXAMPLE
    PS > New-UDTransition -Fade -In -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button8'
    PS > } -Id 'transition8' -Timeout 1000
 
    Timeout|A fade transition with 1000 timeout.
 
    .EXAMPLE
    PS > New-UDTransition -Fade -Children {
    PS > New-UDButton -Text 'Click Me' -Id 'button9'
    PS > } -Id 'transition9' -Timeout 1000
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Set-UDElement -Id 'transition9' -Properties @{
    PS > In = $true
    PS > }
    PS > }
 
    Interaction|A button that causes the transition to happen.
    #>

    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid().ToString(),
        [Parameter(ParameterSetName = "Collapse")]
        [Switch]$Collapse,
        [Parameter(ParameterSetName = "Collapse")]
        [int]$CollapseHeight,
        [Parameter(ParameterSetName = "Fade")]
        [Switch]$Fade,
        [Parameter(ParameterSetName = "Grow")]
        [Switch]$Grow,
        [Parameter(ParameterSetName = "Slide")]
        [Switch]$Slide,
        [Parameter(ParameterSetName = "Slide")]
        [ValidateSet("Left", "Right", "Down", "Up")]
        [string]$SlideDirection = "Down",
        [Parameter(ParameterSetName = "Zoom")]
        [Switch]$Zoom,
        [Parameter(Mandatory)]
        [Alias("Content")]
        [scriptblock]$Children,
        [Parameter()]
        [Switch]$In,
        [Parameter()]
        [int]$Timeout,
        [Parameter()]
        [Hashtable]$Style
    )

    @{
        type           = "mu-transition"
        id             = $Id 
        asset          = $MUAssetId
        isPlugin       = $true 

        transition     = $PSCmdlet.ParameterSetName.ToLower()
        collapseHeight = $CollapseHeight
        slideDirection = $SlideDirection
        timeout        = $Timeout
        in             = $In.IsPresent
        children       = & $Children 
        style          = $Style
    }
}
function New-UDTreeView {
    <#
    .SYNOPSIS
    Creates a new tree view.
     
    .DESCRIPTION
    Creates a new tree view.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Node
    A collection of root nodes to show within the tree view.
     
    .PARAMETER OnNodeClicked
    A script block that is called when a node is clicked. $EventData will contain the node that was clicked.
     
    .PARAMETER Style
    A set of CSS styles to apply to the tree view.
 
    .PARAMETER ClassName
    A CSS className to apply to the tree view.
 
    .PARAMETER Expanded
    Whether the tree view should be expanded by default.
 
    .EXAMPLE
    PS > New-UDTreeView -Node {
    PS > New-UDTreeNode -Name "Root Node" -Id "root1" -Children {
    PS > New-UDTreeNode -Name "Child Node" -Id "child1" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node" -Id "grandchild1" -Leaf
    PS > }
    PS > New-UDTreeNode -Name "Child Node 2" -Id "child2" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node 2" -Id "grandchild2" -Leaf
    PS > }
    PS > }
    PS > } -Id "treeview1"
 
    Tree View|A basic tree view component.
 
    .EXAMPLE
    PS > New-UDTreeView -Node {
    PS > New-UDTreeNode -Name "Root Node" -Id "root2" -Children {
    PS > New-UDTreeNode -Name "Child Node" -Id "child3" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node" -Id "grandchild3" -Leaf
    PS > }
    PS > New-UDTreeNode -Name "Child Node 2" -Id "child4" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node 2" -Id "grandchild4"
    PS > }
    PS > }
    PS > } -Id "treeview2" -OnNodeClicked {
    PS > Show-UDToast -Message "You clicked $($EventData.Id)"
    PS > }
 
    OnNodeClicked|A tree view with an OnNodeClicked script block that shows a toast when a node is clicked.
 
    .EXAMPLE
    PS > New-UDTreeView -Node {
    PS > New-UDTreeNode -Name "Root Node" -Id "root3" -Children {
    PS > New-UDTreeNode -Name "Child Node" -Id "child5" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node" -Id "grandchild5" -Leaf
    PS > }
    PS > New-UDTreeNode -Name "Child Node 2" -Id "child6" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node 2" -Id "grandchild6" -Leaf
    PS > }
    PS > }
    PS > } -Id "treeview3" -Expanded
 
    Expanded|A tree view that is expanded by default.
 
    .EXAMPLE
    PS > New-UDTreeView -Node {
    PS > New-UDTreeNode -Name "Root Node" -Id "root4"
    PS > } -Id "treeview4" -OnNodeClicked {
    PS > New-UDTreeNode -Name "Child Node"
    PS > }
     
    OnNodeClicked|A tree view with an OnNodeClicked script block that adds a child node when a node is clicked.
 
    .EXAMPLE
    PS > New-UDTreeView -Node {
    PS > New-UDTreeNode -Name "Root Node" -Id "root5" -Children {
    PS > New-UDTreeNode -Name "Child Node" -Id "child7" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node" -Id "grandchild7" -Leaf -Icon (New-UDIcon -Icon "File")
    PS > } -Leaf -Icon (New-UDIcon -Icon "Folder") -ExpandedIcon (New-UDIcon -Icon "FolderOpen")
    PS > New-UDTreeNode -Name "Child Node 2" -Id "child8" -Children {
    PS > New-UDTreeNode -Name "Grandchild Node 2" -Id "grandchild8" -Leaf -Icon (New-UDIcon -Icon "File")
    PS > } -Leaf -Icon (New-UDIcon -Icon "Folder") -ExpandedIcon (New-UDIcon -Icon "FolderOpen")
    PS > }
    PS > } -Id "treeview5" -Expanded
 
    Icons|A tree view with icons for nodes.
    #>

    # [Component("Tree View", "ListTree", "Creates a new card.")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter(Mandatory)]
        [ScriptBlock]$Node,
        [Parameter()]
        [Endpoint]$OnNodeClicked,
        [Parameter()]
        [Hashtable]$Style,
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$Expanded
    )

    End {
        if ($OnNodeClicked) {
            $OnNodeClicked.Register($Id, $PSCmdlet)
        }
        
        @{
            assetId       = $AssetId 
            isPlugin      = $true 
            id            = $Id 
            type          = 'mu-treeview'

            node          = & $Node 
            onNodeClicked = $OnNodeClicked
            style         = $Style
            className     = $ClassName
            expanded      = $Expanded.IsPresent
        }
    }
}

function New-UDTreeNode {
    <#
    .SYNOPSIS
    Creates a tree node.
     
    .DESCRIPTION
    Creates a tree node. This cmdlet should be used with New-UDTreeView.
     
    .PARAMETER Name
    The name of the node. This is displayed within the UI.
     
    .PARAMETER Id
    The ID of the node. This is passed to the $EventData property when the OnNodeClicked script block is set.
     
    .PARAMETER Children
    The children of this node. This should be a collection of New-UDTreeNodes.
     
    .PARAMETER Leaf
    This is a leaf node and should not display children. Added in PSU 2.6.
 
    .PARAMETER Icon
    The icon to display for this node. Use New-UDIcon to create this icon. Added in PSU 2.6.
 
    .PARAMETER ExpandedIcon
    The icon to display for this node when it is expanded. Use New-UDIcon to create this icon. Added in PSU 2.6.
 
    .EXAMPLE
    See New-UDTreeView for examples.
    #>

    param(
        [Parameter(Mandatory, Position = 1)]
        [string]$Name,
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [ScriptBlock]$Children,
        [Parameter()]
        [Switch]$Leaf,
        [Parameter()]
        [object]$Icon, 
        [Parameter()]
        [object]$ExpandedIcon,
        [Parameter()]
        [Switch]$Expanded
    )

    End {
        $ChildrenArray = $null
        if ($PSBoundParameters.ContainsKey("Children")) {
            $ChildrenArray = & $Children
        }
        
        @{
            name         = $Name 
            id           = $Id 
            children     = $ChildrenArray 
            icon         = $Icon 
            expandedIcon = $ExpandedIcon
            leaf         = $Leaf.IsPresent
            expanded     = $Expanded.IsPresent
        }
    }
}
function New-UDTypography {
    <#
    .SYNOPSIS
    Typography allows you to configure and display text.
     
    .DESCRIPTION
    Creates typography. Typography allows you to configure text within a dashboard.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Variant
    The type of text to display.
     
    .PARAMETER Text
    The text to format.
     
    .PARAMETER Content
    The content to format.
     
    .PARAMETER Style
    A set of CSS styles to apply to the typography.
     
    .PARAMETER ClassName
    A CSS className to apply to the typography.
     
    .PARAMETER Align
    How to align the typography.
     
    .PARAMETER GutterBottom
    The gutter bottom.
     
    .PARAMETER NoWrap
    Disables text wrapping.
     
    .PARAMETER Paragraph
    Whether this typography is a paragraph.
 
    .PARAMETER Sx
    Theme-based styling hashtable.
 
    .PARAMETER Truncate
    Disables text wrapping and adds ellipsis for overflow.
     
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Variant "h1" -Id "typography1"
 
    Basic Typography|A basic typography component that displays a header.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Variant "h1" -Style @{color = "red"} -Id "typography2"
 
    Typography with Style|A typography component that displays a header with a red color.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Id "typography3" -Align "center"
 
    Alignment|A typography component that displays with a centered alignment.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Id "typography4" -GutterBottom
 
    Gutter Bottom|A typography component that displays with a space below the text.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!" -Id "typography5" -NoWrap
 
    No Wrap|A typography component that displays with no wrapping.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Id "typography6" -FontWeight "bold"
 
    Font Weight|A typography component that displays with a bold font weight.
 
    .EXAMPLE
    PS > New-UDTypography -Text "Hello World" -Id "typography11" -Sx @{color = "red"}
 
    Sx|A typography component that displays with a red color using the theme sx property.
 
    .EXAMPLE
    PS > New-UDTypography -Id "typography12" -Content {
    PS > New-UDButton -Text "Click Me" -Id "button1"
    PS > }
 
    Content|A typography component that displays with a button inside of it.
 
    .EXAMPLE
    PS > @("h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption", "button", "overline", "srOnly", "inherit", "display4", "display3", "display2", "display1", "headline", "title", "subheading") | ForEach-Object {
    PS > New-UDTypography -Variant $_ -Text $_ -GutterBottom
    PS > New-UDElement -Tag 'p' -Content {}
    PS > }
 
    Variants|A typography component that displays all of the variants.
    #>

    [Category("app/component")]
    [Description("Typography allows you to configure and display text.")]
    [DisplayName("Typography")]
    [CmdletBinding(DefaultParameterSetName = "text")]
    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()).ToString(),

        [Parameter()]
        [ValidateSet ("h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption", "button", "overline", "srOnly", "inherit", "display4", "display3", "display2", "display1", "headline", "title", "subheading")]
        [string]$Variant,

        [Parameter(ParameterSetName = "text", Position = 0)]
        [string]$Text,

        [Parameter(ParameterSetName = "content")]
        [scriptblock]$Content,

        [Parameter()]
        [Hashtable]$Style = @{},

        [Parameter()]
        [string]$ClassName,

        [Parameter()]
        [ValidateSet ("inherit", "left", "center", "right", "justify")]
        [string]$Align,

        [Parameter()]
        [Switch]$GutterBottom,

        [Parameter()]
        [Switch]$NoWrap,

        [Parameter()]
        [Switch]$Truncate,

        [Parameter()]
        [ValidateSet('normal', 'bold', 'lighter', 'bolder', '100', '200', '300', '400', '500', '600', '700', '800', '900')]
        [string]$FontWeight,

        [Parameter()]
        [Hashtable]$Sx
    )

    End {

        if ($FontWeight) {
            $Style["fontWeight"] = $FontWeight
        }

        if ($Truncate.IsPresent) {
            $Style["display"] = "inline-block"
            $Style["width"] = "100%"
        }

        $MUTypography = @{
            type         = "mu-typography"
            isPlugin     = $true
            assetId      = $MUAssetId

            id           = $Id
            className    = $ClassName
            variant      = $Variant
            noWrap       = $NoWrap.IsPresent -or $Truncate.IsPresent
            text         = $Text
            style        = $Style
            align        = $Align
            content      = if ($Content) { & $Content } else { $null }
            gutterBottom = $GutterBottom.IsPresent
            sx           = $sx
        }

        $MUTypography.PSTypeNames.Insert(0, 'UniversalDashboard.MaterialUI.Typography') | Out-Null

        $MUTypography
    }
}
function New-UDUpload {
    <#
    .SYNOPSIS
    Upload files
     
    .DESCRIPTION
    Upload files. This component works with UDForm and UDStepper.
     
    .PARAMETER Id
    The ID of the uploader.
     
    .PARAMETER Accept
    The type of files to accept. By default, this component accepts all files.
     
    .PARAMETER OnUpload
    A script block to execute when a file is uploaded. This $body parameter will contain JSON in the following format:
 
    {
        data: 'base64 encoded string of file data',
        name: 'filename',
        type: 'type of file, if known'
    }
 
    $EventData will contain a class with the following properties:
 
    public string Name { get; set; }
    public string FileName { get; set; }
    public DateTime TimeStamp { get; set; }
    public string ContentType { get; set; }
    public string Type => ContentType;
     
    .PARAMETER Text
    The text to display on the upload button.
     
    .PARAMETER Variant
    The variant of button. Defaults to contained. Valid values: "text", "outlined", "contained"
     
    .PARAMETER Color
    The color of the button. Defaults to 'default'. Valid values: "default", "primary", "secondary", "inherit"
 
    .PARAMETER ClassName
    A CSS class to apply to the button.
 
    .PARAMETER HideUploadedFileName
    Hides the file name text that is shown after upload
 
    .PARAMETER Icon
    The icon to show instead of the default icon. Use New-UDIcon to create an icon.
 
    .PARAMETER IconAlignment
    How to align the icon within the button. Valid values are: "left", "right"
 
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload1'
 
    Basic Upload|A basic upload component that shows a toast when a file is uploaded.
     
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload2' -HideUploadedFileName
 
    Hide File Name|Hides the file name text that is shown after upload.
 
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload3' -Icon (New-UDIcon -Icon 'Upload')
 
    Icon|Upload component with an icon instead of the default icon.
 
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload4' -IconAlignment 'right' -Icon (New-UDIcon -Icon 'Upload')
 
    Icon Alignment|Upload component with an icon on the right side of the button.
 
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload5' -Variant 'text'
 
    Variant|Upload component with a text variant.
 
    .EXAMPLE
    PS > New-UDUpload -Text "Upload" -OnUpload {
    PS > Show-UDToast $Body
    PS > } -Id 'upload6' -Accept ".jpg"
 
    Accept|Upload component that only accepts .jpg files.
 
    #>

    [Category("app/component")]
    [Description("Upload files")]
    [DisplayName("Upload")]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [string]$Accept = "*",
        [Parameter()]
        [Endpoint]$OnUpload,
        [Parameter()]
        [string]$Text = "Upload",
        [Parameter()]
        [ValidateSet("text", "outlined", "contained")]
        [string]$Variant = "contained",
        [Parameter()]
        [ValidateSet('default', 'inherit', 'primary', 'secondary', 'success', 'error', 'info', 'warning')]
        [string]$Color = "primary",
        [Parameter()]
        [string]$ClassName,
        [Parameter()]
        [Switch]$HideUploadedFileName,
        [Parameter()]
        $Icon,
        [Parameter ()]
        [ValidateSet("left", "right")]
        [string]$IconAlignment = "left"
    )

    if ($Icon -is [string]) {
        $Icon = New-UDIcon -Icon $Icon
    }

    if ($OnUpload) {
        $OnUpload.Register($Id, $PSCmdlet)
    }

    if ($Color -eq 'default') {
        $Color = 'inherit'
    }

    @{
        type                 = "mu-upload"
        isPlugin             = $true
        assetId              = $MUAssetId
        id                   = $id

        accept               = $Accept 
        onUpload             = $OnUpload
        text                 = $Text
        variant              = $Variant.ToLower()
        color                = $Color.ToLower()
        className            = $ClassName
        hideUploadedFileName = $HideUploadedFileName.IsPresent
        icon                 = $Icon
        iconAlignment        = $IconAlignment.ToLower()
    }
}

function New-UDChartJS {
    <#
    .SYNOPSIS
    Creates a new ChartJS chart.
     
    .DESCRIPTION
    Creates a new ChartJS chart.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER DatasetLabel
    The label for the dataset. This is only used when the chart has a single dataset.
     
    .PARAMETER Type
    The type of chart to create. Valid values are bar, line, area, doughnut, radar, pie, and bubble.
     
    .PARAMETER Options
    A hashtable of options to pass to the chart.
     
    .PARAMETER OnClick
    A script block to execute when the chart is clicked. $EventData will include the element that was clicked.
     
    .PARAMETER Data
    The data to use for the chart. This is an array of objects or hashtables.
     
    .PARAMETER Dataset
    Used when defining multiple datasets. This is an array of hashtables. Use the New-UDChartJSDataset cmdlet to create the dataset.
     
    .PARAMETER DataProperty
    The property to use for the data. This is only used when the chart has a single dataset.
     
    .PARAMETER LabelProperty
    The property to use for the label. This is only used when the chart has a single dataset.
     
    .PARAMETER BackgroundColor
    The background color of the chart. This is only used when the chart has a single dataset.
     
    .PARAMETER BorderColor
    The border color of the chart. This is only used when the chart has a single dataset.
     
    .PARAMETER HoverBackgroundColor
    The hover background color of the chart. This is only used when the chart has a single dataset.
     
    .PARAMETER HoverBorderColor
    The hover border color of the chart. This is only used when the chart has a single dataset.
     
    .PARAMETER BorderWidth
    The border width of the chart. This is only used when the chart has a single dataset.
     
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'bar' -Id 'chart1'
    PS > }
 
    Bar Chart|A bar chart with a data and label property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ x = 1; y = 10; r = 15 }
    PS > @{ x = 12; y = 25; r = 35 }
    PS > @{ x = 8; y = 10; r = 95 }
    PS > @{ x = 6; y = 95; r = 25 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDChartJS -Type 'bubble' -Data $Data -Id 'chart2'
    PS > }
 
    Bubble Chart|A bubble chart with a data property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'line' -BorderWidth 1 -Id 'chart3'
    PS > }
 
    Line Chart|A line chart with a data and label property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "10vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'doughnut' -Id 'chart4'
    PS > }
 
    Doughnut Chart|A doughnut chart with a data and label property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "10vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'pie' -Id 'chart5'
    PS > }
 
    Pie Chart|A pie chart with a data and label property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "10vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'radar' -Id 'chart6'
    PS > }
 
    Radar Chart|A radar chart with a data and label property.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '10vh'; width = "10vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'bar' -BackgroundColor '#808978FF' -HoverBackgroundColor '#808978FF' -HoverBorderColor '#808978FF' -BorderColor '#808978FF' -Id 'chart7'
    PS > }
 
    Color|A bar chart with a data and label property and some configured colors.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'bar' -OnClick {
    PS > Show-UDToast -Message "Clicked: $Body"
    PS > } -Id 'chart8'
    PS > }
 
    Click|A bar chart with a data and label property and a click event.
 
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > $Dataset1 = New-UDChartJSDataset -DataProperty AvailableRam -Label 'Available RAM' -BackgroundColor '#126f8c' -BorderWidth 1
    PS > $Dataset2 = New-UDChartJSDataset -DataProperty UsedRAM -Label 'Used RAM' -BackgroundColor '#8da322' -BorderWidth 1
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '10vh'; width = "10vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -Dataset @($Dataset1, $Dataset2) -LabelProperty Server -Type 'line' -Id 'chart9'
    PS > }
 
 
    Multiple Data Sets|A line chart with multiple data sets.
 
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDDynamic -Content {
    PS > $Data = 1..10 | % {
    PS > [PSCustomObject]@{ Name = $_; value = get-random }
    PS > }
    PS > New-UDChartJS -Type 'bar' -Data $Data -DataProperty Value -Id 'chart10' -LabelProperty Name -BackgroundColor Blue
    PS > } -AutoRefresh -AutoRefreshInterval 1
    PS > }
 
    Auto Refresh|A bar chart that auto refreshes every second.
     
    .EXAMPLE
    PS > $Data = @(
    PS > @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
    PS > @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
    PS > @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
    PS > @{ Server = "Server4"; AvailableRam = 64;; UsedRAM = 26 }
    PS > @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
    PS > )
    PS > New-UDElement -Tag 'div' -Attributes @{ style = @{ height = '20vh'; width = "20vw"; } } -Content {
    PS > New-UDChartJS -Data $Data -DataProperty UsedRAM -LabelProperty Server -Type 'bar' -OnClick {
    PS > Show-UDToast -Message "Clicked: $Body"
    PS > } -Options @{
    PS > plugins = @{
    PS > legend = @{
    PS > title = @{
    PS > display = $true
    PS > text = 'Bar Chart'
    PS > }
    PS > }
    PS > }
    PS > } -Id 'chart11'
    PS > }
 
    Options|You can use ChartJS options defined as a hashtable to further configure the chart. For more information, see https://www.chartjs.org/docs/3.9.1/.
    #>

    [Category("app/component")]
    [Description("A ChartJS component for PowerShell Universal apps.")]
    [DisplayName("ChartJS")]
    [CmdletBinding(DefaultParameterSetName = 'loaddata')]
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter(ParameterSetName = 'Simple')]
        [System.ComponentModel.BrowsableAttribute(0)]
        [string]$DatasetLabel,
        [Parameter()]
        [ValidateSet('bar', 'line', 'area', 'doughnut', 'radar', 'pie', 'bubble')]
        [string]$Type = "bar",
        [Parameter()]
        [Hashtable]$Options,
        [Parameter()]
        [Endpoint]$OnClick,
        [Parameter(Mandatory, ParameterSetName = 'Simple')]
        [Parameter(Mandatory, ParameterSetName = 'Datasets')]
        [System.ComponentModel.BrowsableAttribute(0)]
        $Data,
        [Parameter(ParameterSetName = 'loaddata')]
        [Endpoint]$LoadData = {
            $Data = @(
                @{ Server = "Server1"; AvailableRam = 128; UsedRAM = 10 }
                @{ Server = "Server2"; AvailableRam = 64; UsedRAM = 63 }
                @{ Server = "Server3"; AvailableRam = 48; UsedRAM = 40 }
                @{ Server = "Server4"; AvailableRam = 64; ; UsedRAM = 26 }
                @{ Server = "Server5"; AvailableRam = 128; UsedRAM = 120 }
            )
            
            New-UDChartJSDataset -Data ($Data | Select-Object -Expand AvailableRam) -DataProperty AvailableRam -Label 'Available RAM' -BackgroundColor '#126f8c' -BorderWidth 1
        },
        [Parameter(ParameterSetName = 'Datasets', Mandatory)]
        [System.ComponentModel.BrowsableAttribute(0)]
        [Hashtable[]]$Dataset,
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [string]$DataProperty = '',
        [Parameter()]
        [System.ComponentModel.BrowsableAttribute(0)]
        [string]$LabelProperty = '',
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [UniversalDashboard.Models.DashboardColor[]]$BackgroundColor = @("#808978FF"),
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [UniversalDashboard.Models.DashboardColor[]]$BorderColor = @("#FF8978FF"),
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [UniversalDashboard.Models.DashboardColor[]]$HoverBackgroundColor = @("#807B210C"),
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [UniversalDashboard.Models.DashboardColor[]]$HoverBorderColor = @("#FF7B210C"),
        [Parameter(ParameterSetName = "Simple")]
        [System.ComponentModel.BrowsableAttribute(0)]
        [int]$BorderWidth = 1
    )

    if ($OnClick) {
        $OnClick.Register($Id, $PSCmdlet) | Out-Null
    }

    if ($MyInvocation.BoundParameters.ContainsKey('LoadData')) {
        $LoadData.Register($Id + "loaddata", $PSCmdlet) | Out-Null
    }

    if ($PSCmdlet.ParameterSetName -eq 'Simple') {
        if (-not $DatasetLabel) {
            $DatasetLabel = $DataProperty
        }

        if (-not $Options -and $Type -eq 'Line') {
            $Options = @{
                fill    = $true
                tension = 0.4
            }
        }

        $Dataset += New-UDChartJSDataset -Data $Data -DataProperty $DataProperty -Label $DatasetLabel -BackgroundColor $BackgroundColor -BorderColor $BorderColor -HoverBackgroundColor $HoverBackgroundColor -HoverBorderColor $HoverBorderColor -BorderWidth $BorderWidth -AdditionalOptions $Options
    }

    if (-not ($MyInvocation.BoundParameters.ContainsKey('LoadData'))) {
        Foreach ($datasetDef in $Dataset) {           
            if ($datasetDef.DataProperty) {
                $datasetDef.data = @($Data | ForEach-Object { $_.($datasetDef.DataProperty) })
            } 
        }
    }
    
    $Props = @{
        type      = 'ud-chartjs'
        id        = $id 
        isPlugin  = $true
        assetId   = $AssetId
        chartType = $Type.ToLower()
        options   = $Options
        onClick   = $OnClick
        loadData  = if ($MyInvocation.BoundParameters.ContainsKey('LoadData')) { $LoadData } else { $null }
    }

    if (-not ($MyInvocation.BoundParameters.ContainsKey('LoadData'))) {
        $Props.data = @{
            labels   = @($Data | ForEach-Object { $_.($LabelProperty) })
            datasets = $dataset
        }
    }

    $Props
}


function New-UDChartJSDataset {
    [CmdletBinding()]
    param(
        [object]$Data = @(),
        [string]$DataProperty,
        [string]$Label,
        [UniversalDashboard.Models.DashboardColor[]]$BackgroundColor = @("#807B210C"),
        [UniversalDashboard.Models.DashboardColor[]]$BorderColor = @("#FF7B210C"),
        [int]$BorderWidth,
        [UniversalDashboard.Models.DashboardColor[]]$HoverBackgroundColor = @("#807B210C"),
        [UniversalDashboard.Models.DashboardColor[]]$HoverBorderColor = @("#FF7B210C"),
        [int]$HoverBorderWidth,
        [string]$XAxisId,
        [string]$YAxisId,
        [Hashtable]$AdditionalOptions
    )

    Begin {
        $datasetOptions = @{
            data                 = $Data
            DataProperty         = $DataProperty
            label                = $Label
            backgroundColor      = $BackgroundColor.HtmlColor
            borderColor          = $BorderColor.HtmlColor
            borderWidth          = $BorderWidth
            hoverBackgroundColor = $HoverBackgroundColor.HtmlColor
            hoverBorderColor     = $HoverBorderColor.HtmlColor
            hoverBorderWidth     = $HoverBorderWidth
            xAxisId              = $XAxisId
            yAxisId              = $YAxisId
        }

        if ($AdditionalOptions) {
            $AdditionalOptions.GetEnumerator() | ForEach-Object {
                $datasetOptions.($_.Key) = $_.Value
            }
        }

        $datasetOptions
    }
}


function New-UDChartJSMonitor {
    param(
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [ValidateSet('bar', 'line', 'area', 'doughnut', 'radar', 'pie', 'horizontalBar')]
        [string]$Type = 'line',
        [Parameter()]
        [int]$DataPointHistory = 10,
        [Parameter()]
        [Hashtable]$Options,
        [Parameter()]
        [DashboardColor[]]$ChartBackgroundColor,
        [Parameter()]
        [DashboardColor[]]$ChartBorderColor,
        [Parameter(Mandatory)]
        [string[]]$Labels = @(),
        [Parameter()]
        [Switch]$AutoRefresh,
        [Parameter()]
        [int]$RefreshInterval = 5,
        [Parameter(Mandatory)]
        [Endpoint]$LoadData
    )

    $LoadData.Register($Id, $PSCmdlet) | Out-Null

    @{
        type                 = 'chartjs-monitor'
        id                   = $id 
        assetId              = $AssetId
        isPlugin             = $true
        
        loadData             = $LoadData
        labels               = $Labels
        dataPointHistory     = $DataPointHistory
        chartType            = $Type.ToLower()
        options              = $Options
        autoRefresh          = $AutoRefresh
        refreshInterval      = $RefreshInterval 
        chartBackgroundColor = if ($chartBackgroundColor) { $chartBackgroundColor.HtmlColor } else { @() }
        chartBorderColor     = if ($ChartBorderColor) { $ChartBorderColor.HtmlColor } else { @() }
    }
}


function Out-UDChartJSMonitorData {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        $Data
    )

    Begin {
        New-Variable -Name Items -Value @()
    }

    Process {
        $Items += $Data
    }

    End {
        $Timestamp = [DateTime]::UtcNow
        $dataSets = @()
        foreach ($item in $Items) {
            $dataSets += @{
                x = $Timestamp
                y = $item
            }
        }
        $dataSets | ConvertTo-Json
    }
}
function Add-UDElement {
    <#
    .SYNOPSIS
    Adds an element to a parent element.
     
    .DESCRIPTION
    Adds an element to a parent element. This cmdlet may behave differently depending on the type of parent element.
     
    .PARAMETER ParentId
    The parent element ID to add the item to.
     
    .PARAMETER Content
    The content to add to the parent element.
     
    .PARAMETER Broadcast
    Whether to update all connected dashboards (all users).
     
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Id 'addElement1' -Content {}
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Add-UDElement -ParentId 'addElement1' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > }
 
    Add Element|Adds an element to a parent element.
 
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Id 'addElement2' -Content {}
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Add-UDElement -ParentId 'addElement2' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > } -Broadcast
    PS > }
 
    Broadcast|Adds an element to a parent element and broadcasts it to all connected dashboards.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$ParentId,
        [Parameter(Mandatory)]
        [ScriptBlock]$Content,
        [Parameter()]
        [Switch]$Broadcast
    )

    $NewContent = & $Content

    $Data = @{
        componentId = $ParentId
        elements    = $NewContent
    }

    if ($Broadcast) {
        $DashboardHub.SendWebSocketMessage("addElement", $Data)
    }
    else {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "addElement", $Data)
    }    
}
function Clear-UDElement {
    <#
    .SYNOPSIS
    Removes all children from the specified element.
     
    .DESCRIPTION
    Removes all children from the specified element. This cmdlet may behave differently depending on the type of parent element.
     
    .PARAMETER Id
    The ID of the element to clear.
     
    .PARAMETER Broadcast
    Whether to clear the element on all connected clients.
     
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Id 'clearElement1' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Clear-UDElement -Id 'clearElement1'
    PS > }
 
    Clear Element|Clears an element.
 
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Id 'clearElement2' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Clear-UDElement -Id 'clearElement2' -Broadcast
    PS > }
 
    Broadcast|Clears an element and broadcasts it to all connected dashboards.
    #>
 
    param(
        [Parameter(Mandatory)]
        [string]$Id,
        [Parameter()]
        [Switch]$Broadcast
    )

    if ($Broadcast) {
        $DashboardHub.SendWebSocketMessage("clearElement", $Id)
    }
    else {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "clearElement", $Id)
    }
}

function Get-UDElement {
    <#
    .SYNOPSIS
    Get the state of the specified element.
     
    .DESCRIPTION
    Get the state of the specified element. This cmdlet may behave differently depending on the type of parent element.
     
    .PARAMETER Id
    The ID of the element to retreive the state of.
     
    .EXAMPLE
    PS > New-UDTextbox -Id 'getElement1' -Placeholder 'Enter your name'
    PS > New-UDButton -Text 'Get Value' -OnClick {
    PS > Show-UDToast (Get-UDElement -Id 'getElement1').Value
    PS > }
 
    Get Element|Gets the state of an element.
 
    .EXAMPLE
    PS > New-UDTextbox -Id 'getElement2' -Placeholder 'Enter your name'
    PS > New-UDButton -Text 'Get Value' -OnClick {
    PS > Show-UDToast (Get-UDElement -Id 'getElement2' -Property 'Value')
    PS > }
 
    Get Property|Gets a specific property of an element.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Id,
        [Parameter(Position = 1)]
        [string]$Property
    )
    
    $requestId = ''

    $requestId = [Guid]::NewGuid().ToString()

    $Data = @{
        requestId   = $requestId 
        componentId = $Id
    }

    $Result = $DashboardHub.SendWebSocketMessageWithResult($ConnectionId, "requestState", $Data)
    if ($Result -eq "null") {
        $Result = $stateRequestService.Get($requestId)
    }
    $Object = ConvertFrom-Json -InputObject $Result
    if ($Property) {
        $Object | Select-Object -ExpandProperty $Property -ErrorAction SilentlyContinue
    }
    else {
        $Object
    }   
}

function Hide-UDModal {
    <#
    .SYNOPSIS
    Hide a modal.
     
    .DESCRIPTION
    Hide a modal.
    #>

    [CmdletBinding()]
    param(
    )
    $DashboardHub.SendWebSocketMessage($ConnectionId, "closeModal", $null)
}
function Hide-UDSnackbar {
    <#
    .SYNOPSIS
    Hides a snackbar.
     
    .DESCRIPTION
    Hides a snackbar.
     
    .PARAMETER Id
    The ID of the snackbar to hide. If not specified, all snackbars will be hidden.
    #>

    param(
        [Parameter()]
        [string]$Id
    )

    $DashboardHub.SendWebSocketMessage($ConnectionId, "hideSnackbar", $Id)
}

function Hide-UDToast {
    <#
    .SYNOPSIS
    Hides a toast message.
     
    .DESCRIPTION
    Hides a toast message.
     
    .PARAMETER Id
    The ID of the toast to hide.
    #>

    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Id
    )

    if ($id -notmatch '^[a-zA-Z0-9]+$') {
        throw "Invalid ID. ID must be alphanumeric."
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "hideToast", "x" + $Id)
}

function Invoke-UDJavaScript {
    <#
    .SYNOPSIS
    Invokes JavaScript within the browser. JavaScript is executed with eval()
     
    .DESCRIPTION
    Invokes JavaScript within the browser. JavaScript is executed with eval()
     
    .PARAMETER JavaScript
    The JavaScript to execute.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Invoke-UDJavaScript 'alert("Hello World!")'
    PS > }
 
    Invoke JavaScript|Invokes JavaScript within the browser.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$JavaScript,
        [Parameter()]
        [Switch]$IgnoreResult
    )

    if ($IgnoreResult) {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "invokejavascript", $JavaScript)
        return
    }

    $Result = $DashboardHub.SendWebSocketMessageWithResult($ConnectionId, "invokejavascriptreturn", $JavaScript)
    if ('' -ne $Result) {
        $Result | ConvertFrom-Json
    }
}
function Invoke-UDRedirect {
    <#
    .SYNOPSIS
    Redirect the user to another page.
     
    .DESCRIPTION
    Redirect the user to another page.
     
    .PARAMETER Url
    The URL to redirect the user to.
     
    .PARAMETER OpenInNewWindow
    Whether to open the URL in a new window.
 
    .PARAMETER Native
    Performs a native redirect. This is useful when using relative paths that aren't part of the dashboard.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Invoke-UDRedirect 'https://www.google.com'
    PS > }
 
    Redirect|Redirects the user to another page.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Invoke-UDRedirect 'https://www.google.com' -OpenInNewWindow
    PS > }
 
    Redirect in New Window|Redirects the user to another page in a new window.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Invoke-UDRedirect '/relative/path' -Native
    PS > }
 
    Native Redirect|Performs a native redirect.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Url,
        [Parameter()]
        [Switch]$OpenInNewWindow,
        [Parameter()]
        [Switch]$Native
    )

    $Data = @{
        url             = $Url 
        openInNewWindow = $OpenInNewWindow.IsPresent
        native          = $Native.IsPresent
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "redirect", $Data)
}
function Remove-UDElement {
    <#
    .SYNOPSIS
    Removes an element from the page.
     
    .DESCRIPTION
    Removes an element from the page.
     
    .PARAMETER Id
    The ID of the element to remove.
     
    .PARAMETER ParentId
    Not used
     
    .PARAMETER Broadcast
    Whether to remove this element from the page of all connected users.
     
    .EXAMPLE
    PS > New-UDElement -Id 'removeElement1' -Tag 'div' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Remove-UDElement -Id 'removeElement1'
    PS > }
 
    Remove Element|Removes an element from the page.
 
    .EXAMPLE
    PS > New-UDElement -Id 'removeElement2' -Tag 'div' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Remove-UDElement -Id 'removeElement2' -Broadcast
    PS > }
 
    Broadcast|Removes an element from the page and broadcasts it to all connected dashboards.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Id, 
        [Parameter()]
        [string]$ParentId,
        [Parameter()]
        [Switch]$Broadcast
    )

    $Data = @{
        componentId = $Id 
        parentId    = $ParentId
    }

    if ($Broadcast) {
        $DashboardHub.SendWebSocketMessage("removeElement", $Data)
    }
    else {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "removeElement", $Data)
    }
}

function Select-UDElement {   
    <#
    .SYNOPSIS
    Selects the specified element.
     
    .DESCRIPTION
    Selects the specified element. This cmdlet is useful for selecting input fields.
     
    .PARAMETER Id
    The ID of the element to select.
     
    .PARAMETER ScrollToElement
    Whether to scroll to the element.
     
    .EXAMPLE
    PS > New-UDTextbox -Id 'selectElement1' -Label 'Name'
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Select-UDElement -Id 'txtName'
    PS > }
 
    Select Element|Selects the specified element.
    #>

    param(
        [Parameter(Mandatory, ParameterSetName = "Normal")]
        [string]$Id,
        [Parameter(ParameterSetName = "Normal")]
        [Switch]$ScrollToElement
    )

    $Data = @{
        id              = $Id 
        scrollToElement = $ScrollToElement
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "select", $Data)
}
function Set-UDClipboard {
    <#
    .SYNOPSIS
    Sets string data into the clipboard.
     
    .DESCRIPTION
    Sets string data into the clipboard.
     
    .PARAMETER Data
    The data to set into the clipboard.
     
    .PARAMETER ToastOnSuccess
    Show a toast if the clipboard data was sent successfully.
     
    .PARAMETER ToastOnError
    Show a toast if the clipboard data was not sent successfully.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Set-UDClipboard -Data 'Hello World!' -ToastOnSuccess
    PS > }
 
    Set Clipboard|Sets string data into the clipboard.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Data,
        [Parameter()]
        [Switch]$ToastOnSuccess,
        [Parameter()]
        [Switch]$ToastOnError
    )

    $cpData = @{
        data           = $Data 
        toastOnSuccess = $ToastOnSuccess.IsPresent
        toastOnError   = $ToastOnError.IsPresent
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "clipboard", $cpData)
}

function Set-UDElement {
    <#
    .SYNOPSIS
    Set properties of an element.
     
    .DESCRIPTION
    Set the properties of an element.
     
    .PARAMETER Id
    The element to set properites on.
     
    .PARAMETER Properties
    The properties to set in the form of a hashtable.
     
    .PARAMETER Broadcast
    Whether to update this element on all connected clients.
     
    .PARAMETER Content
    Content to set within the element.
     
    .EXAMPLE
    PS > New-UDButton -Id 'setElement1' -Text 'Disable Me' -OnClick {
    PS > Set-UDElement -Id 'setElement1' -Properties @{
    PS > 'disabled' = $true
    PS > }
    PS > }
 
    Properties|Disables a button.
 
    .EXAMPLE
    PS > New-UDButton -Id 'setElement2' -Text 'Disable Me' -OnClick {
    PS > Set-UDElement -Id 'setElement2' -Properties @{
    PS > 'disabled' = $true
    PS > } -Broadcast
    PS > }
 
    Broadcast|Disables a button and broadcasts it to all connected dashboards.
 
    .EXAMPLE
    PS > New-UDElement -Tag 'div' -Id 'setElement3' -Content {
    PS > New-UDTypography -Text 'Hello World'
    PS > }
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Set-UDElement -Id 'setElement3' -Content {
    PS > New-UDTypography -Text 'Goodbye World'
    PS > }
    PS > }
 
    Content|Sets the content of an element.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Id,
        [Alias("Attributes")]
        [Parameter()]
        [Hashtable]$Properties,
        [Parameter()]
        [Switch]$Broadcast,
        [Parameter()]
        [ScriptBlock]$Content
    )

    if ($Content -and -not $Properties) {
        $Properties = @{}
    }

    if ($Content) {
        $Properties['content'] = [Array](& $Content)
    }

    $Data = @{
        componentId = $Id 
        state       = $Properties
    }

    if ($Broadcast) {
        $DashboardHub.SendWebSocketMessage("setState", $data)
    }
    else {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "setState", $Data)
    }
}
function Set-UDTheme {
    <#
    .SYNOPSIS
    Sets the current theme of the dashboard.
     
    .DESCRIPTION
    Sets the current theme of the dashboard.
     
    .PARAMETER Name
    A named theme to apply.
 
    .PARAMETER Theme
    A custom theme to apply.
     
    .PARAMETER Theme
    The light or dark variant of the theme.
 
    #>

    param(
        [Parameter(ParameterSetName = "Name")]
        [string]$Name,
        [Parameter(ParameterSetName = "Theme")]
        [Hashtable]$Theme,
        [Parameter()]
        [ValidateSet("light", "dark")]
        [string]$Variant = "light"
    )

    $Data = @{
        name    = $Name
        variant = $Variant.ToLower()
        theme   = $Theme | ConvertTo-Json -Depth 10
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "setTheme", $Data)
}
function Show-UDModal {
    <#
    .SYNOPSIS
    The modal component provides a solid foundation for creating dialogs, popovers, lightboxes, or whatever else.
     
    .DESCRIPTION
    Show a modal.
     
    .PARAMETER FullScreen
    Create a full screen modal.
     
    .PARAMETER Footer
    The footer components for the modal.
     
    .PARAMETER Header
    The header components for the modal.
     
    .PARAMETER Content
    The content of the modal.
     
    .PARAMETER Persistent
    Whether the modal can be closed by clicking outside of it.
     
    .PARAMETER FullWidth
    Whether the modal is full width.
     
    .PARAMETER MaxWidth
    The max width of the modal.
 
    .PARAMETER HeaderStyle
    The CSS style for the header portion of the modal.
     
    .PARAMETER Dividers
    Places a divider between header and footer content
     
    .PARAMETER ContentStyle
    The CSS style for the content portion of the modal.
 
    .PARAMETER FooterStyle
    The CSS style for the footer portion of the modal.
 
    .PARAMETER Style
    The CSS style for the modal.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Basic' -OnClick {
    PS > Show-UDModal -Content {
    PS > New-UDTypography -Text "Hello" -Id 'typography1'
    PS > }
    PS > } -Id 'button1'
 
    Basic Modal|A basic modal.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Full Screen' -OnClick {
    PS > Show-UDModal -Content {
    PS > New-UDTypography -Text "Hello"
    PS > } -FullScreen -Footer {
    PS > New-UDButton -Text "Close" -OnClick { Hide-UDModal }
    PS > }
    PS > } -Id 'button2'
 
    Full Screen Modal|A full screen modal.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Full Width' -OnClick {
    PS > Show-UDModal -Content {
    PS > New-UDTypography -Text "Hello"
    PS > } -FullWidth -MaxWidth 'md'
    PS > } -Id 'button3'
 
    Full Width Modal|A full width modal.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Persistent' -OnClick {
    PS > Show-UDModal -Content {
    PS > New-UDTypography -Text "Hello"
    PS > } -Persistent -Footer {
    PS > New-UDButton -Text "Close" -OnClick { Hide-UDModal } -Id 'button5'
    PS > }
    PS > } -Id 'button4'
 
    Persistent Modal|A persistent modal.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Header And Footer' -OnClick {
    PS > Show-UDModal -Content {
    PS > New-UDTypography -Text "Hello"
    PS > } -Header {
    PS > New-UDTypography -Text "Header" -Id 'header'
    PS > } -Footer {
    PS > New-UDButton -Text "Close" -OnClick { Hide-UDModal } -Id 'button6'
    PS > } -Dividers
    PS > } -Id 'button5'
 
    Header And Footer Modal|A modal with a header and footer.
 
    #>

    param(
        [Parameter()]
        [Switch]$FullScreen,
        [Parameter()]
        [ScriptBlock]$Footer = {},
        [Parameter()]
        [ScriptBlock]$Header = {},
        [Parameter()]
        [ScriptBlock]$Content = {},
        [Parameter()]
        [Switch]$Persistent,
        [Parameter()]
        [Switch]$FullWidth,
        [Parameter()]
        [Switch]$FullHeight,
        [Parameter()]
        [ValidateSet("xs", "sm", "md", "lg", "xl")]
        [string]$MaxWidth,
        [Parameter()]
        [Hashtable]$Style,
        [Parameter()]
        [Hashtable]$HeaderStyle,
        [Parameter()]
        [Hashtable]$ContentStyle,
        [Parameter()]
        [Hashtable]$FooterStyle,
        [Parameter()]
        [Switch]$Dividers,
        [Parameter()]
        [Hashtable]$PaperStyle
    )

    $Modal = @{
        dismissible  = -not $Persistent.IsPresent
        fullWidth    = $FullWidth.IsPresent
        fullScreen   = $FullScreen.IsPresent
        style        = $Style
        headerStyle  = $HeaderStyle
        contentStyle = $ContentStyle
        footerStyle  = $FooterStyle
        dividers     = $Dividers.IsPresent
        maxWidth     = $MaxWidth
        fullHeight   = $FullHeight.IsPresent
        paperStyle   = $PaperStyle
    }

    $Endpoint = [Endpoint]::new()
    $Endpoint.ScriptBlock = {}
    $Endpoint.Register('UDModal', $PSCmdlet)
    
    if ($null -ne $Footer) {
        $Modal['footer'] = & $Footer $Endpoint
    }

    if ($null -ne $Header) {
        $Modal['header'] = & $Header $Endpoint
    }

    if ($null -ne $Content) {
        $Modal['content'] = & $Content $Endpoint
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "showModal", $modal)
}

function Show-UDSnackbar {
    <#
    .SYNOPSIS
    Shows a snackbar popup.
     
    .DESCRIPTION
    Shows a snackbar popup.
 
    .PARAMETER Id
    The id of the snackbar. If not specified, a new guid will be generated.
     
    .PARAMETER Message
    The message to display.
     
    .PARAMETER AutoHideDuration
    The number of milliseconds to display the snackbar for. Defaults to 5000.
     
    .PARAMETER Persist
    Whether the snackbar is persistent.
     
    .PARAMETER Variant
    The variant of the snackbar. Can be default, error, success, warning or info.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [int]$AutoHideDuration = 5000,
        [Parameter()]
        [Switch]$Persist, 
        [Parameter()]
        [ValidateSet('default', 'error', 'success', 'warning', 'info')]
        [string]$Variant = 'default'
    )

    $DashboardHub.SendWebSocketMessage($ConnectionId, "showSnackbar", @{
            message          = $message
            autoHideDuration = $AutoHideDuration
            persist          = $Persist.IsPresent
            variant          = $Variant.ToLower()
            key              = $Id
        })
}
function Show-UDToast {
    <#
    .SYNOPSIS
    Displays a toast message to the user.
     
    .DESCRIPTION
    Displays a toast message to the user.
     
    .PARAMETER Message
    The message to display.
     
    .PARAMETER MessageColor
    The text color of the message.
     
    .PARAMETER MessageSize
    The size of the message.
     
    .PARAMETER Duration
    The duration in milleseconds before the message disappears.
     
    .PARAMETER Title
    The title to display.
     
    .PARAMETER TitleColor
    The text color of the title.
     
    .PARAMETER TitleSize
    The size of the title.
     
    .PARAMETER Id
    The ID of the toast. For use with Hide-UDToast.
     
    .PARAMETER BackgroundColor
    The background color of the toast.
     
    .PARAMETER Theme
    Light or dark theme.
     
    .PARAMETER Icon
    The icon to display in the toast.
     
    .PARAMETER IconColor
    The color of the icon.
     
    .PARAMETER Position
    Where to display the toast.
     
    .PARAMETER HideCloseButton
    Hides the close button.
     
    .PARAMETER CloseOnClick
    Closes the toast when clicked.
     
    .PARAMETER CloseOnEscape
    Closes the toast when esc is pressed.
     
    .PARAMETER ReplaceToast
    Replaces an existing toast if one is already showing.
     
    .PARAMETER RightToLeft
    Right to left text.
     
    .PARAMETER Balloon
    Creates a balloon toast.
     
    .PARAMETER Overlay
    Displays an overlay behind the toast.
     
    .PARAMETER OverlayClose
    Allow the user to close the overlay.
     
    .PARAMETER OverlayColor
    The color of the overlay.
     
    .PARAMETER TransitionIn
    The transition in effect.
     
    .PARAMETER TransitionOut
    The transition out effect.
     
    .PARAMETER Broadcast
    Broadcasts the toast to all connected users.
     
    .PARAMETER Persistent
    Prevents the toast from closing due to the duration.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!'
    PS > } -Id 'toast1'
 
    Basic Toast Shows a toast message.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Duration 5000
    PS > } -Id 'toast2'
 
    Duration|Shows a toast message with a longer duration.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Title 'Toast Title'
    PS > } -Id 'toast3'
 
    Title|Shows a toast message with a title.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Title 'Toast Title' -TitleColor 'red'
    PS > } -Id 'toast4'
 
    Title Color|Shows a toast message with a title and a red title color.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Title 'Toast Title' -TitleColor 'red' -TitleSize 'large'
    PS > } -Id 'toast5'
 
    Title Size|Shows a toast message with a title, a red title color, and a large title size.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -MessageColor 'blue'
    PS > } -Id 'toast6'
 
    Message Color|Shows a toast message with a blue message color.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -MessageSize 'large'
    PS > } -Id 'toast7'
 
    Message Size|Shows a toast message with a large message size.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Icon 'check'
    PS > } -Id 'toast8'
 
    Icon|Shows a toast message with a check icon.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Persistent -Id 'MyToast'
    PS > } -Id 'toast9'
    PS > New-UDButton -Text 'Hide Toast' -OnClick {
    PS > Hide-UDToast -Id 'MyToast'
    PS > } -Id 'toast10'
 
    Persistent|Shows a toast message that will not close. The Hide-UDToast command can be used to close the toast.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Position 'bottomRight'
    PS > } -Id 'toast11'
 
    Position|Shows a toast message at the bottom right of the screen.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -HideCloseButton
    PS > } -Id 'toast12'
 
    Hide Close Button|Shows a toast message without a close button.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -CloseOnClick
    PS > } -Id 'toast13'
 
    Close On Click|Shows a toast message that will close when clicked.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -CloseOnEscape
    PS > } -Id 'toast14'
 
    Close On Escape|Shows a toast message that will close when the escape key is pressed.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -ReplaceToast
    PS > } -Id 'toast15'
 
    Replace Toast|Shows a toast message that will replace an existing toast.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -RightToLeft
    PS > } -Id 'toast16'
 
    Right To Left|Shows a toast message with right to left text.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Balloon
    PS > } -Id 'toast17'
 
    Balloon|Shows a toast message as a balloon.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Overlay
    PS > } -Id 'toast18'
 
    Overlay|Shows a toast message as an overlay.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -Theme 'dark'
    PS > } -Id 'toast19'
 
    Theme|Shows a toast message with a dark theme.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -BackgroundColor 'red'
    PS > } -Id 'toast20'
 
    Background Color|Shows a toast message with a red background color.
 
    .EXAMPLE
    PS > New-UDButton -Text 'Show Toast' -OnClick {
    PS > Show-UDToast -Message 'Hello, World!' -TransitionIn 'bounceInLeft' -TransitionOut 'bounceInRight'
    PS > } -Id 'toast21'
 
    Transition|Shows a toast message with a bounce in left transition and a bounce out right transition.
    #>

    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Message,
        [Parameter()]
        [DashboardColor]$MessageColor,
        [Parameter()]
        [string]$MessageSize,
        [Parameter()]
        [int]$Duration = 1000,
        [Parameter()]
        [string]$Title, 
        [Parameter()]
        [DashboardColor]$TitleColor,
        [Parameter()]
        [string]$TitleSize,
        [Parameter()]
        [string]$Id = [Guid]::NewGuid(),
        [Parameter()]
        [DashboardColor]$BackgroundColor,
        [Parameter()]
        [ValidateSet("light", "dark")]
        [string]$Theme,
        [Parameter()]
        [object]$Icon,
        [Parameter()]
        [DashboardColor]$IconColor,
        [Parameter()]
        [ValidateSet("bottomRight", "bottomLeft", "topRight", "topLeft", "topCenter", "bottomCenter", "center")]
        [string]$Position = "topRight",
        [Parameter()]
        [Switch]$HideCloseButton,
        [Parameter()]
        [Switch]$CloseOnClick,
        [Parameter()]
        [Switch]$CloseOnEscape,
        [Parameter()]
        [Switch]$ReplaceToast,
        [Parameter()]
        [Switch]$RightToLeft,
        [Parameter()]
        [Switch]$Balloon,
        [Parameter()]
        [Switch]$Overlay,
        [Parameter()]
        [Switch]$OverlayClose,
        [Parameter()]
        [DashboardColor]$OverlayColor,
        [Parameter()]
        [ValidateSet("bounceInLeft", "bounceInRight", "bounceInUp", "bounceInDown", "fadeIn", "fadeInDown", "fadeInUp", "fadeInLeft", "fadeInRight", "flipInX")]
        [string]$TransitionIn = "fadeInUp",
        [Parameter()]
        [ValidateSet("bounceInLeft", "bounceInRight", "bounceInUp", "bounceInDown", "fadeIn", "fadeInDown", "fadeInUp", "fadeInLeft", "fadeInRight", "flipInX")]
        [string]$TransitionOut = "fadeOut",
        [Parameter()]
        [Switch]$Broadcast,
        [Parameter()]
        [Switch]$Persistent
    )

    $faIcon = $null
    if ($Icon -is [Hashtable]) {
        $UDIcon = $Icon
    }
    else {
        if ($PSBoundParameters.ContainsKey('Icon') -and -not $Icon.StartsWith('fa')) {
            $faIcon = "fa fa-$($Icon.ToLower().Replace("_", "-"))"
        }
        elseif ($PSBoundParameters.ContainsKey('Icon')) {
            $faIcon = $Icon
        }    
    }

    if ($Persistent) {
        $Duration = $false
    }

    # if ($id -notmatch '^[a-zA-Z0-9]+$') {
    # throw "Invalid ID. ID must be alphanumeric."
    # }

    $options = @{
        close           = -not $HideCloseButton.IsPresent
        id              = "x" + $Id
        message         = $Message
        messageColor    = $MessageColor.HtmlColor
        messageSize     = $MessageSize
        title           = $Title
        titleColor      = $TitleColor.HtmlColor
        titleSize       = $TitleSize
        timeout         = $Duration
        position        = $Position
        backgroundColor = $BackgroundColor.HtmlColor
        theme           = $Theme
        icon            = $faIcon
        iconColor       = $IconColor.HtmlColor
        displayMode     = if ($ReplaceToast.IsPresent) { 2 } else { 0 }
        rtl             = $RightToLeft.IsPresent
        balloon         = $Balloon.IsPresent
        overlay         = $Overlay.IsPresent
        overlayClose    = $OverlayClose.IsPresent
        overlayColor    = $OverlayColor.HtmlColor
        closeOnClick    = $CloseOnClick.IsPresent
        closeOnEscape   = $CloseOnEscape.IsPresent
        transitionIn    = $TransitionIn
        transitionOut   = $TransitionOut
        udIcon          = $UDIcon
    }

    if ($Broadcast) {
        $DashboardHub.SendWebSocketMessage("showToast", $options)
    }
    else {
        $DashboardHub.SendWebSocketMessage($ConnectionId, "showToast", $options)
    }
}
function Start-UDDownload {
    <#
    .SYNOPSIS
    Starts the download of a file within the dashboard.
     
    .DESCRIPTION
    Starts the download of a file within the dashboard. Only text files are supported
     
    .PARAMETER FileName
    The name of the file.
 
    .PARAMETER StringData
    The data to be written to the file.
 
    .PARAMETER ContentType
    The content type of the file.
 
    .PARAMETER Url
    A URL to download as string data and send to the user.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Start-UDDownload -FileName 'myfile.txt' -StringData 'Hello World' -ContentType 'text/plain'
    PS > }
 
    Download File|Starts the download of a file within the dashboard.
    #>

    param(
        [Parameter()]
        [string]$FileName = "text.txt",
        [Parameter(Mandatory, ParameterSetName = 'String')]
        [string]$StringData,
        [Parameter()]
        [string]$ContentType = "text/plain",
        [Parameter(Mandatory, ParameterSetName = 'Url')]
        [string]$Url
    )

    if ($Url) {
        $WebRequest = Invoke-WebRequest -Uri $Url
        $StringData = $WebRequest.Content
        $ContentType = $WebRequest.Headers["Content-Type"]
    }

    $Data = @{
        fileName    = $FileName
        stringData  = $StringData
        contentType = $ContentType
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "download", $data)
}
function Sync-UDElement {
    <#
    .SYNOPSIS
    Causes an element to update.
     
    .DESCRIPTION
    Causes an element to update. Not all elements can be updated. For elements that cannot be updated, wrap them in New-UDDynamic and update that.
     
    .PARAMETER Id
    The ID of the element to update.
 
    .PARAMETER ArgumentList
    The argument list to pass to the element.
     
    .PARAMETER Broadcast
    Whether to broadcast the update to all clients.
     
    .EXAMPLE
    PS > New-UDDynamic -Id 'sync1' -Content {
    PS > Get-Date
    PS > }
    PS > New-UDButton -Text 'Refresh' -OnClick {
    PS > Sync-UDElement 'sync1'
    PS > } -Id 'syncButton1'
 
    Sync Element|Syncs an element.
 
    .EXAMPLE
    PS > New-UDDynamic -Id 'sync2' -Content {
    PS > Get-Date
    PS > }
    PS > New-UDButton -Text 'Refresh' -OnClick {
    PS > Sync-UDElement 'sync2' -Broadcast
    PS > } -Id 'syncButton2'
 
    Broadcast|Syncs an element and broadcasts it to all connected dashboards.
    #>

    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string[]]$Id,
        [Parameter()]
        [Switch]$Broadcast,
        [Parameter()]
        [object[]]$ArgumentList,
        [Parameter()]
        [switch]$Wait,
        [Parameter()]
        [int]$Timeout = 5000
    )

    Process {
        foreach ($i in $Id) {
            if ($Broadcast) {
                $DashboardHub.SyncElement($I, $ArgumentList, $Wait, $Timeout)
            }
            else {
                $DashboardHub.SyncElement($I, $ArgumentList, $Wait, $Timeout, $ConnectionId)
            }
        } 
    }
}
function Test-UDConnected {
    <#
    .SYNOPSIS
        Tests if a user is connected to the dashboard.
    #>

    param()
    $Host.PrivateData.ConnectionManager.GetSessionId($ConnectionId)
}
function Wait-UDDebugger {
    <#
    .SYNOPSIS
    Displays a debugging window for dashboards.
     
    .DESCRIPTION
    Displays a debugging window for dashboards.
     
    .PARAMETER Variable
    The variable to display in the debugger. Defaults to all variables.
     
    .EXAMPLE
    PS > New-UDButton -Text 'Click Me' -OnClick {
    PS > Wait-UDDebugger
    PS > } -Id 'debugger1'
 
    Debugger|Displays a debugging window for dashboards.
    #>

    param(
        [Parameter()]
        $Variable = "*"
    )

    $Variables = Get-Variable -Name $Variable -Scope 2 | Where-Object { $null -ne $_.Name } | ForEach-Object { ConvertTo-TreeData -Variable $_ -Prefix '$' } # | Group-Object -Property Name, Path | ForEach-Object { $_.Group | Select-Object -First 1 }

    $data = @{
        Callstack  = Get-PSCallStack | Out-String
        Id         = [Guid]::NewGuid()
        RunspaceId = $Host.Runspace.Id
        ProcessId  = $PID
        Variables  = $Variables
    }

    $DashboardHub.SendWebSocketMessage($ConnectionId, "debugger", $data)

    $UDDebugger.Wait($data.Id)
}

function ConvertTo-TreeData {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Path = '', 
        [Parameter(Mandatory, ValueFromPipeline)]
        $Variable,
        [Parameter()]
        $Depth = 3,
        [Parameter()]
        $Items,
        [Parameter()]
        $Prefix
    )

    Process {
        $root = $null -eq $Items
        if ($Depth -eq 0) {
            return
        }

        if ($null -eq $Items) {
            $Items = [System.Collections.Generic.Dictionary[string, object]]::new()
        }
    
        $ChildPath = $Path
        $ChildPath += $Prefix + $Variable.Name

        $Type = $null 
        if ($null -ne $Variable.Value) {
            $Type = $Variable.Value.GetType().Name
        }

        $Object = [PSCustomObject]@{
            Name         = $Variable.Name 
            DisplayValue = [string]$Variable.Value
            Type         = $Type
            Path         = $ChildPath 
            Id           = [Guid]::NewGuid()
        }

        if (-not $Items.ContainsKey($ChildPath)) {
            $Items.Add($ChildPath, $Object)
        }

        if ($Variable.Value -is [System.Collections.Hashtable] -or $Variable.Value -is [System.Collections.IDictionary]) {

            $index = 0
            $Variable.Value.Keys | ForEach-Object {

                $Type = $null
                if ($null -ne $Variable.Value[$_]) {
                    $Var = @{
                        Name  = $_
                        Value = $Variable.Value[$_]
                    }

                    ConvertTo-TreeData -Path $ChildPath -Variable $Var -Depth ($Depth - 1) -Items $Items -Prefix '/'
                    $Type = $Variable.Value[$_].GetType().Name
                }
                
                $Object = [PSCustomObject]@{
                    Name         = "[$_]"
                    DisplayValue = [string]$_      
                    Type         = $Type
                    Path         = $ChildPath + '/' + "[$_]"
                    Id           = [Guid]::NewGuid()
                }
    
                if (-not $Items.ContainsKey($ChildPath)) {
                    $Items.Add($ChildPath, $Object)
                }

                $Index++
            }
        }
        elseif (($Variable.Value -is [System.Collections.IEnumerable]) -and $Variable.Value -isnot [string]) {

            $index = 0
            $Variable.Value | ForEach-Object {
                if ($null -ne $_) {
                    $Var = @{
                        Name  = "[$index]"
                        Value = $_ 
                    }

                    ConvertTo-TreeData -Path $ChildPath -Variable $Var -Depth ($Depth - 1) -Items $Items -Prefix '/'
                }

                $Type = $null
                if ($null -ne $_) {
                    $Type = $_.GetType().Name
                }
                
                $Object = [PSCustomObject]@{
                    Name         = "[$index]"
                    DisplayValue = [string]$_      
                    Type         = $Type
                    Path         = $ChildPath + '/' + "[$index]"
                    Id           = ([Guid]::NewGuid())
                }
    
                if (-not $Items.ContainsKey($ChildPath)) {
                    $Items.Add($ChildPath, $Object)
                }

                $Index++
            }
        }
        else {
            $Variable.Value.PSObject.Properties | ForEach-Object {
                if ($null -ne $_.Value -and $_.Value -isnot [string]) {
                    ConvertTo-TreeData -Path $ChildPath -Variable $_ -Depth ($Depth - 1) -Items $Items -Prefix '/'
                }
                
                $Object = [PSCustomObject]@{
                    Name         = $_.Name 
                    DisplayValue = [string]$_.Value      
                    Type         = $_.TypeNameOfValue
                    Path         = $ChildPath + '/' + $_.Name
                    Id           = ([Guid]::NewGuid())
                }
    
                if (-not $Items.ContainsKey($ChildPath)) {
                    $Items.Add($ChildPath, $Object)
                }
            } 
        }

        if ($root) {
            $Items.Values
        }
    }
}

function New-UDMapBaseLayer {
    <#
    .SYNOPSIS
    Creates a map base layer.
     
    .DESCRIPTION
    Creates a map base layer.
     
    .PARAMETER Id
    The id of the map base layer. This defaults to a new GUID.
     
    .PARAMETER Name
    The name of the map base layer.
     
    .PARAMETER Content
    The content of the map base layer. This should be a script block that returns a raster tile layer.
     
    .PARAMETER Checked
    Whether or not the map base layer is checked.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter(Mandatory)]
        [string]$Name,
        [Parameter(Mandatory)]
        [ScriptBlock]$Content,
        [Parameter()]
        [Switch]$Checked
    )

    @{
        type     = "map-base-layer"
        isPlugin = $true
        assetId  = $AssetId
        id       = $Id
        name     = $Name
        content  = & $Content
        checked  = $Checked.IsPresent
    }
}
function New-UDMapMarkerClusterLayer {
    <#
    .SYNOPSIS
    Creates a map marker cluster layer.
     
    .DESCRIPTION
    Creates a map marker cluster layer.
     
    .PARAMETER Id
    The id of the map marker cluster layer. This defaults to a new GUID.
     
    .PARAMETER Markers
    The markers to cluster.
     
    .PARAMETER MinimumClusterSize
    The minimum number of markers to cluster.
     
    .PARAMETER GridSize
    The grid size.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [Hashtable[]]$Markers,
        [Parameter()]
        [int]$MinimumClusterSize = 2,
        [Parameter()]
        [int]$GridSize = 60
    )

    @{
        type           = "map-cluster-layer"
        isPlugin       = $true
        assetId        = $assetId 

        id             = $id 
        markers        = $Markers
        minClusterSize = $MinimumClusterSize
        gridSize       = $GridSize
    }
}
function New-UDMapFeatureGroup {
    <#
    .SYNOPSIS
    Creates a map feature group.
     
    .DESCRIPTION
    Creates a map feature group.
     
    .PARAMETER Id
    The id of the map feature group. This defaults to a new GUID.
     
    .PARAMETER Popup
    The popup for the map feature group.
     
    .PARAMETER Content
    The content of the map feature group. This should be a script block that returns a map feature.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [Hashtable]$Popup,
        [Parameter(Mandatory)]
        [Alias('Content')]
        [ScriptBlock]$Children
    )

    End {
        @{
            type     = 'map-feature-group'
            id       = $id 
            isPlugin = $true
            assetId  = $AssetId
            children = & $Children
            popup    = $Popup
        }
    }
}
function ConvertFrom-GeoJson {
    <#
    .SYNOPSIS
    Converts GeoJSON to a map feature.
     
    .DESCRIPTION
    Converts GeoJSON to a map feature.
     
    .PARAMETER GeoJson
    The GeoJSON to convert.
     
    .PARAMETER Icons
    The icons to use for the map markers.
    #>

    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSCustomObject[]]$GeoJson,
        [Parameter()]
        [PSCustomObject[]]$Icons
    )

    Process {
        $Json = $GeoJson 

        $Json | ForEach-Object {
            if ($_.type -eq 'FeatureCollection') {
                $features = foreach ($Feature in $_.features) {
                    $Feature | ConvertFrom-GeoJson -Icons $Icons
                }

                New-UDMapOverlay -Name $_.properties.name -Content {
                    New-UDMapFeatureGroup -Content { $features }
                } -Checked
            }

            if ($_.type -eq 'feature') {
                $Geometry = $_.geometry | ConvertFrom-GeoJson -Icons $Icons
                
                if ($_.properties.DisplayText) {
                    $Geometry.Popup = New-UDMapPopup -Content { New-UDHtml $_.properties.DisplayText }
                }

                if ($_.style.color -and $Geometry.type -ne "map-marker") {
                    $Geometry.FillColor = $_.style.color
                    $Geometry.Color = $_.style.color

                    if ($_.style.weight) {
                        $Geometry.Weight = $_.style.weight
                    }
                }

                if ($_.style.color -and $Geometry.type -eq "map-marker") {

                    $iconName = $_.style.color
                    $Icon = $Icons | Where-Object {
                        $_.IconName -eq $iconName
                    }

                    if ($null -ne $Icon) {
                        $Geometry.Icon = New-UDMapIcon -Url "http://emaps.papertransport.com/e_img/$($Icon.IconFileName)" -Width $Icon.IconWidth -Height $Icon.IconHeight -AnchorX $Icon.IconAnchorX -AnchorY $Icon.IconHeight -PopupAnchorX $Icon.IconPopupX -PopupAnchorY $Icon.IconPopupY
                    }
                }

                $Geometry
            }

            if ($_.type -eq 'polygon') {
                $Coordinates = @()
                $_.coordinates[0] | ForEach-Object {
                    $temp = $_[0]
                    $_[0] = $_[1]
                    $_[1] = $temp

                    $Coordinates += , $_
                }

                New-UDMapVectorLayer -Polygon -Positions $Coordinates -FillOpacity .5
            }

            if ($_.type -eq 'point') {
                $Coordinates = $_.coordinates

                New-UDMapMarker -Latitude $coordinates[1] -Longitude $coordinates[0] -Zindex $_.style.zIndexOffset
            }

            if ($_.type -eq 'linestring') {
                $Coordinates = $_.coordinates

                New-UDMapMarker -Latitude $coordinates[1] -Longitude $coordinates[0] -Zindex $_.style.zIndexOffset
            }

            if ($_.type -eq 'MultiLineString') {
                $Coordinates = @()
                foreach ($array in $_.coordinates) {
                    foreach ($arrayInArray in $array) {
                        $temp = $arrayInArray[0]
                        $arrayInArray[0] = $arrayInArray[1]
                        $arrayInArray[1] = $temp
    
                        $Coordinates += , $arrayInArray
                    }
                }

                New-UDMapVectorLayer -Polyline -Positions $Coordinates
            }
        }
    }
}
function New-UDMapHeatmapLayer {
    <#
    .SYNOPSIS
    Creates a map heatmap layer.
     
    .DESCRIPTION
    Creates a map heatmap layer.
     
    .PARAMETER Points
    The points to use for the heatmap.
     
    .PARAMETER Id
    The id of the map heatmap layer. This defaults to a new GUID.
     
    .PARAMETER MaxIntensity
    The maximum intensity of the heatmap.
     
    .PARAMETER Radius
    The radius of the heatmap.
     
    .PARAMETER MaxZoom
    The maximum zoom of the heatmap.
     
    .PARAMETER MinOpacity
    The minimum opacity of the heatmap.
     
    .PARAMETER Blur
    The blur of the heatmap.
     
    .PARAMETER Gradient
    The gradient of the heatmap.
    #>

    param(
        [Parameter(Mandatory)]
        $Points,
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [double]$MaxIntensity,
        [Parameter()]
        [double]$Radius,
        [Parameter()]
        [int]$MaxZoom,
        [Parameter()]
        [double]$MinOpacity,
        [Parameter()]
        [int]$Blur,
        [Parameter()]
        [Hashtable]$Gradient
    )

    $Options = @{
        type     = 'map-heatmap-layer'
        isPlugin = $true
        assetId  = $AssetId
    }

    foreach ($boundParameter in $PSCmdlet.MyInvocation.BoundParameters.GetEnumerator()) {
        $Options[[char]::ToLowerInvariant($boundParameter.Key[0]) + $boundParameter.Key.Substring(1)] = $boundParameter.Value
    }

    $Options
}
function New-UDMapIcon {
    <#
    .SYNOPSIS
    Creates a map icon.
     
    .DESCRIPTION
    Creates a map icon.
     
    .PARAMETER Url
    The URL of the icon.
     
    .PARAMETER Height
    The height of the icon.
     
    .PARAMETER Width
    The width of the icon.
     
    .PARAMETER AnchorX
    The anchor X of the icon.
     
    .PARAMETER AnchorY
    The anchor Y of the icon.
     
    .PARAMETER PopupAnchorX
    The popup anchor X of the icon.
     
    .PARAMETER PopupAnchorY
    The popup anchor Y of the icon.
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Url,
        [Parameter()]
        [int]$Height,
        [Parameter()]
        [int]$Width,
        [Parameter()]
        [int]$AnchorX,
        [Parameter()]
        [int]$AnchorY,
        [Parameter()]
        [int]$PopupAnchorX,
        [Parameter()]
        [int]$PopupAnchorY
    )

    $Options = @{
    }

    foreach ($boundParameter in $PSCmdlet.MyInvocation.BoundParameters.GetEnumerator()) {
        $Options[[char]::ToLowerInvariant($boundParameter.Key[0]) + $boundParameter.Key.Substring(1)] = $boundParameter.Value
    }

    $Options
}
function New-UDMapLayerControl {
    <#
    .SYNOPSIS
    Creates a map layer control.
     
    .DESCRIPTION
    Creates a map layer control.
     
    .PARAMETER Id
    The id of the map layer control. This defaults to a new GUID.
     
    .PARAMETER Position
    The position of the map layer control. This defaults to topright. Valid values are topright, topleft, bottomright, bottomleft.
     
    .PARAMETER Content
    The content of the map layer control. This should be a script block that returns a map layer control.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ValidateSet("topright", "topleft", "bottomright", "bottomleft")]
        [string]$Position = "topright",
        [Parameter()]
        [ScriptBlock]$Content
    )

    @{
        type     = 'map-layer-control'
        isPlugin = $true
        assetId  = $AssetId
        id       = $Id
        content  = & $Content
        position = $Position
    }
}
function New-UDMap {
    <#
    .SYNOPSIS
    Interactive maps for displaying markers, lines, and polygons.
     
    .DESCRIPTION
    Interactive maps for displaying markers, lines, and polygons.
     
    .PARAMETER Id
    The ID of the component. It defaults to a random GUID.
     
    .PARAMETER Longitude
    The default longitude of the map.
     
    .PARAMETER Latitude
    The default latitude of the map.
     
    .PARAMETER Zoom
    The default zoom level of the map.
     
    .PARAMETER Height
    The height of the map.
     
    .PARAMETER Width
    The width of the map.
     
    .PARAMETER Content
    The script block to call to get the map data.
     
    .PARAMETER ZoomControlPosition
    The zoom control position. Valid values are topright, topleft, bottomright, bottomleft.
     
    .PARAMETER ScaleControlPosition
    The scale control position. Valid values are topright, topleft, bottomright, bottomleft, hide.
     
    .PARAMETER Animate
    Animate the map when it is loaded.
     
    .PARAMETER MaxZoom
    The maximum zoom level of the map.
     
    .EXAMPLE
    PS > New-UDMap -Content {
    PS > New-UDMapRasterLayer -TileServer 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    PS > } -Latitude 43.52107 -Longitude -114.31644 -Zoom 13 -Height '25vh' -Id 'map1'
 
    Basic map|This basic map defines a simple base layer using the wmflabs.org tile server. You can use your own custom tile server by specifying a URL. The map is position over Hailey, Idaho.
 
    .EXAMPLE
    PS > New-UDMap -Content {
    PS > New-UDMapLayerControl -Content {
    PS > New-UDMapBaseLayer -Name 'Color' -Content {
    PS > New-UDMapRasterLayer -TileServer 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    PS > } -Checked
    PS > New-UDMapOverlay -Name 'Marker' -Content {
    PS > New-UDMapMarker -Latitude 51.505 -Longitude -0.09
    PS > } -Checked
    PS > New-UDMapOverlay -Name 'Marker 2' -Content {
    PS > New-UDMapMarker -Latitude 51.555 -Longitude -0.00
    PS > } -Checked
    PS > }
    PS > } -Latitude 51.505 -Longitude -0.09 -Zoom 13 -Height '25vh' -Id 'map2'
 
    Layer Control|You can enable the layer control by using the New-UDMapLayerControl cmdlet. This map defines several layers with components that you can toggle on and off. You can only have one base layer selected as a time. Map overlay layers can toggle on and off.
 
    .EXAMPLE
 
    PS > New-UDMap -Content {
    PS > New-UDMapRasterLayer -TileServer 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    PS > New-UDMapMarker -Latitude "51.105" -Longitude "-0.09" -Popup (
    PS > New-UDMapPopup -Content {
    PS > New-UDAlert -Text "Hello"
    PS > } -MinWidth 200
    PS > )
    PS > } -Latitude 51.505 -Longitude -0.09 -Zoom 13 -Height '25vh' -Id 'map3'
 
    Markers with Popup|You can add markers to the map by using the New-UDMapMarker cmdlet.
 
    .EXAMPLE
 
    PS > New-UDMap -Content {
    PS > New-UDMapRasterLayer -TileServer 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    PS > New-UDMapVectorLayer -Circle -Radius 1000 -Latitude 51.505 -Longitude -0.09 -Color 'red' -FillColor 'blue' -FillOpacity 0.5
    PS > } -Latitude 51.505 -Longitude -0.09 -Zoom 13 -Height '25vh' -Id 'map4'
 
    Vectors|You can add vectors to the map by using the New-UDMapVectorLayer cmdlet.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [float]$Longitude,
        [Parameter()]
        [float]$Latitude,
        [Parameter()]
        [int]$Zoom,
        [Parameter()]
        [string]$Height = '500px',
        [Parameter()]
        [string]$Width = '100%',
        [Parameter(Mandatory)]
        [Alias("Endpoint")]
        [Endpoint]$Content,
        [ValidateSet("topright", "topleft", "bottomright", "bottomleft")]
        [string]$ZoomControlPosition = "topright",
        [ValidateSet("topright", "topleft", "bottomright", "bottomleft", "hide")]
        [string]$ScaleControlPosition = "bottomleft",
        [Parameter()]
        [Switch]$Animate,
        [Parameter()]
        [int]$MaxZoom = 18
    )

    End {
        $Content.Register($Id, $PSCmdlet)

        @{
            assetId              = $AssetId 
            isPlugin             = $true 
            type                 = "ud-map"
            id                   = $Id

            longitude            = $Longitude
            latitude             = $Latitude
            zoom                 = $Zoom
            height               = $Height
            width                = $Width
            zoomControlPosition  = $ZoomControlPosition
            animate              = $Animate.IsPresent
            scaleControlPosition = $ScaleControlPosition
            maxZoom              = $MaxZoom
            content              = $Content
        }
    }
}
function New-UDMapMarker {
    <#
    .SYNOPSIS
    Creates a map marker.
     
    .DESCRIPTION
    Creates a map marker.
     
    .PARAMETER Id
    The id of the map marker. This defaults to a new GUID.
     
    .PARAMETER Longitude
    The longitude of the map marker.
     
    .PARAMETER Latitude
    The latitude of the map marker.
     
    .PARAMETER Attribution
    The attribution of the map marker.
     
    .PARAMETER Opacity
    The opacity of the map marker.
     
    .PARAMETER ZIndex
    The z-index of the map marker.
     
    .PARAMETER Popup
    The popup of the map marker. Use New-UDMapPopup to create a popup.
     
    .PARAMETER Icon
    The icon of the map marker. Use New-UDMapIcon to create an icon.
     
    .PARAMETER GeoJSON
    The GeoJSON of the map marker. This is an alternative to specifying the longitude and latitude.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter(ParameterSetName = "LatLng", Mandatory)]
        [float]$Longitude,
        [Parameter(ParameterSetName = "LatLng", Mandatory)]
        [float]$Latitude,
        [Parameter()]
        [string]$Attribution,
        [Parameter()]
        [int]$Opacity,
        [Parameter()]
        [int]$ZIndex,
        [Parameter()]
        [Hashtable]$Popup,
        [Parameter()]
        [Hashtable]$Icon,
        [Parameter(ParameterSetName = "GeoJSON", Mandatory)]
        [string]$GeoJSON
    )

    if ($PSCmdlet.ParameterSetName -eq 'GeoJSON') {
        $Json = $GeoJSON | ConvertFrom-Json
        $Coordinates = $Json.Geometry.Coordinates
        $Latitude = $Coordinates[1]
        $Longitude = $Coordinates[0]
    }

    @{
        type        = "map-marker"
        isPlugin    = $true
        assetId     = $AssetId

        id          = $id 
        longitude   = $Longitude
        latitude    = $Latitude
        attribution = $Attribution
        opacity     = $Opacity
        zIndex      = $ZIndex
        popup       = $Popup 
        icon        = $Icon
    }
}
function New-UDMapOverlay {
    <#
    .SYNOPSIS
    Creates a map overlay.
     
    .DESCRIPTION
    Creates a map overlay.
     
    .PARAMETER Id
    The id of the map overlay. This defaults to a new GUID.
     
    .PARAMETER Name
    The name of the map overlay.
     
    .PARAMETER Content
    The content of the map overlay. This should be a script block that returns a raster tile layer.
     
    .PARAMETER Checked
    Whether or not the map overlay is checked.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter(Mandatory)]
        [string]$Name,
        [Parameter(Mandatory)]
        [ScriptBlock]$Content,
        [Parameter()]
        [Switch]$Checked
    )

    @{
        type     = 'map-overlay'
        isPlugin = $true
        assetId  = $AssetId

        id       = $id
        name     = $Name 
        content  = & $Content
        checked  = $Checked.IsPresent
    }
}
function New-UDMapPopup {
    <#
    .SYNOPSIS
    Creates a map popup.
     
    .DESCRIPTION
    Creates a map popup.
     
    .PARAMETER Id
    The id of the map popup. This defaults to a new GUID.
     
    .PARAMETER Content
    The content of the map popup.
     
    .PARAMETER Longitude
    The longitude of the map popup.
     
    .PARAMETER Latitude
    The latitude of the map popup.
     
    .PARAMETER MaxWidth
    The max width of the map popup.
     
    .PARAMETER MinWidth
    The min width of the map popup.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [ScriptBlock]$Content,
        [Parameter()]
        [float]$Longitude,
        [Parameter()]
        [float]$Latitude,
        [Parameter()]
        [int]$MaxWidth,
        [Parameter()]
        [int]$MinWidth
    )

    $Options = @{
        type     = "map-popup"
        isPlugin = $true
        assetId  = $AssetId

        id       = $id
        content  = & $Content

        maxWidth = $MaxWidth
        minWidth = $MinWidth
    }

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Longitude")) {
        $Options["longitude"] = $Longitude
    }

    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Latitude")) {
        $Options["latitude"] = $Latitude
    }

    $Options
}
function New-UDMapRasterLayer {
    <#
    .SYNOPSIS
    Creates a map raster layer.
     
    .DESCRIPTION
    Creates a map raster layer.
     
    .PARAMETER Id
    The id of the map raster layer. This defaults to a new GUID.
     
    .PARAMETER TileServer
    The tile server to use. This defaults to OpenStreetMap.
     
    .PARAMETER ApiKey
    The API key to use for the tile server.
     
    .PARAMETER Type
    The type of Bing map to use. This defaults to Aerial.
     
    .PARAMETER Bing
    Whether or not to use Bing maps.
     
    .PARAMETER Attribution
    Attribution text to display on the map.
     
    .PARAMETER Opacity
    The opacity of the map.
     
    .PARAMETER ZIndex
    The z-index of the map.
     
    .PARAMETER Name
    The name of the raster layer.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter(ParameterSetName = "Generic")]
        [string]$TileServer = 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
        [Parameter(ParameterSetName = "Bing", Mandatory)]
        [string]$ApiKey,
        [Parameter(ParameterSetName = "Bing")]
        [ValidateSet("Aerial", "AerialWithLabels", "AerialWithLabelsOnDemand", "CanvasDark", "CanvasLight", "CanvasGray", "Road")]
        [string]$Type = "Aerial",
        [Parameter(ParameterSetName = "Bing", Mandatory)]
        [Switch]$Bing,
        [Parameter()]
        [string]$Attribution,
        [Parameter()]
        [int]$Opacity,
        [Parameter()]
        [int]$ZIndex,
        [Parameter()]
        [string]$Name
    )

    @{
        type        = "map-raster-layer"
        isPlugin    = $true
        assetId     = $AssetId

        id          = $id
        tileServer  = $TileServer
        apiKey      = $ApiKey
        attribution = $Attribution
        opacity     = $Opactiy
        zIndex      = $ZIndex
        name        = $Name
        bing        = $Bing.IsPresent
        mapType     = $Type 
    }
}
function New-UDMapVectorLayer {
    <#
    .SYNOPSIS
    Creates a map vector layer.
     
    .DESCRIPTION
    Creates a map vector layer.
     
    .PARAMETER Id
    The id of the map vector layer. This defaults to a new GUID.
     
    .PARAMETER Color
    The color of the vector layer.
     
    .PARAMETER FillColor
    The fill color of the vector layer.
     
    .PARAMETER FillOpacity
    The fill opacity of the vector layer.
     
    .PARAMETER Weight
    The weight of the vector layer.
     
    .PARAMETER Opacity
    The opacity of the vector layer.
     
    .PARAMETER Circle
    Creates a circle.
     
    .PARAMETER Latitude
    The latitude of the circle.
     
    .PARAMETER Longitude
    The longitude of the circle.
     
    .PARAMETER Radius
    The radius of the circle.
     
    .PARAMETER Polyline
    Creates a polyline.
     
    .PARAMETER Polygon
    Creates a polygon.
     
    .PARAMETER Positions
    The positions of the polyline or polygon.
     
    .PARAMETER Rectangle
    Creates a rectangle.
     
    .PARAMETER LatitudeTopLeft
    The latitude of the top left corner of the rectangle.
     
    .PARAMETER LongitudeTopLeft
    The longitude of the top left corner of the rectangle.
     
    .PARAMETER LatitudeBottomRight
    The latitude of the bottom right corner of the rectangle.
     
    .PARAMETER LongitudeBottomRight
    The longitude of the bottom right corner of the rectangle.
     
    .PARAMETER Popup
    The popup of the vector layer. Use New-UDMapPopup to create a popup.
     
    .PARAMETER GeoJSON
    The GeoJSON of the vector layer. This is an alternative to specifying the longitude and latitude.
    #>

    param(
        [Parameter()]
        [string]$Id = ([Guid]::NewGuid()),
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$Color = 'black',
        [Parameter()]
        [UniversalDashboard.Models.DashboardColor]$FillColor = 'black',
        [Parameter()]
        [double]$FillOpacity = 0.5,
        [Parameter()]
        [int]$Weight = 3,
        [Parameter()]
        [double]$Opacity = 1.0,
        [Parameter(ParameterSetName = 'Circle', Mandatory)]
        [Switch]$Circle,
        [Parameter(ParameterSetName = 'Circle', Mandatory)]
        [double]$Latitude,
        [Parameter(ParameterSetName = 'Circle', Mandatory)]
        [double]$Longitude,
        [Parameter(ParameterSetName = 'Circle', Mandatory)]
        [int]$Radius,
        [Parameter(ParameterSetName = 'Polyline', Mandatory)]
        [Switch]$Polyline,
        [Parameter(ParameterSetName = 'Polygon', Mandatory)]
        [Switch]$Polygon,
        [Parameter(ParameterSetName = 'Polyline', Mandatory)]        
        [Parameter(ParameterSetName = 'Polygon', Mandatory)]        
        [object]$Positions,
        [Parameter(ParameterSetName = 'Rectangle', Mandatory)]
        [Switch]$Rectangle,
        [Parameter(ParameterSetName = 'Rectangle', Mandatory)]
        [double]$LatitudeTopLeft,
        [Parameter(ParameterSetName = 'Rectangle', Mandatory)]
        [double]$LongitudeTopLeft,
        [Parameter(ParameterSetName = 'Rectangle', Mandatory)]
        [double]$LatitudeBottomRight,
        [Parameter(ParameterSetName = 'Rectangle', Mandatory)]
        [double]$LongitudeBottomRight,
        [Parameter(ParameterSetName = 'Circle')]
        [object]$Popup,
        [Parameter(ParameterSetName = 'GeoJSON', Mandatory)]
        [string]$GeoJSON
    )

    if ($PSCmdlet.ParameterSetName -eq 'GeoJSON') {
        $Json = $GeoJSON | ConvertFrom-Json
        if ($Json.Geometry.Type -eq 'multilinestring') {
            $Coordinates = @()
            foreach ($array in $json.geometry.coordinates) {
                foreach ($arrayInArray in $array) {
                    $temp = $arrayInArray[0]
                    $arrayInArray[0] = $arrayInArray[1]
                    $arrayInArray[1] = $temp
                }
                $Coordinates += , $array
            }

            $Positions = $Coordinates
            $Polyline = [Switch]::Present
        }

        if ($Json.Geometry.Type -eq 'linestring') {
            $Coordinates = @()
            $json.geometry.coordinates | ForEach-Object {
                $temp = $_[0]
                $_[0] = $_[1]
                $_[1] = $temp
                $Coordinates += , $_
            }
            $Positions = $Coordinates
            $Polyline = [Switch]::Present
        }

        if ($Json.Geometry.Type -eq 'polygon') {
            $Coordinates = @()
            $json.geometry.coordinates[0] | ForEach-Object {
                $temp = $_[0]
                $_[0] = $_[1]
                $_[1] = $temp
                $Coordinates += , $_
            }
            $Positions = $Coordinates
            $Polygon = [Switch]::Present
        }
    }

    @{
        type                 = "map-vector-layer"
        isPlugin             = $true
        assetId              = $AssetId
        id                   = $Id

        color                = $Color.HtmlColor
        fillColor            = $FillColor.HtmlColor
        fillOpacity          = $FillOpacity
        weight               = $Weight
        opacity              = $Opacity
        circle               = $Circle.IsPresent
        latitude             = $Latitude
        longitude            = $Longitude
        radius               = $Radius
        polyline             = $Polyline.IsPresent
        polygon              = $Polygon.IsPresent
        positions            = $Positions
        rectangle            = $Rectangle.IsPresent
        latitudeTopLeft      = $LatitudeTopLeft
        longitudeTopLeft     = $LongitudeTopLeft
        latitudeBottomRight  = $LatitudeBottomRight
        longitudeBottomRight = $LongitudeBottomRight
        popup                = $Popup
    }
}