# B4XDaisyUIKit - Ask Noosphere

- License: personal-use
- Language: en

- Documents: 31

---

## What is this project about?

What is this project about?

---

## B4XDaisyCountdownItem.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9

#Region Events
#Event: Click (Tag As Object)
#End Region

#DesignerProperty: Key: Value, DisplayName: Value, FieldType: Int, DefaultValue: 0, Description: Current numeric value (0-999).
#DesignerProperty: Key: Digits, DisplayName: Digits, FieldType: Int, DefaultValue: 1, Description: Minimum number of digits to display.
#DesignerProperty: Key: Label, DisplayName: Label, FieldType: String, DefaultValue: , Description: Text label for this segment (e.g. days, hours).
#DesignerProperty: Key: LabelPosition, DisplayName: Label Position, FieldType: String, DefaultValue: RIGHT, List: RIGHT|BOTTOM, Description: Where to place the label relative to the number.
#DesignerProperty: Key: Separator, DisplayName: Separator, FieldType: String, DefaultValue: , Description: Optional separator string (e.g. ":") shown after the number.
#DesignerProperty: Key: TextSize, DisplayName: Font Size, FieldType: String, DefaultValue: text-base, List: text-xs|text-sm|text-base|text-lg|text-xl|text-2xl|text-3xl|text-4xl|text-5xl|text-6xl|text-7xl|text-8xl|text-9xl, Description: Typography size token.
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: DaisyUI semantic color variant.

#Region Variables
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object
    
    ' Internal views
    Private lblValue As Label
    Private lblLabel As Label
    Private lblSeparator As Label
    
    ' Local properties
    Private mnValue As Int = 0
    Private mnDigits As Int = 1
    Private msLabel As String = ""
    Private msLabelPosition As String = "RIGHT"
    Private msSeparator As String = ""
    Private msTextSize As String = "text-base"
    Private msVariant As String = "none"
    ' new style props
    Private msRounded As String = "none"
    Private msShadow As String = "none"
    Private mnLineHeight As Float = B4XDaisyVariants.LINE_HEIGHT_DEFAULT
    
    ' debug border flag (matches countdown container) - off by default
    Private DEBUG_BORDER As Boolean = False
    
    Private mIsResizing As Boolean = False
End Sub
#End Region

#Region Initialization
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent
    
    ' Initialize labels
    lblValue.Initialize("")
    lblLabel.Initialize("")
    lblSeparator.Initialize("")
    
    mBase.AddView(lblValue, 0, 0, 0, 0)
    mBase.AddView(lblLabel, 0, 0, 0, 0)
    mBase.AddView(lblSeparator, 0, 0, 0, 0)
    
    ' Load properties
    msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
    msShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow))
    mnValue = B4XDaisyVariants.GetPropInt(Props, "Value", mnValue)
    mnDigits = B4XDaisyVariants.GetPropInt(Props, "Digits", mnDigits)
    msLabel = B4XDaisyVariants.GetPropString(Props, "Label", msLabel)
    msLabelPosition = B4XDaisyVariants.GetPropString(Props, "LabelPosition", msLabelPosition)
    msSeparator = B4XDaisyVariants.GetPropString(Props, "Separator", msSeparator)
    msTextSize = B4XDaisyVariants.GetPropString(Props, "TextSize", msTextSize)
    msVariant = B4XDaisyVariants.GetPropString(Props, "Variant", msVariant)
    ' LineHeight is now managed globally but can be overridden in code
    ' line-height is resolved dynamically via global variants (see Refresh)
    mnLineHeight = B4XDaisyVariants.LINE_HEIGHT_DEFAULT
    
    ' Apply safety standard #10: Disable clipping
    ' (handled by global style or parent container now)
    Refresh
End Sub
#End Region

#Region Public API
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    ' re-apply background and decorations if theme changed
    mBase.Color = B4XDaisyVariants.ResolveBackgroundColorVariant(msVariant, xui.Color_Transparent)
    Dim radius As Int = B4XDaisyVariants.ResolveRoundedDip(msRounded, 0)
    B4XDaisyVariants.ApplyElevation(mBase, msShadow)
    mBase.SetColorAndBorder(mBase.Color, 0, xui.Color_Transparent, radius)
    Refresh
End Sub

Public Sub Refresh
    ' Format value string based on Digits
    Dim sVal As String = mnValue
    If mnDigits = 2 Then
        sVal = NumberFormat(mnValue, 2, 0)
    Else If mnDigits = 3 Then
        sVal = NumberFormat(mnValue, 3, 0)
    End If
    ' Apply styles
    ' resolve background first (allows variant to color the panel)
    mBase.Color = B4XDaisyVariants.ResolveBackgroundColorVariant(msVariant, xui.Color_Transparent)
    Dim textColor As Int = B4XDaisyVariants.ResolveTextColorVariant(msVariant, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_Black))
    ' radius & elevation
    Dim radius As Int = B4XDaisyVariants.ResolveRoundedDip(msRounded, 0)
    B4XDaisyVariants.ApplyElevation(mBase, msShadow)
    mBase.SetColorAndBorder(mBase.Color, 0, xui.Color_Transparent, radius)
    ' Set text size (tailwind tokens) using shared variant utility
    Dim fontSize As Float = B4XDaisyVariants.ResolveTextSizeDip(msTextSize)
    If fontSize <= 0 Then fontSize = 16dip
    ' Resolve line-height from global variants (matching web behavior)
    Dim tm As Map = B4XDaisyVariants.TailwindTextMetrics(msTextSize, fontSize, fontSize * 1.5)
    mnLineHeight = tm.GetDefault("line_height_dip", B4XDaisyVariants.LINE_HEIGHT_DEFAULT)
        
    lblValue.Text = sVal
    lblValue.TextColor = textColor
    lblValue.TextSize = fontSize
    lblValue.Typeface = Typeface.MONOSPACE
    B4XDaisyVariants.SetLineSpacing(lblValue, mnLineHeight, 0)
    
    if mslabel.trim <> "" then
        lblLabel.Text = msLabel
        lbllabel.visible = true
        lblLabel.TextColor = textColor
        lblLabel.TextSize = fontSize * 0.7 
        B4XDaisyVariants.SetLineSpacing(lblLabel, mnLineHeight, 0)
    else
        lbllabel.visible = false    
    End If

    if msSeparator.trim <> "" then
        lblSeparator.Text = msSeparator
        lblSeparator.Visible = True
        lblSeparator.TextColor = textColor
        lblSeparator.TextSize = fontSize
        B4XDaisyVariants.SetLineSpacing(lblSeparator, mnLineHeight, 0)
    else
        lblSeparator.Visible = False
    end if        
    
    If msLabelPosition = "RIGHT" Then
        Dim measureVal As Int = MeasureTextWidth(lblValue.Text, lblValue.TextSize, lblValue.Typeface)
        Dim measureSep As Int = 0
        Dim measureLab As Int = 0
        If lblSeparator.Visible Then measureSep = MeasureTextWidth(msSeparator, lblSeparator.TextSize, lblSeparator.Typeface)
        If lblLabel.Visible Then measureLab = MeasureTextWidth(msLabel, lblLabel.TextSize, lblLabel.Typeface)
        ' Add generous buffers to prevent cropping
        Dim extraGap As Int = 0
        If measureSep > 0 Then extraGap = 4dip
        mBase.Width = measureVal + 8dip + measureSep + extraGap + measureLab + 8dip
    End If
    
    
    Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
    Dim p As Panel
    p.Initialize("mBase")
    Dim b As B4XView = p
    b.SetLayoutAnimated(0, 0, 0, Width, Height)
    Dim props As Map = CreateMap("Value": mnValue, "Digits": mnDigits, "Label": msLabel, "LabelPosition": msLabelPosition, "Separator": msSeparator, "TextSize": msTextSize, "Variant": msVariant, "Rounded": msRounded, "Shadow": msShadow)
    DesignerCreateView(b, Null, props)
    Return mBase
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    If mBase.IsInitialized = False Then
        CreateView(Width, Height)
    End If
    Parent.AddView(mBase, Left, Top, Width, Height)
    Return mBase
End Sub

Public Sub getView As B4XView
    Return mBase
End Sub

Public Sub getIsInitialized As Boolean
    If mBase.IsInitialized = False Then Return False
    Return lblValue.IsInitialized
End Sub

#Region Getters/Setters
Public Sub getValue As Int
    Return mnValue
End Sub

Public Sub setValue(Value As Int)
    mnValue = Max(0, Min(999, Value))
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getDigits As Int
    Return mnDigits
End Sub

Public Sub setDigits(Value As Int)
    mnDigits = Max(1, Min(3, Value))
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getLabel As String
    Return msLabel
End Sub

Public Sub setLabel(Value As String)
    msLabel = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getLabelPosition As String
    Return msLabelPosition
End Sub

Public Sub setLabelPosition(Value As String)
    msLabelPosition = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getSeparator As String
    Return msSeparator
End Sub

Public Sub setSeparator(Value As String)
    msSeparator = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTextSize As String
    Return msTextSize
End Sub

Public Sub setTextSize(Value As String)
    msTextSize = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVariant As String
    Return msVariant
End Sub

Public Sub setVariant(Value As String)
    msVariant = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getRounded As String
    Return msRounded
End Sub

Public Sub setRounded(Value As String)
    msRounded = B4XDaisyVariants.NormalizeRounded(Value)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getShadow As String
    Return msShadow
End Sub

Public Sub setShadow(Value As String)
    msShadow = B4XDaisyVariants.NormalizeShadow(Value)
    If mBase.IsInitialized Then Refresh
End Sub



Public Sub setTag(Value As Object)
    mTag = Value
End Sub

Public Sub getTag As Object
    Return mTag
End Sub
#End Region
#End Region

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mBase.IsInitialized = False Or mIsResizing Then Return
    mIsResizing = True
    
    ' Layout depends on LabelPosition
    Dim measureVal As Int = MeasureTextWidth(lblValue.Text, lblValue.TextSize, lblValue.Typeface)
    Dim measureSep As Int = IIf(msSeparator = "", 0, MeasureTextWidth(msSeparator, lblSeparator.TextSize, lblSeparator.Typeface))
    Dim measureLab As Int = IIf(msLabel = "", 0, MeasureTextWidth(msLabel, lblLabel.TextSize, lblLabel.Typeface))
    
    If msLabelPosition = "RIGHT" Then
        ' Horizontal layout: [Value][Separator][Label]
        ' Add small gaps and horizontal padding (4dip)
        lblValue.SetLayoutAnimated(0, 4dip, 0, measureVal + 2dip, Height)
        If lblSeparator.Visible Then
            lblSeparator.SetLayoutAnimated(0, 4dip + measureVal + 2dip, 0, measureSep + 2dip, Height)
        Else
            lblSeparator.SetLayoutAnimated(0, 0, 0, 0, Height)
        End If
        If lblLabel.Visible Then
            lblLabel.SetLayoutAnimated(0, 4dip + measureVal + 2dip + measureSep + 2dip, 0, measureLab + 2dip, Height)
        Else
            lblLabel.SetLayoutAnimated(0, 0, 0, 0, Height)
        End If
        
        lblValue.Gravity = Gravity.CENTER_VERTICAL
        lblSeparator.Gravity = Gravity.CENTER_VERTICAL
        lblLabel.Gravity = Gravity.CENTER_VERTICAL
    Else
        ' Vertical layout: [Value + Separator] over [Label]
        lblValue.SetLayoutAnimated(0, 0, 0, Width, Height * 0.75)
        lblSeparator.SetLayoutAnimated(0, Width - measureSep, 0, measureSep, Height * 0.75)
        lblLabel.SetLayoutAnimated(0, 0, Height * 0.75, Width, Height * 0.25)
        
        ' use Gravity to avoid unsupported SetTextAlignment bug
        lblValue.Gravity = Gravity.CENTER
        lblLabel.Gravity = Gravity.CENTER_HORIZONTAL
    End If
    
    mIsResizing = False
End Sub

Private Sub MeasureTextWidth(Text As String, TextSize As Float, tf As Typeface) As Int
    ' delegate to shared safe helper (adds buffer to avoid clipping)
    Return B4XDaisyVariants.MeasureTextWidthSafe(Text, TextSize, tf, 4dip)
End Sub

' helper: convert tailwind text-size string to dip value (delegates to global variant helper)
Private Sub TextSizeTokenToDip(Token As String, DefaultDip As Float) As Float
    Dim res As Float = B4XDaisyVariants.ResolveTextSizeDip(Token)
    If res <= 0 Then res = DefaultDip
    Return res
End Sub

Private Sub mBase_Click
    RaiseClick
End Sub

Private Sub RaiseClick
    Dim payload As Object = mTag
    If payload = Null Then payload = mBase
    If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
        CallSub2(mCallBack, mEventName & "_Click", payload)
    Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
        CallSub(mCallBack, mEventName & "_Click")
    End If
End Sub
#End Region

#Region Cleanup
Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyCountdown.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9

#Region Events
#Event: Click (Tag As Object)
#End Region

#DesignerProperty: Key: Orientation, DisplayName: Orientation, FieldType: String, DefaultValue: horizontal, List: horizontal|vertical, Description: Layout orientation for the segments.
#DesignerProperty: Key: Gap, DisplayName: Gap, FieldType: String, DefaultValue: gap-2, List: gap-0|gap-1|gap-2|gap-3|gap-4|gap-5|gap-6|gap-8, Description: Spacing between segments.
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: String, DefaultValue: transparent, List: transparent|base-100|base-200|base-300|neutral|primary|secondary|accent, Description: Background color for the container.
#DesignerProperty: Key: Border, DisplayName: Border, FieldType: Boolean, DefaultValue: False, Description: Show a border around the container (standard base-300 border).
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: none, List: none|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius token applied to the container and child items.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|shadow|shadow-sm|shadow-md|shadow-lg|shadow-xl|shadow-2xl|shadow-inner, Description: Shadow effect applied to child items.
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue: p-0, List: p-0|p-1|p-2|p-3|p-4|p-5|p-6|p-8, Description: Inner padding for the container.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.

#Region Variables
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object
    Private mVisible As Boolean = True

    
    ' Local properties
    Private msOrientation As String = "horizontal"
    Private msGap As String = "gap-2"
    Private msBackgroundColor As String = "transparent"
    Private mbBorder As Boolean = False
    ' container rounding is now a string token instead of a boolean
    Private msRounded As String = "none"    ' corner radius mode
    Private msPadding As String = "p-0"
    Private msShadow As String = "none"     ' shadow token applied to child items

    ' debug helper: if true we draw a red outline around the whole countdown
    ' (disabled by default in production builds)
    Private DEBUG_BORDER As Boolean = False
    
    Private mItems As List
    Private mIsResizing As Boolean = False
End Sub
#End Region

#Region Initialization
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
    mItems.Initialize
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me
    
    ' Load properties
    If mItems.IsInitialized = False Then mItems.Initialize
    msOrientation = B4XDaisyVariants.GetPropString(Props, "Orientation", msOrientation)
    msGap = B4XDaisyVariants.GetPropString(Props, "Gap", msGap)
    msBackgroundColor = B4XDaisyVariants.GetPropString(Props, "BackgroundColor", msBackgroundColor)
    mbBorder = B4XDaisyVariants.GetPropBool(Props, "Border", mbBorder)
    msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
    msPadding = B4XDaisyVariants.GetPropString(Props, "Padding", msPadding)
    msShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow))
    
    mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
    mBase.Visible = mVisible
    Refresh
End Sub
#End Region

#Region Public API
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    For Each itm As B4XDaisyCountdownItem In mItems
        itm.UpdateTheme
    Next
    Refresh
End Sub

Public Sub Refresh
    If mBase.IsInitialized = False Then Return
    
    ' Apply styling
    Dim bgColor As Int = B4XDaisyVariants.ResolveBackgroundColorVariant(msBackgroundColor, xui.Color_Transparent)
    Dim borderColor As Int = xui.Color_Transparent
    Dim borderWidth As Int = 0
    If mbBorder Then
        borderColor = B4XDaisyVariants.GetTokenColor("--color-base-300", xui.Color_LightGray)
        borderWidth = B4XDaisyVariants.GetBorderDip(1dip)
    End If
    
    
    ' resolve the container rounding token
    Dim radius As Int = B4XDaisyVariants.ResolveRoundedDip(msRounded, 0)
    
    mBase.SetColorAndBorder(bgColor, borderWidth, borderColor, radius)
    B4XDaisyVariants.SetOverflowHidden(mBase)
    
    ' Ensure all items are initialized and added to mBase
    For Each itm As B4XDaisyCountdownItem In mItems
        ' ensure the item itself is ready
        If itm.getIsInitialized = False Then
            itm.CreateView(40dip, 40dip)
        End If
        
        ' propagate rounded/shadow to items (AFTER potential CreateView call)
        itm.setRounded(msRounded)
        itm.setShadow(msShadow)
        
        ' operate on its view only when it's truly initialized
        Dim v As B4XView = itm.getView
        If v.IsInitialized Then
            ' Detach if already has a different parent
            Try
                v.RemoveViewFromParent
            Catch
                Log("B4XDaisyCountdown.Refresh: " & LastException.Message)
                ' Ignore if it has no parent yet or other issues
            End Try
            ' Add to our container using its measured size
            mBase.AddView(v, 0, 0, v.Width, v.Height)
        End If
    Next
    
    ' Layout items
    LayoutItems
End Sub

Public Sub AddItem(Item As B4XDaisyCountdownItem)
    If Item = Null Then Return
    If mItems.IndexOf(Item) = -1 Then mItems.Add(Item)
    ' propagate new item style
    Item.setRounded(msRounded)
    Item.setShadow(msShadow)
    ' Detach from previous parent if any to avoid "child already has a parent" crash
    If Item.getIsInitialized And Item.mBase.IsInitialized Then
        Try
            Item.mBase.RemoveViewFromParent
        Catch
            Log("B4XDaisyCountdown.AddItem: " & LastException.Message)
            ' Ignore if it has no parent yet
        End Try
    End If
    Refresh
End Sub

Public Sub getView As B4XView
    Return mBase
End Sub

Public Sub getIsInitialized As Boolean
    Return mBase.IsInitialized
End Sub

Public Sub RemoveItem(Item As B4XDaisyCountdownItem)
    Dim idx As Int = mItems.IndexOf(Item)
    If idx > -1 Then
        mItems.RemoveAt(idx)
        Item.View.RemoveViewFromParent
        Refresh
    End If
End Sub

Public Sub Clear
    For Each itm As B4XDaisyCountdownItem In mItems
        itm.View.RemoveViewFromParent
    Next
    mItems.Clear
    Refresh
End Sub
Public Sub CreateView(Width As Int, Height As Int) As B4XView
    mBase = xui.CreatePanel("mBase")
    mBase.Color = xui.Color_Transparent
    mBase.SetLayoutAnimated(0, 0, 0, Width, Height)
    Dim props As Map = CreateMap("Orientation": msOrientation, "Gap": msGap, "BackgroundColor": msBackgroundColor, "Border": mbBorder, "Rounded": msRounded, "Shadow": msShadow)
    DesignerCreateView(mBase, Null, props)
    Return mBase
End Sub

'** helper for programmatic insertion **
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    If mBase.IsInitialized = False Then
        ' create the base panel on demand
        mBase = xui.CreatePanel("mBase")
        mBase.Color = xui.Color_Transparent
        Parent.AddView(mBase, Left, Top, Width, Height)
        
        ' set default size so Refresh/Layout work
        Dim props As Map = CreateMap("Orientation": msOrientation, "Gap": msGap, "BackgroundColor": msBackgroundColor, "Border": mbBorder, "Rounded": msRounded, "Shadow": msShadow)
        DesignerCreateView(mBase, Null, props)
    Else
        mBase.RemoveViewFromParent
        Parent.AddView(mBase, Left, Top, Width, Height)
    End If
    Return mBase
End Sub

#Region Getters/Setters
Public Sub getOrientation As String
    Return msOrientation
End Sub

Public Sub setShadow(Value As String)
    msShadow = B4XDaisyVariants.NormalizeShadow(Value)
    If mBase.IsInitialized Then
        For Each itm As B4XDaisyCountdownItem In mItems
            itm.setShadow(msShadow)
        Next
        Refresh
    End If
End Sub

Public Sub getShadow As String
    Return msShadow
End Sub

Public Sub setRounded(Value As String)
    msRounded = B4XDaisyVariants.NormalizeRounded(Value)
    If mBase.IsInitialized Then
        For Each itm As B4XDaisyCountdownItem In mItems
            itm.setRounded(msRounded)
        Next
        Refresh
    End If
End Sub

Public Sub getRounded As String
    Return msRounded
End Sub

Public Sub setOrientation(Value As String)
    msOrientation = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getGap As String
    Return msGap
End Sub

Public Sub setGap(Value As String)
    msGap = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBackgroundColor As String
    Return msBackgroundColor
End Sub

Public Sub setBackgroundColor(Value As String)
    msBackgroundColor = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBorder As Boolean
    Return mbBorder
End Sub

Public Sub setBorder(Value As Boolean)
    mbBorder = Value
    If mBase.IsInitialized Then Refresh
End Sub

' already updated above

Public Sub getPadding As String
    Return msPadding
End Sub

Public Sub setPadding(Value As String)
    msPadding = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub setTag(Value As Object)
    mTag = Value
End Sub

Public Sub getTag As Object
    Return mTag
End Sub
#End Region
#End Region

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mIsResizing Then Return
    mIsResizing = True
    LayoutItems
    mIsResizing = False
End Sub
#End Region

#Region Internal Layout
Private Sub LayoutItems
    If mBase.IsInitialized = False Or mItems.Size = 0 Then Return
    
    Dim padding As Int = B4XDaisyVariants.TailwindSpacingToDip(msPadding, 0)
    Dim gap As Int = B4XDaisyVariants.TailwindSpacingToDip(msGap, 8dip)
    
    Dim currentPos As Int = padding
    For i = 0 To mItems.Size - 1
        Dim itm As B4XDaisyCountdownItem = mItems.Get(i)
        itm.Refresh
        
        If msOrientation = "horizontal" Then
            itm.mBase.Left = currentPos
            itm.mBase.Top = padding
            itm.mBase.Height = mBase.Height - padding * 2
            
            Dim itemW As Int = itm.mBase.Width
            If itemW < 10dip Then itemW = 40dip 
            
            currentPos = currentPos + itemW + gap
        Else
            itm.mBase.Left = padding
            itm.mBase.Top = currentPos
            itm.mBase.Width = mBase.Width - padding * 2
            
            Dim itemH As Int = itm.mBase.Height
            if itemH < 10dip Then itemH = 40dip
            
            currentPos = currentPos + itemH + gap
        End If
    Next
End Sub
#End Region

#Region Cleanup
Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyColorWheel.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.5
@EndOfDesignText@
#Event: ColorChanged (Color As Int)

#DesignerProperty: Key: InitialColor, DisplayName: Initial Color, FieldType: Color, DefaultValue: 0xFFEF4444, Description: The starting color of the wheel.
#DesignerProperty: Key: WheelThickness, DisplayName: Wheel Thickness, FieldType: Int, DefaultValue: 24, Description: Thickness of the Hue ring in dip.
#DesignerProperty: Key: HandleSize, DisplayName: Handle Size, FieldType: Int, DefaultValue: 24, Description: The diameter of the draggable knobs in dip.
#DesignerProperty: Key: Shadow, DisplayName: Handle Shadow, FieldType: String, DefaultValue: md, List: none|xs|sm|md|lg|xl|2xl, Description: Shadow elevation for handles.
#DesignerProperty: Key: WheelReflectsSaturation, DisplayName: Reflect Saturation, FieldType: Boolean, DefaultValue: False, Description: If true, the hue ring colors reflect the selected saturation.
#DesignerProperty: Key: CenterOnParent, DisplayName: Center On Parent, FieldType: Boolean, DefaultValue: False, Description: If true, centers the color wheel inside its parent layout.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.

#IgnoreWarnings:12,9

#Region Variables
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object

	' UI Layers
	Private pnlHue As B4XView
	Private cvsHue As B4XCanvas
	Private HueCanvasReady As Boolean
	Private pnlSV As B4XView
	Private cvsSV As B4XCanvas
	Private SVCanvasReady As Boolean
    
	Private pnlHueHandle As B4XView
	Private pnlSVHandle As B4XView
	
	Private cvsHueHandle As B4XCanvas
	Private HueHandleCanvasReady As Boolean

	' Properties
	Private mColor As Int = 0xFFEF4444
	Private mInitialColor As Int = 0xFFEF4444
	Private mWheelThickness As Int = 24dip
	Private mHandleSize As Int = 24dip
	Private mShadow As String = "md"
	Private mVisible As Boolean = True
	Private mWheelReflectsSaturation As Boolean = False
	Private mCenterOnParent As Boolean = False

	' HSV State
	Private mHueVal As Float = 0     ' 0 to 360
	Private mSatVal As Float = 1     ' 0 to 1
	Private mValVal As Float = 1     ' 0 to 1
End Sub
#End Region

#Region Initialization
''' <summary>
''' Initializes the component.
''' </summary>
Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

''' <summary>
''' Programmatic creation.
''' </summary>
Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("mBase")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub

''' <summary>
''' Designer entry point.
''' </summary>
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	' 1. Hue Disk Host
	Dim p1 As Panel
	p1.Initialize("pnlHue")
	pnlHue = p1
	pnlHue.Color = xui.Color_Transparent
	mBase.AddView(pnlHue, 0, 0, mBase.Width, mBase.Height)
    
	' 2. SV Inner Box Host
	Dim p2 As Panel
	p2.Initialize("pnlSV")
	pnlSV = p2
	mBase.AddView(pnlSV, 0, 0, 1dip, 1dip)

	' Parse properties
	mColor = B4XDaisyVariants.GetPropColor(Props, "InitialColor", mColor)
	mWheelThickness = B4XDaisyVariants.GetPropInt(Props, "WheelThickness", 24) * 1dip
	mHandleSize = B4XDaisyVariants.GetPropInt(Props, "HandleSize", 24) * 1dip
	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mWheelReflectsSaturation = B4XDaisyVariants.GetPropBool(Props, "WheelReflectsSaturation", False)
	mCenterOnParent = B4XDaisyVariants.GetPropBool(Props, "CenterOnParent", False)
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", True)
	mBase.Visible = mVisible

	' 3. Draggable Handles
	Dim h1 As Panel, h2 As Panel
	
	h1.Initialize("")
	pnlHueHandle = h1
	pnlHueHandle.Enabled = False
	pnlHueHandle.Color = xui.Color_Transparent
	
	h2.Initialize("")
	pnlSVHandle = h2
	pnlSVHandle.Enabled = False
    
	mBase.AddView(pnlHueHandle, 0, 0, mHandleSize, mHandleSize)
	pnlSV.AddView(pnlSVHandle, 0, 0, mHandleSize, mHandleSize)

	' Convert initial color to HSV
	RGBToHSV(mColor)
	mInitialColor = mColor
    
	Refresh
End Sub
#End Region

#Region Core APIs
''' <summary>
''' Gets the current Color as an Int.
''' </summary>
Public Sub getColor As Int
	Return mColor
End Sub

''' <summary>
''' Sets the color programmatically and updates the UI.
''' </summary>
Public Sub setColor(NewColor As Int)
	mColor = NewColor
	RGBToHSV(mColor)
	mInitialColor = NewColor
	mInitialHueVal = mHueVal
	If mWheelReflectsSaturation Then
		DrawHueDisk
	End If
	UpdateVisuals
	DrawSVSquare
End Sub

''' <summary>
''' Gets the current HSV values as a Float Array [Hue, Saturation, Value] (Hue 0-360, Sat 0-100, Val 0-100).
''' </summary>
Public Sub getHSV As Float()
	Return Array As Float(mHueVal, mSatVal * 100, mValVal * 100)
End Sub

''' <summary>
''' Sets the HSV values as a Float Array [Hue, Saturation, Value] (Hue 0-360, Sat 0-100, Val 0-100).
''' </summary>
Public Sub setHSV(hsv() As Float)
	If hsv.Length >= 3 Then
		mHueVal = Max(0, Min(360, hsv(0)))
		mSatVal = Max(0, Min(100, hsv(1))) / 100
		mValVal = Max(0, Min(100, hsv(2))) / 100
		mColor = HSVToRGB(mHueVal, mSatVal, mValVal)
		mInitialColor = mColor
		mInitialHueVal = mHueVal
		If mWheelReflectsSaturation Then
			DrawHueDisk
		End If
		UpdateVisuals
		DrawSVSquare
	End If
End Sub

''' <summary>
''' Sets the color via independent HSV values programmatically (Hue 0-360, Saturation 0-1, Value 0-1).
''' </summary>
Public Sub setHSV3(Hue As Float, Saturation As Float, Value As Float)
	mHueVal = Max(0, Min(360, Hue))
	mSatVal = Max(0, Min(1, Saturation))
	mValVal = Max(0, Min(1, Value))
	mColor = HSVToRGB(mHueVal, mSatVal, mValVal)
	mInitialColor = mColor
	mInitialHueVal = mHueVal
	If mWheelReflectsSaturation Then
		DrawHueDisk
	End If
	UpdateVisuals
	DrawSVSquare
End Sub

''' <summary>
''' Gets the current HSL values (Hue 0-360, Sat 0-100, Lightness 0-100).
''' </summary>
Public Sub getHSL As Float()
	Return RGBToHSL(mColor)
End Sub

''' <summary>
''' Sets the color via HSL values (Hue 0-360, Sat 0-100, Lightness 0-100).
''' </summary>
Public Sub setHSL(hsl() As Float)
	If hsl.Length >= 3 Then
		setColor(HSLToRGB(hsl(0), hsl(1), hsl(2)))
	End If
End Sub

''' <summary>
''' Gets the current RGB values as an Int Array [Red, Green, Blue] (each 0 to 255).
''' </summary>
Public Sub getRGB As Int()
	Dim r As Int = Bit.And(Bit.ShiftRight(mColor, 16), 0xFF)
	Dim g As Int = Bit.And(Bit.ShiftRight(mColor, 8), 0xFF)
	Dim b As Int = Bit.And(mColor, 0xFF)
	Return Array As Int(r, g, b)
End Sub

''' <summary>
''' Sets the color via RGB values [Red, Green, Blue] (each 0 to 255).
''' </summary>
Public Sub setRGB(rgb() As Int)
	If rgb.Length >= 3 Then
		setColor(xui.Color_RGB(rgb(0), rgb(1), rgb(2)))
	End If
End Sub

''' <summary>
''' Gets the current color as a lowercase Hex string starting with # (e.g. #ff0000).
''' </summary>
Public Sub getHex As String
	Return ColorToHex(mColor)
End Sub

''' <summary>
''' Sets the color programmatically via Hex string.
''' </summary>
Public Sub setHex(hexStr As String)
	setColor(HexToColor(hexStr))
End Sub

''' <summary>
''' Gets the outer wheel diameter (width/height of the control).
''' </summary>
Public Sub getWheelDiameter As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

''' <summary>
''' Sets the overall diameter of the color wheel.
''' </summary>
Public Sub setWheelDiameter(Diameter As Int)
	If mBase.IsInitialized Then
		mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, Diameter, Diameter)
		Base_Resize(Diameter, Diameter)
	End If
End Sub

''' <summary>
''' Gets whether the outer Hue ring reflects current Saturation selection.
''' </summary>
Public Sub getWheelReflectsSaturation As Boolean
	Return mWheelReflectsSaturation
End Sub

''' <summary>
''' Sets whether the outer Hue ring reflects current Saturation selection.
''' </summary>
Public Sub setWheelReflectsSaturation(Value As Boolean)
	mWheelReflectsSaturation = Value
	DrawHueDisk
	UpdateVisuals
End Sub

''' <summary>
''' Sets the starting initial reference color in memory.
''' </summary>
Public Sub setInitialColor(Value As Int)
	mInitialColor = Value
End Sub

''' <summary>
''' Gets the starting initial reference color.
''' </summary>
Public Sub getInitialColor As Int
	Return mInitialColor
End Sub

Public Sub getView As B4XView
	Return mBase
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub setWheelThickness(Thickness As Int)
	mWheelThickness = Thickness
	Refresh
End Sub

Public Sub setCenterOnParent(Value As Boolean)
	mCenterOnParent = Value
	Refresh
End Sub

Public Sub getCenterOnParent As Boolean
	Return mCenterOnParent
End Sub

Public Sub getWheelThickness As Int
	Return mWheelThickness
End Sub

Public Sub setHandleSize(Size As Int)
	mHandleSize = Size
	If pnlHueHandle.IsInitialized Then
		pnlHueHandle.SetLayoutAnimated(0, pnlHueHandle.Left, pnlHueHandle.Top, mHandleSize, mHandleSize)
	End If
	If pnlSVHandle.IsInitialized Then
		pnlSVHandle.SetLayoutAnimated(0, pnlSVHandle.Left, pnlSVHandle.Top, mHandleSize, mHandleSize)
	End If
	Refresh
End Sub

Public Sub getHandleSize As Int
	Return mHandleSize
End Sub

Public Sub setHandleDiameter(Size As Int)
	setHandleSize(Size)
End Sub

Public Sub getHandleDiameter As Int
	Return getHandleSize
End Sub

#Region Math & Color Conversions
Private Sub GetHueFromColor(c As Int) As Float
	Dim r As Float = Bit.And(Bit.ShiftRight(c, 16), 0xFF) / 255
	Dim g As Float = Bit.And(Bit.ShiftRight(c, 8), 0xFF) / 255
	Dim b As Float = Bit.And(c, 0xFF) / 255
    
	Dim cmax As Float = Max(r, Max(g, b))
	Dim cmin As Float = Min(r, Min(g, b))
	Dim diff As Float = cmax - cmin
    
	Dim h As Float = 0
	If diff = 0 Then
		h = 0
	Else If cmax = r Then
		h = 60 * (((g - b) / diff) Mod 6)
	Else If cmax = g Then
		h = 60 * (((b - r) / diff) + 2)
	Else
		h = 60 * (((r - g) / diff) + 4)
	End If
	If h < 0 Then h = h + 360
	Return h
End Sub

Private Sub ColorToHex(c As Int) As String
	Dim r As Int = Bit.And(Bit.ShiftRight(c, 16), 0xFF)
	Dim g As Int = Bit.And(Bit.ShiftRight(c, 8), 0xFF)
	Dim b As Int = Bit.And(c, 0xFF)
	Return "#" & IntToHex2(r) & IntToHex2(g) & IntToHex2(b)
End Sub

Private Sub IntToHex2(val As Int) As String
	Dim hexChars As String = "0123456789abcdef"
	Dim high As Int = Bit.ShiftRight(val, 4)
	Dim low As Int = Bit.And(val, 0x0F)
	Return hexChars.CharAt(high) & hexChars.CharAt(low)
End Sub

Private Sub HexToColor(hexStr As String) As Int
	Dim cleanHex As String = hexStr.Replace("#", "").Trim.ToLowerCase
	If cleanHex.Length = 3 Then
		Dim c1 As String = cleanHex.CharAt(0)
		Dim c2 As String = cleanHex.CharAt(1)
		Dim c3 As String = cleanHex.CharAt(2)
		cleanHex = c1 & c1 & c2 & c2 & c3 & c3
	End If
	If cleanHex.Length <> 6 Then Return 0xFFFF0000
    
	Dim r As Int = HexToInt(cleanHex.SubString2(0, 2))
	Dim g As Int = HexToInt(cleanHex.SubString2(2, 4))
	Dim b As Int = HexToInt(cleanHex.SubString2(4, 6))
	Return xui.Color_RGB(r, g, b)
End Sub

Private Sub HexToInt(hexVal As String) As Int
	Dim hexChars As String = "0123456789abcdef"
	Dim res As Int = 0
	For i = 0 To hexVal.Length - 1
		Dim c As String = hexVal.CharAt(i)
		Dim val As Int = hexChars.IndexOf(c)
		If val < 0 Then val = 0
		res = res * 16 + val
	Next
	Return res
End Sub

Private Sub RGBToHSL(c As Int) As Float()
	Dim r As Float = Bit.And(Bit.ShiftRight(c, 16), 0xFF) / 255
	Dim g As Float = Bit.And(Bit.ShiftRight(c, 8), 0xFF) / 255
	Dim b As Float = Bit.And(c, 0xFF) / 255
    
	Dim cmax As Float = Max(r, Max(g, b))
	Dim cmin As Float = Min(r, Min(g, b))
	Dim diff As Float = cmax - cmin
    
	Dim h As Float = 0
	Dim s As Float = 0
	Dim l As Float = (cmax + cmin) / 2
    
	If diff = 0 Then
		h = 0
		s = 0
	Else
		If l < 0.5 Then
			s = diff / (cmax + cmin)
		Else
			s = diff / (2 - cmax - cmin)
		End If
        
		If cmax = r Then
			h = 60 * (((g - b) / diff) Mod 6)
		Else If cmax = g Then
			h = 60 * (((b - r) / diff) + 2)
		Else
			h = 60 * (((r - g) / diff) + 4)
		End If
	End If
    
	If h < 0 Then h = h + 360
	Return Array As Float(h, s * 100, l * 100)
End Sub

Private Sub HSLToRGB(h As Float, s As Float, l As Float) As Int
	s = s / 100
	l = l / 100
    
	Dim c As Float = (1 - Abs(2 * l - 1)) * s
	Dim x As Float = c * (1 - Abs(((h / 60) Mod 2) - 1))
	Dim m As Float = l - c / 2
    
	Dim r1 As Float = 0
	Dim g1 As Float = 0
	Dim b1 As Float = 0
	Dim i As Int = Floor(h / 60) Mod 6
	Select i
		Case 0: r1 = c: g1 = x: b1 = 0
		Case 1: r1 = x: g1 = c: b1 = 0
		Case 2: r1 = 0: g1 = c: b1 = x
		Case 3: r1 = 0: g1 = x: b1 = c
		Case 4: r1 = x: g1 = 0: b1 = c
		Case 5: r1 = c: g1 = 0: b1 = x
	End Select
    
	Return xui.Color_RGB((r1 + m) * 255, (g1 + m) * 255, (b1 + m) * 255)
End Sub
#End Region
#End Region

#Region Rendering & Canvas Logic
Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
    
	If mCenterOnParent And mBase.Parent.IsInitialized Then
		Dim parentW As Float = mBase.Parent.Width
		Dim parentH As Float = mBase.Parent.Height
		Dim newLeft As Float = (parentW - w) / 2
		Dim newTop As Float = (parentH - h) / 2
		mBase.Left = newLeft
		mBase.Top = newTop
	End If
    
	' Layout Hue disk to fill the container
	pnlHue.SetLayoutAnimated(0, 0, 0, w, h)
	If HueCanvasReady Then cvsHue.Release
	cvsHue.Initialize(pnlHue)
	HueCanvasReady = True
    
	' Layout SV Box in the center - optimized size for visual balance
	Dim ringCenterRadius As Float = (Min(w, h) / 2) - (mWheelThickness / 2) - 2dip
	Dim innerRadius As Float = ringCenterRadius - (mWheelThickness / 2)
	Dim boxSide As Int = Max(1dip, innerRadius * 1.414) ' Square inside the circle
	pnlSV.SetLayoutAnimated(0, (w - boxSide) / 2, (h - boxSide) / 2, boxSide, boxSide)
	If SVCanvasReady Then cvsSV.Release
	cvsSV.Initialize(pnlSV)
	SVCanvasReady = True
	
	' Layout handles and initialize handle canvases
	pnlHueHandle.SetLayoutAnimated(0, pnlHueHandle.Left, pnlHueHandle.Top, mHandleSize, mHandleSize)
	pnlSVHandle.SetLayoutAnimated(0, pnlSVHandle.Left, pnlSVHandle.Top, mHandleSize, mHandleSize)
	
	If HueHandleCanvasReady Then cvsHueHandle.Release
	cvsHueHandle.Initialize(pnlHueHandle)
	HueHandleCanvasReady = True
    
	DrawHueDisk
	DrawSVSquare
	UpdateVisuals
End Sub

Public Sub Refresh
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub UpdateVisuals
	If mBase.IsInitialized = False Then Return
    
	' Draw active Hue handle (white outline circle with a small dot of current color)
	If HueHandleCanvasReady Then
		cvsHueHandle.ClearRect(cvsHueHandle.TargetRect)
		Dim cx As Float = pnlHueHandle.Width / 2
		Dim cy As Float = pnlHueHandle.Height / 2
		cvsHueHandle.DrawCircle(cx, cy, cx - 1dip, xui.Color_White, False, 2dip)
		
		Dim dotColor As Int = HSVToRGB(mHueVal, 1, 1)
		cvsHueHandle.DrawCircle(cx, cy, cx / 3, dotColor, True, 0)
		cvsHueHandle.Invalidate
	End If

	' Style SV handle (solid circle of current color, with a 2dip white border)
	pnlSVHandle.SetColorAndBorder(mColor, 2dip, xui.Color_White, pnlSVHandle.Width / 2)
    
	B4XDaisyVariants.ApplyElevation(pnlHueHandle, mShadow)
	B4XDaisyVariants.ApplyElevation(pnlSVHandle, mShadow)
    
	PositionHandles
End Sub

Private Sub DrawHueDisk
	If HueCanvasReady = False Then Return
	cvsHue.ClearRect(cvsHue.TargetRect)
	Dim cx As Float = pnlHue.Width / 2
	Dim cy As Float = pnlHue.Height / 2
	
	' Sizing parameters for the inner and outer radius of the hollow ring
	Dim centerR As Float = Min(cx, cy) - (mWheelThickness / 2) - 2dip
	Dim R_outer As Float = centerR + mWheelThickness / 2
	Dim R_inner As Float = centerR - mWheelThickness / 2
    
	Dim sat As Float = 100
	Dim lightness As Float = 50
	If mWheelReflectsSaturation Then
		Dim hsl() As Float = RGBToHSL(mColor)
		sat = hsl(1)
		lightness = hsl(2)
	End If
    
	' Draw a hollow circular Hue spectrum ring using filled quadrilateral segments to prevent edge ridges
	For i = 0 To 359 Step 1
		Dim angle1 As Float = i
		Dim angle2 As Float = i + 1.1 ' Slightly overlap to prevent anti-aliasing gaps between segments
        
		Dim ox1 As Float = cx + R_outer * CosD(angle1)
		Dim oy1 As Float = cy + R_outer * SinD(angle1)
		Dim ix1 As Float = cx + R_inner * CosD(angle1)
		Dim iy1 As Float = cy + R_inner * SinD(angle1)
        
		Dim ox2 As Float = cx + R_outer * CosD(angle2)
		Dim oy2 As Float = cy + R_outer * SinD(angle2)
		Dim ix2 As Float = cx + R_inner * CosD(angle2)
		Dim iy2 As Float = cy + R_inner * SinD(angle2)
        
		Dim col As Int = HSLToRGB((i + 90) Mod 360, sat, lightness)
        
		Dim p As B4XPath
		p.Initialize(ox1, oy1)
		p.LineTo(ox2, oy2)
		p.LineTo(ix2, iy2)
		p.LineTo(ix1, iy1)
		p.LineTo(ox1, oy1)
		cvsHue.DrawPath(p, col, True, 0)
	Next
	
	cvsHue.Invalidate
End Sub

Private Sub DrawSVSquare
	If SVCanvasReady = False Then Return
	cvsSV.ClearRect(cvsSV.TargetRect)
    
	Dim w As Float = pnlSV.Width
	Dim h As Float = pnlSV.Height
	Dim stepSize As Int = 2
    
	Dim hueColor As Int = HSVToRGB(mHueVal, 1, 1)
    
	' 1. Draw horizontal saturation gradient (White to Hue)
	For x = 0 To w Step stepSize
		Dim ratio As Float = x / w
		Dim col As Int = BlendColors(hueColor, xui.Color_White, ratio)
		Dim r As B4XRect
		r.Initialize(x, 0, x + stepSize, h)
		cvsSV.DrawRect(r, col, True, 0)
	Next
    
	' 2. Draw vertical value gradient using black alpha blending (Transparent to Black)
	For y = 0 To h Step stepSize
		Dim ratio As Float = y / h
		Dim alpha As Int = ratio * 255
		Dim col As Int = xui.Color_ARGB(alpha, 0, 0, 0)
		Dim r As B4XRect
		r.Initialize(0, y, w, y + stepSize)
		cvsSV.DrawRect(r, col, True, 0)
	Next
    
	cvsSV.Invalidate
End Sub

Private Sub PositionHandles
	If pnlHueHandle.IsInitialized = False Or pnlSVHandle.IsInitialized = False Then Return
    
	' Position Hue Handle (relative to parent mBase)
	Dim cx As Float = pnlHue.Width / 2
	Dim cy As Float = pnlHue.Height / 2
	Dim r As Float = Min(cx, cy) - (mWheelThickness / 2) - 2dip
	
	Dim angle As Float = (mHueVal - 90 + 360) Mod 360
	Dim hRad As Float = angle * cPI / 180
    
	pnlHueHandle.Left = cx + (r * Cos(hRad)) - (pnlHueHandle.Width / 2)
	pnlHueHandle.Top = cy + (r * Sin(hRad)) - (pnlHueHandle.Height / 2)
    
	' Position SV Handle (relative to parent pnlSV coordinates)
	Dim svX As Float = mSatVal * pnlSV.Width
	Dim svY As Float = (1 - mValVal) * pnlSV.Height
	pnlSVHandle.Left = svX - (pnlSVHandle.Width / 2)
	pnlSVHandle.Top = svY - (pnlSVHandle.Height / 2)
End Sub
#End Region

#Region Touch Handlers
Private Sub pnlHue_Touch (Action As Int, X As Float, Y As Float)
	#If B4A
	DisallowParentIntercept(Action)
	#End If
	Dim cx As Float = pnlHue.Width / 2
	Dim cy As Float = pnlHue.Height / 2
	Dim dx As Float = X - cx
	Dim dy As Float = Y - cy
	Dim dist As Float = Sqrt(dx*dx + dy*dy)
    
	Dim outerR As Float = Min(cx, cy) - (mWheelThickness / 2) - 2dip
	Dim innerR As Float = outerR - (mWheelThickness / 2)
    
	' Radius filter to only register touches close to the Hue ring
	Dim touchPadding As Float = 15dip
	If dist < (innerR - touchPadding) Or dist > (outerR + mWheelThickness/2 + touchPadding) Then
		Return
	End If
    
	' Calculate angle for Hue
	Dim angle As Float = ATan2D(dy, dx)
	If angle < 0 Then angle = angle + 360
	mHueVal = (angle + 90) Mod 360
    
	mColor = HSVToRGB(mHueVal, mSatVal, mValVal)
	DrawSVSquare
	UpdateVisuals
	RaiseColorChanged
End Sub

Private Sub pnlSV_Touch (Action As Int, X As Float, Y As Float)
	#If B4A
	DisallowParentIntercept(Action)
	#End If
	Dim cx As Float = Max(0, Min(X, pnlSV.Width))
	Dim cy As Float = Max(0, Min(Y, pnlSV.Height))
    
	mSatVal = cx / pnlSV.Width
	mValVal = 1 - (cy / pnlSV.Height)
    
	mColor = HSVToRGB(mHueVal, mSatVal, mValVal)
	If mWheelReflectsSaturation Then
		DrawHueDisk
	End If
	UpdateVisuals
	RaiseColorChanged
End Sub

Private Sub RaiseColorChanged
	If xui.SubExists(mCallBack, mEventName & "_ColorChanged", 1) Then
		CallSub2(mCallBack, mEventName & "_ColorChanged", mColor)
	End If
End Sub

#If B4A
Private Sub DisallowParentIntercept(Action As Int)
	If Action = 0 Or Action = 2 Then ' ACTION_DOWN or ACTION_MOVE
		Dim current As JavaObject = mBase
		Do While current.IsInitialized
			Dim parentView As JavaObject
			Try
				parentView = current.RunMethod("getParent", Null)
			Catch
				Exit
			End Try
			If parentView.IsInitialized = False Then Exit
			Try
				parentView.RunMethod("requestDisallowInterceptTouchEvent", Array(True))
			Catch
				Log("B4XDaisyColorWheel.DisallowParentIntercept: " & LastException.Message)
			End Try
			
			' Avoid climbing past Android ViewRootImpl to prevent exceptions
			Try
				Dim parentClass As String = GetType(parentView)
				If parentClass.Contains("ViewRootImpl") Then Exit
			Catch
				Exit
			End Try
			
			current = parentView
		Loop
	End If
End Sub
#End If
#End Region

#Region HSV / RGB Math Helpers
Private Sub HSVToRGB(h As Float, s As Float, v As Float) As Int
	Dim r, g, b As Float
	Dim i As Int = Floor(h / 60)
	Dim f As Float = (h / 60) - i
	Dim p As Float = v * (1 - s)
	Dim q As Float = v * (1 - f * s)
	Dim t As Float = v * (1 - (1 - f) * s)
    
	Select i Mod 6
		Case 0: r = v: g = t: b = p
		Case 1: r = q: g = v: b = p
		Case 2: r = p: g = v: b = t
		Case 3: r = p: g = q: b = v
		Case 4: r = t: g = p: b = v
		Case 5: r = v: g = p: b = q
	End Select
	
	Return xui.Color_RGB(r * 255, g * 255, b * 255)
End Sub

Private Sub RGBToHSV(c As Int)
	Dim r As Float = Bit.And(Bit.ShiftRight(c, 16), 0xFF) / 255
	Dim g As Float = Bit.And(Bit.ShiftRight(c, 8), 0xFF) / 255
	Dim b As Float = Bit.And(c, 0xFF) / 255
    
	Dim cmax As Float = Max(r, Max(g, b))
	Dim cmin As Float = Min(r, Min(g, b))
	Dim diff As Float = cmax - cmin
    
	mValVal = cmax
	If cmax = 0 Then
		mSatVal = 0
	Else
		mSatVal = diff / cmax
	End If
    
	If diff = 0 Then
		mHueVal = 0
	Else If cmax = r Then
		mHueVal = 60 * (((g - b) / diff) Mod 6)
	Else If cmax = g Then
		mHueVal = 60 * (((b - r) / diff) + 2)
	Else
		mHueVal = 60 * (((r - g) / diff) + 4)
	End If
	If mHueVal < 0 Then mHueVal = mHueVal + 360
End Sub

Private Sub BlendColors(c1 As Int, c2 As Int, ratio As Float) As Int
	Dim r1 As Int = Bit.And(Bit.ShiftRight(c1, 16), 0xFF)
	Dim g1 As Int = Bit.And(Bit.ShiftRight(c1, 8), 0xFF)
	Dim b1 As Int = Bit.And(c1, 0xFF)
    
	Dim r2 As Int = Bit.And(Bit.ShiftRight(c2, 16), 0xFF)
	Dim g2 As Int = Bit.And(Bit.ShiftRight(c2, 8), 0xFF)
	Dim b2 As Int = Bit.And(c2, 0xFF)
    
	Return xui.Color_RGB(r2 + (r1 - r2) * ratio, g2 + (g1 - g2) * ratio, b2 + (b1 - b2) * ratio)
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
''' <summary>
''' Attaches the component to an existing target view, completely filling its dimensions.
''' </summary>
Public Sub AttachTo(Target As B4XView)
	AddToParent(Target, 0, 0, Target.Width, Target.Height)
End Sub

''' <summary>
''' Adds the component to a parent B4XView with given parameters.
''' </summary>
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	If Parent.IsInitialized = False Then Return mBase
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("mBase")
		DesignerCreateView(p, Null, CreateMap())
	End If
	Parent.AddView(mBase, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(mBase.Width, mBase.Height)
	Return mBase
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then
		mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
		Base_Resize(Width, Height)
	End If
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then
		mBase.Width = Value
		Base_Resize(mBase.Width, mBase.Height)
	End If
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then
		mBase.Height = Value
		Base_Resize(mBase.Width, mBase.Height)
	End If
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub
#End Region

---

## B4XDaisyCollapseTitle.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

'/**
' * B4XDaisyCollapseTitle
' * -----------------------------------------------------------------------------
' * Clickable header part for B4XDaisyCollapse.
' */

#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: Collapse Title, Description: Title text.
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Title size token.
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit background color override (0 uses parent/theme).
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit text color override (0 uses theme token).
#DesignerProperty: Key: IconName, DisplayName: Icon Name, FieldType: String, DefaultValue:, Description: SVG asset filename shown on the left (e.g. home-solid.svg).
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Semantic color variant � overrides background and text colors.
#DesignerProperty: Key: IconColor, DisplayName: Icon Color, FieldType: Color, DefaultValue: 0x00000000, Description: Override icon color independently (0 = follow text color).
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide title.

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView
	Private lblText As B4XView
	Private pnlIcon As B4XView
	Private iconComp As B4XDaisySvgIcon

	Private mText As String = "Collapse Title"
	Private mSize As String = "md"
	Private mVisible As Boolean = True
	Private mTextColor As Int = 0
	Private mBackColor As Int = 0
	Private mTextSize As Float = 18
	Private mIconName As String = ""
	Private mVariant As String = "none"
	Private mIconColor As Int = -1
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	Dim l As Label
	l.Initialize("lbl")
	lblText = l
	mBase.AddView(lblText, 0, 0, 1dip, 1dip)

	' Build left SVG icon panel
	Dim pi As Panel
	pi.Initialize("")
	pi.Color = xui.Color_Transparent
	pnlIcon = pi
	mBase.AddView(pnlIcon, 0, 0, 1dip, 1dip)
	Dim svgIcon As B4XDaisySvgIcon
	svgIcon.Initialize(Me, "")
	svgIcon.AddToParent(pnlIcon, 0, 0, 22dip, 22dip)
	iconComp = svgIcon

	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mText = B4XDaisyVariants.GetPropString(Props, "Text", mText)
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	mBackColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mBackColor)
	mTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mTextColor)
	mIconName = B4XDaisyVariants.GetPropString(Props, "IconName", mIconName)
	mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	Dim rawIconColor As Int = B4XDaisyVariants.GetPropColor(Props, "IconColor", 0)
	mIconColor = IIf(rawIconColor = 0, -1, rawIconColor)
	ApplySizeMetrics
End Sub

Private Sub ApplySizeMetrics
	Select Case mSize
		Case "xs": mTextSize = 14
		Case "sm": mTextSize = 16
		Case "lg": mTextSize = 20
		Case "xl": mTextSize = 22
		Case Else: mTextSize = 18
	End Select
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	mBase.Visible = mVisible

	' Resolve background and text colors � variant wins over manual overrides
	Dim resolvedBack As Int
	Dim resolvedText As Int
	If mVariant <> "none" Then
		resolvedBack = B4XDaisyVariants.ResolveBackgroundColorVariant(mVariant, xui.Color_Transparent)
		resolvedText = B4XDaisyVariants.ResolveTextColorVariant(mVariant, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55)))
	Else
		resolvedBack = IIf(mBackColor <> 0, mBackColor, xui.Color_Transparent)
		resolvedText = IIf(mTextColor <> 0, mTextColor, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55)))
	End If
	mBase.Color = resolvedBack

	lblText.Text = mText
	lblText.TextSize = mTextSize
	lblText.Font = xui.CreateDefaultBoldFont(mTextSize)
	lblText.TextColor = resolvedText
	lblText.SetTextAlignment("CENTER", "LEFT")
	' Debug border
	#if DEBUG
	' mBase.SetColorAndBorder(resolvedBack, 1dip, xui.Color_Red, 0)
	#end if

	' Refresh SVG icon asset and color
	If pnlIcon.IsInitialized Then
		If mIconName.Trim.Length > 0 Then
			iconComp.setSvgAsset(mIconName)
			Dim iconFinalColor As Int = IIf(mIconColor <> -1, mIconColor, resolvedText)
			iconComp.setColor(iconFinalColor)
		End If
	End If
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim pad As Int = 16dip
	Dim iconSize As Int = 22dip
	Dim iconGap As Int = 8dip
	If mIconName.Trim.Length > 0 Then
		pnlIcon.Visible = True
		pnlIcon.SetLayoutAnimated(0, pad, (h - iconSize) / 2, iconSize, iconSize)
		iconComp.ResizeToParent(pnlIcon)
		lblText.SetLayoutAnimated(0, pad + iconSize + iconGap, 0, Max(1dip, w - (pad + iconSize + iconGap + pad)), h)
	Else
		pnlIcon.Visible = False
		lblText.SetLayoutAnimated(0, pad, 0, Max(1dip, w - (pad * 2)), h)
	End If
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, w, h)
		DesignerCreateView(b, Null, CreateMap("Text": mText, "Size": mSize, "Visible": mVisible))
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, w, h)
	Refresh
	Return mBase
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub setText(Value As String)
	mText = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getText As String
	Return mText
End Sub

Public Sub setTextColor(Value As Int)
	mTextColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTextColor As Int
	If lblText.IsInitialized Then Return lblText.TextColor
	Return mTextColor
End Sub

Public Sub setBackgroundColor(Value As Int)
	mBackColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBackgroundColor As Int
	Return mBackColor
End Sub

Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	ApplySizeMetrics
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getSize As String
	Return mSize
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setIconName(Value As String)
	mIconName = Value
	If mBase.IsInitialized Then
		Refresh
		Base_Resize(mBase.Width, mBase.Height)
	End If
End Sub

Public Sub getIconName As String
	Return mIconName
End Sub

Public Sub setVariant(Value As String)
	mVariant = B4XDaisyVariants.NormalizeVariant(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVariant As String
	Return mVariant
End Sub

Public Sub setIconColor(Value As Int)
	mIconColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIconColor As Int
	Return mIconColor
End Sub

Private Sub lbl_Click
	CallSub2(mCallBack, mEventName & "_Click", mTag)
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCollapseContent.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

'/**
' * B4XDaisyCollapseContent
' * -----------------------------------------------------------------------------
' * Content container part for B4XDaisyCollapse.
' */

#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit background color override (0 uses parent/theme).
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit text color override for child labels (0 uses theme token).
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide content.
#DesignerProperty: Key: AutoResize, DisplayName: Auto Resize, FieldType: Boolean, DefaultValue: True, Description: Automatically resize height to fit child views.

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView
	Private pnlContent As B4XView

	Private mVisible As Boolean = True
	Private mTextColor As Int = 0
	Private mBackColor As Int = 0
	Private mbAutoResize As Boolean = True
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	Dim p As Panel
	p.Initialize("")
	pnlContent = p
	pnlContent.Color = xui.Color_Transparent
	mBase.AddView(pnlContent, 0, 0, 1dip, 1dip)

	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	mBackColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mBackColor)
	mTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mTextColor)
	mbAutoResize = B4XDaisyVariants.GetPropBool(Props, "AutoResize", mbAutoResize)
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	mBase.Visible = mVisible
	If mBackColor <> 0 Then mBase.Color = mBackColor
	
	' Debug border
	#if DEBUG
	' mBase.SetColorAndBorder(mBase.Color, 1dip, xui.Color_Red, 0)
	#end if
	
	' Propagate text color to children (CSS-like inheritance for plain labels)
	If pnlContent.IsInitialized Then
		For i = 0 To pnlContent.NumberOfViews - 1
			Dim v As B4XView = pnlContent.GetView(i)
			ApplyTextColorRecursive(v, mTextColor)
		Next
	End If
	
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub ApplyTextColorRecursive(v As B4XView, clr As Int)
	If clr = 0 Or clr = -1 Then Return
	' Direct Label check (safe — Try/Catch avoids crash on non-Label types)
	Try
		If v Is Label Then
			v.TextColor = clr
		End If
	Catch
		' Not a Label — skip
	End Try
	' Recursive check for nested labels in panels
	Dim childCount As Int = 0
	Try
		childCount = v.NumberOfViews
	Catch
		' Not a panel/container — skip recursion
	End Try
	If childCount > 0 Then
		For i = 0 To childCount - 1
			ApplyTextColorRecursive(v.GetView(i), clr)
		Next
	End If
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	' Add padding similar to DaisyUI collapse-content (typically px-4 pb-4)
	Dim pad As Int = 16dip
	pnlContent.SetLayoutAnimated(0, pad, 0, Max(1dip, w - (pad * 2)), Max(1dip, h - (pad * 1.5)))
	DoAutoResize
End Sub

Private Sub DoAutoResize
	If mbAutoResize = False Then Return
	If mBase.IsInitialized = False Then Return
	If pnlContent.IsInitialized = False Then Return
	Dim maxBottom As Int = 0
	For i = 0 To pnlContent.NumberOfViews - 1
		Dim v As B4XView = pnlContent.GetView(i)
		If v.Visible Then
			maxBottom = Max(maxBottom, v.Top + v.Height)
		End If
	Next
	If maxBottom <= 0 Then Return
	Dim pad As Int = 16dip
	Dim newH As Int = Max(1dip, maxBottom + Round(pad * 1.5))
	If newH <> mBase.Height Then
		mBase.Height = newH
		pnlContent.Height = Max(1dip, maxBottom + pad)
	End If
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, w, h)
		DesignerCreateView(b, Null, CreateMap("Visible": mVisible))
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, w, h)
	Refresh
	Return mBase
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub Relayout
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getContainer As B4XView
	Return pnlContent
End Sub

Public Sub setTextColor(Value As Int)
	mTextColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTextColor As Int
	Return mTextColor
End Sub

Public Sub setBackgroundColor(Value As Int)
	mBackColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBackgroundColor As Int
	Return mBackColor
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setAutoResize(Value As Boolean)
	mbAutoResize = Value
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getAutoResize As Boolean
	Return mbAutoResize
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCollapse.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

'/**
' * B4XDaisyCollapse
' * -----------------------------------------------------------------------------
' * Collapse component for showing and hiding content.
' * Supports arrow/plus icons and daisy variants.
' */

#Event: Click (Tag As Object)
#Event: StateChanged (Open As Boolean)

#DesignerProperty: Key: Open, DisplayName: Open, FieldType: Boolean, DefaultValue: False, Description: Initial expanded state.
#DesignerProperty: Key: Icon, DisplayName: Icon, FieldType: String, DefaultValue: none, List: none|arrow|plus, Description: Expansion indicator icon.
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Semantic variant.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Radius mode.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation level.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide component.
#DesignerProperty: Key: TitleText, DisplayName: Title Text, FieldType: String, DefaultValue: Click to expand, Description: Text shown in the collapse title bar.
#DesignerProperty: Key: TitleVariant, DisplayName: Title Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Semantic color variant applied to the title background.
#DesignerProperty: Key: TitleSize, DisplayName: Title Size, FieldType: String, DefaultValue: text-sm, List: text-xs|text-sm|text-base|text-lg|text-xl|text-2xl, Description: Font size token for title text.
#DesignerProperty: Key: TitleIconName, DisplayName: Title Icon, FieldType: String, DefaultValue:, Description: SVG asset filename shown on the left of the title text (e.g. home-solid.svg).
#DesignerProperty: Key: TitleColor, DisplayName: Title Color, FieldType: Color, DefaultValue: 0x00000000, Description: Override title text color (wins over variant and TitleTextColor; 0 = unset).
#DesignerProperty: Key: TitleIconColor, DisplayName: Title Icon Color, FieldType: Color, DefaultValue: 0x00000000, Description: Override title icon color independently (0 = follow text color).
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: w-full, Description: Component width as Tailwind fraction of parent (w-full = fill parent).
#DesignerProperty: Key: BorderWidth, DisplayName: Border Width, FieldType: String, DefaultValue: border, Description: Tailwind border width utility (e.g. border, border-2, border-0)
#DesignerProperty: Key: BorderStyle, DisplayName: Border Style, FieldType: String, DefaultValue: solid, List: solid|dashed|dotted, Description: The style of the border.
#DesignerProperty: Key: BorderColor, DisplayName: Border Color, FieldType: String, DefaultValue: border-base-300, Description: Tailwind border color utility (e.g. border-primary, border-base-300)
#DesignerProperty: Key: IconPosition, DisplayName: Icon Position, FieldType: String, DefaultValue: right, List: left|right, Description: Position of the expansion indicator (arrow/plus).
#DesignerProperty: Key: GroupName, DisplayName: Group Name, FieldType: String, DefaultValue: , Description: Join multiple collapses into an accordion group.

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView
	' reference to accordion class for parent checks
	Private unusedAccordion As B4XDaisyAccordion
	
	' Parts
	Private pnlTitle As B4XView
	Private pnlContent As B4XView
	Public Title As B4XDaisyCollapseTitle
	Public Content As B4XDaisyCollapseContent
	Private cvsIcon As B4XCanvas
	Private pnlIcon As B4XView
	Private pnlTitleIcon As B4XView
	Private titleIconComp As B4XDaisySvgIcon

	' Props
	Private mOpen As Boolean = False
	Private mIcon As String = "none"
	Private mVariant As String = "none"
	Private mRounded As String = "theme"
	Private mShadow As String = "none"
	Private mVisible As Boolean = True
	' Title props
	Private mTitleText As String = "Click to expand"
	Private mTitleVariant As String = "none"
	Private mTitleSize As String = "text-sm"
	Private mTitleBackgroundColor As Int = -1
	Private mTitleTextColor As Int = -1
	Private mTitleColor As Int = -1
	Private mTitleIconName As String = ""
	Private mTitleIconColor As Int = -1
	Private mWidthMode As String = "w-full"
	Private mBorderWidth As String = "border"
	Private mBorderStyle As String = "solid"
	Private mBorderColor As String = "border-base-300"
	Private mIconPosition As String = "right"
	Private mGroupName As String = ""
	
	' Internal state
	Private mIsAnimating As Boolean = False
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	' Build Title Part
	Dim pt As Panel
	pt.Initialize("pnlTitle")
	pnlTitle = pt
	pnlTitle.Color = xui.Color_Transparent
	mBase.AddView(pnlTitle, 0, 0, 1dip, 1dip)
	
	Title.Initialize(Me, "Title")
	Title.AddToParent(pnlTitle, 0, 0, 1dip, 1dip)
	
	' Build Icon Layer
	Dim pi As Panel
	pi.Initialize("")
	pnlIcon = pi
	pnlTitle.AddView(pnlIcon, 0, 0, 1dip, 1dip)
	cvsIcon.Initialize(pnlIcon)
	pnlIcon.Color = xui.Color_Transparent
	
	' Build Title Left SVG Icon
	Dim pti As Panel
	pti.Initialize("")
	pti.Color = xui.Color_Transparent
	pnlTitleIcon = pti
	pnlTitle.AddView(pnlTitleIcon, 0, 0, 1dip, 1dip)
	Dim svgIcon As B4XDaisySvgIcon
	svgIcon.Initialize(Me, "")
	svgIcon.AddToParent(pnlTitleIcon, 0, 0, 22dip, 22dip)
	svgIcon.SetClickable(False)
	titleIconComp = svgIcon
	
	' Build Content Part
	Dim pc As Panel
	pc.Initialize("")
	pnlContent = pc
	pnlContent.Color = xui.Color_Transparent
	mBase.AddView(pnlContent, 0, 0, 1dip, 1dip)
	pnlContent.Visible = False
	
	Content.Initialize(Me, "Content")
	Content.AddToParent(pnlContent, 0, 0, 1dip, 1dip)

	ApplyDesignerProps(Props)
	Refresh
End Sub

''' <summary>
''' Forces the component to re-evaluate its styling against the currently active global Theme.
''' </summary>
Public Sub UpdateTheme
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mOpen = B4XDaisyVariants.GetPropBool(Props, "Open", mOpen)
	mIcon = B4XDaisyVariants.GetPropString(Props, "Icon", mIcon)
	mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	mRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", mRounded))
	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	mTitleText = B4XDaisyVariants.GetPropString(Props, "TitleText", mTitleText)
	mTitleVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "TitleVariant", mTitleVariant))
	mTitleSize = B4XDaisyVariants.GetPropString(Props, "TitleSize", mTitleSize)
	mTitleIconName = B4XDaisyVariants.GetPropString(Props, "TitleIconName", mTitleIconName)
	Dim rawTitleColor As Int = B4XDaisyVariants.GetPropColor(Props, "TitleColor", 0)
	mTitleColor = IIf(rawTitleColor = 0, -1, rawTitleColor)
	Dim rawTitleIconColor As Int = B4XDaisyVariants.GetPropColor(Props, "TitleIconColor", 0)
	mTitleIconColor = IIf(rawTitleIconColor = 0, -1, rawTitleIconColor)
	mWidthMode = B4XDaisyVariants.GetPropString(Props, "Width", mWidthMode)
	mBorderWidth = B4XDaisyVariants.GetPropString(Props, "BorderWidth", mBorderWidth)
	mBorderStyle = B4XDaisyVariants.GetPropString(Props, "BorderStyle", mBorderStyle)
	mBorderColor = B4XDaisyVariants.GetPropString(Props, "BorderColor", mBorderColor)
	mIconPosition = B4XDaisyVariants.GetPropString(Props, "IconPosition", mIconPosition).ToLowerCase
	mGroupName = B4XDaisyVariants.GetPropString(Props, "GroupName", mGroupName)
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	
	Dim back As Int = B4XDaisyVariants.ResolveBackgroundColorVariant(mVariant, B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_White))
	Dim border As Int = B4XDaisyVariants.GetTokenColor("--color-base-200", xui.Color_RGB(226, 232, 240))
	Dim radius As Float = ResolveRadius
	
	Dim bw As Float = B4XDaisyVariants.TailwindBorderWidthToDip(mBorderWidth, 0dip)
	Dim bc As Int = B4XDaisyVariants.TailwindBorderColorToColor(mBorderColor, xui.Color_Transparent)
	B4XDaisyVariants.ApplyDashedBorder(mBase, back, bw, bc, radius, mBorderStyle)
	B4XDaisyVariants.ApplyElevation(mBase, mShadow)
	mBase.Visible = mVisible
	' RefreshTitle
	' Base_Resize(mBase.Width, mBase.Height)

	Dim contentTextColor As Int = B4XDaisyVariants.ResolveTextColorVariant(mVariant, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(30, 41, 59)))
	Content.setTextColor(contentTextColor)
	Content.setBackgroundColor(xui.Color_Transparent)
	RefreshTitle
	Base_Resize(mBase.Width, mBase.Height)
	UpdateOpenState(False)
End Sub

Private Sub RefreshTitle
	If Title.mBase.IsInitialized = False Then Return
	Title.setText(mTitleText)
	Title.setSize(mTitleSize)
	Dim resolvedTextColor As Int = -1
	If mTitleVariant <> "none" Then
		Title.setBackgroundColor(B4XDaisyVariants.ResolveBackgroundColorVariant(mTitleVariant, xui.Color_Transparent))
		resolvedTextColor = B4XDaisyVariants.ResolveTextColorVariant(mTitleVariant, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(30, 41, 59)))
		Title.setTextColor(resolvedTextColor)
	Else
		' Fallback to main variant for text color if no title variant is set
		' This ensures the expansion icon (which uses Title.getTextColor) gets a valid color
		' even if the title background is transparent.
		resolvedTextColor = B4XDaisyVariants.ResolveTextColorVariant(mVariant, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(30, 41, 59)))
		Title.setTextColor(resolvedTextColor)
		
		If mTitleBackgroundColor <> -1 Then
			Title.setBackgroundColor(mTitleBackgroundColor)
		Else
			Title.setBackgroundColor(xui.Color_Transparent)
		End If
		If mTitleTextColor <> -1 Then
			resolvedTextColor = mTitleTextColor
			Title.setTextColor(mTitleTextColor)
		End If
	End If
	' TitleColor is the final text-color override (wins over variant and TitleTextColor)
	If mTitleColor <> -1 Then
		resolvedTextColor = mTitleColor
		Title.setTextColor(mTitleColor)
	End If
	' Update left SVG icon
	If pnlTitleIcon.IsInitialized Then
		If mTitleIconName.Trim.Length > 0 Then
			titleIconComp.setSvgAsset(mTitleIconName)
			Dim iconColor As Int
			If mTitleIconColor <> -1 Then
				iconColor = mTitleIconColor
			Else
				iconColor = IIf(resolvedTextColor <> -1, resolvedTextColor, B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(30, 41, 59)))
			End If
			titleIconComp.setColor(iconColor)
		End If
	End If
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	If Width < 50dip Then Return ' too narrow to lay out — skip until parent is sized
	Dim w As Int = ResolveComponentWidth(Width)
	' Apply resolved width back to self if it differs (e.g. fractional mode)
	If mWidthMode <> "w-full" And mBase.Parent.IsInitialized Then mBase.Width = w
	' Compute title height from text metrics + padding (CSS: padding 1rem ≈ 16dip each side)
	Dim availTitleW As Int = Max(100dip, w - 64dip)
	Dim titleH As Int = B4XDaisyVariants.MeasureTextHeightSafe(mTitleText, 14, Typeface.DEFAULT_BOLD, availTitleW, 4dip) + 32dip
	titleH = Max(titleH, 40dip) ' minimum height
	Dim titleIconPad As Int = 12dip
	Dim titleIconSize As Int = 22dip
	Dim titleIconGap As Int = 8dip

	Dim bw As Float = B4XDaisyVariants.TailwindBorderWidthToDip(mBorderWidth, 0dip)
	Dim usableW As Int = Max(1dip, w - 2 * bw)
	pnlTitle.SetLayoutAnimated(0, bw, bw, usableW, titleH)

	' Relative positioning based on IconPosition
	Dim titleInnerW As Int = pnlTitle.Width
	Dim iconSize As Int = 24dip
	Dim titleIconSize As Int = 22dip
	Dim edgePad As Int = 16dip
	Dim itemGap As Int = 8dip
	
	If mIconPosition = "left" Then
		' INDICATOR on LEFT, TITLE ICON on RIGHT
		If mIcon <> "none" Then
			pnlIcon.Visible = True
			pnlIcon.Enabled = True
			pnlIcon.SetLayoutAnimated(0, edgePad, (titleH - iconSize) / 2, iconSize, iconSize)
			cvsIcon.Resize(iconSize, iconSize)
			DrawIcon
			' Visual title starts after indicator (edgePad + iconSize + itemGap)
			' Subtract 16dip because B4XDaisyCollapseTitle has its own internal 16dip padding
			Dim titleStart As Int = (edgePad + iconSize + itemGap) - 16dip
			If mTitleIconName.Trim.Length > 0 Then
				pnlTitleIcon.Visible = True
				pnlTitleIcon.SetLayoutAnimated(0, titleInnerW - edgePad - titleIconSize, (titleH - titleIconSize) / 2, titleIconSize, titleIconSize)
				titleIconComp.ResizeToParent(pnlTitleIcon)
				Title.SetLayoutAnimated(0, titleStart, 0, Max(1dip, titleInnerW - edgePad - titleIconSize - itemGap - (titleStart + 16dip)), titleH)
			Else
				pnlTitleIcon.Visible = False
				Title.SetLayoutAnimated(0, titleStart, 0, Max(1dip, titleInnerW - edgePad - (titleStart + 16dip)), titleH)
			End If
		Else
			pnlIcon.Visible = False
			pnlIcon.Enabled = False
			If mTitleIconName.Trim.Length > 0 Then
				' Title icon on right
				pnlTitleIcon.Visible = True
				pnlTitleIcon.SetLayoutAnimated(0, titleInnerW - edgePad - titleIconSize, (titleH - titleIconSize) / 2, titleIconSize, titleIconSize)
				titleIconComp.ResizeToParent(pnlTitleIcon)
				' Title starts at 0 (internal padding yields 16dip indent)
				Title.SetLayoutAnimated(0, 0, 0, Max(1dip, titleInnerW - edgePad - titleIconSize - itemGap - 16dip), titleH)
			Else
				pnlTitleIcon.Visible = False
				' Full width, indented by internal 16dip padding
				Title.SetLayoutAnimated(0, 0, 0, Max(1dip, titleInnerW), titleH)
			End If
		End If
	Else
		' INDICATOR on RIGHT (Default), TITLE ICON on LEFT
		' Expansion icon on right
		If mIcon <> "none" Then
			pnlIcon.Visible = True
			pnlIcon.Enabled = True
			pnlIcon.SetLayoutAnimated(0, titleInnerW - iconSize - edgePad, (titleH - iconSize) / 2, iconSize, iconSize)
			cvsIcon.Resize(iconSize, iconSize)
			DrawIcon
		Else
			pnlIcon.Visible = False
			pnlIcon.Enabled = False
		End If
		
		' Absolute physical start for the text
		Dim textStart As Int = edgePad
		' Where the component view (Title) should start
		Dim titleStart As Int = 0 
		
		If mTitleIconName.Trim.Length > 0 Then
			pnlTitleIcon.Visible = True
			pnlTitleIcon.SetLayoutAnimated(0, edgePad, (titleH - titleIconSize) / 2, titleIconSize, titleIconSize)
			titleIconComp.ResizeToParent(pnlTitleIcon)
			textStart = edgePad + titleIconSize + itemGap
			titleStart = textStart - 16dip
		Else
			pnlTitleIcon.Visible = False
			titleStart = 0 ' Will indent text at 16dip
		End If
		
		Dim titleEnd As Int = titleInnerW - edgePad
		If mIcon <> "none" Then titleEnd = titleInnerW - edgePad - iconSize - itemGap
		
		Title.SetLayoutAnimated(0, titleStart, 0, Max(1dip, titleEnd - (titleStart + 16dip)), titleH)
	End If
	
	Dim usableW As Int = Max(1dip, w - 2 * bw)
	If mOpen Then
		Dim contentDesiredH As Int = MeasureContentHeight
		pnlContent.Visible = True
		pnlContent.SetLayoutAnimated(0, bw, titleH + bw, usableW, contentDesiredH)
		Content.SetLayoutAnimated(0, 0, 0, usableW, contentDesiredH)
	Else
		pnlContent.Visible = False
		pnlContent.SetLayoutAnimated(0, bw, titleH + bw, usableW, 1dip)
		Content.SetLayoutAnimated(0, 0, 0, usableW, 1dip)
	End If
End Sub

Private Sub DrawIcon
	cvsIcon.ClearRect(cvsIcon.TargetRect)
	' Strictly follow the title's rendered text color to ensure parity with the title text
	Dim color As Int = Title.getTextColor
	Dim w As Float = cvsIcon.TargetRect.Width
	Dim h As Float = cvsIcon.TargetRect.Height
	Dim stroke As Float = 2dip
	
	Select Case mIcon
		Case "arrow"
			' Draw Chevron
			Dim path As B4XPath
			If mOpen Then
				' Point Up
				path.Initialize(w * 0.2, h * 0.6)
				path.LineTo(w * 0.5, h * 0.3)
				path.LineTo(w * 0.8, h * 0.6)
			Else
				' Point Down
				path.Initialize(w * 0.2, h * 0.4)
				path.LineTo(w * 0.5, h * 0.7)
				path.LineTo(w * 0.8, h * 0.4)
			End If
			cvsIcon.DrawPath(path, color, False, stroke)
		Case "plus"
			' Draw Plus/Minus
			cvsIcon.DrawLine(w * 0.2, h * 0.5, w * 0.8, h * 0.5, color, stroke)
			If mOpen = False Then
				cvsIcon.DrawLine(w * 0.5, h * 0.2, w * 0.5, h * 0.8, color, stroke)
			End If
	End Select
	cvsIcon.Invalidate
End Sub

Private Sub UpdateOpenState(Animated As Boolean)
	If mBase.IsInitialized = False Or mIsAnimating Then Return
	Dim oldH As Int = mBase.Height
	Dim w As Int = Max(1dip, mBase.Width)
	' Do not animate if view has not been laid out yet (width would be 0 ? NaN in animator)
	Dim duration As Int = IIf(Animated And mBase.Width > 0, 200, 0)
	Dim titleH As Int = Max(1dip, pnlTitle.Height)
	
	mIsAnimating = True
	Dim bw As Float = B4XDaisyVariants.TailwindBorderWidthToDip(mBorderWidth, 0dip)
	Dim usableW As Int = Max(1dip, w - 2 * bw)
	If mOpen Then
		Dim contentH As Int = Max(1dip, MeasureContentHeight)
		pnlContent.Visible = True
		pnlContent.SetLayoutAnimated(duration, bw, titleH + bw, usableW, contentH)
		Content.SetLayoutAnimated(duration, 0, 0, usableW, contentH)
		mBase.Height = titleH + contentH + 2 * bw
	Else
		pnlContent.SetLayoutAnimated(duration, bw, titleH + bw, usableW, 1dip)
		Content.SetLayoutAnimated(duration, 0, 0, usableW, 1dip)
		mBase.Height = titleH + 2 * bw
		If Animated And duration > 0 Then
			Sleep(duration)
			If mOpen = False Then pnlContent.Visible = False
		Else
			pnlContent.Visible = False
		End If
	End If
	mIsAnimating = False
	
	' If standalone (not inside accordion), auto-shift page siblings by the height delta.
	' If inside an accordion, the accordion's Base_Resize handles sibling repositioning.
	Dim parentView As B4XView = mBase.Parent
	If parentView.IsInitialized Then
		If (parentView.Tag Is B4XDaisyAccordion) = False Then
			B4XDaisyVariants.ShiftSiblingsBelow(mBase, mBase.Height - oldH, IIf(Animated, 200, 0))
		End If
	End If
	
	If mIcon <> "none" Then DrawIcon
	
	If xui.SubExists(mCallBack, mEventName & "_StateChanged", 1) Then
		CallSub2(mCallBack, mEventName & "_StateChanged", mOpen)
	End If
	
	If mOpen And mGroupName.Length > 0 Then
		If xui.SubExists(mCallBack, mEventName & "_RequestGroupSelect", 1) Then
			CallSub2(mCallBack, mEventName & "_RequestGroupSelect", mGroupName)
		End If
	End If
	' if we are inside an accordion, let it know so it can reposition siblings
	Dim p As B4XView = mBase.Parent
	If p.IsInitialized Then
		If p.Tag Is B4XDaisyAccordion Then
			Dim acc As B4XDaisyAccordion = p.Tag
			' Always notify the accordion: on open it enforces single-open mode;
			' on close it restacks siblings to remove the gap left by the shrunk item.
			If mOpen Then acc.HandleChildRequestOpen(Me)
			acc.Refresh
		End If
	End If
End Sub

Private Sub MeasureContentHeight As Int
	' Auto-measures height by finding the bottom edge of the lowest visible child
	' plus bottom padding. No explicit height needs to be provided.
	' Align with B4XDaisyCollapseContent padding (16dip * 1.5 bottom)
	Dim pad As Int = 16dip
	Dim total As Int = 0
	Dim container As B4XView = Content.getContainer
	For i = 0 To container.NumberOfViews - 1
		Dim v As B4XView = container.GetView(i)
		If v.Visible Then
			total = Max(total, v.Top + v.Height + (pad * 1.5))
		End If
	Next
	Return Max(48dip, total)
End Sub

Public Sub setOpen(Value As Boolean)
	If mOpen = Value Then Return
	mOpen = Value
	UpdateOpenState(True)
End Sub

Public Sub getOpen As Boolean
	Return mOpen
End Sub

Public Sub Toggle
	setOpen(Not(mOpen))
End Sub

Public Sub setIcon(Value As String)
	mIcon = Value.ToLowerCase
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getIcon As String
	Return mIcon
End Sub

Public Sub setVariant(Value As String)
	mVariant = B4XDaisyVariants.NormalizeVariant(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVariant As String
	Return mVariant
End Sub

Public Sub setRounded(Value As String)
	mRounded = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getRounded As String
	Return mRounded
End Sub

'/**
' * Updates elevation shadow token and refreshes the component.
' * @param Value (String) New value to apply.
' */
Public Sub setShadow(Value As String)
	mShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getShadow As String
	Return mShadow
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setTitleText(Value As String)
	mTitleText = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleText As String
	Return mTitleText
End Sub

Public Sub setTitleVariant(Value As String)
	mTitleVariant = B4XDaisyVariants.NormalizeVariant(Value)
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleVariant As String
	Return mTitleVariant
End Sub

Public Sub setTitleSize(Value As String)
	mTitleSize = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleSize As String
	Return mTitleSize
End Sub

Public Sub setTitleBackgroundColor(Value As Int)
	mTitleBackgroundColor = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleBackgroundColor As Int
	Return mTitleBackgroundColor
End Sub

Public Sub setTitleTextColor(Value As Int)
	mTitleTextColor = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleTextColor As Int
	Return mTitleTextColor
End Sub

Public Sub setTitleIconName(Value As String)
	mTitleIconName = Value
	If mBase.IsInitialized Then
		RefreshTitle
		Base_Resize(mBase.Width, mBase.Height)
	End If
End Sub

Public Sub getTitleIconName As String
	Return mTitleIconName
End Sub

Public Sub setTitleColor(Value As Int)
	mTitleColor = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleColor As Int
	Return mTitleColor
End Sub

Public Sub setTitleIconColor(Value As Int)
	mTitleIconColor = Value
	If mBase.IsInitialized Then RefreshTitle
End Sub

Public Sub getTitleIconColor As Int
	Return mTitleIconColor
End Sub

' Resolves the component width from the stored WidthMode token.
' Uses mBase.Parent width when available, otherwise falls back to RequestedWidth.
Private Sub ResolveComponentWidth(RequestedWidth As Double) As Int
	If mWidthMode = "w-full" Or mWidthMode = "" Then Return Max(1dip, RequestedWidth)
	Dim parentW As Int = IIf(mBase.Parent.IsInitialized And mBase.Parent.Width > 0, mBase.Parent.Width, Max(1dip, RequestedWidth))
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mWidthMode, parentW))
End Sub

' Same as ResolveComponentWidth but uses an explicit parent view (for AddToParent before mBase is attached).
Private Sub ResolveComponentWidth2(Parent As B4XView, RequestedWidth As Int) As Int
	If mWidthMode = "w-full" Or mWidthMode = "" Then Return Max(1dip, RequestedWidth)
	Dim parentW As Int = IIf(Parent.IsInitialized And Parent.Width > 0, Parent.Width, Max(1dip, RequestedWidth))
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mWidthMode, parentW))
End Sub

''' <summary>Forces re-measurement of content height and updates the component size if open.
''' Call this after dynamically adding or removing views from ContentView.</summary>
Public Sub RefreshContent
	If mBase.IsInitialized = False Then Return
	If mOpen Then UpdateOpenState(False)
End Sub

Public Sub setWidth(Value As String)
	mWidthMode = Value
	If mBase.IsInitialized Then
		Base_Resize(mBase.Width, mBase.Height)
	End If
End Sub

Public Sub getWidth As String
	Return mWidthMode
End Sub

' Sets the border width using a Tailwind utility string (e.g. "border", "border-2", "border-0").
Public Sub setBorderStyle(Value As String)
	mBorderStyle = Value.ToLowerCase.Trim
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBorderStyle As String
	Return mBorderStyle
End Sub

Public Sub setBorderWidth(Value As String)
	mBorderWidth = Value
	If mBase.IsInitialized Then Refresh
End Sub

' Gets the current border width utility string.
Public Sub getBorderWidth As String
	Return mBorderWidth
End Sub

' Sets the border color using a Tailwind utility string (e.g. "border-primary", "border-base-300").
Public Sub setBorderColor(Value As String)
	mBorderColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

' Gets the current border color utility string.
Public Sub getBorderColor As String
	Return mBorderColor
End Sub

' Sets the tag property.
Public Sub setTag(Value As Object)
	mTag = Value
End Sub

' Gets the tag property.
Public Sub getTag As Object
	Return mTag
End Sub

' Sets the icon position ("left" or "right").
Public Sub setIconPosition(Value As String)
	mIconPosition = Value.ToLowerCase
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

' Gets the current icon position.
Public Sub getIconPosition As String
	Return mIconPosition
End Sub

Public Sub setGroupName(Value As String)
	mGroupName = Value
End Sub

Public Sub getGroupName As String
	Return mGroupName
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		DesignerCreateView(p, Null, CreateMap())
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Dim resolvedW As Int = ResolveComponentWidth2(Parent, Width)
	Parent.AddView(mBase, Left, Top, resolvedW, Height)
	Refresh
	Return mBase
End Sub

''' <summary>
''' Returns the current rendered height of this collapse item.
''' </summary>
Public Sub GetComputedHeight As Int
    If mBase.IsInitialized = False Then Return 0
    Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub CollapseTitle As B4XDaisyCollapseTitle
	Return Title
End Sub

Public Sub CollapseContent As B4XDaisyCollapseContent
	Return Content
End Sub

' Returns the inner content container view.
' Use this to AddView children or call LoadLayout directly:
'   Dim p As Panel = collapse.ContentView
'   p.LoadLayout("MyLayout")
'   collapse.ContentView.AddView(myLabel, 0, 0, 200dip, 40dip)
Public Sub getContentView As B4XView
	Return Content.getContainer
End Sub

Private Sub ResolveRadius As Float
	If mRounded = "theme" Then Return B4XDaisyVariants.GetRadiusBoxDip(12dip)
	Return B4XDaisyVariants.ResolveRoundedDip(mRounded, B4XDaisyVariants.GetRadiusBoxDip(12dip))
End Sub

Private Sub pnlTitle_Click
	Toggle
	If mEventName.Length > 0 Then
		If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
			CallSub2(mCallBack, mEventName & "_Click", mTag)
		End If
	End If
End Sub

Private Sub Title_Click (Tag As Object)
	pnlTitle_Click
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCheckboxGroup.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9
#Region Events
#Event: ItemChanged (id As String, text As String, checked As Boolean)
#Event: Changed (SelectedIds As List)
#Event: FocusChanged (HasFocus As Boolean)
#End Region

#Region Designer Properties
#DesignerProperty: Key: Legend, DisplayName: Legend, FieldType: String, DefaultValue: Select options, Description: Fieldset legend text
#DesignerProperty: Key: LegendSize, DisplayName: Legend Size, FieldType: String, DefaultValue: theme, List: theme|text-xs|text-sm|text-base|text-lg|text-xl, Description: Legend text size token
#DesignerProperty: Key: LegendBold, DisplayName: Legend Bold, FieldType: Boolean, DefaultValue: False, Description: Render the fieldset legend caption in bold
#DesignerProperty: Key: LabelAbove, DisplayName: Label Above, FieldType: Boolean, DefaultValue: False, Description: If True, the legend text is displayed as a label above the border box
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Optional accent variant for border tint
#DesignerProperty: Key: BorderStyle, DisplayName: Border Style, FieldType: String, DefaultValue: outlined, List: outlined|ghost|inset, Description: Border visual style
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: Int, DefaultValue: 16, Description: Inner content padding in dip
#DesignerProperty: Key: AutoHeight, DisplayName: Auto Height, FieldType: Boolean, DefaultValue: True, Description: Automatically grow to fit added content
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius mode
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: True, Description: Use box radius for container
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl, Description: Elevation shadow level
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Background color (0 = default bg-base-200)
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Legend text color (0 = use theme token)
#DesignerProperty: Key: BorderColor, DisplayName: Border Color, FieldType: Color, DefaultValue: 0x00000000, Description: Border color override (0 = default border-base-300)
#DesignerProperty: Key: BorderSize, DisplayName: Border Size, FieldType: Int, DefaultValue: 1, Description: Border width in dip
#DesignerProperty: Key: InputBorder, DisplayName: Input Border, FieldType: Boolean, DefaultValue: False, Description: When True, apply B4XDaisyInput border color and width to the fieldset
#DesignerProperty: Key: Direction, DisplayName: Direction, FieldType: String, DefaultValue: vertical, List: vertical|horizontal, Description: Items layout direction
#DesignerProperty: Key: Alignment, DisplayName: Checkbox Alignment, FieldType: String, DefaultValue: start, List: start|end, Description: Checkbox element check position
#DesignerProperty: Key: CheckboxColor, DisplayName: Checkbox Color, FieldType: String, DefaultValue: neutral, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Default checkbox color variant
#DesignerProperty: Key: CheckboxSize, DisplayName: Checkbox Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Checkbox size token
#DesignerProperty: Key: Gap, DisplayName: Gap, FieldType: Int, DefaultValue: 8, Description: Gap between elements in dip
#DesignerProperty: Key: RowGap, DisplayName: Row Gap, FieldType: Int, DefaultValue: 8, Description: Row gap for wrapped flow mode in dip
#DesignerProperty: Key: Required, DisplayName: Required, FieldType: Boolean, DefaultValue: False, Description: Whether at least one option must be selected.
#DesignerProperty: Key: HintText, DisplayName: Hint Text, FieldType: String, DefaultValue:, Description: Helper text displayed below the group.
#DesignerProperty: Key: ErrorText, DisplayName: Error Text, FieldType: String, DefaultValue:, Description: Error text displayed below the group when validation fails.
#End Region

#Region Variables
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object

	Private msLegend As String = "Select options"
	Private msLegendSize As String = "theme"
	Private mbLegendBold As Boolean = False
	Private msVariant As String = "none"
	Private msBorderStyle As String = "outlined"
	Private miPadding As Int = 16
	Private mbAutoHeight As Boolean = True
	Private msRounded As String = "theme"
	Private mbRoundedBox As Boolean = True
	Private msShadow As String = "none"
	Private mcBackgroundColor As Int = 0
	Private mcTextColor As Int = 0
	Private mcBorderColor As Int = 0
	Private miBorderSize As Int = 1
	Private mbInputBorder As Boolean = False

	Private msDirection As String = "vertical"
	Private msAlignment As String = "start"
	Private msCheckboxColor As String = "neutral"
	Private msCheckboxSize As String = "md"
	Private miGap As Int = 8
	Private miRowGap As Int = 8

	Private FieldsetComp As B4XDaisyFieldset
	Private FieldsetView As B4XView
	Private CheckboxHost As B4XView

	Private CheckboxDefs As List          ' List(Map("id":String, "text":String))
	Private CheckboxOrder As List         ' List(String)
	Private CheckboxById As Map           ' id -> B4XDaisyCheckbox
	Private CheckboxViewById As Map       ' id -> B4XView
	Private CheckboxTextById As Map       ' id -> text
	Private SelectedLookup As Map         ' id -> True

	Private mbRequired As Boolean = False
	Private mbLabelAbove As Boolean = False
        Private mbValidationTouched As Boolean = False
        Private mbProgrammaticError As Boolean = False
	Private msHintText As String = ""
	Private msErrorText As String = ""
	Private pnlHintText As B4XView
	Private lblHintText As B4XView

	Private mInternalSelectionChange As Boolean = False
End Sub

#End Region

#Region Initialization
''' <summary>
''' Initializes the component.
''' </summary>
Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	CheckboxDefs.Initialize
	CheckboxOrder.Initialize
	CheckboxById.Initialize
	CheckboxViewById.Initialize
	CheckboxTextById.Initialize
	SelectedLookup.Initialize
End Sub

''' <summary>
''' Designer entry point.
''' </summary>
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	mBase.RemoveAllViews

	EnsureFieldset
	ApplyDesignerProps(Props)
	ApplyFieldsetStyle
	RecreateCheckboxViews
	Refresh
End Sub

''' <summary>
''' Creates a programmatic view reference.
''' </summary>
Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub

''' <summary>
''' Adds the component to a parent view.
''' </summary>
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty

	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.Color = xui.Color_Transparent
		b.SetLayoutAnimated(0, 0, 0, Max(1dip, Width), Max(1dip, Height))
		Dim props As Map
		props.Initialize
		props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Max(1dip, Width)))
		props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Max(1dip, Height)))
		Dim dummy As Label
		DesignerCreateView(b, dummy, props)
	End If

	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mbAutoHeight Then h = Max(h, FieldsetView.Height)
	Parent.AddView(mBase, Left, Top, w, h)
	Base_Resize(w, h)
	Return mBase
End Sub

''' <summary>
''' Adds the component to a parent view at the specified coordinates.
''' </summary>
Public Sub AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Return AddToParent(Parent, Left, Top, Width, Height)
End Sub

''' <summary>
''' Returns the underlying base view.
''' </summary>
Public Sub getView As B4XView
	Return mBase
End Sub

''' <summary>
''' Helper accessor for getView.
''' </summary>
Public Sub View As B4XView
	Return getView
End Sub

''' <summary>
''' Returns true if visual panels are ready.
''' </summary>
Public Sub IsReady As Boolean
	Return mBase.IsInitialized And FieldsetView.IsInitialized And CheckboxHost.IsInitialized
End Sub
#End Region

#Region Rendering and Layout
Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub Refresh
	If mBase.IsInitialized = False Then Return
	ApplyFieldsetStyle
	RebuildLayout
End Sub

Private Sub EnsureFieldset
	If FieldsetView.IsInitialized Then Return

	Dim fs As B4XDaisyFieldset
	fs.Initialize(Me, "checkboxgroupfieldset")
	FieldsetComp = fs
	FieldsetView = FieldsetComp.AddToParent(mBase, 0, 0, Max(1dip, mBase.Width), Max(1dip, mBase.Height))

	Dim pHost As Panel
	pHost.Initialize("")
	CheckboxHost = pHost
	CheckboxHost.Color = xui.Color_Transparent
	FieldsetComp.AddContentView(CheckboxHost, 0, 0, 1dip, 1dip)

	' Hint/error text panel below the fieldset
	Dim pHT As Panel
	pHT.Initialize("")
	pnlHintText = pHT
	pnlHintText.Color = xui.Color_Transparent
	mBase.AddView(pnlHintText, 0, 0, 1dip, 1dip)
	Dim lHT As Label
	lHT.Initialize("")
	lblHintText = lHT
	lblHintText.Color = xui.Color_Transparent
	lblHintText.SetTextAlignment("TOP", "LEFT")
	lblHintText.As(Label).SingleLine = False
	pnlHintText.AddView(lblHintText, 0, 0, 1dip, 1dip)
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	msLegend = B4XDaisyVariants.GetPropString(Props, "Legend", msLegend)
	msLegendSize = B4XDaisyVariants.GetPropString(Props, "LegendSize", msLegendSize)
	mbLegendBold = B4XDaisyVariants.GetPropBool(Props, "LegendBold", mbLegendBold)
	msVariant = B4XDaisyVariants.GetPropString(Props, "Variant", msVariant)
	msBorderStyle = B4XDaisyVariants.GetPropString(Props, "BorderStyle", msBorderStyle)
	miPadding = B4XDaisyVariants.GetPropInt(Props, "Padding", miPadding)
	mbAutoHeight = B4XDaisyVariants.GetPropBool(Props, "AutoHeight", mbAutoHeight)
	msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
	mbRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mbRoundedBox)
	msShadow = B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow)
	mcBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mcBackgroundColor)
	mcTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mcTextColor)
	mcBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mcBorderColor)
	miBorderSize = B4XDaisyVariants.GetPropInt(Props, "BorderSize", miBorderSize)
	mbInputBorder = B4XDaisyVariants.GetPropBool(Props, "InputBorder", mbInputBorder)

	msDirection = B4XDaisyVariants.GetPropString(Props, "Direction", msDirection)
	msAlignment = B4XDaisyVariants.GetPropString(Props, "Alignment", msAlignment)
	msCheckboxColor = B4XDaisyVariants.GetPropString(Props, "CheckboxColor", msCheckboxColor)
	msCheckboxSize = B4XDaisyVariants.GetPropString(Props, "CheckboxSize", msCheckboxSize)
	miGap = Max(0, B4XDaisyVariants.GetPropInt(Props, "Gap", miGap))
	miRowGap = Max(0, B4XDaisyVariants.GetPropInt(Props, "RowGap", miRowGap))
	mbRequired = B4XDaisyVariants.GetPropBool(Props, "Required", mbRequired)
	mbLabelAbove = B4XDaisyVariants.GetPropBool(Props, "LabelAbove", mbLabelAbove)
	msHintText = B4XDaisyVariants.GetPropString(Props, "HintText", msHintText)
	msErrorText = B4XDaisyVariants.GetPropString(Props, "ErrorText", msErrorText)
End Sub


Private Sub ResolveLegendText As String
	If mbLabelAbove Then Return msLegend
	If mbRequired And msLegend.Length > 0 Then
		If msLegend.Contains("*") = False Then
			Return msLegend & " *"
		End If
	End If
	Return msLegend
End Sub

Private Sub ResolveLegendColor As Int
	Dim c As Int = mcTextColor
	If c = 0 Then c = B4XDaisyVariants.GetTokenColor("--color-base-content", 0xFF333333)
	If mbRequired And (mbLabelAbove = False) Then
		Return B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444)
	End If
	Return c
End Sub
Private Sub ApplyFieldsetStyle
	If FieldsetComp.IsInitialized = False Then Return
	FieldsetComp.BeginUpdate
	FieldsetComp.setLegend(ResolveLegendText)
	FieldsetComp.setTextColor(ResolveLegendColor)
	FieldsetComp.setLegendSize(ResolveLegendSize(msCheckboxSize, msLegendSize))
	FieldsetComp.setLegendBold(mbLegendBold)
	FieldsetComp.setVariant(msVariant)
	FieldsetComp.setAutoHeight(mbAutoHeight)
	FieldsetComp.setPadding(miPadding)
	FieldsetComp.setRounded(msRounded)
	FieldsetComp.setRoundedBox(mbRoundedBox)
	FieldsetComp.setBorderStyle(msBorderStyle)
	FieldsetComp.setBorderSize(miBorderSize)
	FieldsetComp.setInputBorder(mbInputBorder)
	FieldsetComp.setShadow(msShadow)
	FieldsetComp.setBackgroundColor(mcBackgroundColor)
	FieldsetComp.setLabelAbove(mbLabelAbove)
	FieldsetComp.setRequired(mbRequired)
	If msErrorText.Length > 0 Then
		FieldsetComp.setBorderColor(B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444))
	Else
		FieldsetComp.setBorderColor(mcBorderColor)
	End If
	FieldsetComp.EndUpdate
End Sub

Private Sub ResolveLegendSize(controlSize As String, legendSizeProp As String) As String
	If legendSizeProp <> "theme" Then Return legendSizeProp
	Select Case controlSize.ToLowerCase
		Case "xs": Return "text-xs"
		Case "sm": Return "text-sm"
		Case "md": Return "text-sm"
		Case "lg": Return "text-base"
		Case "xl": Return "text-lg"
		Case Else: Return "text-sm"
	End Select
End Sub

Private Sub RebuildLayout
	If IsReady = False Then Return

	Dim w As Int = Max(1dip, mBase.Width)
	Dim initialH As Int = Max(1dip, mBase.Height)
	FieldsetView.SetLayoutAnimated(0, 0, 0, w, initialH)
	FieldsetComp.Refresh

	Dim contentPanel As B4XView = FieldsetComp.GetContentPanel
	Dim contentW As Int = Max(1dip, contentPanel.Width)

	Dim hostH As Int = LayoutCheckboxes(contentW)
	CheckboxHost.SetLayoutAnimated(0, 0, 0, contentW, hostH)
	FieldsetComp.Refresh

	Dim fieldsetH As Int = Max(1dip, FieldsetView.Height)
	Dim gap As Int = 4dip
	Dim errH As Int = MeasureHintHeight(w)
	Dim targetH As Int = fieldsetH
	If errH > 0 Then targetH = targetH + gap + errH
	FieldsetView.SetLayoutAnimated(0, 0, 0, w, fieldsetH)
	LayoutHintPanel(w, fieldsetH, gap)

	If mbAutoHeight Then
		If Abs(mBase.Height - targetH) > 1dip Then
			mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, targetH)
		End If
	End If
End Sub

Private Sub MeasureHintHeight(Width As Int) As Int
	Dim displayedHint As String = msHintText
	If msErrorText.Length > 0 Then displayedHint = msErrorText
	If displayedHint.Length = 0 Then Return 0
	Dim txtSize As Float = B4XDaisyVariants.ResolveTextSizeDip("text-xs")
	Return B4XDaisyVariants.MeasureTextHeightSafe(msErrorText, txtSize, Typeface.DEFAULT, Max(1dip, Width - 4dip), 4dip)
End Sub

Private Sub LayoutHintPanel(Width As Int, Top As Int, Gap As Int)
	If pnlHintText.IsInitialized = False Then Return
	Dim errH As Int = MeasureHintHeight(Width)
	If errH > 0 Then
		pnlHintText.Visible = True
		pnlHintText.SetLayoutAnimated(0, 0, Top + Gap, Width, errH)
		lblHintText.SetLayoutAnimated(0, 0, 0, Width, errH)
		lblHintText.Text = msErrorText
		Dim txtSize As Float = B4XDaisyVariants.ResolveTextSizeDip("text-xs")
		lblHintText.TextSize = txtSize
		lblHintText.TextColor = B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444)
		lblHintText.SetTextAlignment("TOP", "LEFT")
	Else
		pnlHintText.Visible = False
		pnlHintText.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
End Sub

Private Sub LayoutCheckboxes(ContentW As Int) As Int
	If CheckboxHost.IsInitialized = False Then Return 1dip
	Dim x As Int = 0
	Dim y As Int = 0
	Dim rowH As Int = 0
	Dim gapDip As Int = miGap * 1dip
	Dim rowGapDip As Int = miRowGap * 1dip

	If msDirection.ToLowerCase = "vertical" Then
		For Each id As String In CheckboxOrder
			If CheckboxViewById.ContainsKey(id) = False Then Continue
			Dim v As B4XView = CheckboxViewById.Get(id)
			Dim cb As B4XDaisyCheckbox = CheckboxById.Get(id)
			
			Dim prefW As Int = ContentW
			Dim prefH As Int = cb.mBase.Height
			If prefH <= 0 Then prefH = 24dip
			
			v.SetLayoutAnimated(0, 0, y, prefW, prefH)
			cb.Base_Resize(prefW, prefH)
			y = y + prefH + gapDip
		Next
		If CheckboxOrder.Size = 0 Then Return 1dip
		Return Max(1dip, y - gapDip)
	Else
		' Horizontal Flow Layout (wrap)
		For Each id As String In CheckboxOrder
			If CheckboxViewById.ContainsKey(id) = False Then Continue
			Dim v As B4XView = CheckboxViewById.Get(id)
			Dim cb As B4XDaisyCheckbox = CheckboxById.Get(id)
			
			Dim bw As Int = v.Width
			Dim bh As Int = v.Height
			
			If x > 0 And x + bw > ContentW Then
				x = 0
				y = y + rowH + rowGapDip
				rowH = 0
			End If

			v.SetLayoutAnimated(0, x, y, bw, bh)
			cb.Base_Resize(bw, bh)
			
			x = x + bw + gapDip
			rowH = Max(rowH, bh)
		Next
		If CheckboxOrder.Size = 0 Then Return 1dip
		Return Max(1dip, y + rowH)
	End If
End Sub

Private Sub RecreateCheckboxViews
	If CheckboxHost.IsInitialized = False Then Return
	CheckboxHost.RemoveAllViews
	CheckboxOrder.Clear
	CheckboxById.Clear
	CheckboxViewById.Clear
	CheckboxTextById.Clear

	NormalizeSelectionAgainstDefinitions

	For i = 0 To CheckboxDefs.Size - 1
		Dim def As Map = CheckboxDefs.Get(i)
		Dim id As String = def.GetDefault("id", "")
		Dim txt As String = def.GetDefault("text", "")
		If id.Length = 0 Then Continue
		
		Dim startChecked As Boolean = SelectedLookup.ContainsKey(id)
		If def.ContainsKey("checked") Then startChecked = ResolveBoolValue(def.Get("checked"), startChecked)
		If startChecked Then
			SelectedLookup.Put(id, True)
		Else
			SelectedLookup.Remove(id)
		End If

		Dim cb As B4XDaisyCheckbox
		cb.Initialize(Me, "checkbox")
		cb.setTag(id)
		cb.setText(txt)
		cb.setVariant(msCheckboxColor)
		cb.setSize(msCheckboxSize)
		cb.setPosition(msAlignment)
		cb.setChecked(startChecked)
		
		' Dynamic size estimation
		Dim cbView As B4XView = cb.AddToParent(CheckboxHost, 0, 0, 0, 0)
		CheckboxOrder.Add(id)
		CheckboxById.Put(id, cb)
		CheckboxViewById.Put(id, cbView)
		CheckboxTextById.Put(id, txt)
	Next

	ApplySelectionState(False)
	RebuildLayout
End Sub

Private Sub NormalizeSelectionAgainstDefinitions
	Dim keep As Map
	keep.Initialize
	For i = 0 To CheckboxDefs.Size - 1
		Dim d As Map = CheckboxDefs.Get(i)
		Dim id As String = d.GetDefault("id", "")
		If id.Length > 0 Then keep.Put(id, True)
	Next

	Dim keysToRemove As List
	keysToRemove.Initialize
	For Each k As String In SelectedLookup.Keys
		If keep.ContainsKey(k) = False Then keysToRemove.Add(k)
	Next
	For Each k As String In keysToRemove
		SelectedLookup.Remove(k)
	Next
End Sub

Private Sub ApplySelectionState(RaiseEvents As Boolean)
	mInternalSelectionChange = True
	For Each id As String In CheckboxOrder
		If CheckboxById.ContainsKey(id) = False Then Continue
		Dim cb As B4XDaisyCheckbox = CheckboxById.Get(id)
		Dim shouldBeChecked As Boolean = SelectedLookup.ContainsKey(id)
		If cb.getChecked <> shouldBeChecked Then cb.setChecked(shouldBeChecked)
	Next
	mInternalSelectionChange = False

	If RaiseEvents Then
		RaiseChanged
	End If
End Sub
#End Region

#Region Checkbox Event Handling
Private Sub checkbox_FocusChanged(HasFocus As Boolean)
        ' Route focus changes through public validation hooks:
        ' clear transient errors on focus, re-validate on blur.
        If HasFocus Then
                ReceiveFocus
        Else
                Blur
        End If
        If xui.SubExists(mCallBack, mEventName & "_FocusChanged", 1) Then
                CallSub2(mCallBack, mEventName & "_FocusChanged", HasFocus)
        End If
End Sub

Private Sub checkbox_Checked(Checked As Boolean)
	If mInternalSelectionChange Then Return
	Dim cb As B4XDaisyCheckbox = Sender
	Dim id As String = cb.getTag
	
	If Checked Then
		SelectedLookup.Put(id, True)
	Else
		SelectedLookup.Remove(id)
	End If

	RaiseItemChanged(id, Checked)
	RaiseChanged
End Sub

Private Sub RaiseItemChanged(Id As String, Checked As Boolean)
	Dim subName As String = mEventName & "_ItemChanged"
	Dim text As String = CheckboxTextById.GetDefault(Id, "")
	#If B4A
	If xui.SubExists(mCallBack, subName, 3) Then
		Dim jo As JavaObject = mCallBack
		jo.RunMethod("_" & subName.ToLowerCase, Array(Id, text, Checked))
	Else If xui.SubExists(mCallBack, subName, 1) Then
		Dim item As Map
		item.Initialize
		item.Put("id", Id)
		item.Put("text", text)
		item.Put("checked", Checked)
		CallSub2(mCallBack, subName, item)
	End If
	#Else If B4J
	If xui.SubExists(mCallBack, subName, 3) Then
		Dim jo As JavaObject = mCallBack
		jo.RunMethod("_" & subName.ToLowerCase, Array(Id, text, Checked))
	Else If xui.SubExists(mCallBack, subName, 1) Then
		Dim item As Map
		item.Initialize
		item.Put("id", Id)
		item.Put("text", text)
		item.Put("checked", Checked)
		CallSub2(mCallBack, subName, item)
	End If
	#Else If B4i
	If xui.SubExists(mCallBack, subName, 3) Then
		Dim no As NativeObject = mCallBack
		no.RunMethod("_" & subName.ToLowerCase & ":::", Array(Id, text, Checked))
	Else If xui.SubExists(mCallBack, subName, 1) Then
		Dim item As Map
		item.Initialize
		item.Put("id", Id)
		item.Put("text", text)
		item.Put("checked", Checked)
		CallSub2(mCallBack, subName, item)
	End If
	#End If
End Sub

Private Sub RaiseChanged
	If xui.SubExists(mCallBack, mEventName & "_Changed", 1) Then
		CallSub2(mCallBack, mEventName & "_Changed", getSelectedIds)
	End If
End Sub
#End Region

#Region Public Items API
Public Sub AddItem(Id As String, Text As String)
	Dim idNorm As String = IIf(Id = Null, "", Id.Trim)
	Dim txt As String = IIf(Text = Null, "", Text.Trim)
	If txt.Length = 0 Then txt = idNorm
	If txt.Length = 0 Then txt = "Item " & (CheckboxDefs.Size + 1)
	If idNorm.Length = 0 Then idNorm = BuildFallbackId(txt, CheckboxDefs.Size + 1)

	Dim idx As Int = FindDefinitionIndex(idNorm)
	If idx >= 0 Then
		Dim existing As Map = CheckboxDefs.Get(idx)
		existing.Put("text", txt)
		CheckboxDefs.Set(idx, existing)
	Else
		Dim uniqueId As String = EnsureUniqueId(idNorm)
		CheckboxDefs.Add(CreateMap("id": uniqueId, "text": txt))
	End If
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub RemoveItem(Id As String)
	If Id = Null Then Return
	Dim key As String = Id.Trim
	If key.Length = 0 Then Return
	Dim idx As Int = FindDefinitionIndex(key)
	If idx < 0 Then Return
	CheckboxDefs.RemoveAt(idx)
	SelectedLookup.Remove(key)
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub Clear
	CheckboxDefs.Clear
	SelectedLookup.Clear
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub setItems(Items As Map)
	CheckboxDefs.Clear
	SelectedLookup.Clear
	If Items = Null Then
		If mBase.IsInitialized Then RecreateCheckboxViews
		Return
	End If
	BuildDefinitionsFromMap(Items)
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub getItems As Map
	Dim out As Map
	out.Initialize
	For i = 0 To CheckboxDefs.Size - 1
		Dim src As Map = CheckboxDefs.Get(i)
		Dim id As String = src.Get("id")
		Dim txt As String = src.Get("text")
		out.Put(id, txt)
	Next
	Return out
End Sub
#End Region

#Region Selection API
Private Sub setSelectedIds(Ids As List)
	SelectedLookup.Clear
	If Ids.IsInitialized = False Then
		If mBase.IsInitialized Then ApplySelectionState(True)
		Return
	End If

	For Each rawId As Object In Ids
		Dim id As String = rawId
		If CheckboxById.ContainsKey(id) Or FindDefinitionIndex(id) >= 0 Then
			SelectedLookup.Put(id, True)
		End If
	Next

	If mBase.IsInitialized Then ApplySelectionState(True)
End Sub

Private Sub getSelectedIds As List
	Dim out As List
	out.Initialize
	For Each id As String In CheckboxOrder
		If SelectedLookup.ContainsKey(id) Then out.Add(id)
	Next
	Return out
End Sub

''' <summary>
''' Sets checked items from a semicolon-delimited string of IDs.
''' Example: "apples;bananas;oranges"
''' </summary>
Public Sub setChecked(CheckedIds As String)
	SelectedLookup.Clear
	If CheckedIds = Null Then
		If mBase.IsInitialized Then ApplySelectionState(True)
		Return
	End If
	Dim parts() As String = Regex.Split(";", CheckedIds)
	For Each id As String In parts
		id = id.Trim
		If id.Length > 0 And (CheckboxById.ContainsKey(id) Or FindDefinitionIndex(id) >= 0) Then
			SelectedLookup.Put(id, True)
		End If
	Next
	If mBase.IsInitialized Then ApplySelectionState(True)
End Sub

''' <summary>
''' Gets checked item IDs as a semicolon-delimited string.
''' Returns empty string if none checked.
''' </summary>
Public Sub getChecked As String
	Dim out As List
	out.Initialize
	For Each id As String In CheckboxOrder
		If SelectedLookup.ContainsKey(id) Then out.Add(id)
	Next
	Dim sb As StringBuilder
	sb.Initialize
	For i = 0 To out.Size - 1
		If i > 0 Then sb.Append(";")
		sb.Append(out.Get(i))
	Next
	Return sb.ToString
End Sub

Public Sub SetItemChecked(Id As String, Checked As Boolean)
	If Id = Null Then Return
	Dim key As String = Id.Trim
	If key.Length = 0 Then Return
	If Checked Then
		SelectedLookup.Put(key, True)
	Else
		SelectedLookup.Remove(key)
	End If
	If mBase.IsInitialized Then ApplySelectionState(True)
End Sub

Public Sub CheckItem(Id As String)
	SetItemChecked(Id, True)
End Sub

Public Sub UncheckItem(Id As String)
	SetItemChecked(Id, False)
End Sub

Public Sub IsItemChecked(Id As String) As Boolean
	If Id = Null Then Return False
	Return SelectedLookup.ContainsKey(Id.Trim)
End Sub
#End Region

#Region Styling Getters/Setters
Public Sub setLegend(Value As String)
	msLegend = Value
	Refresh
End Sub

Public Sub getLegend As String
	Return msLegend
End Sub

Public Sub setLegendSize(Value As String)
	msLegendSize = Value
	Refresh
End Sub

Public Sub getLegendSize As String
	Return msLegendSize
End Sub

Public Sub setLegendBold(Value As Boolean)
	mbLegendBold = Value
	Refresh
End Sub

Public Sub getLegendBold As Boolean
	Return mbLegendBold
End Sub

Public Sub setVariant(Value As String)
	msVariant = Value
	Refresh
End Sub

Public Sub getVariant As String
	Return msVariant
End Sub

Public Sub setDirection(Value As String)
	msDirection = Value
	Refresh
End Sub

Public Sub getDirection As String
	Return msDirection
End Sub

Public Sub setAlignment(Value As String)
	msAlignment = Value
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub getAlignment As String
	Return msAlignment
End Sub

Public Sub setCheckboxColor(Value As String)
	msCheckboxColor = Value
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub getCheckboxColor As String
	Return msCheckboxColor
End Sub

Public Sub setCheckboxSize(Value As String)
	msCheckboxSize = Value
	If mBase.IsInitialized Then RecreateCheckboxViews
End Sub

Public Sub getCheckboxSize As String
	Return msCheckboxSize
End Sub

Public Sub setAutoHeight(Value As Boolean)
	mbAutoHeight = Value
	Refresh
End Sub

Public Sub getAutoHeight As Boolean
	Return mbAutoHeight
End Sub

Public Sub setPadding(Value As Int)
	miPadding = Value
	Refresh
End Sub

Public Sub getPadding As Int
	Return miPadding
End Sub

Public Sub setGap(Value As Int)
	miGap = Value
	Refresh
End Sub

Public Sub getGap As Int
	Return miGap
End Sub

Public Sub setRowGap(Value As Int)
	miRowGap = Value
	Refresh
End Sub

Public Sub getRowGap As Int
	Return miRowGap
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

' Required / error validation helpers
Public Sub setRequired(Value As Boolean)
	mbRequired = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setLabelAbove(Value As Boolean)
	mbLabelAbove = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getLabelAbove As Boolean
	Return mbLabelAbove
End Sub

Public Sub setHintText(Value As String)
	If Value = Null Then Value = ""
	msHintText = Value.Trim
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getHintText As String
	Return msHintText
End Sub

Public Sub getRequired As Boolean
	Return mbRequired
End Sub

Public Sub setErrorText(Value As String)
	If Value = Null Then Value = ""
	msErrorText = Value.Trim
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getErrorText As String
	Return msErrorText
End Sub

Public Sub ShowError(ErrorMessage As String)
        msErrorText = ErrorMessage
        mbProgrammaticError = True
        If mBase.IsInitialized Then Refresh
End Sub

Public Sub ClearError
        msErrorText = ""
        mbProgrammaticError = False
        mbValidationTouched = False
        If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIsValid As Boolean
	Return (msErrorText.Length = 0)
End Sub

''' <summary>
''' Validates the group. When Required=True and no option is selected,
''' shows the configured ErrorText (or a default message) and returns False.
''' </summary>
Public Sub Validate As Boolean
	If mbRequired = False Then
                mbProgrammaticError = False
		ClearError
		Return True
	End If
	If SelectedLookup.Size > 0 Then
                mbProgrammaticError = False
		ClearError
		Return True
	End If
	Dim msg As String = msErrorText
	If msg.Length = 0 Then msg = "This field is required."
        mbValidationTouched = True
	ShowError(msg)
	Return False
End Sub

''' <summary>
''' Called when the checkbox group receives focus. Clears any transient
''' validation error visual so the user can interact without a distracting red border.
''' </summary>
Public Sub ReceiveFocus
        If msErrorText.Length > 0 And mbProgrammaticError = False Then
                msErrorText = ""
                If mBase.IsInitialized Then Refresh
        End If
End Sub

''' <summary>
''' Called when the component loses focus.
''' </summary>
Public Sub Blur
    ' No state changes on blur; skip Refresh to avoid a full re-layout on every focus loss.
End Sub
Public Sub setBorderStyle(Value As String)
	msBorderStyle = B4XDaisyVariants.NormalizeFieldsetBorderStyle(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderStyle As String
	Return msBorderStyle
End Sub

Public Sub setRounded(Value As String)
	msRounded = IIf(Value = Null, "theme", Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getRounded As String
	Return msRounded
End Sub

Public Sub isRounded As Boolean
	Return B4XDaisyVariants.NormalizeRounded(msRounded) <> "rounded-none"
End Sub

Public Sub setRoundedBox(Value As Boolean)
	mbRoundedBox = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub isRoundedBox As Boolean
	Return mbRoundedBox
End Sub

Public Sub setShadow(Value As String)
	msShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getShadow As String
	Return msShadow
End Sub

Public Sub setBackgroundColor(Value As Int)
	mcBackgroundColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBackgroundColor As Int
	Return mcBackgroundColor
End Sub

Public Sub setTextColor(Value As Int)
	mcTextColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getTextColor As Int
	Return mcTextColor
End Sub

Public Sub setBorderColor(Value As Int)
	mcBorderColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderColor As Int
	Return mcBorderColor
End Sub

Public Sub setBorderSize(Value As Int)
	miBorderSize = Max(0, Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderSize As Int
	Return miBorderSize
End Sub

Public Sub setInputBorder(Value As Boolean)
	mbInputBorder = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getInputBorder As Boolean
	Return mbInputBorder
End Sub
#End Region

#Region Internal Helpers
Private Sub BuildDefinitionsFromMap(Items As Map)
	If Items.IsInitialized = False Then Return
	Dim idx As Int = 1
	For Each rawKey As Object In Items.Keys
		Dim rawValue As Object = Items.Get(rawKey)
		Dim id As String = IIf(rawKey = Null, "", rawKey)
		id = id.Trim
		If rawValue Is Map Then
			Dim it As Map = rawValue
			Dim def As Map = CloneMap(it)
			If id.Length > 0 And def.ContainsKey("id") = False Then def.Put("id", id)
			If def.ContainsKey("text") = False And def.ContainsKey("label") Then def.Put("text", def.Get("label"))
			AddDefinitionFromMap(def, idx)
		Else
			Dim txt As String = IIf(rawValue = Null, "", rawValue)
			AddDefinitionItem(id, txt, idx)
		End If
		idx = idx + 1
	Next
End Sub



Private Sub AddDefinitionItem(Id As String, Text As String, Index As Int)
	Dim def As Map
	def.Initialize
	def.Put("id", Id)
	def.Put("text", Text)
	AddDefinitionFromMap(def, Index)
End Sub

Private Sub AddDefinitionFromMap(Source As Map, Index As Int)
	Dim def As Map = CloneMap(Source)
	Dim idNorm As String = GetDefString(def, Array As String("id"), "")
	Dim txt As String = GetDefString(def, Array As String("text", "label"), "")
	idNorm = idNorm.Trim
	If txt.Length = 0 Then txt = idNorm
	If txt.Length = 0 Then txt = "Item " & Index
	If idNorm.Length = 0 Then idNorm = BuildFallbackId(txt, Index)
	If DefinitionContainsId(idNorm) Then idNorm = EnsureUniqueId(idNorm)
	def.Put("id", idNorm)
	def.Put("text", txt)
	CheckboxDefs.Add(def)
	If def.ContainsKey("checked") Then
		If ResolveBoolValue(def.Get("checked"), False) Then
			SelectedLookup.Put(idNorm, True)
		Else
			SelectedLookup.Remove(idNorm)
		End If
	End If
End Sub

Private Sub BuildFallbackId(Text As String, Index As Int) As String
	Dim src As String = IIf(Text = Null, "", Text.ToLowerCase.Trim)
	If src.Length = 0 Then Return "item-" & Index
	src = src.Replace(" ", "-")
	src = src.Replace(":", "-")
	src = src.Replace("|", "-")
	src = src.Replace(",", "-")
	src = src.Replace("/", "-")
	src = src.Replace("\", "-")
	Do While src.Contains("--")
		src = src.Replace("--", "-")
	Loop
	If src.StartsWith("-") Then src = src.SubString(1)
	If src.EndsWith("-") Then src = src.SubString2(0, src.Length - 1)
	If src.Length = 0 Then src = "item-" & Index
	Return src
End Sub

Private Sub EnsureUniqueId(Value As String) As String
	Dim baseId As String = IIf(Value = Null, "", Value.Trim)
	If baseId.Length = 0 Then baseId = "item"
	Dim candidate As String = baseId
	Dim n As Int = 2
	Do While DefinitionContainsId(candidate)
		candidate = baseId & "-" & n
		n = n + 1
	Loop
	Return candidate
End Sub

Private Sub DefinitionContainsId(Id As String) As Boolean
	For i = 0 To CheckboxDefs.Size - 1
		Dim d As Map = CheckboxDefs.Get(i)
		If d.GetDefault("id", "") = Id Then Return True
	Next
	Return False
End Sub

Private Sub FindDefinitionIndex(Id As String) As Int
	For i = 0 To CheckboxDefs.Size - 1
		Dim d As Map = CheckboxDefs.Get(i)
		If d.GetDefault("id", "") = Id Then Return i
	Next
	Return -1
End Sub

Private Sub CloneMap(Source As Map) As Map
	Dim out As Map
	out.Initialize
	If Source.IsInitialized = False Then Return out
	For Each k As Object In Source.Keys
		out.Put(k, Source.Get(k))
	Next
	Return out
End Sub

Private Sub GetDefValue(Def As Map, Keys() As String, DefaultValue As Object) As Object
	If Def.IsInitialized = False Then Return DefaultValue
	For i = 0 To Keys.Length - 1
		Dim k As String = Keys(i)
		If Def.ContainsKey(k) Then Return Def.Get(k)
	Next
	Return DefaultValue
End Sub

Private Sub GetDefString(Def As Map, Keys() As String, DefaultValue As String) As String
	Dim raw As Object = GetDefValue(Def, Keys, DefaultValue)
	If raw = Null Then Return DefaultValue
	Dim s As String = raw
	s = s.Trim
	If s.Length = 0 Then Return DefaultValue
	Return s
End Sub

Private Sub ResolveBoolValue(Value As Object, DefaultValue As Boolean) As Boolean
	If Value = Null Then Return DefaultValue
	If Value Is Boolean Then Return Value
	If IsNumber(Value) Then Return (Value <> 0)
	Dim s As String = Value
	s = s.ToLowerCase.Trim
	Select Case s
		Case "true", "1", "yes", "y", "on"
			Return True
		Case "false", "0", "no", "n", "off", ""
			Return False
		Case Else
			Return DefaultValue
	End Select
End Sub
#End Region

''' <summary>
''' Gets the complete height of the component.
''' </summary>
Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

#Region Cleanup
Public Sub Release
	For Each id As String In CheckboxOrder
		If CheckboxById.ContainsKey(id) Then
			Dim cb As B4XDaisyCheckbox = CheckboxById.Get(id)
			cb.Release
			cb.setTag(Null)
		End If
	Next
	CheckboxById.Clear
	CheckboxViewById.Clear
	CheckboxDefs.Clear
	CheckboxOrder.Clear
	CheckboxTextById.Clear
	SelectedLookup.Clear
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyCheckbox.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9
#Region Events
#Event: Checked (Checked As Boolean)
#Event: Click (Tag As Object)
#Event: FocusChanged (HasFocus As Boolean)
#End Region

#Region Designer Properties
#DesignerProperty: Key: GroupName, DisplayName: Group Name, FieldType: String, DefaultValue: , Description: Checkbox group name.
#DesignerProperty: Key: Checked, DisplayName: Checked, FieldType: Boolean, DefaultValue: False, Description: Checked state.
#DesignerProperty: Key: Indeterminate, DisplayName: Indeterminate, FieldType: Boolean, DefaultValue: False, Description: Indeterminate state.
#DesignerProperty: Key: Value, DisplayName: Value, FieldType: String, DefaultValue: , Description: Value assigned to the checkbox.
#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: , Description: Label text.
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Color variant.
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Size variant.
#DesignerProperty: Key: Position, DisplayName: Position, FieldType: String, DefaultValue: start, List: start|end, Description: Position alignment.
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: Enabled state.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation shadow level.
#DesignerProperty: Key: CheckedBackgroundColor, DisplayName: Checked Background Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override checked background color.
#DesignerProperty: Key: CheckedBorderColor, DisplayName: Checked Border Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override checked border color.
#DesignerProperty: Key: CheckedTextColor, DisplayName: Checked Checkmark Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override checked checkmark/text color.
#End Region

#Region Variables
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object

	Private pnlSurface As B4XView
	Private pnlCheckSquare As B4XView
	Private pnlCheckCanvas As B4XView
	Private lblText As B4XView
	Private cvs As B4XCanvas

	Private mChecked As Boolean = False
	Private mIndeterminate As Boolean = False
	Private mText As String = ""
	Private mTextCS As Object = Null ' CharSequence (CSBuilder) label, used when TextCS is set
	Private mHasTextCS As Boolean = False
	Private mbMultiline As Boolean = False
	Private mfLineSpacingExtra As Float = 0    ' extra space (pixels) added between label lines
	Private mfLineSpacingMult As Float = 1.0   ' line-height multiplier applied to the label
	Private mbRequired As Boolean = False
        Private mbValidationTouched As Boolean = False
        Private mbProgrammaticError As Boolean = False
	Private msErrorText As String = ""
	Private mVariant As String = "none"
	Private mSize As String = "md"
	Private msPosition As String = "start"
	Private mEnabled As Boolean = True
	Private mVisible As Boolean = True

	Private mcBackgroundColor As Int = 0
	Private mcTextColor As Int = 0
	Private mcBorderColor As Int = 0
	Private mcCheckedBackgroundColor As Int = 0
	Private mcCheckedBorderColor As Int = 0
	Private mcCheckedTextColor As Int = 0

	Private mShadow As String = "none"
	Private mValue As String = ""
	Private mGroupName As String = ""
	' DaisyUI token support map for parity validation:
	' [clip-path:none] [--tw-content:"✔︎"] align-middle appearance-none bg-base-100
	' bg-current bg-indigo-500 bg-transparent block border border-base-300
	' border-indigo-600 checkbox checkbox-accent checkbox-error checkbox-info
	' checkbox-lg checkbox-md checkbox-neutral checkbox-primary checkbox-secondary
	' checkbox-sm checkbox-success checkbox-warning checkbox-xl checkbox-xs
	' checked:bg-orange-400 checked:border-orange-500 checked:text-orange-800
	' cursor-not-allowed cursor-pointer fieldset fieldset-legend inline-block
	' label opacity-0 opacity-100 opacity-20 p-[0.125rem] p-[0.1875rem]
	' p-[0.25rem] p-[0.3125rem] p-[0.375rem] p-1 p-4 relative rotate-0
	' rotate-45 rounded-box rounded-selector shrink-0 size-full text-accent-content
	' text-base-content text-error-content text-info-content text-neutral-content
	' text-primary-content text-secondary-content text-success-content
	' text-warning-content w-64
End Sub
#End Region

#Region Initialization
''' <summary>
''' Initializes the component.
''' </summary>
Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

''' <summary>
''' Designer entry point.
''' </summary>
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	' 1. Create outer surface panel for click containment and label + square grouping
	Dim pSurface As Panel
	pSurface.Initialize("pnlSurface")
	pnlSurface = pSurface
	pnlSurface.Color = xui.Color_Transparent
	mBase.AddView(pnlSurface, 0, 0, mBase.Width, mBase.Height)

	' 2. Create inner panel representing the checkable square
	Dim pCheck As Panel
	pCheck.Initialize("")
	pnlCheckSquare = pCheck
	pnlCheckSquare.Color = xui.Color_Transparent
	pnlSurface.AddView(pnlCheckSquare, 0, 0, 24dip, 24dip)

	' Isolated transparent canvas panel inside checkable square for drawing tick/minus glyphs
	Dim pCanvas As Panel
	pCanvas.Initialize("")
	pnlCheckCanvas = pCanvas
	pnlCheckCanvas.Color = xui.Color_Transparent
	pnlCheckSquare.AddView(pnlCheckCanvas, 0, 0, 24dip, 24dip)

	' Initialize B4XCanvas on the isolated canvas panel
	cvs.Initialize(pnlCheckCanvas)

	' 3. Create the text label
	Dim l As Label
	l.Initialize("")
	l.SingleLine = True
	lblText = l
	lblText.Color = xui.Color_Transparent
	lblText.SetTextAlignment("CENTER", "LEFT")
	pnlSurface.AddView(lblText, 0, 0, 1dip, 1dip)

	' 4. Read properties
	mChecked = B4XDaisyVariants.GetPropBool(Props, "Checked", mChecked)
	mIndeterminate = B4XDaisyVariants.GetPropBool(Props, "Indeterminate", mIndeterminate)
	mText = B4XDaisyVariants.GetPropString(Props, "Text", mText)
	mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	msPosition = B4XDaisyVariants.GetPropString(Props, "Position", msPosition)
	mEnabled = B4XDaisyVariants.GetPropBool(Props, "Enabled", mEnabled)
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)

	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mValue = B4XDaisyVariants.GetPropString(Props, "Value", mValue)
	mGroupName = B4XDaisyVariants.GetPropString(Props, "GroupName", mGroupName)

	mcBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mcBackgroundColor)
	mcTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mcTextColor)
	mcBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mcBorderColor)
	mcCheckedBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "CheckedBackgroundColor", mcCheckedBackgroundColor)
	mcCheckedBorderColor = B4XDaisyVariants.GetPropColor(Props, "CheckedBorderColor", mcCheckedBorderColor)
	mcCheckedTextColor = B4XDaisyVariants.GetPropColor(Props, "CheckedTextColor", mcCheckedTextColor)

	mBase.Visible = mVisible
	#If B4A
		' setFocusable(True) allows keyboard/accessibility focus on the panel.
		' setFocusableInTouchMode is intentionally omitted: it would cause Android
		' to consume the first touch as a focus event, requiring a double-tap.
		Dim jo As JavaObject = pnlSurface
		jo.RunMethod("setFocusable", Array(True))
	#End If
	Refresh
End Sub
#End Region

#Region Public API
''' <summary>
''' Sets the Checked state.
''' </summary>

Private Sub ResolveLabelText(BaseText As String) As String
	Return BaseText
End Sub

Public Sub setChecked(Value As Boolean)
	mChecked = Value
	If Value Then mIndeterminate = False
	Refresh
End Sub

''' <summary>
''' Gets the Checked state.
''' </summary>
Public Sub getChecked As Boolean
	Return mChecked
End Sub

''' <summary>
''' Sets the Value.
''' </summary>
Public Sub setValue(Value As String)
	mValue = Value
End Sub

''' <summary>
''' Gets the Value.
''' </summary>
Public Sub getValue As String
	Return mValue
End Sub

''' <summary>
''' Sets the GroupName.
''' </summary>
Public Sub setGroupName(Value As String)
	mGroupName = Value
End Sub

''' <summary>
''' Gets the GroupName.
''' </summary>
Public Sub getGroupName As String
	Return mGroupName
End Sub

''' <summary>
''' Returns the component role.
''' </summary>
Public Sub getRole As String
	Return "checkbox"
End Sub

''' <summary>
''' Sets the Indeterminate state.
''' </summary>
Public Sub setIndeterminate(Value As Boolean)
	mIndeterminate = Value
	If Value Then mChecked = False
	Refresh
End Sub

''' <summary>
''' Gets the Indeterminate state.
''' </summary>
Public Sub getIndeterminate As Boolean
	Return mIndeterminate
End Sub

''' <summary>
''' Sets the Label Text.
''' </summary>
Public Sub setText(Value As String)
	mText = Value
	Refresh
End Sub

''' <summary>
''' Gets the Label Text.
''' </summary>
Public Sub getText As String
	Return mText
End Sub

''' <summary>
''' Sets the label text as a CharSequence (CSBuilder). When set, this takes precedence
''' over Text and supports clickable spans (call EnableClickEvents on the CSBuilder is
''' handled internally so tap events route to the module that built the CSBuilder).
''' </summary>
Public Sub setTextCS(Value As Object)
	mTextCS = Value
	mHasTextCS = True
	If mBase.IsInitialized Then Refresh
End Sub

''' <summary>
''' Gets the CharSequence label (CSBuilder) if set.
''' </summary>
Public Sub getTextCS As Object
	Return mTextCS
End Sub

''' <summary>
''' Enables multi-line wrapping for the label (use with TextCS for tappable links).
''' </summary>
Public Sub setMultiline(Value As Boolean)
	mbMultiline = Value
	If mBase.IsInitialized Then Refresh
End Sub

''' <summary>
''' Gets whether the label wraps across multiple lines.
''' </summary>
Public Sub getMultiline As Boolean
	Return mbMultiline
End Sub

''' <summary>
''' Extra spacing (pixels, pass e.g. 4dip) added between label lines. Applied to
''' multi-line / TextCS labels via the native TextView.setLineSpacing API.
''' </summary>
Public Sub setLineSpacing(Extra As Float)
	mfLineSpacingExtra = Extra
	If mBase.IsInitialized Then Refresh
End Sub

''' <summary>
''' Gets the extra line spacing (pixels) applied between label lines.
''' </summary>
Public Sub getLineSpacing As Float
	Return mfLineSpacingExtra
End Sub

''' <summary>
''' Line-height multiplier applied together with LineSpacing (default 1.0).
''' </summary>
Public Sub setLineSpacingMult(Value As Float)
	mfLineSpacingMult = Value
	If mBase.IsInitialized Then Refresh
End Sub

''' <summary>
''' Gets the line-height multiplier.
''' </summary>
Public Sub getLineSpacingMult As Float
	Return mfLineSpacingMult
End Sub


''' <summary>
''' Sets the Variant.
''' </summary>
Public Sub setVariant(Value As String)
	mVariant = B4XDaisyVariants.NormalizeVariant(Value)
	Refresh
End Sub

''' <summary>
''' Gets the Variant.
''' </summary>
Public Sub getVariant As String
	Return mVariant
End Sub

''' <summary>
''' Sets the Size.
''' </summary>
Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	Refresh
End Sub

''' <summary>
''' Gets the Size.
''' </summary>
Public Sub getSize As String
	Return mSize
End Sub



''' <summary>
''' Sets the Position alignment.
''' </summary>
Public Sub setPosition(Value As String)
	msPosition = Value
	Refresh
End Sub

''' <summary>
''' Gets the Position alignment.
''' </summary>
Public Sub getPosition As String
	Return msPosition
End Sub



''' <summary>
''' Sets the Enabled state.
''' </summary>
Public Sub setEnabled(Value As Boolean)
	mEnabled = Value
	Refresh
End Sub

''' <summary>
''' Gets the Enabled state.
''' </summary>
Public Sub getEnabled As Boolean
	Return mEnabled
End Sub

''' <summary>
''' Sets the Visible state.
''' </summary>
Public Sub setVisible(Value As Boolean)
	mVisible = Value
	mBase.Visible = Value
	Refresh
End Sub

''' <summary>
''' Gets the Visible state.
''' </summary>
Public Sub getVisible As Boolean
	Return mVisible
End Sub

''' <summary>
''' Sets the Shadow elevation level.
''' </summary>
Public Sub setShadow(Value As String)
	mShadow = B4XDaisyVariants.NormalizeShadow(Value)
	Refresh
End Sub

''' <summary>
''' Gets the Shadow elevation level.
''' </summary>
Public Sub getShadow As String
	Return mShadow
End Sub

''' <summary>
''' Sets the component tag.
''' </summary>
Public Sub setTag(Value As Object)
	mTag = Value
End Sub

''' <summary>
''' Gets the component tag.
''' </summary>
Public Sub getTag As Object
	Return mTag
End Sub

''' <summary>
''' Sets the background color override.
''' </summary>

' Required / error validation helpers
Public Sub setRequired(Value As Boolean)
	mbRequired = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getRequired As Boolean
	Return mbRequired
End Sub

Public Sub setErrorText(Value As String)
	If Value = Null Then Value = ""
	msErrorText = Value.Trim
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getErrorText As String
	Return msErrorText
End Sub

Public Sub getIsValid As Boolean
	Return (msErrorText.Length = 0)
End Sub

''' <summary>
''' Dynamically sets the checkbox into an error validation state and displays the specified error text.
''' </summary>
Public Sub ShowError(ErrorMessage As String)
        msErrorText = ErrorMessage
        mbProgrammaticError = True
        If mBase.IsInitialized Then Refresh
End Sub

''' <summary>
''' Clears any dynamic error state and returns the checkbox to normal.
''' </summary>
Public Sub ClearError
        msErrorText = ""
        mbProgrammaticError = False
        mbValidationTouched = False
        If mBase.IsInitialized Then Refresh
End Sub

Public Sub Validate As Boolean
	If mbRequired = False Then
        mbProgrammaticError = False
		msErrorText = ""
		Return True
	End If
	If mChecked Then
        mbProgrammaticError = False
		msErrorText = ""
		Return True
	End If
        mbValidationTouched = True
	If msErrorText.Length = 0 Then msErrorText = "This field is required."
	If mBase.IsInitialized Then Refresh
	Return False
End Sub

Public Sub setBackgroundColor(Color As Int)
	mcBackgroundColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the background color override.
''' </summary>
Public Sub getBackgroundColor As Int
	Return mcBackgroundColor
End Sub

''' <summary>
''' Sets the border color override.
''' </summary>
Public Sub setBorderColor(Color As Int)
	mcBorderColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the border color override.
''' </summary>
Public Sub getBorderColor As Int
	Return mcBorderColor
End Sub

''' <summary>
''' Sets the text/checkmark color override.
''' </summary>
Public Sub setTextColor(Color As Int)
	mcTextColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the text/checkmark color override.
''' </summary>
Public Sub getTextColor As Int
	Return mcTextColor
End Sub

''' <summary>
''' Sets the checked background color override.
''' </summary>
Public Sub setCheckedBackgroundColor(Color As Int)
	mcCheckedBackgroundColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the checked background color override.
''' </summary>
Public Sub getCheckedBackgroundColor As Int
	Return mcCheckedBackgroundColor
End Sub

''' <summary>
''' Sets the checked border color override.
''' </summary>
Public Sub setCheckedBorderColor(Color As Int)
	mcCheckedBorderColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the checked border color override.
''' </summary>
Public Sub getCheckedBorderColor As Int
	Return mcCheckedBorderColor
End Sub

''' <summary>
''' Sets the checked checkmark color override.
''' </summary>
Public Sub setCheckedTextColor(Color As Int)
	mcCheckedTextColor = Color
	Refresh
End Sub

''' <summary>
''' Gets the checked checkmark color override.
''' </summary>
Public Sub getCheckedTextColor As Int
	Return mcCheckedTextColor
End Sub

''' <summary>
''' Forces the component to re-resolve theme-aware styling.
''' </summary>
Public Sub UpdateTheme
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

''' <summary>
''' Applies the current component state to the UI.
''' </summary>
Public Sub Refresh
	If mBase.IsInitialized = False Then Return
	mBase.Alpha = IIf(mEnabled, 1.0, 0.3)
	Base_Resize(mBase.Width, mBase.Height)
End Sub

''' <summary>
''' Adds the component to a parent B4XView.
''' </summary>
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	If Parent.IsInitialized = False Then Return mBase
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("mBase")
		DesignerCreateView(p, Null, CreateMap())
	End If
	
	' Handle fit-content sizing if dimensions are <= 0
	Dim targetW As Int = Width
	Dim targetH As Int = Height
	If targetW <= 0 Then targetW = EstimatePreferredWidth
	If targetH <= 0 Then targetH = EstimatePreferredHeight
	
	Parent.AddView(mBase, Left, Top, targetW, targetH)
	Refresh
	Return mBase
End Sub

''' <summary>
''' Returns the core view instance.
''' </summary>
Public Sub getView As B4XView
	Return mBase
End Sub

''' <summary>
''' Gets the complete height of the component.
''' </summary>
Public Sub getComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

''' <summary>
''' Requests focus for the component.
''' </summary>
Public Sub RequestFocus
	If pnlSurface.IsInitialized Then pnlSurface.RequestFocus
End Sub

''' <summary>
''' Sets or clears focus for the component.
''' </summary>
Public Sub setFocus(Value As Boolean)
	If pnlSurface.IsInitialized = False Then Return
	#If B4A
		Dim jo As JavaObject = pnlSurface
		If Value Then jo.RunMethod("requestFocus", Null) Else jo.RunMethod("clearFocus", Null)
	#End If
End Sub

''' <summary>
''' Called when the checkbox receives focus. Clears any transient
''' validation error visual so the user can interact without a distracting red border.
''' </summary>
Public Sub ReceiveFocus
        If msErrorText.Length > 0 And mbProgrammaticError = False Then
                msErrorText = ""
                If mBase.IsInitialized Then Refresh
        End If
End Sub

''' <summary>
''' Called when the component loses focus.
''' </summary>
Public Sub Blur
    ' No state changes on blur; skip Refresh to avoid re-layout on every focus loss.
End Sub
#End Region

#Region Drawing and Layout
Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	If pnlSurface.IsInitialized = False Then Return

	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)

	pnlSurface.SetLayoutAnimated(0, 0, 0, w, h)

	Dim squareSize As Float = ResolveCheckboxSizeDip
	pnlCheckSquare.SetLayoutAnimated(0, 0, 0, squareSize, squareSize)
	If pnlCheckCanvas.IsInitialized Then
		pnlCheckCanvas.SetLayoutAnimated(0, 0, 0, squareSize, squareSize)
	End If
	cvs.Resize(squareSize, squareSize)

	Dim hasText As Boolean
	Dim textVal As String
	If mHasTextCS Then
		hasText = True
	Else
		textVal = mText
		If mbMultiline = False Then textVal = B4XDaisyVariants.NormalizeSingleLineText(mText)
		hasText = textVal.Length > 0
	End If

	If hasText = False Then
		' Position checkbox square in host panel based on position alignment
		Dim leftX As Float = 0
		If msPosition = "end" Then
			leftX = w - squareSize
		End If
		Dim topY As Float = (h - squareSize) / 2
		pnlCheckSquare.SetLayoutAnimated(0, leftX, topY, squareSize, squareSize)
		lblText.Visible = False
	Else
		lblText.Visible = True
		' B4A: toggle single-line vs multi-line wrapping on the native label.
		#If B4A
		Dim lblNative As Label = lblText
		lblNative.SingleLine = Not(mbMultiline)
		If mbMultiline And mfLineSpacingExtra > 0 Then
			Dim jolbl As JavaObject = lblNative
			jolbl.RunMethod("setLineSpacing", Array As Object(mfLineSpacingExtra, mfLineSpacingMult))
		End If
		#End If
		If mHasTextCS Then
			' CharSequence (CSBuilder) label with clickable spans.
			#If B4A
			Dim cs As CSBuilder = mTextCS
			lblNative.Text = cs
			' Custom movement method: only taps landing on a ClickableSpan are consumed (and
			' fire the span's onClick -> Terms_Click / Privacy_Click). Taps on the non-link
			' label text are NOT consumed so they fall through to the surface and toggle
			' the checkbox. The label is made non-clickable so it never steals non-link taps.
			Dim nativeMe As JavaObject = Me
			Dim mm As Object = nativeMe.RunMethod("createLinkOnlyMovementMethod", Null)
			Dim jolbl As JavaObject = lblNative
			jolbl.RunMethod("setMovementMethod", Array As Object(mm))
			jolbl.RunMethod("setClickable", Array As Object(False))
			#End If
		Else
			lblText.Text = ResolveLabelText(textVal)
		End If
		Dim fontSize As Float = ResolveTextSizePoints
		lblText.Font = xui.CreateDefaultFont(fontSize)

		' Apply Text Color from Active Theme
		Dim visualColors As Map = ResolveVisualColors
		Dim labelColor As Int = visualColors.Get("label")
		If msErrorText.Length > 0 Then
			labelColor = B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444)
		End If
		lblText.TextColor = labelColor

		Dim gap As Float = 8dip
		Dim topY As Float = (h - squareSize) / 2

		If msPosition = "end" Then
			' Label on the left, Checkbox on the right
			Dim textLeft As Float = 0
			Dim textWidth As Float = w - squareSize - gap
			lblText.SetLayoutAnimated(0, textLeft, 0, textWidth, h)
			lblText.SetTextAlignment("CENTER", "LEFT")

			Dim leftX As Float = w - squareSize
			pnlCheckSquare.SetLayoutAnimated(0, leftX, topY, squareSize, squareSize)
		Else
			' Label on the right, Checkbox on the left
			Dim leftX As Float = 0
			pnlCheckSquare.SetLayoutAnimated(0, leftX, topY, squareSize, squareSize)

			Dim textLeft As Float = squareSize + gap
			Dim textWidth As Float = w - textLeft
			lblText.SetLayoutAnimated(0, textLeft, 0, textWidth, h)
			lblText.SetTextAlignment("CENTER", "LEFT")
		End If
	End If

	DrawCheckbox
End Sub

Private Sub DrawCheckbox
	If pnlCheckSquare.IsInitialized = False Then Return

	Dim xColors As Map = ResolveVisualColors
	Dim bgColor As Int = xColors.Get("back")
	Dim borderColor As Int = xColors.Get("border")
	Dim tickColor As Int = xColors.Get("text")

	Dim bw As Float = Max(0, B4XDaisyVariants.GetBorderDip(1dip))
	Dim sz As Float = pnlCheckSquare.Width
	Dim radius As Float = ResolveCheckboxRadiusDip

	' 1. Draw rounded box via GradientDrawable (B4A native)
	#If B4A
	Try
		Dim gd As JavaObject
		gd.InitializeNewInstance("android.graphics.drawable.GradientDrawable", Null)
		gd.RunMethod("setShape", Array(0))        ' RECTANGLE
		gd.RunMethod("setColor", Array(bgColor))
		Dim r As Float = Max(0, radius)
		Dim radii() As Float = Array As Float(r, r, r, r, r, r, r, r)
		gd.RunMethod("setCornerRadii", Array(radii))
		If bw > 0 Then gd.RunMethod("setStroke", Array(Round(bw), borderColor))
		Dim joPanel As JavaObject = pnlCheckSquare
		joPanel.RunMethod("setBackground", Array(gd))
	Catch
		pnlCheckSquare.SetColorAndBorder(bgColor, bw, borderColor, radius)
	End Try
	#Else
	pnlCheckSquare.SetColorAndBorder(bgColor, bw, borderColor, radius)
	#End If

	' Apply elevation shadow to the checkbox square (follows corner radius)
	B4XDaisyVariants.ApplyElevation(pnlCheckSquare, mShadow)

	' 2. Draw tick or minus glyph via B4XCanvas
	cvs.ClearRect(cvs.TargetRect)
	
	' DaisyUI checkbox uses p-[0.25rem] padding for md, so inner checkmark area is smaller
	' Checkmark stroke scales with size (DaisyUI: 2px base, scales with size)
	Dim strokeThickness As Float = Max(2dip, 2.5dip * (sz / 24dip))
	
	If mChecked Then
		' Checkmark: L-shape - DaisyUI uses clip-path polygon for proportions
		' Approximating: start ~20% from left, 50% from top; mid ~45%, 70%; end ~70%, 30%
		Dim startX As Float = 0.22 * sz
		Dim startY As Float = 0.50 * sz
		Dim midX As Float = 0.45 * sz
		Dim midY As Float = 0.70 * sz
		Dim endX As Float = 0.72 * sz
		Dim endY As Float = 0.30 * sz
		Dim p As B4XPath
		p.Initialize(startX, startY)
		p.LineTo(midX, midY)
		p.LineTo(endX, endY)
		cvs.DrawPath(p, tickColor, False, strokeThickness)
	Else If mIndeterminate Then
		' Indeterminate: horizontal line centered (DaisyUI translates -35% vertically)
		Dim sx As Float = 0.20 * sz
		Dim ex As Float = 0.80 * sz
		Dim cy As Float = 0.45 * sz  ' Slightly higher like DaisyUI's translate: 0 -35%
		cvs.DrawLine(sx, cy, ex, cy, tickColor, strokeThickness)
	End If
	cvs.Invalidate
End Sub

Private Sub ResolveVisualColors As Map
	Dim tokens As Map = B4XDaisyVariants.GetActiveTokens
	Dim base100 As Int = tokens.GetDefault("--color-base-100", xui.Color_White)
	Dim baseContent As Int = tokens.GetDefault("--color-base-content", xui.Color_RGB(31, 41, 55))

	' DaisyUI checkbox border: color-mix(in oklab, var(--color-base-content) 20%, #0000)
	' Approximate as 20% opacity baseContent over transparent
	Dim borderColor As Int = B4XDaisyVariants.AlphaColor(baseContent, 0.20)

	Dim back As Int = base100
	Dim border As Int = borderColor
	Dim text As Int = baseContent

	Dim variantKey As String = mVariant.ToLowerCase
	If variantKey = "none" Then
		If mChecked Then
			back = baseContent
			border = baseContent
			text = base100
		Else If mIndeterminate Then
			back = B4XDaisyVariants.AlphaColor(baseContent, 0.20)
			border = B4XDaisyVariants.AlphaColor(baseContent, 0.20)
			text = baseContent
		Else
			back = base100
			border = borderColor
			text = xui.Color_Transparent
		End If
	Else
		Dim variantBg As Int = B4XDaisyVariants.ResolveBackgroundColorVariant(variantKey, baseContent)
		Dim variantText As Int = B4XDaisyVariants.ResolveTextColorVariant(variantKey, xui.Color_White)
		Dim variantBorder As Int = B4XDaisyVariants.AlphaColor(variantBg, 0.20)

		If mChecked Then
			back = variantBg
			border = variantBg
			text = variantText
		Else If mIndeterminate Then
			back = B4XDaisyVariants.AlphaColor(variantBg, 0.20)
			border = variantBorder
			text = variantBg
		Else
			back = base100
			border = variantBorder
			text = xui.Color_Transparent
		End If
	End If

	' Apply custom properties override
	Dim lblColor As Int = baseContent
	If mChecked Then
		If mcCheckedBackgroundColor <> 0 Then
			back = mcCheckedBackgroundColor
		Else If mcBackgroundColor <> 0 Then
			back = mcBackgroundColor
		End If
		
		If mcCheckedBorderColor <> 0 Then
			border = mcCheckedBorderColor
		Else If mcBorderColor <> 0 Then
			border = mcBorderColor
		End If
		
		If mcCheckedTextColor <> 0 Then
			text = mcCheckedTextColor
		Else If mcTextColor <> 0 Then
			text = mcTextColor
		End If
	Else
		If mcBackgroundColor <> 0 Then back = mcBackgroundColor
		If mcBorderColor <> 0 Then border = mcBorderColor
	End If

	Return CreateMap("back": back, "border": border, "text": text, "label": lblColor)
End Sub

Private Sub ResolveCheckboxSizeDip As Float
	Select Case mSize.ToLowerCase
		Case "xs": Return 16dip
		Case "sm": Return 20dip
		Case "md": Return 24dip
		Case "lg": Return 28dip
		Case "xl": Return 32dip
		Case Else
			Return B4XDaisyVariants.TailwindSizeToDip(mSize, 24dip)
	End Select
End Sub

Private Sub ResolveTextSizePoints As Float
	Select Case mSize.ToLowerCase
		Case "xs": Return 11
		Case "sm": Return 12
		Case "md": Return 14
		Case "lg": Return 18
		Case "xl": Return 22
		Case Else: Return 14
	End Select
End Sub

Private Sub ResolveCheckboxRadiusDip As Float
	Return B4XDaisyVariants.GetRadiusSelectorDip(4dip)
End Sub

Private Sub EstimatePreferredWidth As Int
	Dim squareSize As Float = ResolveCheckboxSizeDip
	If mText.Length = 0 Then Return squareSize
	Dim fontSize As Float = ResolveTextSizePoints
	Return squareSize + 8dip + EstimateTextWidth(mText, fontSize)
End Sub

Private Sub EstimatePreferredHeight As Int
	Dim squareSize As Float = ResolveCheckboxSizeDip
	If mText.Length = 0 Then Return squareSize
	Dim fontSize As Float = ResolveTextSizePoints
	Return Max(squareSize, EstimateTextHeight(mText, fontSize))
End Sub

Private Sub EstimateTextWidth(Text As String, FontSize As Float) As Int
	If Text.Length = 0 Then Return 0
	Try
		If lblText.IsInitialized Then
			Dim l As Label = lblText
			Return B4XDaisyVariants.MeasureTextWidthSafe(Text, FontSize, l.Typeface, 2dip)
		End If
	Catch
		Log("B4XDaisyCheckbox.EstimateTextWidth: " & LastException.Message)
	End Try
	Return Max(1dip, Ceil(Text.Length * FontSize * 0.62) + 2dip)
End Sub

Private Sub EstimateTextHeight(Text As String, FontSize As Float) As Int
	If Text.Length = 0 Then Return 0
	Try
		If lblText.IsInitialized Then
			Dim l As Label = lblText
			Return B4XDaisyVariants.MeasureTextHeightSafe(Text, FontSize, l.Typeface, 2000dip, 2dip)
		End If
	Catch
		Log("B4XDaisyCheckbox.EstimateTextHeight: " & LastException.Message)
	End Try
	Return Max(1dip, Ceil(FontSize * 1.3) + 2dip)
End Sub
#End Region

#Region Click Event Handling
Private Sub pnlSurface_Click
	If mEnabled = False Then Return
	mChecked = Not(mChecked)
	mIndeterminate = False
	Refresh
	RaiseCheckedEvent
	RaiseClick
End Sub

Private Sub RaiseCheckedEvent
	If xui.SubExists(mCallBack, mEventName & "_Checked", 1) Then
		CallSub2(mCallBack, mEventName & "_Checked", mChecked)
	End If
End Sub

Private Sub RaiseClick
	Dim payload As Object = mTag
	If payload = Null Then payload = mBase
	If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
		CallSub2(mCallBack, mEventName & "_Click", payload)
	Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
		CallSub(mCallBack, mEventName & "_Click")
	End If
End Sub

Private Sub pnlSurface_FocusChanged(HasFocus As Boolean)
        ' Route focus changes through public validation hooks:
        ' clear transient errors on focus, re-validate on blur.
        If HasFocus Then
                ReceiveFocus
        Else
                Blur
        End If
        If xui.SubExists(mCallBack, mEventName & "_FocusChanged", 1) Then
                CallSub2(mCallBack, mEventName & "_FocusChanged", HasFocus)
        End If
End Sub
#End Region

#Region Cleanup
Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub Release
	pnlSurface = Null
	pnlCheckSquare = Null
	pnlCheckCanvas = Null
	lblText = Null
	Try
		cvs.Release
	Catch
		Log("B4XDaisyCheckbox.Release: " & LastException.Message)
	End Try
	cvs = Null
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region


#If Java
import android.text.Layout;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * Returns a MovementMethod that ONLY consumes touches which land on a ClickableSpan
 * (firing the span's onClick on ACTION_UP). Touches on non-link text are not consumed,
 * so they fall through to the parent view (the checkbox surface) and toggle the box.
 * This avoids the OEM-dependent behaviour of the stock LinkMovementMethod, which can
 * either swallow non-link taps or let link taps also trigger the parent's click.
 */
public Object createLinkOnlyMovementMethod() {
	return new LinkMovementMethod() {
		@Override
		public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
			int action = event.getAction();
			if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
				int x = (int) event.getX();
				int y = (int) event.getY();
				x -= widget.getTotalPaddingLeft();
				y -= widget.getTotalPaddingTop();
				x += widget.getScrollX();
				y += widget.getScrollY();
				Layout layout = widget.getLayout();
				if (layout == null) return false;
				int line = layout.getLineForVertical(y);
				int off = layout.getOffsetForHorizontal(line, x);
				ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
				if (link.length != 0) {
					if (action == MotionEvent.ACTION_UP) {
						link[0].onClick(widget);
					}
					return true;   // consume link touches -> no checkbox toggle
				}
				return false;      // non-link -> fall through to surface -> toggle
			}
			return false;
		}
	};
}
#End If

---

## B4XDaisyChatBubble.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
'B4XChatBubble.bas
'Chat bubble inspired by daisyUI chat component, native XUI + cached drawing

#Event: AvatarClick (Payload As Object)
#Event: BubbleClick (Tag As Object)

#DesignerProperty: Key: AvatarMask, DisplayName: Avatar Mask, FieldType: String, DefaultValue: squircle, List: circle|square|squircle|decagon|diamond|heart|hexagon|hexagon-2|pentagon|star|star-2|triangle|triangle-2|triangle-3|triangle-4|half-1|half-2, Description: Mask shape used for the bubble avatar
#DesignerProperty: Key: AvatarSize, DisplayName: Avatar Size, FieldType: Int, DefaultValue: 40, Description: Avatar size in dip
#DesignerProperty: Key: Id, DisplayName: Id, FieldType: String, DefaultValue:, Description: Message author id
#DesignerProperty: Key: FromId, DisplayName: From Id, FieldType: String, DefaultValue:, Description: Current user id used to resolve outgoing side
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: neutral, List: neutral|primary|secondary|accent|info|success|warning|error, Description: Daisy variant for bubble colors
#DesignerProperty: Key: Side, DisplayName: Side, FieldType: String, DefaultValue: start, List: start|end, Description: Bubble alignment when id-based side is not used
#DesignerProperty: Key: BubbleStyle, DisplayName: Bubble Style, FieldType: String, DefaultValue: rounded, List: rounded|block, Description: Bubble visual style
#DesignerProperty: Key: MaxWidthPercent, DisplayName: Max Width %, FieldType: Int, DefaultValue: 90, Description: Maximum bubble width as a percent of row width
#DesignerProperty: Key: UseFromToColors, DisplayName: Use From/To Colors, FieldType: Boolean, DefaultValue: False, Description: Use explicit from/to colors instead of variant defaults
#DesignerProperty: Key: FromBackgroundColor, DisplayName: From Background, FieldType: Color, DefaultValue: 0xFFE5E7EB, Description: Background color for outgoing (from) bubbles
#DesignerProperty: Key: FromTextColor, DisplayName: From Text, FieldType: Color, DefaultValue: 0xFF111827, Description: Text color for outgoing (from) bubbles
#DesignerProperty: Key: ToBackgroundColor, DisplayName: To Background, FieldType: Color, DefaultValue: 0xFFDBEAFE, Description: Background color for incoming (to) bubbles
#DesignerProperty: Key: ToTextColor, DisplayName: To Text, FieldType: Color, DefaultValue: 0xFF1E3A8A, Description: Text color for incoming (to) bubbles
#DesignerProperty: Key: ShowOnline, DisplayName: Show Online Indicator, FieldType: Boolean, DefaultValue: True, Description: Show avatar online/offline indicator
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Tailwind/spacing padding utilities (eg p-2, px-3, 2)
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1)

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object

	'Layout tokens
	Private Gap As Float = 2dip
	Private OuterMargin As Float = 10dip
	Private PaddingH As Float = 16dip
	Private PaddingV As Float = 8dip
	Private RowPadY As Float = 4dip
	Private RowMetaGapY As Float = 4dip
	Private mPadding As String = ""
	Private mMargin As String = ""
	Private mAvatarSize As Float = 40dip
	Private TailSize As Float = 11dip
	Private Corner As Float = 18dip
	Private MaxWidthPct As Float = 0.90
	Private MinBubbleWidth As Int = 44dip   'requested minimum width
	Private MinBubbleHeight As Int = 32dip  '2rem-ish
	Private mBubbleStyle As String = "rounded" 'rounded|block

	'Options
	Private mSide As String = "start" 'start|end
	Private mVariant As String = "neutral"
	Private ShowOutline As Boolean = False
	Private OutlineColor As Int = 0
	Private OutlineWidth As Float = 1dip
	Private BubbleId As String = ""
	Private BubbleFromId As String = ""

	Private AvatarVisible As Boolean = True
	Private AvatarBmp As B4XBitmap
	Private AvatarTag As Object
	Private AvatarStatus As String = "none" 'none|online|offline
	Private AvatarBorderWidth As Float = 1.2dip
	Private AvatarBorderInset As Float = 1.5dip
	Private AvatarBorderColor As Int = 0xFFF5F5F5
	Private mAvatarMask As String = "squircle" 'circle|square|squircle|decagon|diamond|heart|hexagon|hexagon-2|pentagon|star|star-2|triangle|triangle-2|triangle-3|triangle-4|half-1|half-2
	Private OnlineIndicatorVisible As Boolean = True
	Private AvatarOnlineColor As Int = 0xFF2ECC71
	Private AvatarOfflineColor As Int = 0xFFB4B4B4
	
	Private mUseFromToColors As Boolean = False
	Private mFromBackgroundColor As Int = 0xFFE5E7EB
	Private mFromTextColor As Int = 0xFF111827
	Private mToBackgroundColor As Int = 0xFFDBEAFE
	Private mToTextColor As Int = 0xFF1E3A8A
	Private VariantPalette As Map
	Private DefaultVariantPalette As Map

	Private HeaderText As String = ""
	Private HeaderTimeText As String = ""
	Private FooterText As String = ""
	Private MessageText As String = ""
	Private HeaderVisible As Boolean = True
	Private HeaderNameVisible As Boolean = True
	Private HeaderTimeVisible As Boolean = True
	Private FooterVisible As Boolean = True
	Private BubbleVisible As Boolean = True

	'Status
	Private StatusMode As String = "none" 'none|sent|delivered|read
	Private StatusText As String = ""     'optional extra text

	'Overrides
	Private OverrideBackColor As Int = 0
	Private OverrideTextColor As Int = 0
	Private OverrideMutedColor As Int = 0

	'Views
	Private pnlRow As B4XView
	Private ivAvatar As B4XView
	Private ChatAvatar As B4XDaisyAvatar
	Private pnlBubble As B4XView
	Private ivBubbleBg As B4XView

	Private lblHeaderName As B4XView
	Private lblHeaderTime As B4XView
	Private lblFooter As B4XView

	'Content slot
	Private pnlContent As B4XView
	Private lblMessage As B4XView
	Private ivContent As B4XView
	Private CustomContent As B4XView
	Private ContentMode As String = "text" 'text|image|custom
	Private ContentImage As B4XBitmap
	Private ContentImageMaxH As Int = 220dip
	Private DebugBorders As Boolean = False

	'Drawing cache
	Private cvs As B4XCanvas
	Private CanvasReady As Boolean = False
	Private LastW As Int = -1, LastH As Int = -1
	Private LastBack As Int = 0
	Private LastSide As String = ""
	Private LastTail As Boolean = True
	Private LastOutline As Boolean = False
	Private LastOutlineColor As Int = 0
	Private LastOutlineWidth As Int = 0
	Private LastStyle As String = ""

	Private tu As StringUtils
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	InitializeDefaultPalette
End Sub

'Programmatic creation path (without loading from Designer)
Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim pp As Panel
	pp.Initialize("")
	Dim p As B4XView = pp
	p.Color = xui.Color_Transparent
	p.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim dummy As Label
	DesignerCreateView(p, dummy, CreateMap())
	Return mBase
End Sub

Public Sub AddToParent(Parent As B4XView)
	AddToParentAt(Parent, 0, 0, Parent.Width, Parent.Height)
End Sub

Public Sub AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	If Parent.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim v As B4XView = CreateView(w, h)
	Parent.AddView(v, Left, Top, w, h)
End Sub

Public Sub View As B4XView
	Dim empty As B4XView
	If pnlContent.IsInitialized Then Return pnlContent
	If mBase.IsInitialized Then Return mBase
	Return empty
End Sub

Public Sub AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	If pnlContent.IsInitialized Then
		pnlContent.AddView(ChildView, Left, Top, Width, Height)
		Return
	End If
	If mBase.IsInitialized Then mBase.AddView(ChildView, Left, Top, Width, Height)
End Sub

Sub IsReady As Boolean
	Return mBase.IsInitialized And pnlRow.IsInitialized And mBase.Width > 0
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	mBase.Color = xui.Color_Transparent

	Dim pRow As Panel
	pRow.Initialize("")
	pnlRow = pRow
	pnlRow.Color = xui.Color_Transparent
	mBase.AddView(pnlRow, 0, 0, mBase.Width, mBase.Height)

	'Avatar (shared Daisy avatar renderer)
	Dim da As B4XDaisyAvatar
	da.Initialize(Me, "chatavatar")
	ChatAvatar = da
	ivAvatar = ChatAvatar.CreateView(mAvatarSize, mAvatarSize)
	ivAvatar.Color = xui.Color_Transparent
	pnlRow.AddView(ivAvatar, 0, 0, mAvatarSize, mAvatarSize)

	'Bubble
	Dim pBubble As Panel
	pBubble.Initialize("")
	pnlBubble = pBubble
	pnlBubble.Color = xui.Color_Transparent
	pnlRow.AddView(pnlBubble, 0, 0, 100dip, 50dip)

	Dim pBg As Panel
	pBg.Initialize("")
	ivBubbleBg = pBg
	ivBubbleBg.Color = xui.Color_Transparent
	pnlBubble.AddView(ivBubbleBg, 0, 0, pnlBubble.Width, pnlBubble.Height)

	'Header / Footer
	lblHeaderName = CreateLabel(11, False, True)
	lblHeaderTime = CreateLabel(11, False, True)
	lblFooter = CreateLabel(11, False, True)

	pnlRow.AddView(lblHeaderName, 0, 0, 10dip, 10dip)
	pnlRow.AddView(lblHeaderTime, 0, 0, 10dip, 10dip)
	pnlRow.AddView(lblFooter, 0, 0, 10dip, 10dip)

	'Content slot panel
	Dim pContent As Panel
	pContent.Initialize("")
	pnlContent = pContent
	pnlContent.Color = xui.Color_Transparent
	pnlBubble.AddView(pnlContent, 0, 0, 10dip, 10dip)

	'Text content
	lblMessage = CreateLabel(14, False, False)
	lblMessage.SetTextAlignment("TOP", "LEFT")
	pnlContent.AddView(lblMessage, 0, 0, 10dip, 10dip)

	'Image content
	Dim iv3 As ImageView
	iv3.Initialize("")
	ivContent = iv3
	pnlContent.AddView(ivContent, 0, 0, 10dip, 10dip)
	ivContent.Visible = False
	
	ApplyDesignerProps(Props)

	setVariant(mVariant)
	setSide(mSide)

	'Soft elevation where supported (keep subtle)
	ApplyElevation(2dip)
End Sub

Private Sub CreateLabel(Size As Float, Bold As Boolean, SingleLine As Boolean) As B4XView
	Dim l As Label
	l.Initialize("")
	l.SingleLine = SingleLine
	Dim v As B4XView = l
	v.Color = xui.Color_Transparent
	Dim f As B4XFont
	If Bold Then f = xui.CreateDefaultBoldFont(Size) Else f = xui.CreateDefaultFont(Size)
	v.Font = f
	Return v
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	If Props.IsInitialized = False Then Return
	
	mAvatarMask = B4XDaisyVariants.NormalizeMask(B4XDaisyVariants.GetPropString(Props, "AvatarMask", mAvatarMask))
	mAvatarSize = Max(16dip, B4XDaisyVariants.GetPropDip(Props, "AvatarSize", 40))
	setSide(B4XDaisyVariants.GetPropString(Props, "Side", mSide))
	setBubbleStyle(B4XDaisyVariants.GetPropString(Props, "BubbleStyle", mBubbleStyle))
	Dim mwp As Float = B4XDaisyVariants.GetPropFloat(Props, "MaxWidthPercent", 90)
	If mwp > 1 Then mwp = mwp / 100
	setMaxWidthPercent(mwp)
	'Backward compatibility with older designer keys.
	If Props.ContainsKey("AvatarWidth") Then mAvatarSize = Max(16dip, B4XDaisyVariants.GetPropDip(Props, "AvatarWidth", mAvatarSize))
	If Props.ContainsKey("AvatarHeight") Then mAvatarSize = Max(16dip, B4XDaisyVariants.GetPropDip(Props, "AvatarHeight", mAvatarSize))
	OnlineIndicatorVisible = B4XDaisyVariants.GetPropBool(Props, "ShowOnline", True)
	
	mUseFromToColors = B4XDaisyVariants.GetPropBool(Props, "UseFromToColors", mUseFromToColors)
	mFromBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "FromBackgroundColor", mFromBackgroundColor)
	mFromTextColor = B4XDaisyVariants.GetPropInt(Props, "FromTextColor", mFromTextColor)
	mToBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "ToBackgroundColor", mToBackgroundColor)
	mToTextColor = B4XDaisyVariants.GetPropInt(Props, "ToTextColor", mToTextColor)
	mPadding = B4XDaisyVariants.GetPropString(Props, "Padding", mPadding)
	mMargin = B4XDaisyVariants.GetPropString(Props, "Margin", mMargin)
	BubbleId = B4XDaisyVariants.GetPropString(Props, "Id", "")
	BubbleFromId = B4XDaisyVariants.GetPropString(Props, "FromId", "")
	setVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
End Sub



Private Sub ResolveContentRectForBounds(Width As Float, Height As Float) As B4XRect
	Dim host As B4XRect
	host.Initialize(0, 0, Max(1dip, Width), Max(1dip, Height))
	Dim box As Map = BuildBoxModel
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Return B4XDaisyBoxModel.ResolveContentRect(outerRect, box)
End Sub

Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	ApplySpacingSpecToBox(box, mPadding, mMargin)
	Return box
End Sub

Private Sub ApplySpacingSpecToBox(Box As Map, PaddingSpec As String, MarginSpec As String)
	Dim rtl As Boolean = False
	Dim p As String = IIf(PaddingSpec = Null, "", PaddingSpec.Trim)
	Dim m As String = IIf(MarginSpec = Null, "", MarginSpec.Trim)
	If p.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(p, Array As String("p-", "px-", "py-", "pt-", "pr-", "pb-", "pl-", "ps-", "pe-")) Then
			B4XDaisyBoxModel.ApplyPaddingUtilities(Box, p, rtl)
		Else
			Dim pv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(p, 0dip)
			Box.Put("padding_left", pv)
			Box.Put("padding_right", pv)
			Box.Put("padding_top", pv)
			Box.Put("padding_bottom", pv)
		End If
	End If
	If m.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(m, Array As String("m-", "mx-", "my-", "mt-", "mr-", "mb-", "ml-", "ms-", "me-", "-m-", "-mx-", "-my-", "-mt-", "-mr-", "-mb-", "-ml-", "-ms-", "-me-")) Then
			B4XDaisyBoxModel.ApplyMarginUtilities(Box, m, rtl)
		Else
			Dim mv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(m, 0dip)
			Box.Put("margin_left", mv)
			Box.Put("margin_right", mv)
			Box.Put("margin_top", mv)
			Box.Put("margin_bottom", mv)
		End If
	End If
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If pnlRow.IsInitialized = False Then Return
	Dim contentRect As B4XRect = ResolveContentRectForBounds(Width, Height)
	pnlRow.SetLayoutAnimated(0, contentRect.Left, contentRect.Top, contentRect.Width, contentRect.Height)
	LayoutNow(contentRect.Width, contentRect.Height)
End Sub

Public Sub setId(Value As String)
	If Value = Null Then
		BubbleId = ""
	Else
		BubbleId = Value
	End If
End Sub

Public Sub getId As String
	Return BubbleId
End Sub

Public Sub setFromId(Value As String)
	If Value = Null Then
		BubbleFromId = ""
	Else
		BubbleFromId = Value
	End If
End Sub

Public Sub getFromId As String
	Return BubbleFromId
End Sub

Public Sub GetUsedHeight As Int
	Dim w As Int = 320dip
	If mBase.IsInitialized And mBase.Width > 0 Then w = mBase.Width
	Return MeasureHeight(w)
End Sub

'========================
' Public API (daisy-like)
'========================

Public Sub setSide(s As String)
	If s <> "start" And s <> "end" Then s = "start"
	mSide = s
End Sub

Public Sub getSide As String
	Return mSide
End Sub

Public Sub setVariant(v As String)
	If v = Null Then
		mVariant = "neutral"
		Return
	End If
	mVariant = v.ToLowerCase.Trim
	If mVariant.Length = 0 Then mVariant = "neutral"
End Sub

Public Sub getVariant As String
	Return mVariant
End Sub

Public Sub setBubbleStyle(StyleName As String)
	Dim s As String = StyleName.ToLowerCase.Trim
	If s <> "block" Then s = "rounded"
	mBubbleStyle = s
End Sub

Public Sub SetOutline(Enabled As Boolean, Color As Int, Width As Float)
	ShowOutline = Enabled
	OutlineColor = Color
	OutlineWidth = Width
End Sub

Public Sub getBubbleStyle As String
	Return mBubbleStyle
End Sub

Public Sub setMaxWidthPercent(p As Float)
	If p < 0.3 Then p = 0.3
	If p > 0.95 Then p = 0.95
	MaxWidthPct = p
End Sub

Public Sub getMaxWidthPercent As Float
	Return MaxWidthPct
End Sub

Public Sub setPadding(Value As String)
	mPadding = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getPadding As String
	Return mPadding
End Sub

Public Sub setMargin(Value As String)
	mMargin = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getMargin As String
	Return mMargin
End Sub

Public Sub SetAvatarVisible(b As Boolean)
	AvatarVisible = b
End Sub

Public Sub SetAvatarBitmap(bmp As B4XBitmap, Tag As Object)
	AvatarBmp = bmp
	AvatarTag = Tag
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub SetAvatarStatus(Mode As String)
	Dim m As String = Mode.ToLowerCase
	If m <> "online" And m <> "offline" Then m = "none"
	AvatarStatus = m
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub SetAvatarStatusColors(OnlineColor As Int, OfflineColor As Int)
	If OnlineColor <> 0 Then AvatarOnlineColor = OnlineColor
	If OfflineColor <> 0 Then AvatarOfflineColor = OfflineColor
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub GetAvatarOnlineColor As Int
	Return AvatarOnlineColor
End Sub

Public Sub GetAvatarOfflineColor As Int
	Return AvatarOfflineColor
End Sub

Public Sub SetAvatarBorder(Color As Int, Width As Float)
	AvatarBorderColor = Color
	AvatarBorderWidth = Max(0, Width)
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub SetAvatarBorderInset(Inset As Float)
	AvatarBorderInset = Max(0, Inset)
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setAvatarMask(MaskName As String)
	mAvatarMask = B4XDaisyVariants.NormalizeMask(MaskName)
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatarMask As String
	Return mAvatarMask
End Sub

Public Sub SetGlobalMask(MaskName As String)
	setAvatarMask(MaskName)
End Sub

Public Sub setAvatarSize(Size As Float)
	mAvatarSize = Max(16dip, Size)
End Sub

Public Sub SetAvatarWidth(Width As Float)
	mAvatarSize = Max(16dip, Width)
End Sub

Public Sub SetAvatarHeight(Height As Float)
	mAvatarSize = Max(16dip, Height)
End Sub

Public Sub GetAvatarWidth As Float
	Return mAvatarSize
End Sub

Public Sub GetAvatarHeight As Float
	Return mAvatarSize
End Sub

Public Sub getAvatarSize As Float
	Return mAvatarSize
End Sub

Public Sub setShowOnline(Show As Boolean)
	OnlineIndicatorVisible = Show
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getShowOnline As Boolean
	Return OnlineIndicatorVisible
End Sub

Public Sub setFromBackgroundColor(Color As Int)
	mFromBackgroundColor = Color
	mUseFromToColors = True
End Sub

Public Sub getFromBackgroundColor As Int
	Return mFromBackgroundColor
End Sub

Public Sub setFromTextColor(Color As Int)
	mFromTextColor = Color
	mUseFromToColors = True
End Sub

Public Sub getFromTextColor As Int
	Return mFromTextColor
End Sub

Public Sub setToBackgroundColor(Color As Int)
	mToBackgroundColor = Color
	mUseFromToColors = True
End Sub

Public Sub getToBackgroundColor As Int
	Return mToBackgroundColor
End Sub

Public Sub setToTextColor(Color As Int)
	mToTextColor = Color
	mUseFromToColors = True
End Sub

Public Sub getToTextColor As Int
	Return mToTextColor
End Sub

Public Sub SetFromToColors(FromBack As Int, FromText As Int, ToBack As Int, ToText As Int)
	mFromBackgroundColor = FromBack
	mFromTextColor = FromText
	mToBackgroundColor = ToBack
	mToTextColor = ToText
	mUseFromToColors = True
End Sub

Public Sub setUseFromToColors(Enabled As Boolean)
	mUseFromToColors = Enabled
End Sub

Public Sub getUseFromToColors As Boolean
	Return mUseFromToColors
End Sub

Public Sub SetVariantPalette(Palette As Map)
	InitializeDefaultPalette
	If Palette.IsInitialized Then
		VariantPalette = Palette
	Else
		VariantPalette = DefaultVariantPalette
	End If
End Sub

Public Sub GetVariantPalette As Map
	InitializeDefaultPalette
	Return VariantPalette
End Sub

Public Sub SetColors(BackOverride As Int, TextOverride As Int, MutedOverride As Int)
	OverrideBackColor = BackOverride
	OverrideTextColor = TextOverride
	OverrideMutedColor = MutedOverride
End Sub

Public Sub SetHeader(Text As String)
	HeaderText = Text
End Sub

Public Sub SetHeaderTime(Text As String)
	HeaderTimeText = Text
End Sub

Public Sub SetHeaderParts(NameText As String, TimeText As String)
	HeaderText = NameText
	HeaderTimeText = TimeText
End Sub

Public Sub SetHeaderVisible(b As Boolean)
	HeaderVisible = b
End Sub

Public Sub SetHeaderNameVisible(b As Boolean)
	HeaderNameVisible = b
End Sub

Public Sub SetHeaderTimeVisible(b As Boolean)
	HeaderTimeVisible = b
End Sub

Public Sub SetFooter(Text As String)
	FooterText = Text
End Sub

Public Sub SetFooterVisible(b As Boolean)
	FooterVisible = b
End Sub

Public Sub SetMessage(Text As String)
	MessageText = Text
	ContentMode = "text"
End Sub

Public Sub SetBubbleVisible(b As Boolean)
	BubbleVisible = b
End Sub

Public Sub SetDebugBorders(Enabled As Boolean)
	DebugBorders = Enabled
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub GetDebugBorders As Boolean
	Return DebugBorders
End Sub

Public Sub SetStatus(Mode As String, ExtraText As String)
	'Mode: none|sent|delivered|read
	StatusMode = NormalizeStatusMode(Mode)
	StatusText = ExtraText
End Sub

Private Sub NormalizeStatusMode(Mode As String) As String
	If Mode = Null Then Return "none"
	Dim m As String = Mode.ToLowerCase.Trim
	Select Case m
		Case "sent", "delivered", "read"
			Return m
		Case Else
			Return "none"
	End Select
End Sub

'Content slot: image
Public Sub SetImage(bmp As B4XBitmap, MaxHeight As Int)
	ContentImage = bmp
	If MaxHeight > 0 Then ContentImageMaxH = MaxHeight
	ContentMode = "image"
End Sub

'Content slot: custom view
Public Sub SetCustomContent(v As B4XView)
	CustomContent = v
	ContentMode = "custom"
End Sub

'Convenience: set all in one go
Public Sub SetContentAll(Header As String, Body As String, Footer As String, SideNow As String, VariantNow As String)
	SetHeader(Header)
	SetMessage(Body)
	SetFooter(Footer)
	setSide(SideNow)
	setVariant(VariantNow)
End Sub

'CLV: measure row height given available row width
Public Sub MeasureHeight(AvailableWidth As Int) As Int
	Dim sideEff As String = EffectiveSide
	Dim bubbleW As Int = ComputeBubbleWidth(AvailableWidth)
	Dim tailLeft As Int = IIf(sideEff="start", TailSize, 0)
	Dim tailRight As Int = IIf(sideEff="end", TailSize, 0)
	Dim contentW As Int = bubbleW - (PaddingH*2) - tailLeft - tailRight
	If contentW < 80dip Then contentW = 80dip
	Dim bubbleBodyW As Int = bubbleW - tailLeft - tailRight
	If bubbleBodyW < 80dip Then bubbleBodyW = 80dip

	Dim showHeaderName As Boolean = HeaderVisible And HeaderNameVisible And HeaderText.Length > 0
	Dim showHeaderTime As Boolean = HeaderVisible And HeaderTimeVisible And HeaderTimeText.Length > 0
	Dim headerLayout As Map = ResolveHeaderLayoutWidths(bubbleBodyW, showHeaderName, showHeaderTime)
	Dim nameW As Int = headerLayout.Get("name_w")
	Dim timeW As Int = headerLayout.Get("time_w")
	Dim hh As Int = 0
	If showHeaderName Or showHeaderTime Then
		Dim hName As Int = IIf(showHeaderName, MeasureSingleLineHeight(lblHeaderName), 0)
		Dim hTime As Int = IIf(showHeaderTime, MeasureSingleLineHeight(lblHeaderTime), 0)
		hh = Max(hName, hTime)
	End If

	Dim bodyH As Int
	If BubbleVisible Then
		Select Case ContentMode
			Case "text"
				bodyH = MeasureLabelHeight(lblMessage, MessageText, contentW)
			Case "image"
				bodyH = MeasureImageHeight(contentW)
			Case "custom"
				bodyH = 60dip 'fallback; custom content should be sized by caller
		End Select
	Else
		bodyH = 0
	End If

	Dim footerCombined As String = FooterComposite
	Dim fh As Int = 0
	If FooterVisible And footerCombined.Length > 0 Then
		fh = MeasureSingleLineHeight(lblFooter)
	End If

	Dim minVisualBubbleH As Int = Max(44dip, IIf(AvatarVisible, mAvatarSize, 0))
	Dim bubbleH As Int = IIf(BubbleVisible, Max(Max(MinBubbleHeight, minVisualBubbleH), PaddingV + bodyH + PaddingV), 0)
	Dim rowH As Int = (RowPadY * 2) + IIf(hh > 0, hh + RowMetaGapY, 0) + bubbleH + IIf(fh > 0, RowMetaGapY + fh, 0)
	rowH = Max(rowH, (RowPadY * 2) + IIf(hh > 0, hh + RowMetaGapY, 0) + IIf(AvatarVisible, mAvatarSize, 0))
	Return rowH
End Sub

'========================
' Layout & drawing
'========================

Private Sub LayoutNow(RowW As Int, RowH As Int)
	Dim sideEff As String = EffectiveSide
	'Colors (theme + overrides)
	Dim cmap As Map
	cmap = ResolveColors
	Dim back As Int = cmap.Get("back")
	Dim txt As Int = cmap.Get("text")
	Dim muted As Int = cmap.Get("muted")
	Dim metaMuted As Int = xui.Color_RGB(120, 120, 120)

	Dim bubbleW As Int = ComputeBubbleWidth(RowW)
	Dim tailLeft As Int = IIf(sideEff="start", TailSize, 0)
	Dim tailRight As Int = IIf(sideEff="end", TailSize, 0)
	Dim contentW As Int = bubbleW - (PaddingH*2) - tailLeft - tailRight
	If contentW < 80dip Then contentW = 80dip
	Dim bubbleBodyW As Int = bubbleW - tailLeft - tailRight
	If bubbleBodyW < 80dip Then bubbleBodyW = 80dip
	Dim contentX As Int = PaddingH + tailLeft

	Dim showHeaderName As Boolean = HeaderVisible And HeaderNameVisible And HeaderText.Length > 0
	Dim showHeaderTime As Boolean = HeaderVisible And HeaderTimeVisible And HeaderTimeText.Length > 0
	Dim headerLayout As Map = ResolveHeaderLayoutWidths(bubbleBodyW, showHeaderName, showHeaderTime)
	Dim nameW As Int = headerLayout.Get("name_w")
	Dim timeW As Int = headerLayout.Get("time_w")
	Dim gapW As Int = headerLayout.Get("gap_w")
	Dim hh As Int = 0
	Dim hName As Int = IIf(showHeaderName And nameW > 0, MeasureSingleLineHeight(lblHeaderName), 0)
	Dim hTime As Int = IIf(showHeaderTime And timeW > 0, MeasureSingleLineHeight(lblHeaderTime), 0)
	If showHeaderName Or showHeaderTime Then hh = Max(hName, hTime)
	
	Dim footerCombined As String = FooterComposite
	Dim fh As Int = 0
	lblFooter.Visible = FooterVisible And (footerCombined.Length > 0)
	lblFooter.TextColor = metaMuted
	If lblFooter.Visible Then
		lblFooter.Text = footerCombined
		fh = MeasureSingleLineHeight(lblFooter)
	End If
	
	Dim topY As Int = RowPadY
	Dim bubbleY As Int = topY + IIf(hh > 0, hh + RowMetaGapY, 0)

	'Measure content body height
	Dim bodyH As Int
	If BubbleVisible Then
		Select Case ContentMode
			Case "text"
				bodyH = MeasureLabelHeight(lblMessage, MessageText, contentW)
			Case "image"
				bodyH = MeasureImageHeight(contentW)
			Case "custom"
				If CustomContent.IsInitialized Then
					bodyH = Max(1dip, CustomContent.Height)
				Else
					bodyH = 60dip
				End If
		End Select
	Else
		bodyH = 0
	End If
	Dim minVisualBubbleH As Int = Max(44dip, IIf(AvatarVisible, mAvatarSize, 0))
	Dim bubbleH As Int = IIf(BubbleVisible, Max(Max(MinBubbleHeight, minVisualBubbleH), PaddingV + bodyH + PaddingV), 0)

	Dim aW As Int = 0
	Dim bubbleX As Int
	If sideEff = "start" Then
		If AvatarVisible Then aW = mAvatarSize
		bubbleX = OuterMargin + IIf(AvatarVisible, aW + Gap, 0)
	Else
		If AvatarVisible Then aW = mAvatarSize
		bubbleX = RowW - OuterMargin - bubbleW - IIf(AvatarVisible, aW + Gap, 0)
	End If
	
	'Avatar aligned with bubble block (not header/footer)
	If AvatarVisible Then
		ivAvatar.Visible = True
		Dim avatarX As Int = IIf(sideEff = "start", OuterMargin, RowW - OuterMargin - mAvatarSize)
		' Align avatar bottom edge with the chat bubble bottom edge (never below bubble bottom).
		Dim avatarY As Int = IIf(BubbleVisible, bubbleY + bubbleH - mAvatarSize, bubbleY)
		ivAvatar.SetLayoutAnimated(0, avatarX, avatarY, mAvatarSize, mAvatarSize)
		DrawAvatar
	Else
		ivAvatar.Visible = False
	End If
	
	pnlBubble.Visible = BubbleVisible
	If BubbleVisible Then
		pnlBubble.SetLayoutAnimated(0, bubbleX, bubbleY, bubbleW, bubbleH)
		ivBubbleBg.SetLayoutAnimated(0, 0, 0, bubbleW, bubbleH)
	Else
		pnlBubble.SetLayoutAnimated(0, bubbleX, bubbleY, 0, 0)
	End If
	
	Dim headerX As Int = bubbleX + tailLeft
	Dim footerX As Int = headerX
	Dim renderHeaderName As Boolean = showHeaderName And nameW > 0
	Dim renderHeaderTime As Boolean = showHeaderTime And timeW > 0
	lblHeaderName.Visible = renderHeaderName
	lblHeaderTime.Visible = renderHeaderTime
	lblHeaderName.TextColor = xui.Color_Black
	lblHeaderTime.TextColor = metaMuted
	lblHeaderName.SetTextAlignment("CENTER", "LEFT")
	lblHeaderTime.SetTextAlignment("CENTER", "LEFT")
	lblFooter.SetTextAlignment("CENTER", IIf(sideEff = "start", "LEFT", "RIGHT"))
	Dim nameX As Int = headerX
	Dim timeX As Int = headerX
	If renderHeaderName And renderHeaderTime Then
		If sideEff = "end" Then
			Dim groupW As Int = nameW + gapW + timeW
			nameX = headerX + bubbleBodyW - groupW
		End If
		timeX = nameX + nameW + gapW
	Else If renderHeaderName Then
		If sideEff = "end" Then nameX = headerX + bubbleBodyW - nameW
	Else If renderHeaderTime Then
		If sideEff = "end" Then timeX = headerX + bubbleBodyW - timeW
	End If
	If renderHeaderName Then
		lblHeaderName.Text = FitSingleLineText(lblHeaderName, HeaderText, nameW)
		lblHeaderName.SetLayoutAnimated(0, nameX, topY, nameW, hh)
	End If
	If renderHeaderTime Then
		lblHeaderTime.Text = FitSingleLineText(lblHeaderTime, HeaderTimeText, timeW)
		lblHeaderTime.SetLayoutAnimated(0, timeX, topY, timeW, hh)
	End If
	If lblFooter.Visible Then
		lblFooter.Text = FitSingleLineText(lblFooter, footerCombined, bubbleBodyW)
		lblFooter.SetLayoutAnimated(0, footerX, bubbleY + bubbleH + RowMetaGapY, bubbleBodyW, fh)
	End If

	'Content slot inside bubble
	If BubbleVisible Then
		Dim contentY As Int = Max(PaddingV, (bubbleH - bodyH) / 2)
		pnlContent.SetLayoutAnimated(0, contentX, contentY, contentW, bodyH)
	Else
		pnlContent.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If

	If BubbleVisible Then
		Select Case ContentMode
			Case "text"
				ivContent.Visible = False
				If CustomContent.IsInitialized Then CustomContent.RemoveViewFromParent
				lblMessage.Visible = True
				lblMessage.Text = MessageText
				lblMessage.TextColor = txt
				lblMessage.SetLayoutAnimated(0, 0, 0, contentW, bodyH)

			Case "image"
				lblMessage.Visible = False
				If CustomContent.IsInitialized Then CustomContent.RemoveViewFromParent
				ivContent.Visible = True
				If ContentImage.IsInitialized Then ivContent.SetBitmap(ContentImage)
				ivContent.SetLayoutAnimated(0, 0, 0, contentW, bodyH)

			Case "custom"
				lblMessage.Visible = False
				ivContent.Visible = False
				If CustomContent.IsInitialized Then
					If CustomContent.Parent.IsInitialized = False Then pnlContent.AddView(CustomContent, 0, 0, contentW, 60dip)
					CustomContent.SetLayoutAnimated(0, 0, 0, contentW, bodyH)
				Else
					'Fallback
					lblMessage.Visible = True
					lblMessage.Text = ""
					lblMessage.SetLayoutAnimated(0, 0, 0, contentW, 1dip)
				End If
		End Select
	Else
		lblMessage.Visible = False
		ivContent.Visible = False
	End If

	'Bubble background draw (cached)
	If BubbleVisible Then
		If OutlineColor = 0 Then OutlineColor = Blend(back, xui.Color_Black, 0.25)
		DrawBubbleIfNeeded(bubbleW, bubbleH, back, sideEff, True, ShowOutline, OutlineColor, OutlineWidth, mBubbleStyle)
	End If

End Sub

Private Sub DrawAvatar
	If ChatAvatar.IsInitialized = False Then Return
	If ivAvatar.IsInitialized = False Or ivAvatar.Visible = False Then Return
	ChatAvatar.SetChatImage(True)
	ChatAvatar.SetShadow("none")
	ChatAvatar.SetCenterOnParent(False)
	ChatAvatar.SetVariant("none")
	ChatAvatar.SetUseVariantStatusColors(False)
	ChatAvatar.SetAvatarMask(mAvatarMask)
	ChatAvatar.SetAvatarStatus(AvatarStatus)
	ChatAvatar.SetAvatarStatusColors(AvatarOnlineColor, AvatarOfflineColor)
	ChatAvatar.SetShowOnline(OnlineIndicatorVisible)
	ChatAvatar.SetAvatarBorder(AvatarBorderColor, AvatarBorderWidth)
	ChatAvatar.SetAvatarBorderInset(AvatarBorderInset)
	Dim effectiveStatus As String = AvatarStatus
	If OnlineIndicatorVisible = False Then effectiveStatus = "none"
	Dim statusColor As Int = 0
	If effectiveStatus = "online" Then statusColor = AvatarOnlineColor
	If effectiveStatus = "offline" Then statusColor = AvatarOfflineColor
	Dim ringColor As Int = IIf(statusColor <> 0, statusColor, AvatarBorderColor)
	ChatAvatar.SetRingColor(ringColor)
	ChatAvatar.SetAvatarBitmap(AvatarBmp, AvatarTag)
	ChatAvatar.ResizeToParent(ivAvatar)
End Sub

Private Sub FooterComposite As String
	Dim ticks As String = ""
	Select Case StatusMode
		Case "sent"      : ticks = " ?"
		Case "delivered" : ticks = " ??"
		Case "read"      : ticks = " ??"
		Case Else        : ticks = ""
	End Select
	Dim extra As String = ""
	If StatusText.Length > 0 Then extra = " ? " & StatusText
	
	If FooterText.Length = 0 And ticks.Length = 0 And extra.Length = 0 Then Return ""
	Return FooterText & ticks & extra
End Sub

Private Sub MeasureImageHeight(contentW As Int) As Int
	If ContentImage.IsInitialized = False Then Return 120dip
	Dim iw As Int = ContentImage.Width
	Dim ih As Int = ContentImage.Height
	If iw <= 0 Or ih <= 0 Then Return 120dip
	Dim scaledH As Int = ih * (contentW / iw)
	If scaledH > ContentImageMaxH Then scaledH = ContentImageMaxH
	If scaledH < 80dip Then scaledH = 80dip
	Return scaledH
End Sub

Private Sub MeasureLabelHeight(v As B4XView, Text As String, Width As Int) As Int
	Dim l As Label = v
	l.Width = Max(1dip, Width)
	Dim h As Int = tu.MeasureMultilineTextHeight(l, Text)
	Return Max(1dip, h)
End Sub

Private Sub MeasureSingleLineHeight(v As B4XView) As Int
	Dim l As Label = v
	Dim oldW As Int = l.Width
	l.Width = Max(120dip, oldW)
	Dim h As Int = tu.MeasureMultilineTextHeight(l, "Ag")
	l.Width = oldW
	Return Max(1dip, h)
End Sub

Private Sub MeasureSingleLineWidth(v As B4XView, Text As String) As Int
	If Text.Length = 0 Then Return 0
	Dim cleaned As String = Text.Replace(CRLF, Chr(10))
	Dim lines() As String = Regex.Split(Chr(10), cleaned)
	Dim l As Label = v
	Dim maxW As Float = 0
	For Each ln As String In lines
		Dim w As Float = B4XDaisyVariants.MeasureTextWidthSafe(ln, l.TextSize, l.Typeface, 0)
		If w > maxW Then maxW = w
	Next
	Return Max(1dip, Ceil(maxW) + 2dip)
End Sub

Private Sub FitSingleLineText(v As B4XView, Text As String, MaxWidth As Int) As String
	If MaxWidth <= 0 Then Return ""
	Dim normalized As String = Text.Replace(CRLF, " ").Replace(Chr(10), " ").Trim
	If normalized.Length = 0 Then Return ""
	If MeasureSingleLineWidth(v, normalized) <= MaxWidth Then Return normalized
	Dim dots As String = "..."
	If MeasureSingleLineWidth(v, dots) > MaxWidth Then Return ""
	Dim lo As Int = 0
	Dim hi As Int = normalized.Length
	Do While lo < hi
		Dim mid As Int = (lo + hi + 1) / 2
		Dim candidate As String = normalized.SubString2(0, mid) & dots
		If MeasureSingleLineWidth(v, candidate) <= MaxWidth Then
			lo = mid
		Else
			hi = mid - 1
		End If
	Loop
	If lo <= 0 Then Return dots
	Return normalized.SubString2(0, lo) & dots
End Sub

Private Sub ResolveHeaderLayoutWidths(BubbleBodyW As Int, ShowHeaderName As Boolean, ShowHeaderTime As Boolean) As Map
	Dim result As Map
	result.Initialize
	Dim minTimeW As Int = 20dip
	Dim preferredGapW As Int = 4dip
	Dim gapW As Int = preferredGapW
	Dim nameW As Int = 0
	Dim timeW As Int = 0
	Dim desiredNameW As Int = 0
	Dim desiredTimeW As Int = 0
	
	If ShowHeaderName Then
		desiredNameW = Min(BubbleBodyW, MeasureSingleLineWidth(lblHeaderName, HeaderText))
	End If
	If ShowHeaderTime Then
		desiredTimeW = Min(BubbleBodyW, Max(minTimeW, MeasureSingleLineWidth(lblHeaderTime, HeaderTimeText)))
	End If
	
	If ShowHeaderName And ShowHeaderTime Then
		Dim fullHeaderW As Int = desiredNameW + preferredGapW + desiredTimeW
		If fullHeaderW <= BubbleBodyW Then
			'Everything fits in one row: render full name + time (no name truncation).
			nameW = desiredNameW
			timeW = desiredTimeW
			gapW = preferredGapW
		Else
			'Overflow: preserve time width and let name be ellipsized to remaining space.
			timeW = Min(desiredTimeW, BubbleBodyW)
			Dim remaining As Int = BubbleBodyW - timeW
			If remaining <= 0 Then
				nameW = 0
				gapW = 0
			Else
				gapW = IIf(remaining > preferredGapW, preferredGapW, 0)
				nameW = Max(0, remaining - gapW)
			End If
		End If
	Else If ShowHeaderName Then
		nameW = BubbleBodyW
	Else If ShowHeaderTime Then
		timeW = Min(BubbleBodyW, Max(minTimeW, desiredTimeW))
	End If
	
	result.Put("name_w", nameW)
	result.Put("time_w", timeW)
	result.Put("gap_w", gapW)
	Return result
End Sub

Private Sub MaxBubbleWidth(RowW As Int) As Int
	Dim avatarSlot As Int = IIf(AvatarVisible, mAvatarSize + Gap, 0)
	Dim hardMax As Int = RowW - (OuterMargin * 2) - avatarSlot
	Dim pctMax As Int = RowW * MaxWidthPct
	Dim m As Int = Min(hardMax, pctMax)
	Return Max(MinBubbleWidth, m)
End Sub

Private Sub EstimateDesiredContentWidth(maxContentW As Int) As Int
	Dim desired As Int = 1dip
	
	If BubbleVisible Then
		Select Case ContentMode
			Case "text"
				desired = Max(desired, MeasureSingleLineWidth(lblMessage, MessageText))
			Case "image"
				If ContentImage.IsInitialized Then
					desired = Max(desired, Min(maxContentW, Max(44dip, ContentImage.Width)))
				End If
			Case "custom"
				If CustomContent.IsInitialized Then
					desired = Max(desired, CustomContent.Width)
				End If
		End Select
	End If

	If desired > maxContentW Then desired = maxContentW
	Return Max(1dip, desired)
End Sub

Private Sub ComputeBubbleWidth(RowW As Int) As Int
	Dim sideEff As String = EffectiveSide
	Dim maxBubble As Int = MaxBubbleWidth(RowW)
	Dim tailLeft As Int = IIf(sideEff="start", TailSize, 0)
	Dim tailRight As Int = IIf(sideEff="end", TailSize, 0)
	Dim chrome As Int = (PaddingH * 2) + tailLeft + tailRight
	Dim maxContentW As Int = Max(1dip, maxBubble - chrome)
	Dim desiredContentW As Int = EstimateDesiredContentWidth(maxContentW)
	Dim w As Int = chrome + desiredContentW
	If w > maxBubble Then w = maxBubble
	If w < MinBubbleWidth Then w = MinBubbleWidth
	Return w
End Sub

Private Sub ResolveColors As Map
	InitializeDefaultPalette
	Dim sideEff As String = EffectiveSide
	Dim pal As Map
	If VariantPalette.IsInitialized Then
		pal = VariantPalette
	Else
		pal = DefaultVariantPalette
	End If
	Dim vm As Map
	If pal.IsInitialized And pal.ContainsKey(mVariant) Then
		vm = pal.Get(mVariant)
	Else If pal.IsInitialized And pal.ContainsKey("neutral") Then
		vm = pal.Get("neutral")
	Else
		vm = BuildVariantMap(0xFFE5E7EB, 0xFF111827)
	End If

	Dim back As Int
	Dim txt As Int
	Dim muted As Int
	back = GetMapIntSafe(vm, "back", 0xFFE5E7EB)
	txt = GetMapIntSafe(vm, "text", 0xFF111827)
	muted = GetMapIntSafe(vm, "muted", Blend(txt, xui.Color_Gray, 0.35))
	
	If mUseFromToColors Then
		If sideEff = "start" Then
			back = mFromBackgroundColor
			txt = mFromTextColor
		Else
			back = mToBackgroundColor
			txt = mToTextColor
		End If
		muted = Blend(txt, xui.Color_Gray, 0.35)
	End If

	If OverrideBackColor <> 0 Then back = OverrideBackColor
	If OverrideTextColor <> 0 Then txt = OverrideTextColor
	If OverrideMutedColor <> 0 Then muted = OverrideMutedColor
	
	Dim result As Map
	result.Initialize
	result.Put("back", back)
	result.Put("text", txt)
	result.Put("muted", muted)
	Return result
End Sub

Private Sub GetMapIntSafe(m As Map, Key As String, DefaultValue As Int) As Int
	If m.IsInitialized = False Then Return DefaultValue
	If m.ContainsKey(Key) = False Then Return DefaultValue
	Dim o As Object = m.Get(Key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub InitializeDefaultPalette
	If DefaultVariantPalette.IsInitialized Then
		If VariantPalette.IsInitialized = False Then VariantPalette = DefaultVariantPalette
		Return
	End If
	DefaultVariantPalette = BuildDefaultPalette
	VariantPalette = DefaultVariantPalette
End Sub

Private Sub BuildDefaultPalette As Map
	Dim m As Map
	m.Initialize
	m.Put("neutral", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-base-300", xui.Color_RGB(241,245,249)), B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_Black)))
	m.Put("primary", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-primary", xui.Color_RGB(219,234,254)), B4XDaisyVariants.GetTokenColor("--color-primary-content", xui.Color_RGB(30,64,175))))
	m.Put("secondary", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-secondary", xui.Color_RGB(243,232,255)), B4XDaisyVariants.GetTokenColor("--color-secondary-content", xui.Color_RGB(88,28,135))))
	m.Put("accent", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-accent", xui.Color_RGB(204,251,241)), B4XDaisyVariants.GetTokenColor("--color-accent-content", xui.Color_RGB(19,78,74))))
	m.Put("info", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-info", xui.Color_RGB(224,242,254)), B4XDaisyVariants.GetTokenColor("--color-info-content", xui.Color_RGB(12,74,110))))
	m.Put("success", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-success", xui.Color_RGB(220,252,231)), B4XDaisyVariants.GetTokenColor("--color-success-content", xui.Color_RGB(20,83,45))))
	m.Put("warning", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-warning", xui.Color_RGB(254,249,195)), B4XDaisyVariants.GetTokenColor("--color-warning-content", xui.Color_RGB(113,63,18))))
	m.Put("error", BuildVariantMap(B4XDaisyVariants.GetTokenColor("--color-error", xui.Color_RGB(254,226,226)), B4XDaisyVariants.GetTokenColor("--color-error-content", xui.Color_RGB(127,29,29))))
	Return m
End Sub

Private Sub EffectiveSide As String
	Dim s As String = mSide
	If B4XDaisyVariants.IsRtl Then
		If s = "start" Then
			s = "end"
		Else If s = "end" Then
			s = "start"
		End If
	End If
	Return s
End Sub

Private Sub BuildVariantMap(back As Int, text As Int) As Map
	Dim vm As Map
	vm.Initialize
	vm.Put("back", back)
	vm.Put("text", text)
	vm.Put("muted", Blend(text, xui.Color_Gray, 0.35))
	Return vm
End Sub

Private Sub DrawBubbleIfNeeded(w As Int, h As Int, back As Int, sideNow As String, tail As Boolean, outline As Boolean, oColor As Int, oWidth As Float, styleNow As String)
	If w <= 0 Or h <= 0 Then Return
	Dim ow As Int = Max(0, oWidth)
	If w = LastW And h = LastH And back = LastBack And sideNow = LastSide And tail = LastTail And outline = LastOutline And oColor = LastOutlineColor And ow = LastOutlineWidth And styleNow = LastStyle Then Return

	LastW = w : LastH = h : LastBack = back : LastSide = sideNow
	LastTail = tail : LastOutline = outline : LastOutlineColor = oColor : LastOutlineWidth = ow
	LastStyle = styleNow
	
	If CanvasReady Then cvs.Release
	cvs.Initialize(ivBubbleBg)
	CanvasReady = True
	cvs.ClearRect(cvs.TargetRect)
	
	Dim tailPadLeft As Float = IIf(sideNow="start" And tail, TailSize, 0)
	Dim tailPadRight As Float = IIf(sideNow="end" And tail, TailSize, 0)
	
	Dim rr As B4XRect
	rr.Initialize(tailPadLeft, 0, w - tailPadRight, h)
	Dim fieldRadius As Float = B4XDaisyVariants.GetRadiusFieldDip(Corner)
	Dim useCorner As Float = IIf(styleNow = "block", 9dip, fieldRadius)
	Dim bubblePath As B4XPath
	bubblePath.InitializeRoundedRect(rr, useCorner)
	
	'Fill
	cvs.DrawPath(bubblePath, back, True, 0)
	'chat-start/end behavior: remove one bottom corner radius like daisyUI rounded-es/ee-none.
	Dim cornerFix As B4XRect
	Dim cornerFixW As Float = Max(useCorner, TailSize + 1dip)
	If sideNow = "start" Then
		cornerFix.Initialize(rr.Left, rr.Bottom - useCorner, rr.Left + cornerFixW, rr.Bottom)
	Else
		cornerFix.Initialize(rr.Right - cornerFixW, rr.Bottom - useCorner, rr.Right, rr.Bottom)
	End If
	cvs.DrawRect(cornerFix, back, True, 0)
	
	'Tail
	If tail Then
		'Anchor tail to bottom edge for a smoother silhouette.
		'Keep tail baseline flush with bubble bottom edge.
		Dim bottomY As Float = h
		Dim attachTopY As Float
		If styleNow = "block" Then
			attachTopY = Max(useCorner, bottomY - TailSize * 0.85)
		Else
			attachTopY = Max(useCorner, bottomY - TailSize * 0.75)
		End If
		Dim p As B4XPath
		If sideNow="start" Then
			p.Initialize(0, bottomY)
			p.LineTo(TailSize, attachTopY)
			p.LineTo(TailSize, bottomY)
			p.LineTo(0, bottomY)
		Else
			p.Initialize(w, bottomY)
			p.LineTo(w - TailSize, attachTopY)
			p.LineTo(w - TailSize, bottomY)
			p.LineTo(w, bottomY)
		End If
		cvs.DrawPath(p, back, True, 0)
	End If

	'Outline (optional)
	If outline Then
		cvs.DrawPath(bubblePath, oColor, False, OutlineWidth)
		If tail Then
			Dim bottomY2 As Float = h
			Dim attachTopY2 As Float
			If styleNow = "block" Then
				attachTopY2 = Max(useCorner, bottomY2 - TailSize * 0.85)
			Else
				attachTopY2 = Max(useCorner, bottomY2 - TailSize * 0.75)
			End If
			Dim p2 As B4XPath
			If sideNow="start" Then
				p2.Initialize(0, bottomY2)
				p2.LineTo(TailSize, attachTopY2)
				p2.LineTo(TailSize, bottomY2)
				p2.LineTo(0, bottomY2)
			Else
				p2.Initialize(w, bottomY2)
				p2.LineTo(w - TailSize, attachTopY2)
				p2.LineTo(w - TailSize, bottomY2)
				p2.LineTo(w, bottomY2)
			End If
			cvs.DrawPath(p2, oColor, False, OutlineWidth)
		End If
	End If

	cvs.Invalidate
End Sub

Private Sub ApplyElevation(e As Float)
	'No-op by default. Native elevation is platform / API dependent.
End Sub

Private Sub Blend(c1 As Int, c2 As Int, t As Double) As Int
	Dim r1 As Int = Bit.And(Bit.ShiftRight(c1, 16), 0xFF)
	Dim g1 As Int = Bit.And(Bit.ShiftRight(c1, 8), 0xFF)
	Dim b1 As Int = Bit.And(c1, 0xFF)
	Dim r2 As Int = Bit.And(Bit.ShiftRight(c2, 16), 0xFF)
	Dim g2 As Int = Bit.And(Bit.ShiftRight(c2, 8), 0xFF)
	Dim b2 As Int = Bit.And(c2, 0xFF)
	Return xui.Color_RGB(r1 + (r2-r1)*t, g1 + (g2-g1)*t, b1 + (b2-b1)*t)
End Sub

'========================
' Events
'========================

Private Sub chatavatar_AvatarClick(Payload As Object)
	If xui.SubExists(mCallBack, mEventName & "_AvatarClick", 1) Then
		CallSub2(mCallBack, mEventName & "_AvatarClick", Payload)
	End If
End Sub

Public Sub RaiseBubbleClick(Tag As Object)
	If xui.SubExists(mCallBack, mEventName & "_BubbleClick", 1) Then
		CallSub2(mCallBack, mEventName & "_BubbleClick", Tag)
	End If
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyChat.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Event: AvatarClick (Tag As Object)

#DesignerProperty: Key: AvatarMask, DisplayName: Avatar Mask, FieldType: String, DefaultValue: squircle, List: circle|square|squircle|decagon|diamond|heart|hexagon|hexagon-2|pentagon|star|star-2|triangle|triangle-2|triangle-3|triangle-4|half-1|half-2, Description: Mask shape used for message avatars
#DesignerProperty: Key: AvatarSize, DisplayName: Avatar Size, FieldType: Int, DefaultValue: 40, Description: Avatar size in dip for each message row
#DesignerProperty: Key: FromBackgroundColor, DisplayName: From Background, FieldType: Color, DefaultValue: 0xFF4338CA, Description: Background color for outgoing (from) bubbles
#DesignerProperty: Key: FromTextColor, DisplayName: From Text, FieldType: Color, DefaultValue: 0xFFFFFFFF, Description: Text color for outgoing (from) bubbles
#DesignerProperty: Key: ToBackgroundColor, DisplayName: To Background, FieldType: Color, DefaultValue: 0xFF0EA5E9, Description: Background color for incoming (to) bubbles
#DesignerProperty: Key: ToTextColor, DisplayName: To Text, FieldType: Color, DefaultValue: 0xFF082F49, Description: Text color for incoming (to) bubbles
#DesignerProperty: Key: UseFromToColors, DisplayName: Use From/To Colors, FieldType: Boolean, DefaultValue: True, Description: Use explicit from/to colors instead of theme defaults
#DesignerProperty: Key: Theme, DisplayName: Theme, FieldType: String, DefaultValue: light, List: light|default, Description: Theme preset used for default chat colors
#DesignerProperty: Key: DateTimeFormat, DisplayName: Date Time Format, FieldType: String, DefaultValue: D, j M Y H:i, Description: Accepts Java DateFormat or flatpickr tokens (eg H:i, Y-m-d H:i)
#DesignerProperty: Key: UseTimeAgo, DisplayName: Use Time Ago, FieldType: Boolean, DefaultValue: False, Description: Show relative timestamps (for example, 5m ago)
#DesignerProperty: Key: ShowTimeAgoForToday, DisplayName: Time Ago For Today, FieldType: Boolean, DefaultValue: True, Description: When enabled and UseTimeAgo is true, today's times show as time-ago while older dates use DateTimeFormat
#DesignerProperty: Key: VerticalGap, DisplayName: Vertical Gap, FieldType: Int, DefaultValue: 8, Description: Vertical spacing in dip between message rows
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: Int, DefaultValue: 0, Description: Explicit chat width in dip (0 uses base width)
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: Int, DefaultValue: 0, Description: Explicit chat height in dip (0 uses base height)
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Tailwind/spacing padding utilities (eg p-2, px-3, 2)
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1)

'B4XDaisyChat
'Self-contained chat component that manages bubble list, rendering, and runtime updates.
'https://flatpickr.js.org/formatting/
#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object
	Private sv As ScrollView
	Private pnlContent As B4XView
	Private ItemPanels As List
	Private BubbleById As Map
	Private RowById As Map
	Private MessageById As Map
	Private BubbleIds As List
	Private BubbleIndex As Int

	Private AvatarFiles As List
	Private AvatarCache As Map 'filename -> B4XBitmap

	Private mAvatarMask As String = "squircle"
	Private mAvatarSize As Float = 40dip
	Private mFromBackgroundColor As Int = 0xFF4338CA
	Private mFromTextColor As Int = 0xFFFFFFFF
	Private mToBackgroundColor As Int = 0xFF0EA5E9
	Private mToTextColor As Int = 0xFF082F49
	Private mUseFromToColors As Boolean = True
	Private Themes As Map
	Private CurrentTheme As String = "light"
	Private mDateTimeFormat As String = "D, j M Y H:i"
	Private mUseTimeAgo As Boolean = False
	Private mShowTimeAgoForToday As Boolean = True
	Private OnlineIndicatorsVisible As Boolean = True
	Private mVerticalGap As Int = 8dip
	Private ChatWidth As Int = 0
	Private ChatHeight As Int = 0
	Private mPadding As String = ""
	Private mMargin As String = ""
	Private AvatarOnlineColor As Int = 0xFF2ECC71
	Private AvatarOfflineColor As Int = 0xFFB4B4B4
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	InitializeThemes
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	sv.Initialize(0)
	mBase.AddView(sv, 0, 0, mBase.Width, mBase.Height)
	pnlContent = sv.Panel
	pnlContent.Color = xui.Color_Transparent

	ItemPanels.Initialize
	BubbleById.Initialize
	RowById.Initialize
	MessageById.Initialize
	BubbleIds.Initialize
	BubbleIndex = 1
	AvatarFiles.Initialize
	AvatarCache.Initialize
	InitializeThemes

	ApplyDesignerProps(Props)
	ApplyComponentSize
End Sub

Public Sub Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	If sv.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(0, 0, 0, Width, Height)
	ChatWidth = Width
	ChatHeight = Height
	Dim contentRect As B4XRect = ResolveContentRectForBounds(Width, Height)
	sv.SetLayoutAnimated(0, contentRect.Left, contentRect.Top, contentRect.Width, contentRect.Height)
	RelayoutAll
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	If sv.IsInitialized = False Then Return
	ChatWidth = Width
	ChatHeight = Height
	Dim contentRect As B4XRect = ResolveContentRectForBounds(Width, Height)
	sv.SetLayoutAnimated(0, contentRect.Left, contentRect.Top, contentRect.Width, contentRect.Height)
	RelayoutAll
End Sub

Public Sub AddToParent(Parent As B4XView)
	AddToParentAt(Parent, 0, 0, Parent.Width, Parent.Height)
End Sub

Public Sub AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	If Parent.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, w, h)
	
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(w))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(h))
	
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Parent.AddView(mBase, Left, Top, w, h)
End Sub

Public Sub View As B4XView
	Dim empty As B4XView
	If pnlContent.IsInitialized Then Return pnlContent
	If mBase.IsInitialized Then Return mBase
	Return empty
End Sub

Public Sub AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	If pnlContent.IsInitialized Then
		pnlContent.AddView(ChildView, Left, Top, Width, Height)
		Return
	End If
	If mBase.IsInitialized Then mBase.AddView(ChildView, Left, Top, Width, Height)
End Sub

Sub IsReady As Boolean
	Return mBase.IsInitialized And sv.IsInitialized And mBase.Width > 0
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mAvatarMask = B4XDaisyVariants.NormalizeMask(B4XDaisyVariants.GetPropString(Props, "AvatarMask", mAvatarMask))
	mAvatarSize = Max(16dip, getPropDip(Props, "AvatarSize", mAvatarSize))
	mFromBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "FromBackgroundColor", mFromBackgroundColor)
	mFromTextColor = B4XDaisyVariants.GetPropInt(Props, "FromTextColor", mFromTextColor)
	mToBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "ToBackgroundColor", mToBackgroundColor)
	mToTextColor = B4XDaisyVariants.GetPropInt(Props, "ToTextColor", mToTextColor)
	mUseFromToColors = B4XDaisyVariants.GetPropBool(Props, "UseFromToColors", mUseFromToColors)
	setTheme(B4XDaisyVariants.GetPropString(Props, "Theme", CurrentTheme))
	mDateTimeFormat = B4XDaisyVariants.NormalizeDateTimeFormat(B4XDaisyVariants.GetPropString(Props, "DateTimeFormat", mDateTimeFormat), "D, j M Y H:i")
	mUseTimeAgo = B4XDaisyVariants.GetPropBool(Props, "UseTimeAgo", mUseTimeAgo)
	mShowTimeAgoForToday = B4XDaisyVariants.GetPropBool(Props, "ShowTimeAgoForToday", mShowTimeAgoForToday)
	mVerticalGap = Max(0, getPropDip(Props, "VerticalGap", mVerticalGap))
	ChatWidth = Max(0, getPropDip(Props, "Width", 0))
	ChatHeight = Max(0, getPropDip(Props, "Height", 0))
	mPadding = B4XDaisyVariants.GetPropString(Props, "Padding", mPadding)
	mMargin = B4XDaisyVariants.GetPropString(Props, "Margin", mMargin)
	
	AvatarOnlineColor = B4XDaisyVariants.GetPropInt(Props, "OnlineColor", AvatarOnlineColor)
	AvatarOfflineColor = B4XDaisyVariants.GetPropInt(Props, "OfflineColor", AvatarOfflineColor)
End Sub








Public Sub setTag(Value As Object)
	mTag = Value
	If mBase.IsInitialized = False Then Return
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub setPadding(Value As String)
	mPadding = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getPadding As String
	Return mPadding
End Sub

Public Sub setMargin(Value As String)
	mMargin = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getMargin As String
	Return mMargin
End Sub

Private Sub getPropDip(Props As Map, Key As String, DefaultDipValue As Float) As Float
	If Props.ContainsKey(Key) = False Then Return DefaultDipValue
	Return B4XDaisyVariants.GetPropFloat(Props, Key, 0) * 1dip
End Sub

Private Sub ApplyComponentSize
	If mBase.IsInitialized = False Then Return
	Dim w As Int = IIf(ChatWidth > 0, ChatWidth, mBase.Width)
	Dim h As Int = IIf(ChatHeight > 0, ChatHeight, mBase.Height)
	If w <= 0 Then w = 1dip
	If h <= 0 Then h = 1dip
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, h)
	Resize(w, h)
End Sub

Private Sub ResolveContentRectForBounds(Width As Float, Height As Float) As B4XRect
	Dim host As B4XRect
	host.Initialize(0, 0, Max(1dip, Width), Max(1dip, Height))
	Dim box As Map = BuildBoxModel
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Return B4XDaisyBoxModel.ResolveContentRect(outerRect, box)
End Sub

Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	ApplySpacingSpecToBox(box, mPadding, mMargin)
	Return box
End Sub

Private Sub ApplySpacingSpecToBox(Box As Map, PaddingSpec As String, MarginSpec As String)
	Dim rtl As Boolean = False
	Dim p As String = IIf(PaddingSpec = Null, "", PaddingSpec.Trim)
	Dim m As String = IIf(MarginSpec = Null, "", MarginSpec.Trim)
	If p.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(p, Array As String("p-", "px-", "py-", "pt-", "pr-", "pb-", "pl-", "ps-", "pe-")) Then
			B4XDaisyBoxModel.ApplyPaddingUtilities(Box, p, rtl)
		Else
			Dim pv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(p, 0dip)
			Box.Put("padding_left", pv)
			Box.Put("padding_right", pv)
			Box.Put("padding_top", pv)
			Box.Put("padding_bottom", pv)
		End If
	End If
	If m.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(m, Array As String("m-", "mx-", "my-", "mt-", "mr-", "mb-", "ml-", "ms-", "me-", "-m-", "-mx-", "-my-", "-mt-", "-mr-", "-mb-", "-ml-", "-ms-", "-me-")) Then
			B4XDaisyBoxModel.ApplyMarginUtilities(Box, m, rtl)
		Else
			Dim mv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(m, 0dip)
			Box.Put("margin_left", mv)
			Box.Put("margin_right", mv)
			Box.Put("margin_top", mv)
			Box.Put("margin_bottom", mv)
		End If
	End If
End Sub

' removed local getProp helpers; use B4XDaisyVariants.* directly


Private Sub ListWidth As Int
	If mBase.IsInitialized = False Then Return 320dip
	Dim w As Int = mBase.Width
	If w <= 0 Then w = 320dip
	Return w
End Sub

Public Sub Clear
	If ItemPanels.IsInitialized Then
		For Each row As B4XView In ItemPanels
			row.RemoveViewFromParent
		Next
		ItemPanels.Clear
	End If
	If BubbleById.IsInitialized Then BubbleById.Clear
	If RowById.IsInitialized Then RowById.Clear
	If MessageById.IsInitialized Then MessageById.Clear
	If BubbleIds.IsInitialized Then BubbleIds.Clear
	BubbleIndex = 1
	If pnlContent.IsInitialized Then pnlContent.Height = IIf(sv.IsInitialized, sv.Height, 0)
	If sv.IsInitialized Then sv.ScrollPosition = 0
End Sub

Public Sub setConversations(Messages As List)
	Clear
	If Messages.IsInitialized = False Then Return
	For Each m As Map In Messages
		AddMessage(m, False)
	Next
End Sub

Public Sub ClearConversations
	Clear
End Sub

Public Sub AppendMessage(Message As Map) As String
	Return AddMessage(Message, False)
End Sub

Public Sub AppendMessageAndScroll(Message As Map, Smooth As Boolean) As String
	Dim id As String = AddMessage(Message, False)
	If Smooth Then
		SmoothScrollToMessage(id, 450)
	Else
		ScrollToMessage(id)
	End If
	Return id
End Sub

Public Sub ScrollToMessage(BubbleId As String)
	If RowById.IsInitialized = False Or sv.IsInitialized = False Then Return
	If RowById.ContainsKey(BubbleId) = False Then Return
	Dim row As B4XView = RowById.Get(BubbleId)
	Dim target As Int = ClampScrollPosition(row.Top)
	sv.ScrollPosition = target
End Sub

Public Sub ScrollToTop
	If sv.IsInitialized = False Then Return
	sv.ScrollPosition = 0
End Sub

Public Sub ScrollToBottom
	If sv.IsInitialized = False Then Return
	sv.ScrollPosition = MaxScrollPosition
End Sub

Public Sub SmoothScrollToTop(DurationMs As Int) As ResumableSub
	Wait For (SmoothScrollToPosition(0, DurationMs)) Complete (ok As Boolean)
	Return ok
End Sub

Public Sub SmoothScrollToBottom(DurationMs As Int) As ResumableSub
	Wait For (SmoothScrollToPosition(MaxScrollPosition, DurationMs)) Complete (ok As Boolean)
	Return ok
End Sub

Public Sub SmoothScrollToMessage(BubbleId As String, DurationMs As Int) As ResumableSub
	If RowById.IsInitialized = False Or RowById.ContainsKey(BubbleId) = False Then Return False
	Dim row As B4XView = RowById.Get(BubbleId)
	Wait For (SmoothScrollToPosition(row.Top, DurationMs)) Complete (ok As Boolean)
	Return ok
End Sub

Public Sub SmoothScrollToPosition(Target As Int, DurationMs As Int) As ResumableSub
	If sv.IsInitialized = False Then Return False
	Dim targetClamped As Int = ClampScrollPosition(Target)
	Dim startPos As Int = sv.ScrollPosition
	If DurationMs <= 0 Or startPos = targetClamped Then
		sv.ScrollPosition = targetClamped
		Return True
	End If
	Dim frameMs As Int = 16
	Dim steps As Int = Max(1, DurationMs / frameMs)
	Dim stepDelay As Int = Max(10, DurationMs / steps)
	For i = 1 To steps
		Dim t As Double = i / steps
		Dim eased As Double = t * t * (3 - 2 * t)
		Dim pos As Int = startPos + (targetClamped - startPos) * eased
		sv.ScrollPosition = pos
		Sleep(stepDelay)
	Next
	sv.ScrollPosition = targetClamped
	Return True
End Sub

Public Sub getMessageById(BubbleId As String) As Map
	Dim result As Map
	result.Initialize
	If MessageById.IsInitialized = False Then Return result
	If MessageById.ContainsKey(BubbleId) = False Then Return result
	Return CloneMap(MessageById.Get(BubbleId))
End Sub

Public Sub getMessage(BubbleId As String) As Map
	Return GetMessageById(BubbleId)
End Sub

Public Sub UpdateMessageById(BubbleId As String, Fields As Map) As Boolean
	If MessageById.IsInitialized = False Or MessageById.ContainsKey(BubbleId) = False Then Return False
	If Fields.IsInitialized = False Then Return False
	Dim merged As Map = CloneMap(MessageById.Get(BubbleId))
	For Each k As String In Fields.Keys
		merged.Put(k, Fields.Get(k))
	Next
	Return ReplaceMessageById(BubbleId, merged)
End Sub

Public Sub UpdateMessage(Message As Map) As Boolean
	If Message.IsInitialized = False Then Return False
	Dim bubbleId As String = ResolveMessageId(Message)
	If bubbleId.Length = 0 Then Return False
	Dim fields As Map = CloneMap(Message)
	If fields.ContainsKey("id") Then fields.Remove("id")
	If fields.ContainsKey("Id") Then fields.Remove("Id")
	If fields.Size = 0 Then Return MessageById.IsInitialized And MessageById.ContainsKey(bubbleId)
	Return UpdateMessageById(bubbleId, fields)
End Sub

Public Sub UpdateHeaderById(BubbleId As String, HeaderName As String, HeaderTime As String) As Boolean
	Dim m As Map
	m.Initialize
	m.Put("header", HeaderName)
	m.Put("header_time", HeaderTime)
	Return UpdateMessageById(BubbleId, m)
End Sub

Public Sub UpdateFooterById(BubbleId As String, FooterText As String) As Boolean
	Dim m As Map
	m.Initialize
	m.Put("footer", FooterText)
	Return UpdateMessageById(BubbleId, m)
End Sub

Public Sub UpdateAvatarById(BubbleId As String, AvatarBitmap As B4XBitmap) As Boolean
	Dim m As Map
	m.Initialize
	m.Put("avatar_bitmap", AvatarBitmap)
	m.Put("avatar", "")
	Return UpdateMessageById(BubbleId, m)
End Sub

Public Sub UpdateOnlineStatusById(BubbleId As String, Status As String, OnlineColor As Int) As Boolean
	Dim m As Map
	m.Initialize
	m.Put("avatar_status", Status)
	If OnlineColor <> 0 Then m.Put("avatar_online_color", OnlineColor)
	Return UpdateMessageById(BubbleId, m)
End Sub

Public Sub ReplaceMessageById(BubbleId As String, Message As Map) As Boolean
	If BubbleById.IsInitialized = False Or BubbleById.ContainsKey(BubbleId) = False Then Return False
	If Message.IsInitialized = False Then Return False
	Dim bubble As B4XDaisyChatBubble = BubbleById.Get(BubbleId)
	Dim source As Map = CloneMap(Message)
	source.Put("id", BubbleId)
	ConfigureBubbleFromMessage(bubble, source)
	MessageById.Put(BubbleId, source)
	RelayoutAll
	Return True
End Sub

Public Sub DeleteMessageById(BubbleId As String) As Boolean
	If BubbleIds.IsInitialized = False Then Return False
	Dim idx As Int = IndexOfBubbleId(BubbleId)
	If idx = -1 Then Return False
	Dim row As B4XView = ItemPanels.Get(idx)
	row.RemoveViewFromParent
	ItemPanels.RemoveAt(idx)
	BubbleIds.RemoveAt(idx)
	If BubbleById.IsInitialized Then BubbleById.Remove(BubbleId)
	If RowById.IsInitialized Then RowById.Remove(BubbleId)
	If MessageById.IsInitialized Then MessageById.Remove(BubbleId)
	RelayoutAll
	Return True
End Sub

Public Sub AddMessage(Message As Map, ScrollTo As Boolean) As String
	If Message.IsInitialized = False Then Message = CreateMap()
	Dim source As Map = CloneMap(Message)
	
	Dim bubble As B4XDaisyChatBubble
	bubble.Initialize(Me, "bubble")
	bubble.CreateView(ListWidth, 10dip)
	ConfigureBubbleFromMessage(bubble, source)
	Dim bubbleId As String = AddBubbleRow(bubble, ResolveMessageId(source))
	source.Put("id", bubbleId)
	MessageById.Put(bubbleId, source)
	If ScrollTo Then ScrollToMessage(bubbleId)
	Return bubbleId
End Sub

Private Sub ConfigureBubbleFromMessage(bubble As B4XDaisyChatBubble, Message As Map)
	Dim side As String = GetMapString(Message, "side", "start")
	If side <> "end" Then side = "start"
	Dim variantRaw As String = GetMapString(Message, "variant", "")
	Dim hasVariant As Boolean = variantRaw.Trim.Length > 0
	Dim variant As String = "neutral"
	If hasVariant Then variant = variantRaw
	Dim text As String = GetMapString(Message, "text", "")
	Dim header As String = GetMapString(Message, "header", "")
	Dim headerTimeRaw As String = GetMapString(Message, "header_time", "")
	Dim headerTime As String = ResolveHeaderTime(Message, headerTimeRaw)
	Dim footer As String = GetMapString(Message, "footer", "")
	Dim statusMode As String = NormalizeBubbleStatusMode(GetMapString(Message, "status_mode", "none"))
	Dim statusText As String = GetMapString(Message, "status_text", "")
	Dim localUseFromTo As Boolean = mUseFromToColors
	If hasVariant Then localUseFromTo = False
	If Message.ContainsKey("use_from_to_colors") Then localUseFromTo = GetMapBool(Message, "use_from_to_colors", localUseFromTo)
	Dim messageTheme As String = GetMapString(Message, "theme", "")
	Dim messagePalette As Map = GetMapMap(Message, "theme_palette")
	Dim resolvedPalette As Map
	If messagePalette.IsInitialized Then
		resolvedPalette = messagePalette
	Else
		resolvedPalette = ResolvePalette(messageTheme)
	End If
	Dim showHeader As Boolean
	If Message.ContainsKey("show_header") Then
		showHeader = getMapBool(Message, "show_header", False)
	Else
		showHeader = (header.Length > 0 Or headerTime.Length > 0)
	End If
	Dim showFooter As Boolean
	If Message.ContainsKey("show_footer") Then
		showFooter = getMapBool(Message, "show_footer", False)
	Else
		showFooter = (footer.Length > 0 Or statusMode <> "none" Or statusText.Length > 0)
	End If
	Dim avatarStatus As String = getMapString(Message, "avatar_status", "none")
	If avatarStatus = "" Then avatarStatus = "none"
	Dim onlineColor As Int = getMapInt(Message, "avatar_online_color", AvatarOnlineColor)
	Dim offlineColor As Int = getMapInt(Message, "avatar_offline_color", AvatarOfflineColor)
	Dim avatarMaskNow As String = getMapString(Message, "avatar_mask", mAvatarMask)
	Dim backOverride As Int = getMapInt(Message, "back_color", 0)
	Dim textOverride As Int = getMapInt(Message, "text_color", 0)
	Dim mutedOverride As Int = getMapInt(Message, "muted_color", 0)
	
	bubble.SetSide(side)
	bubble.SetVariant(variant)
	bubble.SetBubbleStyle("block")
	bubble.SetAvatarSize(mAvatarSize)
	bubble.SetGlobalMask(avatarMaskNow)
	bubble.SetShowOnline(OnlineIndicatorsVisible)
	bubble.SetAvatarStatusColors(onlineColor, offlineColor)
	bubble.SetVariantPalette(resolvedPalette)
	bubble.SetFromToColors(mFromBackgroundColor, mFromTextColor, mToBackgroundColor, mToTextColor)
	bubble.SetUseFromToColors(localUseFromTo)
	bubble.SetColors(backOverride, textOverride, mutedOverride)
	bubble.SetHeaderVisible(showHeader)
	If showHeader Then
		bubble.SetHeaderParts(header, headerTime)
	Else
		bubble.SetHeaderParts("", "")
	End If
	bubble.SetMessage(text)
	bubble.SetFooter(footer)
	bubble.SetStatus(statusMode, statusText)
	bubble.SetFooterVisible(showFooter)
	
	Dim abmp As B4XBitmap = getAvatarBitmapFromMessage(Message)
	Dim hasAvatar As Boolean = abmp.IsInitialized
	bubble.SetAvatarVisible(hasAvatar)
	If hasAvatar Then
		bubble.SetAvatarBitmap(abmp, getMapObject(Message, "avatar_tag", Null))
		If OnlineIndicatorsVisible Then bubble.SetAvatarStatus(avatarStatus) Else bubble.SetAvatarStatus("none")
	Else
		bubble.SetAvatarStatus("none")
	End If
End Sub

Private Sub getAvatarBitmapFromMessage(Message As Map) As B4XBitmap
	Dim empty As B4XBitmap
	If Message.ContainsKey("avatar_bitmap") Then
		Dim bmp As B4XBitmap = Message.Get("avatar_bitmap")
		If bmp.IsInitialized Then Return bmp
	End If
	Dim fileName As String = GetMapString(Message, "avatar", "")
	If fileName.Trim.Length = 0 Then Return empty
	Return AvatarByFile(fileName)
End Sub

Private Sub AddBubbleRow(bubble As B4XDaisyChatBubble, PreferredId As String) As String
	Dim pp As Panel
	pp.Initialize("")
	Dim row As B4XView = pp
	row.Color = xui.Color_Transparent
	Dim bubbleId As String = PreferredId
	If bubbleId.Trim.Length = 0 Then
		bubbleId = "msg_" & BubbleIndex
		BubbleIndex = BubbleIndex + 1
	End If
	If BubbleById.ContainsKey(bubbleId) Then
		Dim n As Int = 2
		Dim baseId As String = bubbleId
		Do While BubbleById.ContainsKey(baseId & "_" & n)
			n = n + 1
		Loop
		bubbleId = baseId & "_" & n
	End If
	
	Dim rowW As Int = ListWidth
	Dim rowH As Int = bubble.MeasureHeight(rowW)
	row.AddView(bubble.mBase, 0, 0, rowW, rowH)
	bubble.Base_Resize(rowW, rowH)
	Dim actualH As Int = Max(rowH, bubble.GetUsedHeight)
	If actualH <> rowH Then
		rowH = actualH
		row.SetLayoutAnimated(0, 0, 0, rowW, rowH)
		bubble.mBase.SetLayoutAnimated(0, 0, 0, rowW, rowH)
		bubble.Base_Resize(rowW, rowH)
	End If
	
	Dim y As Int = 0
	If ItemPanels.Size > 0 Then
		Dim prev As B4XView = ItemPanels.Get(ItemPanels.Size - 1)
		y = prev.Top + prev.Height + mVerticalGap
	End If
	row.Tag = bubble
	bubble.SetId(bubbleId)
	bubble.mBase.Tag = bubbleId
	pnlContent.AddView(row, 0, y, rowW, rowH)
	ItemPanels.Add(row)
	BubbleById.Put(bubbleId, bubble)
	RowById.Put(bubbleId, row)
	BubbleIds.Add(bubbleId)
	UpdateContentHeight
	Return bubbleId
End Sub

Private Sub ResolveMessageId(Message As Map) As String
	Dim id As String = GetMapString(Message, "id", "")
	Return id.Trim
End Sub

Private Sub RelayoutAll
	If ItemPanels.IsInitialized = False Then Return
	Dim y As Int = 0
	Dim rowW As Int = ListWidth
	For Each row As B4XView In ItemPanels
		Dim bubble As B4XDaisyChatBubble = row.Tag
		Dim rowH As Int = bubble.MeasureHeight(rowW)
		row.SetLayoutAnimated(0, 0, y, rowW, rowH)
		bubble.mBase.SetLayoutAnimated(0, 0, 0, rowW, rowH)
		bubble.Base_Resize(rowW, rowH)
		Dim actualH As Int = Max(rowH, bubble.GetUsedHeight)
		If actualH <> rowH Then
			rowH = actualH
			row.SetLayoutAnimated(0, 0, y, rowW, rowH)
			bubble.mBase.SetLayoutAnimated(0, 0, 0, rowW, rowH)
			bubble.Base_Resize(rowW, rowH)
		End If
		y = y + rowH + mVerticalGap
	Next
	UpdateContentHeight
End Sub

Private Sub UpdateContentHeight
	Dim h As Int = 0
	If ItemPanels.Size > 0 Then
		Dim last As B4XView = ItemPanels.Get(ItemPanels.Size - 1)
		h = last.Top + last.Height + mVerticalGap
	End If
	If sv.IsInitialized Then h = Max(h, sv.Height)
	pnlContent.Height = h
End Sub

Private Sub MaxScrollPosition As Int
	If sv.IsInitialized = False Or pnlContent.IsInitialized = False Then Return 0
	Return Max(0, pnlContent.Height - sv.Height)
End Sub

Private Sub ClampScrollPosition(Value As Int) As Int
	Return Max(0, Min(Value, MaxScrollPosition))
End Sub

Private Sub getMapString(m As Map, key As String, DefaultValue As String) As String
	If m.ContainsKey(key) = False Then Return DefaultValue
	Dim o As Object = m.Get(key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub NormalizeBubbleStatusMode(Mode As String) As String
	If Mode = Null Then Return "none"
	Dim m As String = Mode.ToLowerCase.Trim
	Select Case m
		Case "sent", "delivered", "read"
			Return m
		Case Else
			Return "none"
	End Select
End Sub

Private Sub getMapBool(m As Map, key As String, DefaultValue As Boolean) As Boolean
	If m.ContainsKey(key) = False Then Return DefaultValue
	Dim o As Object = m.Get(key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub getMapInt(m As Map, key As String, DefaultValue As Int) As Int
	If m.ContainsKey(key) = False Then Return DefaultValue
	Dim o As Object = m.Get(key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub getMapObject(m As Map, key As String, DefaultValue As Object) As Object
	If m.ContainsKey(key) = False Then Return DefaultValue
	Dim o As Object = m.Get(key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub getMapMap(m As Map, key As String) As Map
	Dim result As Map
	If m.ContainsKey(key) = False Then Return result
	Dim o As Object = m.Get(key)
	If o = Null Then Return result
	result = o
	Return result
End Sub

Private Sub CloneMap(Source As Map) As Map
	Dim result As Map
	result.Initialize
	If Source.IsInitialized = False Then Return result
	For Each k As Object In Source.Keys
		result.Put(k, Source.Get(k))
	Next
	Return result
End Sub

Private Sub IndexOfBubbleId(BubbleId As String) As Int
	If BubbleIds.IsInitialized = False Then Return -1
	For i = 0 To BubbleIds.Size - 1
		If BubbleIds.Get(i) = BubbleId Then Return i
	Next
	Return -1
End Sub

Private Sub ResolveHeaderTime(Message As Map, HeaderTimeText As String) As String
	Dim explicitText As String = HeaderTimeText.Trim
	Dim explicitMillis As Long = ParseTimeMillis(explicitText)
	Dim messageMillis As Long = ExtractMessageTimeMillis(Message)
	Dim resolvedMillis As Long = IIf(explicitMillis > 0, explicitMillis, messageMillis)
	If mUseTimeAgo Then
		If resolvedMillis > 0 Then
			If mShowTimeAgoForToday Then
				If IsSameDeviceDate(resolvedMillis, DateTime.Now) Then
					Return FormatTimeAgo(resolvedMillis)
				Else
					Return FormatTimestamp(resolvedMillis)
				End If
			Else
				Return FormatTimeAgo(resolvedMillis)
			End If
		End If
		Return explicitText
	End If
	If resolvedMillis > 0 Then Return FormatTimestamp(resolvedMillis)
	If explicitText.Length > 0 Then Return explicitText
	Return ""
End Sub

Private Sub IsSameDeviceDate(Ticks1 As Long, Ticks2 As Long) As Boolean
	Return DateTime.GetYear(Ticks1) = DateTime.GetYear(Ticks2) And _
		DateTime.GetMonth(Ticks1) = DateTime.GetMonth(Ticks2) And _
		DateTime.GetDayOfMonth(Ticks1) = DateTime.GetDayOfMonth(Ticks2)
End Sub

Private Sub ExtractMessageTimeMillis(Message As Map) As Long
	Dim keys As List
	keys.Initialize
	keys.AddAll(Array("time_millis", "timestamp", "created_at", "time", "header_time"))
	For Each k As String In keys
		If Message.ContainsKey(k) = False Then Continue
		Dim raw As Object = Message.Get(k)
		Dim parsed As Long = ParseTimeMillis(raw)
		If parsed > 0 Then Return parsed
	Next
	Return 0
End Sub

Private Sub ParseTimeMillis(Value As Object) As Long
	If Value = Null Then Return 0
	Dim t As String = Value
	t = t.Trim
	If t.Length = 0 Then Return 0
	If Regex.IsMatch("^[0-9]+$", t) Then
		Dim n As Long = t
		If n < 1000000000000 Then n = n * 1000
		Return n
	End If
	Return ParseDateTimeText(t)
End Sub

Private Sub ParseDateTimeText(Text As String) As Long
	Dim prevFormat As String = DateTime.DateFormat
	Dim patterns As List
	patterns.Initialize
	patterns.AddAll(Array("yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss", mDateTimeFormat))
	For Each fmt As String In patterns
		Try
			DateTime.DateFormat = fmt
			Dim ticks As Long = DateTime.DateParse(Text)
			DateTime.DateFormat = prevFormat
			Return ticks
		Catch
			DateTime.DateFormat = prevFormat
		End Try
	Next
	DateTime.DateFormat = prevFormat
	Return 0
End Sub

Private Sub FormatTimestamp(ValueMillis As Long) As String
	Return B4XDaisyVariants.FormatDateTime(mDateTimeFormat, ValueMillis)
End Sub

Private Sub FormatTimeAgo(ValueMillis As Long) As String
	Dim delta As Long = Max(0, DateTime.Now - ValueMillis)
	Dim sec As Long = delta / DateTime.TicksPerSecond
	If sec < 60 Then Return "just now"
	Dim minutes As Long = sec / 60
	If minutes < 60 Then Return minutes & "m ago"
	Dim hours As Long = minutes / 60
	If hours < 24 Then Return hours & "h ago"
	Dim days As Long = hours / 24
	If days < 7 Then Return days & "d ago"
	Return FormatTimestamp(ValueMillis)
End Sub

Public Sub LoadAvatarFilesFromAssets(Files As List)
	AvatarFiles.Clear
	AvatarCache.Clear
	If Files.IsInitialized = False Then Return
	For Each fn As String In Files
		If File.Exists(File.DirAssets, fn) Then
			AvatarFiles.Add(fn)
			AvatarCache.Put(fn, xui.LoadBitmap(File.DirAssets, fn))
		End If
	Next
End Sub

Public Sub setAvatarFiles(Files As List)
	LoadAvatarFilesFromAssets(Files)
End Sub

Public Sub getAvatarFiles As List
	Dim r As List
	r.Initialize
	For Each fn As String In AvatarFiles
		r.Add(fn)
	Next
	Return r
End Sub

Public Sub RandomAvatarFileOrBlank(BlankPct As Int) As String
	If AvatarFiles.IsInitialized = False Or AvatarFiles.Size = 0 Then Return ""
	If Rnd(0, 100) < BlankPct Then Return ""
	Return AvatarFiles.Get(Rnd(0, AvatarFiles.Size))
End Sub

Public Sub RandomAvatarStatus As String
	Dim r As Int = Rnd(0, 100)
	If r < 35 Then Return "online"
	If r < 65 Then Return "offline"
	Return "none"
End Sub

Private Sub AvatarByFile(FileName As String) As B4XBitmap
	Dim empty As B4XBitmap
	Dim key As String = FileName.Trim
	If key.Length = 0 Then Return empty
	If AvatarCache.IsInitialized = False Then AvatarCache.Initialize
	If AvatarCache.ContainsKey(key) Then Return AvatarCache.Get(key)
	
	Dim bmp As B4XBitmap = LoadBitmapFromPathIfExists(key)
	If bmp.IsInitialized = False Then
		If File.Exists(File.DirAssets, key) Then bmp = xui.LoadBitmap(File.DirAssets, key)
	End If
	If bmp.IsInitialized Then AvatarCache.Put(key, bmp)
	Return bmp
End Sub

Private Sub LoadBitmapFromPathIfExists(FullPath As String) As B4XBitmap
	Dim empty As B4XBitmap
	Dim p As String = FullPath.Trim
	If p.Length = 0 Then Return empty
	Dim slash1 As Int = p.LastIndexOf("/")
	Dim slash2 As Int = p.LastIndexOf("\")
	Dim slash As Int = Max(slash1, slash2)
	If slash <= 0 Then Return empty
	Dim dir As String = p.SubString2(0, slash)
	Dim fn As String = p.SubString(slash + 1)
	If dir.Length = 0 Or fn.Length = 0 Then Return empty
	If File.Exists(dir, fn) = False Then Return empty
	Return xui.LoadBitmap(dir, fn)
End Sub

Public Sub setBubbleAvatarStatusById(BubbleId As String, Mode As String)
	If BubbleById.IsInitialized = False Then Return
	If BubbleById.ContainsKey(BubbleId) = False Then Return
	Dim bubble As B4XDaisyChatBubble = BubbleById.Get(BubbleId)
	bubble.SetAvatarStatus(Mode)
	If MessageById.IsInitialized And MessageById.ContainsKey(BubbleId) Then
		Dim m As Map = CloneMap(MessageById.Get(BubbleId))
		m.Put("avatar_status", Mode)
		MessageById.Put(BubbleId, m)
	End If
End Sub

Public Sub getBubbleIds As List
	Dim result As List
	result.Initialize
	If BubbleIds.IsInitialized = False Then Return result
	For Each id As String In BubbleIds
		result.Add(id)
	Next
	Return result
End Sub

Public Sub setAvatarMask(Mask As String)
	mAvatarMask = B4XDaisyVariants.NormalizeMask(Mask)
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getAvatarMask As String
	Return mAvatarMask
End Sub

Public Sub setMask(Mask As String)
	setAvatarMask(Mask)
End Sub

Public Sub setAvatarSize(Size As Int)
	mAvatarSize = Max(16dip, Size)
	ReconfigureAllBubblesFromMessages(True)
End Sub

Public Sub getAvatarSize As Int
	Return Round(mAvatarSize)
End Sub

Public Sub setFromBackgroundColor(Color As Int)
	mFromBackgroundColor = Color
	mUseFromToColors = True
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getFromBackgroundColor As Int
	Return mFromBackgroundColor
End Sub

Public Sub setFromTextColor(Color As Int)
	mFromTextColor = Color
	mUseFromToColors = True
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getFromTextColor As Int
	Return mFromTextColor
End Sub

Public Sub setToBackgroundColor(Color As Int)
	mToBackgroundColor = Color
	mUseFromToColors = True
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getToBackgroundColor As Int
	Return mToBackgroundColor
End Sub

Public Sub setToTextColor(Color As Int)
	mToTextColor = Color
	mUseFromToColors = True
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getToTextColor As Int
	Return mToTextColor
End Sub

Public Sub setFromToColors(FromBack As Int, FromText As Int, ToBack As Int, ToText As Int)
	mFromBackgroundColor = FromBack
	mFromTextColor = FromText
	mToBackgroundColor = ToBack
	mToTextColor = ToText
	mUseFromToColors = True
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub setUseFromToColors(Enabled As Boolean)
	mUseFromToColors = Enabled
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getUseFromToColors As Boolean
	Return mUseFromToColors
End Sub

Public Sub setTheme(Name As String)
	InitializeThemes
	Dim key As String = NormalizeThemeName(Name)
	If Themes.ContainsKey(key) Then
		CurrentTheme = key
		ReconfigureAllBubblesFromMessages(False)
	End If
End Sub

Public Sub getTheme As String
	InitializeThemes
	Return CurrentTheme
End Sub

Public Sub setDateTimeFormat(Value As String)
	mDateTimeFormat = B4XDaisyVariants.NormalizeDateTimeFormat(Value, "D, j M Y H:i")
	ReconfigureAllBubblesFromMessages(True)
End Sub

Public Sub getDateTimeFormat As String
	Return mDateTimeFormat
End Sub

Public Sub setUseTimeAgo(Enabled As Boolean)
	mUseTimeAgo = Enabled
	ReconfigureAllBubblesFromMessages(True)
End Sub

Public Sub getUseTimeAgo As Boolean
	Return mUseTimeAgo
End Sub

Public Sub setShowTimeAgoForToday(Enabled As Boolean)
	mShowTimeAgoForToday = Enabled
	ReconfigureAllBubblesFromMessages(True)
End Sub

Public Sub getShowTimeAgoForToday As Boolean
	Return mShowTimeAgoForToday
End Sub

Public Sub RegisterTheme(Name As String, PaletteMap As Map)
	InitializeThemes
	If PaletteMap.IsInitialized = False Then Return
	Dim key As String = NormalizeThemeName(Name)
	If key.Length = 0 Then Return
	Themes.Put(key, PaletteMap)
	If key = CurrentTheme Then ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getPalette As Map
	Return ResolvePalette("")
End Sub

Public Sub CreateVariant(BackColor As Int, TextColor As Int) As Map
	Return VariantMap(BackColor, TextColor)
End Sub

Public Sub ShowOnline(Enabled As Boolean)
	OnlineIndicatorsVisible = Enabled
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub setOnlineStatusColors(OnlineColor As Int, OfflineColor As Int)
	If OnlineColor <> 0 Then AvatarOnlineColor = OnlineColor
	If OfflineColor <> 0 Then AvatarOfflineColor = OfflineColor
	ReconfigureAllBubblesFromMessages(False)
End Sub

Public Sub getOnlineStatusColor As Int
	Return AvatarOnlineColor
End Sub

Public Sub getOfflineStatusColor As Int
	Return AvatarOfflineColor
End Sub

Public Sub setVerticalGap(Gap As Int)
	mVerticalGap = Max(0, Gap)
	RelayoutAll
End Sub

Public Sub getVerticalGap As Int
	Return mVerticalGap
End Sub

Public Sub setWidth(Value As Int)
	ChatWidth = Max(1dip, Value)
	ApplyComponentSize
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return ChatWidth
End Sub

Public Sub setHeight(Value As Int)
	ChatHeight = Max(1dip, Value)
	ApplyComponentSize
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return ChatHeight
End Sub

Public Sub setSize(Width As Int, Height As Int)
	ChatWidth = Max(1dip, Width)
	ChatHeight = Max(1dip, Height)
	ApplyComponentSize
End Sub

Private Sub ReconfigureAllBubblesFromMessages(DoRelayout As Boolean)
	If BubbleIds.IsInitialized = False Then Return
	If BubbleById.IsInitialized = False Then Return
	If MessageById.IsInitialized = False Then Return
	For Each id As String In BubbleIds
		If BubbleById.ContainsKey(id) = False Then Continue
		Dim bubble As B4XDaisyChatBubble = BubbleById.Get(id)
		Dim source As Map
		If MessageById.ContainsKey(id) Then
			source = CloneMap(MessageById.Get(id))
		Else
			source.Initialize
		End If
		source.Put("id", id)
		ConfigureBubbleFromMessage(bubble, source)
		MessageById.Put(id, source)
	Next
	If DoRelayout Then RelayoutAll
End Sub
Private Sub bubble_AvatarClick(tag As Object)
	If xui.SubExists(mCallBack, mEventName & "_AvatarClick", 1) Then
		CallSub2(mCallBack, mEventName & "_AvatarClick", tag)
	End If
End Sub

Private Sub ResolvePalette(ThemeName As String) As Map
	InitializeThemes
	Dim key As String = NormalizeThemeName(ThemeName)
	If key.Length > 0 And Themes.ContainsKey(key) Then Return Themes.Get(key)
	If Themes.ContainsKey(CurrentTheme) Then Return Themes.Get(CurrentTheme)
	Return Themes.Get("light")
End Sub

Private Sub NormalizeThemeName(Name As String) As String
	If Name = Null Then Return ""
	Return Name.ToLowerCase.Trim
End Sub

Private Sub InitializeThemes
	If Themes.IsInitialized Then Return
	Themes.Initialize
	RegisterDefaultThemes
	CurrentTheme = "light"
End Sub

Private Sub RegisterDefaultThemes
	'FlyonUI theme tokens mapped to component variants.
	Dim pDefault As Map = CreatePalette( _
		xui.Color_RGB(61,42,81), xui.Color_RGB(254,253,255), _
		xui.Color_RGB(192,132,252), xui.Color_RGB(243,232,255), _
		xui.Color_RGB(151,146,158), xui.Color_RGB(61,42,81), _
		xui.Color_RGB(96,165,250), xui.Color_RGB(251,207,232), _
		xui.Color_RGB(34,211,238), xui.Color_RGB(207,250,254), _
		xui.Color_RGB(52,211,153), xui.Color_RGB(209,250,229), _
		xui.Color_RGB(251,146,60), xui.Color_RGB(255,237,213), _
		xui.Color_RGB(252,141,121), xui.Color_RGB(255,245,237))
	Themes.Put("default", pDefault)

	Dim pLight As Map = CreatePalette( _
		xui.Color_RGB(61,42,81), xui.Color_RGB(254,253,255), _
		xui.Color_RGB(192,132,252), xui.Color_RGB(243,232,255), _
		xui.Color_RGB(151,146,158), xui.Color_RGB(61,42,81), _
		xui.Color_RGB(96,165,250), xui.Color_RGB(251,207,232), _
		xui.Color_RGB(34,211,238), xui.Color_RGB(207,250,254), _
		xui.Color_RGB(52,211,153), xui.Color_RGB(209,250,229), _
		xui.Color_RGB(251,146,60), xui.Color_RGB(255,237,213), _
		xui.Color_RGB(252,141,121), xui.Color_RGB(255,245,237))
	Themes.Put("light", pLight)
End Sub

Private Sub CreatePalette(neutralBack As Int, neutralText As Int, _
	primaryBack As Int, primaryText As Int, _
	secondaryBack As Int, secondaryText As Int, _
	accentBack As Int, accentText As Int, _
	infoBack As Int, infoText As Int, _
	successBack As Int, successText As Int, _
	warningBack As Int, warningText As Int, _
	errorBack As Int, errorText As Int) As Map

	Dim m As Map
	m.Initialize
	m.Put("neutral", VariantMap(neutralBack, neutralText))
	m.Put("primary", VariantMap(primaryBack, primaryText))
	m.Put("secondary", VariantMap(secondaryBack, secondaryText))
	m.Put("accent", VariantMap(accentBack, accentText))
	m.Put("info", VariantMap(infoBack, infoText))
	m.Put("success", VariantMap(successBack, successText))
	m.Put("warning", VariantMap(warningBack, warningText))
	m.Put("error", VariantMap(errorBack, errorText))
	Return m
End Sub

Private Sub VariantMap(back As Int, text As Int) As Map
	Dim vm As Map
	vm.Initialize
	vm.Put("back", back)
	vm.Put("text", text)
	vm.Put("muted", Blend(text, xui.Color_Gray, 0.35))
	Return vm
End Sub

Private Sub Blend(c1 As Int, c2 As Int, t As Double) As Int
	Dim r1 As Int = Bit.And(Bit.ShiftRight(c1, 16), 0xFF)
	Dim g1 As Int = Bit.And(Bit.ShiftRight(c1, 8), 0xFF)
	Dim b1 As Int = Bit.And(c1, 0xFF)
	Dim r2 As Int = Bit.And(Bit.ShiftRight(c2, 16), 0xFF)
	Dim g2 As Int = Bit.And(Bit.ShiftRight(c2, 8), 0xFF)
	Dim b2 As Int = Bit.And(c2, 0xFF)
	Return xui.Color_RGB(r1 + (r2-r1)*t, g1 + (g2-g1)*t, b1 + (b2-b1)*t)
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyCarouselItem.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Region Events
#Event: Click (Tag As Object)
#End Region

#Region Designer Properties
#DesignerProperty: Key: ItemType, DisplayName: Item Type, FieldType: String, DefaultValue: image, List: image|svg|custom, Description: Type of content to display.
#DesignerProperty: Key: Source, DisplayName: Source, FieldType: String, DefaultValue: , Description: Image file or SVG content/asset.
#DesignerProperty: Key: Snap, DisplayName: Snap Position, FieldType: String, DefaultValue: start, List: start|center|end, Description: Carousel snapping position.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: rounded-none, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius variant.
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: w-full, Description: Item width as a Tailwind class: w-full, w-1/2, w-64, w-[150px] etc.
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-full, Description: Item height as a Tailwind class: h-full, h-48, h-[200px] etc.
#DesignerProperty: Key: ImageWidth, DisplayName: Image Width, FieldType: String, DefaultValue: w-full, Description: Width of the image/content inside the item frame. w-full = 100% of item width.
#DesignerProperty: Key: ImageHeight, DisplayName: Image Height, FieldType: String, DefaultValue: h-full, Description: Height of the image/content inside the item frame. h-full = 100% of item height.
#DesignerProperty: Key: ImageResizeMode, DisplayName: Image Resize Mode, FieldType: String, List: FIT|FILL|FILL_NO_DISTORTIONS|FILL_WIDTH|FILL_HEIGHT|NONE, DefaultValue: FILL_NO_DISTORTIONS, Description: How to scale the image within the carousel item frame.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: Enabled state.
#End Region

#Region Variables
#IgnoreWarnings:12,9
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object
    Private mVisible As Boolean = True

    
    ' Internal views
    Public mImage As B4XView
    Private mImageComp As B4XDaisyImage
    Private mIcon As B4XDaisySvgIcon
    Private mContainer As B4XView
    
    ' Local properties
    Private msItemType As String = "image"
    Private msSource As String = ""
    Private msSnap As String = "start"
    Private msRounded As String = "rounded-none"
    Private msWidth As String = "w-full"
    Private msHeight As String = "h-full"
    Private msImageWidth As String = "w-full"
    Private msImageHeight As String = "h-full"
    Private msImageResizeMode As String = "FILL_NO_DISTORTIONS"
    
    Private mIsResizing As Boolean = False
End Sub
#End Region

#Region Initialization
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent
    
    ' Load properties
    msItemType = B4XDaisyVariants.GetPropString(Props, "ItemType", msItemType)
    msSource = B4XDaisyVariants.GetPropString(Props, "Source", msSource)
    msSnap = B4XDaisyVariants.GetPropString(Props, "Snap", msSnap)
    msRounded = B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded)
    msWidth = B4XDaisyVariants.GetPropString(Props, "Width", msWidth)
    msHeight = B4XDaisyVariants.GetPropString(Props, "Height", msHeight)
    msImageWidth = B4XDaisyVariants.GetPropString(Props, "ImageWidth", msImageWidth)
    msImageHeight = B4XDaisyVariants.GetPropString(Props, "ImageHeight", msImageHeight)
    msImageResizeMode = B4XDaisyVariants.GetPropString(Props, "ImageResizeMode", msImageResizeMode)
    
    UpdateContent
    mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
    mBase.Visible = mVisible
    Refresh
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
    Dim p As Panel
    p.Initialize("mBase")
    ' Pre-size so mBase.Width/Height are correct during DesignerCreateView.
    p.Width = Width
    p.Height = Height
    Dim b As B4XView = p
    b.Color = xui.Color_Transparent
    ' Carry over any properties set before AddToParent so they are not overwritten
    ' by the empty-props defaults in DesignerCreateView.
    Dim props As Map
    props.Initialize
    props.Put("ItemType", msItemType)
    props.Put("Source", msSource)
    props.Put("Snap", msSnap)
    props.Put("Rounded", msRounded)
    props.Put("Width", msWidth)
    props.Put("Height", msHeight)
    props.Put("ImageWidth", msImageWidth)
    props.Put("ImageHeight", msImageHeight)
    props.Put("ImageResizeMode", msImageResizeMode)
    Dim dummy As Label
    DesignerCreateView(b, dummy, props)
    Base_Resize(Width, Height)
    Return mBase
End Sub
#End Region

#Region Public API
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

Public Sub Refresh
    If mBase.IsInitialized = False Then Return
    
    ' Apply rounded
    Dim radius As Int = B4XDaisyVariants.ResolveRoundedDip(msRounded, 0)
    mBase.SetColorAndBorder(mBase.Color, 0, 0, radius)
    
    ' Clip to outline (simulates overflow-hidden)
    B4XDaisyVariants.SetOverflowHidden(mBase)
    
    ' Snap behavior is handled by the parent Carousel component
    
    ' Guard: only propagate resize when mBase has been measured by Android's layout system.
    ' When Refresh is called from DesignerCreateView, mBase.Width is still 0 (the Panel
    ' hasn't had a layout pass). Calling Base_Resize(0, 0) would zero out mImage.
    ' CreateView and LayoutItems call Base_Resize explicitly with the correct dimensions.
    If mBase.Width > 0 And mBase.Height > 0 Then
        Base_Resize(mBase.Width, mBase.Height)
    End If
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    
    ' Resolve actual size from Tailwind tokens; passed W/H are the "full" reference.
    Dim refW As Int = Width
    If refW <= 0 Then refW = Parent.Width
    Dim refH As Int = Height
    If refH <= 0 Then refH = Parent.Height
    Dim actualW As Int = B4XDaisyVariants.TailwindSizeToDip(msWidth, refW)
    Dim actualH As Int = B4XDaisyVariants.TailwindSizeToDip(msHeight, refH)
    If actualW < 1 Then actualW = Max(1, refW)
    If actualH < 1 Then actualH = Max(1, refH)
    
    If mBase.IsInitialized = False Then
        CreateView(actualW, actualH)
    End If
    Parent.AddView(mBase, Left, Top, actualW, actualH)
    Return mBase
End Sub

Private Sub UpdateContent
    If mBase.IsInitialized = False Then Return
    
    ' Clear previous content
    mBase.RemoveAllViews
    
    Select msItemType
        Case "image"
            mImageComp.Initialize(Me, "")
            mImageComp.mBackgroundColor = 0
            mImageComp.setResizeMode(msImageResizeMode)
            mImageComp.AddToParent(mBase, 0, 0, Max(1, mBase.Width), Max(1, mBase.Height))
            mImage = mImageComp.mBase
            If msSource <> "" Then
                Try
                    mImageComp.Load(File.DirAssets, msSource)
                Catch
                    Log("Failed to load image: " & msSource)
                End Try
            End If
            UpdateImageLayout(mBase.Width, mBase.Height)
            
        Case "svg"
            mIcon.Initialize(Me, "")
            mIcon.AddToParent(mBase, 0, 0, mBase.Width, mBase.Height)
            mIcon.SetClickable(False)
            If msSource.ToLowerCase.EndsWith(".svg") Then
                mIcon.setSvgAsset(msSource)
            Else
                mIcon.setSvgContent(msSource)
            End If
            
        Case "custom"
            Dim p As Panel
            p.Initialize("")
            mContainer = p
            mBase.AddView(mContainer, 0, 0, mBase.Width, mBase.Height)
    End Select
End Sub

Public Sub getItemType As String
    Return msItemType
End Sub

Public Sub setItemType(Value As String)
    msItemType = Value
    UpdateContent
    Refresh
End Sub

Public Sub getSource As String
    Return msSource
End Sub

Public Sub setSource(Value As String)
    msSource = Value
    UpdateContent
    Refresh
End Sub

Public Sub getSnap As String
    Return msSnap
End Sub

Public Sub setSnap(Value As String)
    msSnap = Value
    Refresh
End Sub

Public Sub getRounded As String
    Return msRounded
End Sub

Public Sub setRounded(Value As String)
    msRounded = Value
    Refresh
End Sub

Public Sub getTag As Object
    Return mTag
End Sub

Public Sub setTag(Value As Object)
    mTag = Value
End Sub

Public Sub getContainer As B4XView
    If msItemType <> "custom" Then Return Null
    Return mContainer
End Sub

Public Sub getWidth As String
    Return msWidth
End Sub

Public Sub setWidth(Value As String)
    msWidth = Value
End Sub

Public Sub getHeight As String
    Return msHeight
End Sub

Public Sub setHeight(Value As String)
    msHeight = Value
End Sub

Public Sub getImageWidth As String
    Return msImageWidth
End Sub

Public Sub setImageWidth(Value As String)
    msImageWidth = Value
    If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getImageHeight As String
    Return msImageHeight
End Sub

Public Sub setImageHeight(Value As String)
    msImageHeight = Value
    If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getVisible As Boolean
    If mBase.IsInitialized = False Then Return True
    Return mBase.Visible
End Sub

Public Sub setVisible(Value As Boolean)
	    mVisible = Value
	    If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getEnabled As Boolean
    If mBase.IsInitialized = False Then Return True
    Return mBase.Enabled
End Sub

Public Sub setEnabled(Value As Boolean)
    If mBase.IsInitialized Then mBase.Enabled = Value
End Sub

Public Sub getImageResizeMode As String
    Return msImageResizeMode
End Sub

Public Sub setImageResizeMode(Value As String)
    msImageResizeMode = Value
    If mImageComp.IsInitialized Then mImageComp.setResizeMode(Value)
    If mBase.IsInitialized Then UpdateImageLayout(mBase.Width, mBase.Height)
End Sub

Private Sub UpdateImageLayout(Width As Float, Height As Float)
    If mImage.IsInitialized = False Then Return
    
    If msSource = "" Or msItemType <> "image" Then
        mImage.SetLayoutAnimated(0, 0, 0, Width, Height)
        Return
    End If
    
    Dim maxW As Float = B4XDaisyVariants.TailwindSizeToDip(msImageWidth, Width)
    If maxW < 1 Then maxW = Width
    Dim maxH As Float = B4XDaisyVariants.TailwindSizeToDip(msImageHeight, Height)
    If maxH < 1 Then maxH = Height
    
    Dim Left As Int = Round(Width / 2 - maxW / 2)
    Dim Top As Int = Round(Height / 2 - maxH / 2)
    
    mImage.SetLayoutAnimated(0, Left, Top, Round(maxW), Round(maxH))
End Sub

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mIsResizing Then Return
    mIsResizing = True
    
    Dim imgW As Int = B4XDaisyVariants.TailwindSizeToDip(msImageWidth, Width)
    If imgW < 1 Then imgW = Width
    Dim imgH As Int = B4XDaisyVariants.TailwindSizeToDip(msImageHeight, Height)
    If imgH < 1 Then imgH = Height
    
    If mImage.IsInitialized Then UpdateImageLayout(Width, Height)
    If mIcon.IsInitialized Then mIcon.Base_Resize(imgW, imgH)
    If mContainer.IsInitialized Then mContainer.SetLayoutAnimated(0, 0, 0, Width, Height)
    
    mIsResizing = False
End Sub

Private Sub mBase_Click
    RaiseClick
End Sub

Private Sub RaiseClick
    Dim payload As Object = mTag
    If payload = Null Then payload = mBase
    If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
        CallSub2(mCallBack, mEventName & "_Click", payload)
    Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
        CallSub(mCallBack, mEventName & "_Click")
    End If
End Sub
#End Region

#Region Cleanup
Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region



#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCarousel.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Region Events
#Event: Click (Tag As Object)
#Event: PageChanged (Index As Int)
#End Region

#Region Designer Properties
#DesignerProperty: Key: Orientation, DisplayName: Orientation, FieldType: String, DefaultValue: horizontal, List: horizontal|vertical, Description: Carousel scroll orientation.
#DesignerProperty: Key: Snap, DisplayName: Snap Position, FieldType: String, DefaultValue: start, List: start|center|end, Description: Carousel snapping behavior.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius variant.
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: False, Description: Apply the DaisyUI rounded-box semantic corner radius. Takes priority over the Rounded property when True.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl, Description: Box shadow elevation level.
#DesignerProperty: Key: NavigationButtons, DisplayName: Navigation Buttons, FieldType: Boolean, DefaultValue: False, Description: Show prev/next navigation buttons overlaid on the carousel.
#DesignerProperty: Key: IndicatorButtons, DisplayName: Indicator Buttons, FieldType: Boolean, DefaultValue: False, Description: Show indicator dot buttons overlaid at the bottom of the carousel.
#DesignerProperty: Key: AutoPlay, DisplayName: Auto Play, FieldType: Boolean, DefaultValue: False, Description: Automatically advance slides on a timed interval.
#DesignerProperty: Key: AutoPlayInterval, DisplayName: AutoPlay Interval (ms), FieldType: Int, DefaultValue: 3000, Description: Milliseconds between auto-advance steps when AutoPlay is enabled.
#DesignerProperty: Key: ItemGap, DisplayName: Item Gap, FieldType: Int, DefaultValue: 0, Description: Gap in pixels between carousel items (space-x-N / space-y-N equivalent).
#DesignerProperty: Key: Gap, DisplayName: Gap (Token), FieldType: String, DefaultValue: , Description: Space between items as a Tailwind/DaisyUI spacing token: space-x-4, gap-2, 16px etc. Overrides Item Gap when non-empty.
#DesignerProperty: Key: ContentPadding, DisplayName: Content Padding, FieldType: Int, DefaultValue: 0, Description: Inner padding in pixels of the scroll area inside the carousel container (p-N equivalent).
#DesignerProperty: Key: Padding, DisplayName: Padding (Token), FieldType: String, DefaultValue: , Description: Inner content padding as a Tailwind/DaisyUI spacing token: p-4, p-2, 8px etc. Overrides Content Padding when non-empty.
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: w-full, Description: Width as a Tailwind class: w-full, w-64, w-1/2, w-[300px] etc.
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-[300px], Description: Height as a Tailwind class: h-[300px], h-48, h-full, h-[200px], h-auto (auto-fits tallest item) etc.
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: String, DefaultValue: , Description: Background color as a DaisyUI/Tailwind token: neutral, base-200, primary, transparent etc.
#DesignerProperty: Key: IndicatorBackgroundColor, DisplayName: Indicator Bg Color, FieldType: Color, DefaultValue: 0x50000000, Description: Color of the indicator strip background.
#DesignerProperty: Key: IndicatorActiveColor, DisplayName: Indicator Active Color, FieldType: Color, DefaultValue: 0xFFFFFFFF, Description: Color of the active indicator dot.
#DesignerProperty: Key: IndicatorInactiveColor, DisplayName: Indicator Inactive Color, FieldType: Color, DefaultValue: 0x78FFFFFF, Description: Color of the inactive indicator dots.
#DesignerProperty: Key: IndicatorDotSize, DisplayName: Indicator Dot Size, FieldType: Int, DefaultValue: 10, Description: Size of the indicator dots.
#DesignerProperty: Key: IndicatorDotGap, DisplayName: Indicator Dot Gap, FieldType: Int, DefaultValue: 6, Description: Spacing between indicator dots.
#DesignerProperty: Key: IndicatorOffset, DisplayName: Indicator Offset, FieldType: Int, DefaultValue: 0, Description: Distance to offset the indicator dots strip from its default edge.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: Enabled state.
#End Region

#Region Variables
#IgnoreWarnings:12,9
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object
    Private mVisible As Boolean = True

    
    ' Internal views
    Private mScrollView As B4XView ' HorizontalScrollView or ScrollView
    Private mHScrollView As HorizontalScrollView ' Typed ref for horizontal ï¿½ needed to wire ScrollChanged event
    Private mVScrollView As ScrollView ' Typed ref for vertical ï¿½ needed to wire ScrollChanged event
    Private mContentPanel As B4XView
    
    ' Local properties
    Private msOrientation As String = "horizontal"
    Private msSnap As String = "start"
    Private msRounded As String = "theme"
    Private mbRoundedBox As Boolean = False
    Private msShadow As String = "none"
    
    Private mItems As List
    Private mIsResizing As Boolean = False
    Private mSnapTimer As Timer
    ' Prevents snap timer from re-triggering during programmatic smooth scroll.
    Private mIsProgrammaticScroll As Boolean = False
    Private mProgrammaticTimer As Timer
    ' Polls HorizontalScrollView.ScrollPosition since Initialize("") suppresses ScrollChanged event.
    Private mScrollPollTimer As Timer
    Private mLastScrollPos As Int = 0
    
    ' Auto-play (timed slide advance)
    Private mbAutoPlay As Boolean = False
    Private mnAutoPlayInterval As Int = 3000
    Private mAutoPlayTimer As Timer
    Private mAutoPlayUserPausedTimer As Timer  ' resumes autoplay after user interaction
    
    ' Overlay controls
    Private mScrollHost As B4XView
    Private mBtnPrev As B4XView
    Private mBtnNext As B4XView
    Private mPnlIndicators As B4XView
    Private mDotViews As List
    Private mbNavigationButtons As Boolean = False
    Private mbIndicatorButtons As Boolean = False
    Private mbEnabled As Boolean = True
    Private mCurrentIndex As Int = 0
    Private mnItemGap As Int = 0
    Private msGap As String = ""          ' Tailwind token override for item gap (e.g. space-x-4)
    Private mnContentPadding As Int = 0
    Private msPaddingToken As String = "" ' Tailwind token override for content padding (e.g. p-4)
    Private msWidth As String = "w-full"
    Private msHeight As String = "h-[300px]"
    Private msBackgroundColor As String = ""
    Private mnIndicatorBackgroundColor As Int = 0x50000000
    Private mnIndicatorActiveColor As Int = 0xFFFFFFFF
    Private mnIndicatorInactiveColor As Int = 0x78FFFFFF
    Private mnIndicatorDotSize As Int = 10dip
    Private mnIndicatorDotGap As Int = 6dip
    Private mnIndicatorOffset As Int = 0dip
End Sub
#End Region

#Region Initialization
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
    mItems.Initialize
    mDotViews.Initialize
    mProgrammaticTimer.Initialize("mProgrammaticTimer", 500)
    mProgrammaticTimer.Enabled = False
    mScrollPollTimer.Initialize("mScrollPollTimer", 80)
    mScrollPollTimer.Enabled = False
    mAutoPlayTimer.Initialize("mAutoPlayTimer", 3000)
    mAutoPlayTimer.Enabled = False
    mAutoPlayUserPausedTimer.Initialize("mAutoPlayUserPausedTimer", 3000)
    mAutoPlayUserPausedTimer.Enabled = False
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent
    
    ' Load properties
    msOrientation = B4XDaisyVariants.GetPropString(Props, "Orientation", msOrientation)
    msSnap = B4XDaisyVariants.GetPropString(Props, "Snap", msSnap)
    msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
    mbNavigationButtons = B4XDaisyVariants.GetPropBool(Props, "NavigationButtons", mbNavigationButtons)
    mbIndicatorButtons = B4XDaisyVariants.GetPropBool(Props, "IndicatorButtons", mbIndicatorButtons)
    mnItemGap = B4XDaisyVariants.GetPropInt(Props, "ItemGap", mnItemGap)
    msGap = B4XDaisyVariants.GetPropString(Props, "Gap", msGap)
    If msGap <> "" Then mnItemGap = B4XDaisyVariants.TailwindSpacingToDip(msGap, 0)
    mnContentPadding = B4XDaisyVariants.GetPropInt(Props, "ContentPadding", mnContentPadding)
    msPaddingToken = B4XDaisyVariants.GetPropString(Props, "Padding", msPaddingToken)
    If msPaddingToken <> "" Then mnContentPadding = B4XDaisyVariants.TailwindSpacingToDip(msPaddingToken, 0)
    msWidth = B4XDaisyVariants.GetPropString(Props, "Width", msWidth)
    msHeight = B4XDaisyVariants.GetPropString(Props, "Height", msHeight)
    msBackgroundColor = B4XDaisyVariants.GetPropString(Props, "BackgroundColor", msBackgroundColor)
    mnIndicatorBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "IndicatorBackgroundColor", mnIndicatorBackgroundColor)
    mnIndicatorActiveColor = B4XDaisyVariants.GetPropInt(Props, "IndicatorActiveColor", mnIndicatorActiveColor)
    mnIndicatorInactiveColor = B4XDaisyVariants.GetPropInt(Props, "IndicatorInactiveColor", mnIndicatorInactiveColor)
    mnIndicatorDotSize = B4XDaisyVariants.GetPropInt(Props, "IndicatorDotSize", 10) * 1dip
    mnIndicatorDotGap = B4XDaisyVariants.GetPropInt(Props, "IndicatorDotGap", 6) * 1dip
    mnIndicatorOffset = B4XDaisyVariants.GetPropInt(Props, "IndicatorOffset", 0) * 1dip
    mbRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mbRoundedBox)
    msShadow = B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow)
    mbAutoPlay = B4XDaisyVariants.GetPropBool(Props, "AutoPlay", mbAutoPlay)
    mnAutoPlayInterval = B4XDaisyVariants.GetPropInt(Props, "AutoPlayInterval", mnAutoPlayInterval)
    mbEnabled = B4XDaisyVariants.GetPropBool(Props, "Enabled", mbEnabled)
    If mnAutoPlayInterval < 500 Then mnAutoPlayInterval = 500
    
    mSnapTimer.Initialize("mSnapTimer", 200)
    mSnapTimer.Enabled = False
    
    UpdateScrollContainer
    mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
    mBase.Visible = mVisible
    Refresh
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
    Dim p As Panel
    p.Initialize("mBase")
    ' Pre-size the panel so mBase.Width/Height are correct when DesignerCreateView
    ' calls Refresh -> LayoutOverlays, preventing overlays being placed at 0,0.
    p.Width = Width
    p.Height = Height
    Dim b As B4XView = p
    b.Color = xui.Color_Transparent
    ' Carry over any properties set before AddToParent so they are not overwritten
    ' by empty-prop defaults in DesignerCreateView (same fix applied to CarouselItem).
    Dim props As Map
    props.Initialize
    props.Put("Orientation", msOrientation)
    props.Put("Snap", msSnap)
    props.Put("Rounded", msRounded)
    props.Put("NavigationButtons", mbNavigationButtons)
    props.Put("IndicatorButtons", mbIndicatorButtons)
    props.Put("ItemGap", mnItemGap)
    props.Put("Gap", msGap)
    props.Put("ContentPadding", mnContentPadding)
    props.Put("Padding", msPaddingToken)
    props.Put("Width", msWidth)
    props.Put("Height", msHeight)
    props.Put("BackgroundColor", msBackgroundColor)
    props.Put("RoundedBox", mbRoundedBox)
    props.Put("Shadow", msShadow)
    props.Put("AutoPlay", mbAutoPlay)
    props.Put("AutoPlayInterval", mnAutoPlayInterval)
    props.Put("IndicatorBackgroundColor", mnIndicatorBackgroundColor)
    props.Put("IndicatorActiveColor", mnIndicatorActiveColor)
    props.Put("IndicatorInactiveColor", mnIndicatorInactiveColor)
    props.Put("IndicatorDotSize", mnIndicatorDotSize / 1dip)
    props.Put("IndicatorDotGap", mnIndicatorDotGap / 1dip)
    props.Put("IndicatorOffset", mnIndicatorOffset / 1dip)
    props.Put("Enabled", mbEnabled)
    props.Put("Visible", mVisible)
    Dim dummy As Label
    DesignerCreateView(b, dummy, props)
    Base_Resize(Width, Height)
    Return mBase
End Sub
#End Region

#Region Public API
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

Public Sub Refresh
    If mBase.IsInitialized = False Then Return
    If mScrollHost.IsInitialized = False Then Return
    
    ' RoundedBox (DaisyUI semantic token) takes priority over the manual Rounded string.
    Dim radius As Int
    If mbRoundedBox Then
        radius = B4XDaisyVariants.ResolveRoundedDip("rounded-box", 0)
    Else
        radius = B4XDaisyVariants.ResolveRoundedDip(msRounded, 0)
    End If
    
    ' Resolve background color from Tailwind/DaisyUI token string.
    Dim bgColor As Int
    If msBackgroundColor = "" Or msBackgroundColor = "transparent" Or msBackgroundColor = "none" Then
        bgColor = xui.Color_Transparent
    Else
        ' Strip bg- prefix if present so "bg-neutral" and "neutral" both work.
        Dim colorToken As String = msBackgroundColor
        If colorToken.ToLowerCase.StartsWith("bg-") Then colorToken = colorToken.SubString(3)
        bgColor = B4XDaisyVariants.ResolveBackgroundColorVariant(colorToken, xui.Color_Transparent)
    End If
    
    ' Apply rounded corners + background color to mBase (no clip ï¿½ keeps overlays visible).
    mBase.SetColorAndBorder(bgColor, 0, 0, radius)
    ' Apply shadow elevation (DaisyUI shadow utility).
    B4XDaisyVariants.ApplyElevation(mBase, msShadow)
    ' Disabled: dim the whole carousel so it reads as inactive.
    mBase.Alpha = IIf(mbEnabled, 1.0, 0.3)
    
    ' Apply rounded corners + clip to mScrollHost so scroll content is masked.
    mScrollHost.SetColorAndBorder(xui.Color_Transparent, 0, 0, radius)
    B4XDaisyVariants.SetOverflowHidden(mScrollHost)
    
    ' Update items layout
    LayoutItems
    
    ' Build/refresh overlaid nav buttons and indicator dots
    LayoutOverlays
    
    ' Sync auto-play timer: ensure running when enabled+has items, paused otherwise.
    If mbAutoPlay And mItems.Size > 0 Then
        mAutoPlayTimer.Interval = mnAutoPlayInterval
        If mAutoPlayTimer.Enabled = False And mAutoPlayUserPausedTimer.Enabled = False Then
            mAutoPlayTimer.Enabled = True
        End If
    Else If mbAutoPlay = False Then
        mAutoPlayTimer.Enabled = False
    End If
    
    Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    
    ' Resolve actual dimensions from Tailwind width/height tokens.
    ' The passed Width/Height serve as the "full" reference (e.g. w-full -> passed Width).
    ' Fall back to parent dimensions when the caller passes 0.
    Dim refW As Int = Width
    If refW <= 0 Then refW = Parent.Width
    Dim refH As Int = Height
    If refH <= 0 Then refH = Parent.Height
    Dim actualW As Int = B4XDaisyVariants.TailwindSizeToDip(msWidth, refW)
    Dim actualH As Int
    If msHeight = "h-auto" Or msHeight = "auto" Then
        ' Height will be resolved after items are measured in Refresh/LayoutItems.
        actualH = 1dip
    Else
        actualH = B4XDaisyVariants.TailwindSizeToDip(msHeight, refH)
    End If
    If actualW < 1 Then actualW = Max(1, refW)
    If actualH < 1 Then actualH = Max(1, refH)
    
    If mBase.IsInitialized = False Then
        CreateView(actualW, actualH)
    End If
    Parent.AddView(mBase, Left, Top, actualW, actualH)
    Return mBase
End Sub

Private Sub UpdateScrollContainer
    If mBase.IsInitialized = False Then Return
    
    ' Remove old scroll host (its children ï¿½ mScrollView ï¿½ are removed with it)
    If mScrollHost.IsInitialized Then mScrollHost.RemoveViewFromParent
    
    ' mScrollHost is the clipped inner area; overlay views live on mBase above it
    Dim pHost As Panel
    pHost.Initialize("")
    mScrollHost = pHost
    mScrollHost.Color = xui.Color_Transparent
    Dim cp As Int = mnContentPadding
    mBase.AddView(mScrollHost, cp, cp, Max(1, mBase.Width - cp * 2), Max(1, mBase.Height - cp * 2))
    
    If msOrientation = "horizontal" Then
        mHScrollView.Initialize(0, "")
        mScrollView = mHScrollView
        mScrollPollTimer.Enabled = True
        ' Hide scrollbar (DaisyUI: scrollbar-width: none / ::-webkit-scrollbar { display: none })
        Dim joH As JavaObject = mScrollView
        joH.RunMethod("setHorizontalScrollBarEnabled", Array(False))
    Else
        mVScrollView.Initialize(0)
        mScrollView = mVScrollView
        ' Hide scrollbar (DaisyUI: scrollbar-width: none / ::-webkit-scrollbar { display: none })
        Dim jo As JavaObject = mVScrollView
        jo.RunMethod("setVerticalScrollBarEnabled", Array(False))
        mScrollPollTimer.Enabled = False
    End If
    
    mScrollHost.AddView(mScrollView, 0, 0, mScrollHost.Width, mScrollHost.Height)
    
    ' Get Inner Panel (Content Panel)
    If msOrientation = "horizontal" Then
        mContentPanel = mHScrollView.Panel
    Else
        mContentPanel = mVScrollView.Panel
    End If
    
    mContentPanel.Color = xui.Color_Transparent
    
    ' Re-parent any existing items into the new content panel, preserving their sizes
    For Each itm As B4XDaisyCarouselItem In mItems
        Dim savedW As Int = itm.mBase.Width
        Dim savedH As Int = itm.mBase.Height
        If savedW < 1 Then savedW = 100dip
        If savedH < 1 Then savedH = 100dip
        If itm.mBase.Parent <> Null Then itm.mBase.RemoveViewFromParent
        mContentPanel.AddView(itm.mBase, 0, 0, savedW, savedH)
    Next
End Sub

Public Sub AddItem(Item As B4XDaisyCarouselItem)
    If mItems.IndexOf(Item) = -1 Then mItems.Add(Item)
    If Item.mBase.IsInitialized = False Or Item.mBase.Parent <> mContentPanel Then
        ' Auto-parent the item into the content panel, resolving its Width/Height tokens
        ' against the scroll host dimensions (more reliable than mContentPanel which may be 0).
        ' LayoutItems will re-resolve them accurately at Refresh time.
        If Item.mBase.IsInitialized And Item.mBase.Parent <> Null Then Item.mBase.RemoveViewFromParent
        Dim refW As Int = mScrollHost.Width
        If refW < 1 Then refW = Max(1, mBase.Width - mnContentPadding * 2)
        Dim refH As Int = mScrollHost.Height
        If refH < 1 Then refH = Max(1, mBase.Height - mnContentPadding * 2)
        Item.AddToParent(mContentPanel, 0, 0, refW, refH)
    End If
    Refresh
End Sub

Public Sub RemoveItem(Item As B4XDaisyCarouselItem)
    Dim idx As Int = mItems.IndexOf(Item)
    If idx > -1 Then
        mItems.RemoveAt(idx)
        Item.mBase.RemoveViewFromParent
        Refresh
    End If
End Sub

Public Sub Clear
    For Each itm As B4XDaisyCarouselItem In mItems
        itm.mBase.RemoveViewFromParent
    Next
    mItems.Clear
    mCurrentIndex = 0
    Refresh
End Sub

Private Sub LayoutItems
    If mItems.Size = 0 Then Return
    
    Dim currentPos As Int = 0
    Dim itemCount As Int = mItems.Size
    For i = 0 To itemCount - 1
        Dim itm As B4XDaisyCarouselItem = mItems.Get(i)
        Dim gap As Int = 0
        If i < itemCount - 1 Then gap = mnItemGap
        
        If msOrientation = "horizontal" Then
            Dim scrollH As Int = mScrollHost.Height
            If scrollH < 1 Then scrollH = mBase.Height - mnContentPadding * 2
            Dim scrollW As Int = mScrollHost.Width
            If scrollW < 1 Then scrollW = mBase.Width - mnContentPadding * 2
            ' Re-resolve width and height tokens against current scroll host dimensions.
            Dim itemW As Int = B4XDaisyVariants.TailwindSizeToDip(itm.Width, scrollW)
            ' w-auto: item frame inherits its ImageWidth token size
            If itm.Width = "w-auto" Or itm.Width = "auto" Then
                Dim autoW As Int = B4XDaisyVariants.TailwindSizeToDip(itm.ImageWidth, scrollW)
                If autoW > 0 Then itemW = autoW
            End If
            If itemW < 1 Then itemW = itm.mBase.Width
            If itemW < 1 Then itemW = scrollW
            Dim itemH As Int = B4XDaisyVariants.TailwindSizeToDip(itm.Height, scrollH)
            ' h-auto: item frame inherits its ImageHeight token size
            If itm.Height = "h-auto" Or itm.Height = "auto" Then
                Dim autoH As Int = B4XDaisyVariants.TailwindSizeToDip(itm.ImageHeight, scrollH)
                If autoH > 0 Then itemH = autoH
            End If
            If itemH < 1 Then itemH = itm.mBase.Height
            If itemH < 1 Then itemH = scrollH
            itm.mBase.SetLayoutAnimated(0, currentPos, 0, itemW, itemH)
            itm.Base_Resize(itemW, itemH)
            currentPos = currentPos + itemW + gap
        Else
            Dim scrollW2 As Int = mScrollHost.Width
            If scrollW2 < 1 Then scrollW2 = mBase.Width - mnContentPadding * 2
            Dim scrollH2 As Int = mScrollHost.Height
            If scrollH2 < 1 Then scrollH2 = mBase.Height - mnContentPadding * 2
            ' Re-resolve width and height tokens against current scroll host dimensions.
            Dim itemH2 As Int = B4XDaisyVariants.TailwindSizeToDip(itm.Height, scrollH2)
            ' h-auto: item frame inherits its ImageHeight token size
            If itm.Height = "h-auto" Or itm.Height = "auto" Then
                Dim autoH2 As Int = B4XDaisyVariants.TailwindSizeToDip(itm.ImageHeight, scrollH2)
                If autoH2 > 0 Then itemH2 = autoH2
            End If
            If itemH2 < 1 Then itemH2 = itm.mBase.Height
            If itemH2 < 1 Then itemH2 = scrollH2
            Dim itemW2 As Int = B4XDaisyVariants.TailwindSizeToDip(itm.Width, scrollW2)
            ' w-auto: item frame inherits its ImageWidth token size
            If itm.Width = "w-auto" Or itm.Width = "auto" Then
                Dim autoW2 As Int = B4XDaisyVariants.TailwindSizeToDip(itm.ImageWidth, scrollW2)
                If autoW2 > 0 Then itemW2 = autoW2
            End If
            If itemW2 < 1 Then itemW2 = itm.mBase.Width
            If itemW2 < 1 Then itemW2 = scrollW2
            itm.mBase.SetLayoutAnimated(0, 0, currentPos, itemW2, itemH2)
            itm.Base_Resize(itemW2, itemH2)
            currentPos = currentPos + itemH2 + gap
        End If
    Next
    
    ' h-auto: resize mBase and mScrollHost to the tallest (H) or widest (V) item.
    Dim isAutoH As Boolean = (msHeight = "h-auto" Or msHeight = "auto")
    If isAutoH And msOrientation = "horizontal" Then
        Dim maxItemH As Int = 0
        For i = 0 To mItems.Size - 1
            Dim itmH As B4XDaisyCarouselItem = mItems.Get(i)
            maxItemH = Max(maxItemH, itmH.mBase.Height)
        Next
        If maxItemH > 0 Then
            Dim newH As Int = maxItemH + mnContentPadding * 2
            mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, mBase.Width, newH)
            Dim cp As Int = mnContentPadding
            mScrollHost.SetLayoutAnimated(0, cp, cp, Max(1, mBase.Width - cp * 2), Max(1, newH - cp * 2))
        End If
    End If

    ' Update content panel size
    Dim scrollHostH As Int = mScrollHost.Height
    Dim scrollHostW As Int = mScrollHost.Width
    If scrollHostH < 1 Then scrollHostH = mBase.Height - mnContentPadding * 2
    If scrollHostW < 1 Then scrollHostW = mBase.Width - mnContentPadding * 2
    If msOrientation = "horizontal" Then
        mContentPanel.Width = Max(currentPos, scrollHostW)
        mContentPanel.Height = scrollHostH
    Else
        mContentPanel.Width = scrollHostW
        mContentPanel.Height = Max(currentPos, scrollHostH)
    End If
End Sub

Public Sub ScrollToItem(Index As Int)
    If Index < 0 Or Index >= mItems.Size Then Return
    Dim itm As B4XDaisyCarouselItem = mItems.Get(Index)
    Dim viewportSize As Int
    If msOrientation = "horizontal" Then
        viewportSize = mScrollHost.Width
        If viewportSize < 1 Then viewportSize = mBase.Width - mnContentPadding * 2
    Else
        viewportSize = mScrollHost.Height
        If viewportSize < 1 Then viewportSize = mBase.Height - mnContentPadding * 2
    End If
    
    Dim targetPos As Int
    Select msSnap
        Case "start"
            If msOrientation = "horizontal" Then
                targetPos = itm.mBase.Left
            Else
                targetPos = itm.mBase.Top
            End If
        Case "center"
            If msOrientation = "horizontal" Then
                targetPos = itm.mBase.Left + (itm.mBase.Width / 2) - (viewportSize / 2)
            Else
                targetPos = itm.mBase.Top + (itm.mBase.Height / 2) - (viewportSize / 2)
            End If
        Case "end"
            If msOrientation = "horizontal" Then
                targetPos = (itm.mBase.Left + itm.mBase.Width) - viewportSize
            Else
                targetPos = (itm.mBase.Top + itm.mBase.Height) - viewportSize
            End If
    End Select
    
    targetPos = Max(0, targetPos)
    
    ' Use smooth scroll (DaisyUI: scroll-behavior: smooth).
    ' Set flag so snap timer is suppressed during the animation.
    mIsProgrammaticScroll = True
    mProgrammaticTimer.Enabled = False
    mProgrammaticTimer.Enabled = True
    
    Dim joScroll As JavaObject = mScrollView
    If msOrientation = "horizontal" Then
        joScroll.RunMethod("smoothScrollTo", Array(targetPos, 0))
    Else
        joScroll.RunMethod("smoothScrollTo", Array(0, targetPos))
    End If
    
    Dim oldIndex As Int = mCurrentIndex
    mCurrentIndex = Index
    UpdateDots
    If oldIndex <> mCurrentIndex Then
        If xui.SubExists(mCallBack, mEventName & "_PageChanged", 1) Then
            CallSub2(mCallBack, mEventName & "_PageChanged", mCurrentIndex)
        End If
    End If
End Sub

Public Sub getOrientation As String
    Return msOrientation
End Sub

Public Sub setOrientation(Value As String)
    msOrientation = Value
    UpdateScrollContainer
    Refresh
End Sub

Public Sub getSnap As String
    Return msSnap
End Sub

Public Sub setSnap(Value As String)
    msSnap = Value
    Refresh
End Sub

Public Sub getRounded As String
    Return msRounded
End Sub

Public Sub setRounded(Value As String)
    msRounded = Value
    Refresh
End Sub

Public Sub getTag As Object
    Return mTag
End Sub

Public Sub setTag(Value As Object)
    mTag = Value
End Sub

Public Sub getItemGap As Int
    Return mnItemGap
End Sub

Public Sub setItemGap(Value As Int)
    mnItemGap = Value
    msGap = ""  ' raw dip wins; clear token override
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getGap As String
    Return msGap
End Sub

Public Sub setGap(Value As String)
    msGap = Value
    If Value <> "" Then mnItemGap = B4XDaisyVariants.TailwindSpacingToDip(Value, 0)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getContentPadding As Int
    Return mnContentPadding
End Sub

Public Sub setContentPadding(Value As Int)
    mnContentPadding = Value
    msPaddingToken = ""  ' raw dip wins; clear token override
    If mBase.IsInitialized Then
        UpdateScrollContainer
        Refresh
    End If
End Sub

Public Sub getPadding As String
    Return msPaddingToken
End Sub

Public Sub setPadding(Value As String)
    msPaddingToken = Value
    If Value <> "" Then mnContentPadding = B4XDaisyVariants.TailwindSpacingToDip(Value, 0)
    If mBase.IsInitialized Then
        UpdateScrollContainer
        Refresh
    End If
End Sub

Public Sub getWidth As String
    Return msWidth
End Sub

Public Sub setWidth(Value As String)
    msWidth = Value
End Sub

Public Sub getHeight As String
    Return msHeight
End Sub

Public Sub setHeight(Value As String)
    msHeight = Value
End Sub

Public Sub getBackgroundColor As String
    Return msBackgroundColor
End Sub

Public Sub setBackgroundColor(Value As String)
    msBackgroundColor = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getNavigationButtons As Boolean
    Return mbNavigationButtons
End Sub

Public Sub setNavigationButtons(Value As Boolean)
    mbNavigationButtons = Value
    If mBase.IsInitialized Then LayoutOverlays
End Sub

Public Sub getIndicatorButtons As Boolean
    Return mbIndicatorButtons
End Sub

Public Sub setIndicatorButtons(Value As Boolean)
    mbIndicatorButtons = Value
    If mBase.IsInitialized Then LayoutOverlays
End Sub

Public Sub getAutoPlay As Boolean
    Return mbAutoPlay
End Sub

Public Sub setAutoPlay(Value As Boolean)
    mbAutoPlay = Value
    If Value Then
        StartAutoPlay
    Else
        StopAutoPlay
    End If
End Sub

Public Sub getAutoPlayInterval As Int
    Return mnAutoPlayInterval
End Sub

Public Sub setAutoPlayInterval(Value As Int)
    mnAutoPlayInterval = Max(500, Value)
    mAutoPlayTimer.Interval = mnAutoPlayInterval
End Sub

' Start auto-sliding at the current AutoPlayInterval.
Public Sub StartAutoPlay
    If mItems.Size = 0 Then Return
    mbAutoPlay = True
    mAutoPlayUserPausedTimer.Enabled = False
    mAutoPlayTimer.Interval = mnAutoPlayInterval
    mAutoPlayTimer.Enabled = True
End Sub

' Stop auto-sliding and disable all auto-play timers.
Public Sub StopAutoPlay
    mbAutoPlay = False
    mAutoPlayTimer.Enabled = False
    mAutoPlayUserPausedTimer.Enabled = False
End Sub

Public Sub getCurrentIndex As Int
    Return mCurrentIndex
End Sub

Public Sub getVisible As Boolean
    If mBase.IsInitialized = False Then Return True
    Return mBase.Visible
End Sub

Public Sub setVisible(Value As Boolean)
	    mVisible = Value
	    If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getEnabled As Boolean
    Return mbEnabled
End Sub

Public Sub setEnabled(Value As Boolean)
    mbEnabled = Value
    If mBase.IsInitialized Then
        mBase.Enabled = Value
        Refresh
    End If
End Sub

Public Sub getRoundedBox As Boolean
    Return mbRoundedBox
End Sub

Public Sub setRoundedBox(Value As Boolean)
    mbRoundedBox = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getShadow As String
    Return msShadow
End Sub

Public Sub setShadow(Value As String)
    msShadow = B4XDaisyVariants.NormalizeShadow(Value)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIndicatorBackgroundColor As Int
    Return mnIndicatorBackgroundColor
End Sub

Public Sub setIndicatorBackgroundColor(Value As Int)
    mnIndicatorBackgroundColor = Value
    If mPnlIndicators.IsInitialized Then mPnlIndicators.Color = Value
End Sub

Public Sub getIndicatorActiveColor As Int
    Return mnIndicatorActiveColor
End Sub

Public Sub setIndicatorActiveColor(Value As Int)
    mnIndicatorActiveColor = Value
    If mBase.IsInitialized Then UpdateDots
End Sub

Public Sub getIndicatorInactiveColor As Int
    Return mnIndicatorInactiveColor
End Sub

Public Sub setIndicatorInactiveColor(Value As Int)
    mnIndicatorInactiveColor = Value
    If mBase.IsInitialized Then UpdateDots
End Sub

Public Sub getIndicatorDotSize As Int
    Return mnIndicatorDotSize
End Sub

Public Sub setIndicatorDotSize(Value As Int)
    mnIndicatorDotSize = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIndicatorDotGap As Int
    Return mnIndicatorDotGap
End Sub

Public Sub setIndicatorDotGap(Value As Int)
    mnIndicatorDotGap = Value
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIndicatorOffset As Int
    Return mnIndicatorOffset
End Sub

Public Sub setIndicatorOffset(Value As Int)
    mnIndicatorOffset = Value
    If mBase.IsInitialized Then Refresh
End Sub

' Build (or rebuild) the nav buttons and indicator dots overlaid on mBase.
' Called from Refresh and whenever NavigationButtons/IndicatorButtons props change.
Private Sub LayoutOverlays
    If mBase.IsInitialized = False Then Return
    
    Dim NAV_SIZE As Int = 38dip
    Dim NAV_MARGIN As Int = 8dip
    Dim DOT_SIZE As Int = mnIndicatorDotSize
    Dim DOT_GAP As Int = mnIndicatorDotGap
    Dim INDICATOR_STRIP As Int = Max(32dip, DOT_SIZE + 12dip)   ' height (horizontal) or width (vertical) of dot strip
    Dim isVertical As Boolean = (msOrientation = "vertical")
    
    ' --- Navigation prev/next buttons ---
    If mBtnPrev.IsInitialized Then mBtnPrev.RemoveViewFromParent
    If mBtnNext.IsInitialized Then mBtnNext.RemoveViewFromParent
    
    If mbNavigationButtons Then
        If isVertical Then
            ' Vertical: prev on top-center, next on bottom-center; use ?/?
            Dim btnCenterX As Int = (mBase.Width - NAV_SIZE) / 2
            
            Dim pPrev As Panel
            pPrev.Initialize("mBtnPrev")
            mBtnPrev = pPrev
            mBtnPrev.SetColorAndBorder(xui.Color_ARGB(180, 0, 0, 0), 0, 0, NAV_SIZE / 2)
            mBase.AddView(mBtnPrev, btnCenterX, NAV_MARGIN, NAV_SIZE, NAV_SIZE)
            Dim lblPrev As Label
            lblPrev.Initialize("")
            lblPrev.Text = Chr(9650)   ' ?
            lblPrev.Gravity = Gravity.CENTER
            lblPrev.TextColor = xui.Color_White
            lblPrev.TextSize = 16
            mBtnPrev.AddView(lblPrev, 0, 0, NAV_SIZE, NAV_SIZE)
            
            Dim pNext As Panel
            pNext.Initialize("mBtnNext")
            mBtnNext = pNext
            mBtnNext.SetColorAndBorder(xui.Color_ARGB(180, 0, 0, 0), 0, 0, NAV_SIZE / 2)
            mBase.AddView(mBtnNext, btnCenterX, mBase.Height - NAV_MARGIN - NAV_SIZE, NAV_SIZE, NAV_SIZE)
            Dim lblNext As Label
            lblNext.Initialize("")
            lblNext.Text = Chr(9660)   ' ?
            lblNext.Gravity = Gravity.CENTER
            lblNext.TextColor = xui.Color_White
            lblNext.TextSize = 16
            mBtnNext.AddView(lblNext, 0, 0, NAV_SIZE, NAV_SIZE)
        Else
            ' Horizontal: prev on left-center, next on right-center; use ?/?
            Dim btnCenterY As Int = (mBase.Height - NAV_SIZE) / 2
            
            Dim pPrev As Panel
            pPrev.Initialize("mBtnPrev")
            mBtnPrev = pPrev
            mBtnPrev.SetColorAndBorder(xui.Color_ARGB(180, 0, 0, 0), 0, 0, NAV_SIZE / 2)
            mBase.AddView(mBtnPrev, NAV_MARGIN, btnCenterY, NAV_SIZE, NAV_SIZE)
            Dim lblPrev As Label
            lblPrev.Initialize("")
            lblPrev.Text = Chr(10094)  ' ?
            lblPrev.Gravity = Gravity.CENTER
            lblPrev.TextColor = xui.Color_White
            lblPrev.TextSize = 16
            mBtnPrev.AddView(lblPrev, 0, 0, NAV_SIZE, NAV_SIZE)
            
            Dim pNext As Panel
            pNext.Initialize("mBtnNext")
            mBtnNext = pNext
            mBtnNext.SetColorAndBorder(xui.Color_ARGB(180, 0, 0, 0), 0, 0, NAV_SIZE / 2)
            mBase.AddView(mBtnNext, mBase.Width - NAV_MARGIN - NAV_SIZE, btnCenterY, NAV_SIZE, NAV_SIZE)
            Dim lblNext As Label
            lblNext.Initialize("")
            lblNext.Text = Chr(10095)  ' ?
            lblNext.Gravity = Gravity.CENTER
            lblNext.TextColor = xui.Color_White
            lblNext.TextSize = 16
            mBtnNext.AddView(lblNext, 0, 0, NAV_SIZE, NAV_SIZE)
        End If
    End If
    
    ' --- Indicator dots ---
    If mPnlIndicators.IsInitialized Then mPnlIndicators.RemoveViewFromParent
    mDotViews.Clear
    
    If mbIndicatorButtons And mItems.Size > 0 Then
        Dim p As Panel
        p.Initialize("")
        mPnlIndicators = p
        mPnlIndicators.Color = mnIndicatorBackgroundColor
        
        If isVertical Then
            ' Vertical: dot column on the right edge, vertically centered, offset inwards
            mBase.AddView(mPnlIndicators, mBase.Width - INDICATOR_STRIP - mnIndicatorOffset, 0, INDICATOR_STRIP, mBase.Height)
            
            Dim totalH As Int = mItems.Size * (DOT_SIZE + DOT_GAP) - DOT_GAP
            Dim startY As Int = (mBase.Height - totalH) / 2
            Dim dotX As Int = (INDICATOR_STRIP - DOT_SIZE) / 2
            
            For i = 0 To mItems.Size - 1
                Dim dot As Panel
                dot.Initialize("mDotBtn")
                dot.Tag = i
                Dim dv As B4XView = dot
                mPnlIndicators.AddView(dv, dotX, startY + i * (DOT_SIZE + DOT_GAP), DOT_SIZE, DOT_SIZE)
                mDotViews.Add(dv)
            Next
        Else
            ' Horizontal: dot row along the bottom edge, horizontally centered, offset upwards
            mBase.AddView(mPnlIndicators, 0, mBase.Height - INDICATOR_STRIP - mnIndicatorOffset, mBase.Width, INDICATOR_STRIP)
            
            Dim totalW As Int = mItems.Size * (DOT_SIZE + DOT_GAP) - DOT_GAP
            Dim startX As Int = (mBase.Width - totalW) / 2
            Dim dotY As Int = (INDICATOR_STRIP - DOT_SIZE) / 2
            
            For i = 0 To mItems.Size - 1
                Dim dot As Panel
                dot.Initialize("mDotBtn")
                dot.Tag = i
                Dim dv As B4XView = dot
                mPnlIndicators.AddView(dv, startX + i * (DOT_SIZE + DOT_GAP), dotY, DOT_SIZE, DOT_SIZE)
                mDotViews.Add(dv)
            Next
        End If
        UpdateDots
    End If

    ' Reflect the component enabled state on the interactive overlays so a disabled
    ' carousel cannot be driven by its nav buttons or indicator dots.
    If mBtnPrev.IsInitialized Then mBtnPrev.Enabled = mbEnabled
    If mBtnNext.IsInitialized Then mBtnNext.Enabled = mbEnabled
    For i = 0 To mDotViews.Size - 1
        Dim dView As B4XView = mDotViews.Get(i)
        dView.Enabled = mbEnabled
    Next
End Sub

' Refresh which dot is highlighted to match mCurrentIndex.
Private Sub UpdateDots
    If mDotViews.Size = 0 Then Return
    Dim DOT_SIZE As Int = mnIndicatorDotSize
    For i = 0 To mDotViews.Size - 1
        Dim dv As B4XView = mDotViews.Get(i)
        If i = mCurrentIndex Then
            dv.SetColorAndBorder(mnIndicatorActiveColor, 0, 0, DOT_SIZE / 2)
        Else
            dv.SetColorAndBorder(mnIndicatorInactiveColor, 0, 0, DOT_SIZE / 2)
        End If
    Next
End Sub

Private Sub mBtnPrev_Click
    If mbEnabled = False Then Return
    ScrollToItem(Max(0, mCurrentIndex - 1))
End Sub

Private Sub mBtnNext_Click
    If mbEnabled = False Then Return
    ScrollToItem(Min(mItems.Size - 1, mCurrentIndex + 1))
End Sub

Private Sub mDotBtn_Click
    If mbEnabled = False Then Return
    Dim clickedDot As Panel = Sender
    If clickedDot.Tag <> Null Then
        ScrollToItem(clickedDot.Tag)
    End If
End Sub

Private Sub mScrollView_ScrollChanged(Position As Int)
    ' Suppress snap timer during programmatic smooth scroll.
    If mIsProgrammaticScroll Then Return
    ' User-initiated scroll: pause auto-play briefly so user can view the current slide.
    If mbAutoPlay Then
        mAutoPlayTimer.Enabled = False
        mAutoPlayUserPausedTimer.Interval = Max(2000, mnAutoPlayInterval)
        mAutoPlayUserPausedTimer.Enabled = False
        mAutoPlayUserPausedTimer.Enabled = True
    End If
    ' Reset snap debounce timer.
    mSnapTimer.Enabled = False
    mSnapTimer.Enabled = True
End Sub

' Vertical ScrollView typed-variable event ï¿½ delegates to shared snap logic.
Private Sub mVScrollView_ScrollChanged(Position As Int)
    mScrollView_ScrollChanged(Position)
End Sub

' Horizontal ScrollView typed-variable event ï¿½ delegates to shared snap logic.
Private Sub mHScrollView_ScrollChanged(Position As Int)
    mScrollView_ScrollChanged(Position)
End Sub

' Disallow parent (page ScrollView) from intercepting touch events when the
' carousel is vertical ï¿½ prevents the page from stealing vertical scroll gestures.
Private Sub mBase_Touch(Action As Int, X As Float, Y As Float) As Boolean
    If msOrientation = "vertical" Then
        Dim joBase As JavaObject = mBase
        Dim par As JavaObject = joBase.RunMethod("getParent", Null)
        ' ACTION_DOWN=0, ACTION_MOVE=2: lock parent; ACTION_UP=1, ACTION_CANCEL=3: release
        Dim disallow As Boolean = (Action = 0 Or Action = 2)
        par.RunMethod("requestDisallowInterceptTouchEvent", Array(disallow))
    End If
    Return False ' never consume ï¿½ let mVScrollView handle the scroll
End Sub

Private Sub mSnapTimer_Tick
    mSnapTimer.Enabled = False
    SnapToNearestItem
End Sub

' Resets the programmatic-scroll guard 500ms after smooth scroll starts,
' allowing the snap timer to respond to user drags again.
Private Sub mProgrammaticTimer_Tick
    mProgrammaticTimer.Enabled = False
    mIsProgrammaticScroll = False
End Sub

' Advances the carousel to the next slide, wrapping around to the first after the last.
Private Sub mAutoPlayTimer_Tick
    If mItems.Size = 0 Then Return
    Dim nextIdx As Int = (mCurrentIndex + 1) Mod mItems.Size
    ScrollToItem(nextIdx)
End Sub

' Resumes auto-play after the user-scroll pause period expires.
Private Sub mAutoPlayUserPausedTimer_Tick
    mAutoPlayUserPausedTimer.Enabled = False
    If mbAutoPlay And mItems.Size > 0 Then
        mAutoPlayTimer.Interval = mnAutoPlayInterval
        mAutoPlayTimer.Enabled = True
    End If
End Sub

' Polls HorizontalScrollView.ScrollPosition every 80ms to detect user scroll
' (compensates for mHScrollView using empty event name to avoid setEventName crash).
Private Sub mScrollPollTimer_Tick
    If mHScrollView.IsInitialized = False Then Return
    If msOrientation <> "horizontal" Then Return
    If mIsProgrammaticScroll Then Return
    Dim pos As Int = mHScrollView.ScrollPosition
    If pos <> mLastScrollPos Then
        mLastScrollPos = pos
        ' User-initiated horizontal scroll: pause auto-play briefly.
        If mbAutoPlay Then
            mAutoPlayTimer.Enabled = False
            mAutoPlayUserPausedTimer.Interval = Max(2000, mnAutoPlayInterval)
            mAutoPlayUserPausedTimer.Enabled = False
            mAutoPlayUserPausedTimer.Enabled = True
        End If
        ' Mirror what mScrollView_ScrollChanged would do: reset snap debounce timer.
        mSnapTimer.Enabled = False
        mSnapTimer.Enabled = True
    End If
End Sub

Private Sub SnapToNearestItem
    If mItems.Size = 0 Then Return
    
    Dim scrollPos As Int
    Dim viewportSize As Int
    If msOrientation = "horizontal" Then
        Dim hsv As HorizontalScrollView = mScrollView
        scrollPos = hsv.ScrollPosition
        viewportSize = mScrollHost.Width
        If viewportSize < 1 Then viewportSize = mBase.Width - mnContentPadding * 2
    Else
        scrollPos = mVScrollView.ScrollPosition
        viewportSize = mScrollHost.Height
        If viewportSize < 1 Then viewportSize = mBase.Height - mnContentPadding * 2
    End If
    
    Dim bestIndex As Int = 0
    Dim bestDist As Int = 999999
    
    For i = 0 To mItems.Size - 1
        Dim itm As B4XDaisyCarouselItem = mItems.Get(i)
        Dim snapPos As Int
        Select msSnap
            Case "start"
                If msOrientation = "horizontal" Then
                    snapPos = itm.mBase.Left
                Else
                    snapPos = itm.mBase.Top
                End If
            Case "center"
                If msOrientation = "horizontal" Then
                    snapPos = itm.mBase.Left + (itm.mBase.Width / 2) - (viewportSize / 2)
                Else
                    snapPos = itm.mBase.Top + (itm.mBase.Height / 2) - (viewportSize / 2)
                End If
            Case "end"
                If msOrientation = "horizontal" Then
                    snapPos = (itm.mBase.Left + itm.mBase.Width) - viewportSize
                Else
                    snapPos = (itm.mBase.Top + itm.mBase.Height) - viewportSize
                End If
        End Select
        Dim dist As Int = Abs(scrollPos - snapPos)
        If dist < bestDist Then
            bestDist = dist
            bestIndex = i
        End If
    Next
    
    ScrollToItem(bestIndex)
End Sub
#End Region

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mIsResizing Then Return
    mIsResizing = True
    
    If mScrollHost.IsInitialized Then
        Dim cp As Int = mnContentPadding
        Dim hW As Int = Max(1, Width - cp * 2)
        Dim hH As Int = Max(1, Height - cp * 2)
        mScrollHost.SetLayoutAnimated(0, cp, cp, hW, hH)
        If mScrollView.IsInitialized Then
            mScrollView.SetLayoutAnimated(0, 0, 0, hW, hH)
        End If
        LayoutItems
    End If
    
    ' Rebuild overlays at new dimensions (handles dot reposition and orientation changes).
    LayoutOverlays
    
    mIsResizing = False
End Sub

Private Sub mBase_Click
    RaiseClick
End Sub

Private Sub RaiseClick
    Dim payload As Object = mTag
    If payload = Null Then payload = mBase
    If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
        CallSub2(mCallBack, mEventName & "_Click", payload)
    Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
        CallSub(mCallBack, mEventName & "_Click")
    End If
End Sub
#End Region

#Region Cleanup
''' <summary>
''' Returns the current rendered height of this carousel.
''' </summary>
Public Sub GetComputedHeight As Int
    If mBase.IsInitialized = False Then Return 0
    Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCardTitle.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: Card Title, Description: Title text.
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Title size token.
#DesignerProperty: Key: Centered, DisplayName: Centered, FieldType: Boolean, DefaultValue: False, Description: Center align title text.
#DesignerProperty: Key: Gap, DisplayName: Gap, FieldType: Int, DefaultValue: 8, Description: Horizontal gap between title text and extra components.
#DesignerProperty: Key: SingleLine, DisplayName: Single Line, FieldType: Boolean, DefaultValue: False, Description: Prevent text wrapping.
#DesignerProperty: Key: Ellipsize, DisplayName: Ellipsize, FieldType: String, DefaultValue: none, List: none|start|middle|end|marquee, Description: Truncate with ellipsis when text overflows.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide title.
#DesignerProperty: Key: AutoResize, DisplayName: Auto Resize, FieldType: Boolean, DefaultValue: True, Description: Automatically resize height to fit text and extra components.

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView
	Private lblText As B4XView
	Private pnlExtras As B4XView

	Private mText As String = "Card Title"
	Private mSize As String = "md"
	Private mCentered As Boolean = False
	Private mGapDip As Int = 8dip
	Private mVisible As Boolean = True
	Private mSingleLine As Boolean = False
	Private msEllipsize As String = "none"
	Private mTextColor As Int = 0
	Private mTextSize As Float = 18
	Private tu As StringUtils
	Private mbAutoResize As Boolean = True
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	
	Dim l As Label
	l.Initialize("")
	lblText = l
	mBase.AddView(lblText, 0, 0, 1dip, 1dip)

	Dim pe As Panel
	pe.Initialize("")
	pnlExtras = pe
	pnlExtras.Color = xui.Color_Transparent
	mBase.AddView(pnlExtras, 0, 0, 0, 0)

	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mText = B4XDaisyVariants.GetPropString(Props, "Text", mText)
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	mCentered = B4XDaisyVariants.GetPropBool(Props, "Centered", mCentered)
	mGapDip = Max(0, B4XDaisyVariants.GetPropInt(Props, "Gap", mGapDip))
	mSingleLine = B4XDaisyVariants.GetPropBool(Props, "SingleLine", mSingleLine)
	msEllipsize = B4XDaisyVariants.GetPropString(Props, "Ellipsize", msEllipsize).ToLowerCase.Trim
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	mbAutoResize = B4XDaisyVariants.GetPropBool(Props, "AutoResize", mbAutoResize)
	ApplySizeMetrics
End Sub

Private Sub ApplySizeMetrics
	Select Case mSize
		Case "xs": mTextSize = 14
		Case "sm": mTextSize = 16
		Case "lg": mTextSize = 20
		Case "xl": mTextSize = 22
		Case Else: mTextSize = 18
	End Select
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	mBase.Visible = mVisible
	lblText.Text = mText
	lblText.TextSize = mTextSize
	lblText.Font = xui.CreateDefaultBoldFont(mTextSize)
	If mTextColor <> 0 Then
		lblText.TextColor = mTextColor
	Else
		lblText.TextColor = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55))
	End If
	lblText.SetTextAlignment("CENTER", "LEFT")
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, h)
	LayoutTitleRow(w, h)
	DoAutoResize
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, w, h)
		DesignerCreateView(b, Null, CreateMap("Text": mText, "Size": mSize, "Centered": mCentered, "Gap": mGapDip, "SingleLine": mSingleLine, "Ellipsize": msEllipsize, "Visible": mVisible, "AutoResize": mbAutoResize))
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, w, h)
	Refresh
	Return mBase
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub setText(Value As String)
	mText = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getText As String
	Return mText
End Sub

Public Sub setTextColor(Value As Int)
	mTextColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTextColor As Int
	Return mTextColor
End Sub

Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	ApplySizeMetrics
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getSize As String
	Return mSize
End Sub

Public Sub setCentered(Value As Boolean)
	mCentered = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getCentered As Boolean
	Return mCentered
End Sub

Public Sub setGapDip(Value As Int)
	mGapDip = Max(0, Value)
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getGapDip As Int
	Return mGapDip
End Sub

Public Sub setGap(Value As Int)
	setGapDip(Value)
End Sub

Public Sub getGap As Int
	Return getGapDip
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setSingleLine(Value As Boolean)
	mSingleLine = Value
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getSingleLine As Boolean
	Return mSingleLine
End Sub

Public Sub setEllipsize(Value As String)
	msEllipsize = Value.ToLowerCase.Trim
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getEllipsize As String
	Return msEllipsize
End Sub

Public Sub getTextSize As Float
	Return mTextSize
End Sub

Public Sub getLabel As B4XView
	Return lblText
End Sub

Public Sub getExtrasContainer As B4XView
	Return pnlExtras
End Sub

Public Sub getContainer As B4XView
	Return mBase
End Sub

Public Sub Relayout
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub setAutoResize(Value As Boolean)
	mbAutoResize = Value
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getAutoResize As Boolean
	Return mbAutoResize
End Sub


Private Sub LayoutTitleRow(AvailableWidth As Int, AvailableHeight As Int)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, AvailableWidth)
	Dim h As Int = Max(1dip, AvailableHeight)
	Dim extrasW As Int = MeasureExtrasWidth
	Dim extrasH As Int = MeasureExtrasHeight
	Dim hasExtras As Boolean = extrasW > 0
	Dim hasText As Boolean = B4XDaisyVariants.NormalizeSingleLineText(mText).Length > 0
	Dim gap As Int = IIf(hasExtras And hasText, mGapDip, 0)

	Dim textDesired As Int = MeasureSingleLineWidth(B4XDaisyVariants.NormalizeSingleLineText(mText), mTextSize)
	Dim textW As Int = Max(1dip, Min(Max(1dip, w - extrasW - gap), textDesired))
	If hasText = False Then textW = 0

	Dim contentW As Int = textW + extrasW + gap
	Dim startX As Int = 0
	If mCentered Then
		startX = Max(0, Round((w - contentW) / 2))
	End If

	lblText.SetLayoutAnimated(0, startX, 0, textW, h)
	Try
		Dim joLbl As JavaObject = lblText
		Dim l As Label = lblText
		l.SingleLine = mSingleLine
		Dim joTruncate As JavaObject
		joTruncate.InitializeStatic("android.text.TextUtils$TruncateAt")
		Select Case msEllipsize
			Case "start":   joLbl.RunMethod("setEllipsize", Array As Object(joTruncate.GetField("START")))
			Case "middle":  joLbl.RunMethod("setEllipsize", Array As Object(joTruncate.GetField("MIDDLE")))
			Case "end":     joLbl.RunMethod("setEllipsize", Array As Object(joTruncate.GetField("END")))
			Case "marquee": joLbl.RunMethod("setEllipsize", Array As Object(joTruncate.GetField("MARQUEE")))
			Case Else:      joLbl.RunMethod("setEllipsize", Array As Object(Null))
		End Select
	Catch
		Log("B4XDaisyCardTitle.LayoutTitleRow: " & LastException.Message)
	End Try
	If mCentered And hasExtras = False Then
		lblText.SetTextAlignment("CENTER", "CENTER")
	Else
		lblText.SetTextAlignment("CENTER", "LEFT")
	End If

	If hasExtras Then
		' Optical alignment tweak: badges/icons look slightly low when mathematically centered.
		Dim extrasY As Int = Max(0, Round((h - extrasH) / 2) - 2dip)
		pnlExtras.SetLayoutAnimated(0, startX + textW + gap, extrasY, extrasW, extrasH)
		LayoutExtrasChildren(extrasH)
	Else
		pnlExtras.SetLayoutAnimated(0, startX + textW, 0, 0, 0)
	End If
End Sub

Private Sub LayoutExtrasChildren(HostHeight As Int)
	If pnlExtras.IsInitialized = False Then Return
	Dim x As Int = 0
	For i = 0 To pnlExtras.NumberOfViews - 1
		Dim v As B4XView = pnlExtras.GetView(i)
		If v.Visible = False Then Continue
		Dim vw As Int = Max(1dip, v.Width)
		Dim vh As Int = Max(1dip, v.Height)
		Dim y As Int = Max(0, Round((HostHeight - vh) / 2))
		v.SetLayoutAnimated(0, x, y, vw, vh)
		x = x + vw + mGapDip
	Next
End Sub

Private Sub MeasureExtrasWidth As Int
	If pnlExtras.IsInitialized = False Then Return 0
	Dim total As Int = 0
	Dim countVisible As Int = 0
	For i = 0 To pnlExtras.NumberOfViews - 1
		Dim v As B4XView = pnlExtras.GetView(i)
		If v.Visible = False Then Continue
		total = total + Max(1dip, v.Width)
		countVisible = countVisible + 1
	Next
	If countVisible > 1 Then total = total + ((countVisible - 1) * mGapDip)
	Return Max(0, total)
End Sub

Private Sub MeasureExtrasHeight As Int
	If pnlExtras.IsInitialized = False Then Return 0
	Dim h As Int = 0
	For i = 0 To pnlExtras.NumberOfViews - 1
		Dim v As B4XView = pnlExtras.GetView(i)
		If v.Visible = False Then Continue
		h = Max(h, Max(1dip, v.Height))
	Next
	Return Max(0, h)
End Sub

Private Sub MeasureSingleLineWidth(Text As String, FontSize As Float) As Int
	If Text.Length = 0 Then Return 0
	If lblText.IsInitialized Then
		Try
			Dim l As Label = lblText
			Return B4XDaisyVariants.MeasureTextWidthSafe(Text, FontSize, l.Typeface, 2dip)
		Catch
		End Try 'ignore
	End If
	Return Max(1dip, Ceil(Text.Length * FontSize * 0.62) + 2dip)
End Sub

Private Sub DoAutoResize
	If mbAutoResize = False Then Return
	If mBase.IsInitialized = False Then Return
	If lblText.IsInitialized = False Then Return
	Dim extrasH As Int = MeasureExtrasHeight
	Dim newH As Int
	If mSingleLine Then
		newH = Max(1dip, Round(mTextSize * 1.4))
	Else
		Dim l As Label = lblText
		l.SingleLine = False
		newH = tu.MeasureMultilineTextHeight(lblText, mText)
		If newH <= 0 Then newH = Max(1dip, Round(mTextSize * 1.4))
	End If
	newH = Max(newH, extrasH)
	If newH <> mBase.Height Then
		mBase.Height = newH
	End If
End Sub


#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCardBody.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Body size token.
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: auto, Description: Body height token (auto, h-32, h-[120px], etc).
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide body container.

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView

	Private mSize As String = "md"
	Private mHeight As String = "auto"
	Private mVisible As Boolean = True
	Private mPaddingDip As Int = 24dip
	Private mGapDip As Int = 8dip
	Private mBodyTextSize As Float = 14
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	mHeight = NormalizeHeightSpec(B4XDaisyVariants.GetPropString(Props, "Height", mHeight))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	ApplyMetrics
End Sub

Private Sub ApplyMetrics
	Select Case mSize
		Case "xs"
			mPaddingDip = 8dip    '0.5rem in CSS
			mBodyTextSize = 11
		Case "sm"
			mPaddingDip = 16dip   '1rem
			mBodyTextSize = 12
		Case "lg"
			mPaddingDip = 32dip   '2rem
			mBodyTextSize = 16
		Case "xl"
			mPaddingDip = 40dip   '2.5rem
			mBodyTextSize = 18
		Case Else
			mPaddingDip = 24dip   '1.5rem
			mBodyTextSize = 14
	End Select
	' The Daisy CSS uses a fixed gap of `gap-2` (0.5rem ? 8px) between
	' children inside .card-body regardless of size.  Our previous formula
	' scaled the gap with padding, causing small cards to have a much smaller
	' spacing than the CSS.  Use a constant 8dip to match the stylesheet.
	mGapDip = 8dip
End Sub

Private Sub Refresh
	If BaseIsInitialized = False Then Return
	mBase.Visible = mVisible
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If BaseIsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim baseH As Int = Max(1dip, Height)
	Dim h As Int = ResolveHeightDip(baseH)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, h)
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int
	If Height > 0 Then
		h = Max(1dip, Height)
	Else
		h = ResolveHeightDip(Max(1dip, mPaddingDip * 2))
	End If
	If BaseIsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, w, h)
		DesignerCreateView(b, Null, CreateMap("Size": mSize, "Height": mHeight, "Visible": mVisible))
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, w, h)
	Refresh
	Return mBase
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If BaseIsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If BaseIsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	ApplyMetrics
	If BaseIsInitialized Then Refresh
End Sub

Public Sub getSize As String
	Return mSize
End Sub

Public Sub setHeight(Value As String)
	mHeight = NormalizeHeightSpec(Value)
	If BaseIsInitialized Then Refresh
End Sub

Public Sub getHeight As String
	Return mHeight
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If BaseIsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub getPaddingDip As Int
	Return mPaddingDip
End Sub

Public Sub getGapDip As Int
	Return mGapDip
End Sub

Public Sub getBodyTextSize As Float
	Return mBodyTextSize
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub getContainer As B4XView
	Return mBase
End Sub



Private Sub ResolveHeightDip(DefaultHeight As Int) As Int
	If IsAutoHeightSpec Then Return ResolveAutoHeightDip(DefaultHeight)
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mHeight, DefaultHeight))
End Sub

Private Sub ResolveAutoHeightDip(DefaultHeight As Int) As Int
	If BaseIsInitialized = False Then Return Max(1dip, DefaultHeight)
	Dim maxBottom As Int = 0
	For i = 0 To mBase.NumberOfViews - 1
		Dim v As B4XView = mBase.GetView(i)
		If v.Visible = False Then Continue
		maxBottom = Max(maxBottom, v.Top + v.Height)
	Next
	If maxBottom > 0 Then Return Max(1dip, maxBottom + mPaddingDip)
	Return Max(1dip, DefaultHeight)
End Sub

Private Sub BaseIsInitialized As Boolean
	If mBase = Null Then Return False
	Return mBase.IsInitialized
End Sub

Private Sub IsAutoHeightSpec As Boolean
	Dim s As String = NormalizeHeightSpec(mHeight)
	Return s = "auto"
End Sub

Private Sub NormalizeHeightSpec(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.ToLowerCase.Trim)
	If s.Length = 0 Then Return "auto"
	Return s
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCardActions.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#DesignerProperty: Key: GapDip, DisplayName: Gap Dip, FieldType: Int, DefaultValue: 8, Description: Gap between action items.
#DesignerProperty: Key: Wrap, DisplayName: Wrap, FieldType: Boolean, DefaultValue: True, Description: Wrap action items when row is full.
#DesignerProperty: Key: Justify, DisplayName: Justify, FieldType: String, DefaultValue: start, List: start|center|End, Description: Horizontal row alignment.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show Or hide actions.

#IgnoreWarnings:12,9

Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView

	Private mGapDip As Int = 8dip
	Private mWrap As Boolean = True
	Private mJustify As String = "start"
	Private mVisible As Boolean = True
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mGapDip = Max(0, B4XDaisyVariants.GetPropInt(Props, "GapDip", 8)) * 1dip
	mWrap = B4XDaisyVariants.GetPropBool(Props, "Wrap", mWrap)
	mJustify = NormalizeJustify(B4XDaisyVariants.GetPropString(Props, "Justify", mJustify))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	mBase.Visible = mVisible
	Relayout
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, Max(1dip, Width), Max(1dip, Height))
	Relayout
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, w, h)
		DesignerCreateView(b, Null, CreateMap("GapDip": mGapDip / 1dip, "Wrap": mWrap, "Justify": mJustify, "Visible": mVisible))
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, w, h)
	Refresh
	Return mBase
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub Relayout
	If mBase.IsInitialized = False Then Return

	Dim containerW As Int = Max(1dip, mBase.Width)
	Dim rows As List
	rows.Initialize

	Dim currentItems As List
	currentItems.Initialize
	Dim rowW As Int = 0
	Dim rowH As Int = 0

	For i = 0 To mBase.NumberOfViews - 1
		Dim v As B4XView = mBase.GetView(i)
		If v.Visible = False Then Continue

		Dim vw As Int = Max(1dip, v.Width)
		Dim vh As Int = Max(1dip, v.Height)
		vw = Min(containerW, vw)

		Dim addW As Int = vw
		If currentItems.Size > 0 Then addW = addW + mGapDip

		If mWrap And currentItems.Size > 0 And rowW + addW > containerW Then
			rows.Add(CreateMap("items": currentItems, "width": rowW, "height": rowH))
			currentItems.Initialize
			currentItems.Add(v)
			rowW = vw
			rowH = vh
		Else
			currentItems.Add(v)
			rowW = rowW + addW
			rowH = Max(rowH, vh)
		End If
	Next

	If currentItems.Size > 0 Then
		rows.Add(CreateMap("items": currentItems, "width": rowW, "height": rowH))
	End If

	Dim y As Int = 0
	For Each row As Map In rows
		Dim items As List = row.Get("items")
		Dim usedW As Int = row.Get("width")
		Dim lineH As Int = row.Get("height")
		Dim x As Int = ResolveRowStartX(containerW, usedW)
		For Each v As B4XView In items
			Dim vw As Int = Max(1dip, Min(containerW, v.Width))
			Dim vh As Int = Max(1dip, v.Height)
			Dim vy As Int = y
			v.SetLayoutAnimated(0, x, vy, vw, vh)
			x = x + vw + mGapDip
		Next
		y = y + lineH + mGapDip
	Next
End Sub

Private Sub ResolveRowStartX(ContainerWidth As Int, UsedWidth As Int) As Int
	Select Case mJustify
		Case "center"
			Return Max(0, (ContainerWidth - UsedWidth) / 2)
		Case "End"
			Return Max(0, ContainerWidth - UsedWidth)
		Case Else
			Return 0
	End Select
End Sub

Public Sub setGapDip(Value As Int)
	mGapDip = Max(0, Value)
	If mBase.IsInitialized Then Relayout
End Sub

Public Sub getGapDip As Int
	Return mGapDip
End Sub

Public Sub setWrap(Value As Boolean)
	mWrap = Value
	If mBase.IsInitialized Then Relayout
End Sub

Public Sub getWrap As Boolean
	Return mWrap
End Sub

Public Sub setJustify(Value As String)
	mJustify = NormalizeJustify(Value)
	If mBase.IsInitialized Then Relayout
End Sub

Public Sub getJustify As String
	Return mJustify
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub getContainer As B4XView
	Return mBase
End Sub

Private Sub NormalizeJustify(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.ToLowerCase.Trim)
	Select Case s
		Case "center" : Return "center"
		Case "end" : Return "End"
		Case "start" : Return "start"
		Case Else : Return "start"
	End Select
End Sub


#Region B4XView Layout Wrapper Methods
Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyCard.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#Event: Click (Tag As Object)
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: w-full, Description: Card width token.
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: auto, Description: Card height token (auto, h-64, h-[500px], etc).
#DesignerProperty: Key: Title, DisplayName: Title, FieldType: String, DefaultValue: Card Title, Description: Title text.
#DesignerProperty: Key: ImagePath, DisplayName: Image Path, FieldType: String, DefaultValue:, Description: Asset Or full path.
#DesignerProperty: Key: ImageWidth, DisplayName: Image Width, FieldType: String, DefaultValue: w-full, Description: Image width token.
#DesignerProperty: Key: ImageHeight, DisplayName: Image Height, FieldType: String, DefaultValue: h-full, Description: Image height token.
#DesignerProperty: Key: ImageClasses, DisplayName: Image Classes, FieldType: String, DefaultValue:, Description: Extra image utility classes.
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Size token.
#DesignerProperty: Key: Style, DisplayName: Style, FieldType: String, DefaultValue: none, List: none|border|dash, Description: Border style.
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Semantic variant For full-card background/text colors.
#DesignerProperty: Key: LayoutMode, DisplayName: Layout Mode, FieldType: String, DefaultValue: top, List: top|bottom|side|overlay|none, Description: Figure placement.
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit card background color override (0 uses theme token).
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Explicit text color override For all content inside card body (0 uses theme token).
#DesignerProperty: Key: PlaceItemsCenter, DisplayName: Place Items Center, FieldType: Boolean, DefaultValue: False, Description: Centers title/actions content similar To place-items-center.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Radius mode.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: sm, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation level.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show Or hide card.
#IgnoreWarnings:12,9

Sub Class_Globals
	Private xui As XUI
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object
	Public mBase As B4XView
	Private pnlFigure As B4XView
	Private avatarFigure As B4XDaisyAvatar
	Private pnlOverlay As B4XView
	Private pnlBody As B4XView
	Private pnlTitle As B4XView
	Private pnlContent As B4XView
	Private partTitle As B4XDaisyCardTitle
	Private pnlActions As B4XView
	Private partActions As B4XDaisyCardActions
	Private partBody As B4XDaisyCardBody
	Private mWidth As String = "w-full"
	Private mHeight As String = "auto"
	Private mTitle As String = "Card Title"
	Private mImagePath As String = ""
	Private mImageWidth As String = "w-full"
	Private mImageHeight As String = "h-full"
	Private mImageClasses As String = ""
	Private mImageBitmap As B4XBitmap
	Private mSize As String = "md"
	Private mCardStyle As String = "none"
	Private mVariant As String = "none"
	Private mLayoutMode As String = "top"
	Private mRounded As String = "theme"
	Private mShadow As String = "sm"
	Private mVisible As Boolean = True
	Private mPlaceItemsCenter As Boolean = False
	Private mTitleVisible As Boolean = True
	Private mActionsVisible As Boolean = True
	Private mImageVisible As Boolean = True
	Private mBackColor As Int = 0
	Private mTextColor As Int = 0
	Private mApplyingAutoHeight As Boolean = False
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	Dim pf As Panel
	pf.Initialize("surface")
	pnlFigure = pf
	mBase.AddView(pnlFigure, 0, 0, 1dip, 1dip)
	avatarFigure.Initialize(Me, "")
	avatarFigure.AddToParent(pnlFigure, 0, 0, 1dip, 1dip)
	avatarFigure.setAvatarType("image")
	avatarFigure.setRoundedBox(False)
	avatarFigure.setMask("none")
	avatarFigure.setCenterOnParent(False)
	avatarFigure.setChatImage(False)
	avatarFigure.setShadow("none")
	Dim po As Panel
	po.Initialize("")
	pnlOverlay = po
	pnlOverlay.Color = xui.Color_Transparent
	pnlFigure.AddView(pnlOverlay, 0, 0, 1dip, 1dip)
	Dim pb As Panel
	pb.Initialize("surface")
	pnlBody = pb
	mBase.AddView(pnlBody, 0, 0, 1dip, 1dip)
	partBody.Initialize(Me, "")
	Dim pt As Panel
	pt.Initialize("surface")
	pnlTitle = pt
	pnlBody.AddView(pnlTitle, 0, 0, 1dip, 1dip)
	partTitle.Initialize(Me, "")
	partTitle.AddToParent(pnlTitle, 0, 0, 1dip, 1dip)
	Dim pc As Panel
	pc.Initialize("")
	pnlContent = pc
	pnlBody.AddView(pnlContent, 0, 0, 1dip, 1dip)
	Dim pa As Panel
	pa.Initialize("surface")
	pnlActions = pa
	pnlBody.AddView(pnlActions, 0, 0, 1dip, 1dip)
	partActions.Initialize(Me, "")
	partActions.AddToParent(pnlActions, 0, 0, 1dip, 1dip)
	partActions.Wrap = True
	partActions.GapDip = 8dip
	ApplyDesignerProps(Props)
	Refresh
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mWidth = B4XDaisyVariants.GetPropString(Props, "Width", mWidth)
	mHeight = NormalizeHeightSpec(B4XDaisyVariants.GetPropString(Props, "Height", mHeight))
	mTitle = B4XDaisyVariants.GetPropString(Props, "Title", mTitle)
	mImagePath = B4XDaisyVariants.GetPropString(Props, "ImagePath", mImagePath)
	mImageWidth = B4XDaisyVariants.GetPropString(Props, "ImageWidth", mImageWidth)
	mImageHeight = B4XDaisyVariants.GetPropString(Props, "ImageHeight", mImageHeight)
	mImageClasses = B4XDaisyVariants.GetPropString(Props, "ImageClasses", mImageClasses)
	mImageBitmap = LoadBitmapFromPath(mImagePath)
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	Dim styleProp As String = B4XDaisyVariants.GetPropString(Props, "Style", "")
	If styleProp.Trim.Length = 0 Then styleProp = B4XDaisyVariants.GetPropString(Props, "CardStyle", "none")
	mCardStyle = NormalizeStyle(styleProp)
	mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	mLayoutMode = NormalizeLayout(B4XDaisyVariants.GetPropString(Props, "LayoutMode", mLayoutMode))
	mBackColor = B4XDaisyVariants.GetPropInt(Props, "BackgroundColor", mBackColor)
	mTextColor = B4XDaisyVariants.GetPropInt(Props, "TextColor", mTextColor)
	mPlaceItemsCenter = B4XDaisyVariants.GetPropBool(Props, "PlaceItemsCenter", mPlaceItemsCenter)
	mRounded = NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", mRounded))
	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
End Sub

Private Sub Refresh
	If mBase.IsInitialized = False Then Return
	Dim defaultBack As Int = B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_White)
	Dim defaultText As Int = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55))
	Dim variantBack As Int = B4XDaisyVariants.ResolveBackgroundColorVariant(mVariant, defaultBack)
	Dim variantText As Int = B4XDaisyVariants.ResolveTextColorVariant(mVariant, defaultText)
	Dim back As Int = IIf(mBackColor <> 0, mBackColor, variantBack)
	Dim txt As Int = IIf(mTextColor <> 0, mTextColor, variantText)
	Dim bdr As Int = B4XDaisyVariants.GetTokenColor("--color-base-200", xui.Color_RGB(226, 232, 240))
	Dim borderSize As Int = IIf(mCardStyle = "none", 0, 1dip)
	B4XDaisyVariants.ApplyDashedBorder(mBase, back, borderSize, bdr, ResolveRadius, mCardStyle)
	B4XDaisyVariants.ApplyElevation(mBase, mShadow)
	mBase.Visible = mVisible
	partBody.Size = mSize
	partTitle.Text = mTitle
	partTitle.Size = mSize
	partTitle.Centered = mPlaceItemsCenter
	partTitle.TextColor = txt
	partActions.Justify = IIf(mPlaceItemsCenter, "center", "End")
	partActions.GapDip = partBody.GapDip
	Base_Resize(mBase.Width, mBase.Height)
	ApplyBodyTextColor(txt)
	ApplyBodyContentPlacement
End Sub

Private Sub ApplyBodyTextColor(ColorValue As Int)
	If pnlBody.IsInitialized = False Then Return
	ApplyBodyTextColorRecursive(pnlBody, ColorValue)
End Sub

Private Sub ApplyBodyTextColorRecursive(ParentView As B4XView, ColorValue As Int)
	If ParentView.IsInitialized = False Then Return
	ApplyNativeTextColor(ParentView, ColorValue)
	Dim childCount As Int
	Try
		childCount = ParentView.NumberOfViews
	Catch
		Return
	End Try
	For i = 0 To childCount - 1
		Dim child As B4XView = ParentView.GetView(i)
		ApplyBodyTextColorRecursive(child, ColorValue)
	Next
End Sub

Private Sub ApplyNativeTextColor(Target As B4XView, ColorValue As Int)
	#If B4A
		Try
			Dim jo As JavaObject = Target
			jo.RunMethod("setTextColor", Array(ColorValue))
		Catch
		End Try  'ignore
		#Else
		Dim ignore As Int = ColorValue
		Dim viewIgnore As Object = Target
	#End If
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim mode As String = EffectiveLayout(w)
	partBody.Size = mSize
	partTitle.Size = mSize
	Dim pad As Int = partBody.PaddingDip
	Dim gap As Int = partBody.GapDip
	partTitle.Centered = mPlaceItemsCenter
	Dim hasImg As Boolean = EnsureImageLoaded
	Dim showFigure As Boolean = mImageVisible And (mode <> "none") And hasImg
	Select Case mode
		Case "side"
			Dim fw As Int = IIf(showFigure, Max(110dip, Round(w * 0.42)), 0)
			If showFigure Then
				pnlFigure.Visible = True
				pnlFigure.SetLayoutAnimated(0, 0, 0, fw, h)
			Else
				CollapseFigure
			End If
			pnlBody.SetLayoutAnimated(0, fw, 0, Max(1dip, w - fw), h)
			If showFigure Then LayoutFigure(fw, h, False)
		Case "bottom"
			Dim fhBottom As Int = IIf(showFigure, Max(96dip, Min(Round(w * 0.62), Round(h * 0.66))), 0)
			pnlBody.SetLayoutAnimated(0, 0, 0, w, Max(1dip, h - fhBottom))
			If showFigure Then
				pnlFigure.Visible = True
				pnlFigure.SetLayoutAnimated(0, 0, h - fhBottom, w, fhBottom)
				LayoutFigure(w, fhBottom, False)
			Else
				CollapseFigure
			End If
		Case "overlay"
			pnlBody.SetLayoutAnimated(0, 0, 0, w, h)
			If showFigure Then
				pnlFigure.Visible = True
				pnlFigure.SetLayoutAnimated(0, 0, 0, w, h)
				LayoutFigure(w, h, True)
			Else
				CollapseFigure
			End If
		Case Else
			Dim fhTop As Int = IIf(showFigure, Max(96dip, Min(Round(w * 0.62), Round(h * 0.66))), 0)
			If showFigure Then
				pnlFigure.Visible = True
				pnlFigure.SetLayoutAnimated(0, 0, 0, w, fhTop)
			Else
				CollapseFigure
			End If
			pnlBody.SetLayoutAnimated(0, 0, fhTop, w, Max(1dip, h - fhTop))
			If showFigure Then LayoutFigure(w, fhTop, False)
	End Select
	LayoutBody(pnlBody.Width, pnlBody.Height, pad, gap)
	ApplyBodyContentPlacement
	ApplyAutoHeightIfNeeded(w, h)
End Sub

Private Sub ApplyBodyContentPlacement
	If pnlContent.IsInitialized = False Then Return
	ApplyBodyContentPlacementRecursive(pnlContent)
End Sub

Private Sub ApplyBodyContentPlacementRecursive(ParentView As B4XView)
	If ParentView.IsInitialized = False Then Return
	Dim childCount As Int
	Try
		childCount = ParentView.NumberOfViews
	Catch
		Return
	End Try
	For i = 0 To childCount - 1
		Dim child As B4XView = ParentView.GetView(i)
		ApplyBodyContentPlacementToView(child)
		ApplyBodyContentPlacementRecursive(child)
	Next
End Sub

Private Sub ApplyBodyContentPlacementToView(Target As B4XView)
	If Target.IsInitialized = False Then Return
	Try
		If Target Is Label Then
			Dim align As String = IIf(mPlaceItemsCenter, "CENTER", "LEFT")
			Target.SetTextAlignment("TOP", align)
		End If
	Catch
		' Not a Label — skip
	End Try
End Sub

Private Sub CollapseFigure
	If pnlFigure.IsInitialized Then
		pnlFigure.Visible = False
		pnlFigure.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
	If pnlOverlay.IsInitialized Then
		pnlOverlay.Visible = False
		pnlOverlay.Color = xui.Color_Transparent
		pnlOverlay.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
	Dim avatarView As B4XView = avatarFigure.View
	If avatarView.IsInitialized Then avatarView.SetLayoutAnimated(0, 0, 0, 0, 0)
End Sub

Private Sub LayoutFigure(Width As Int, Height As Int, OverlayMode As Boolean)
	If pnlFigure.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim iw As Int = ResolveImageWidthDip(w)
	Dim ih As Int = ResolveImageHeightDip(h)
	Dim ix As Int = Round((w - iw) / 2)
	Dim iy As Int = Round((h - ih) / 2)
	Dim avatarView As B4XView = avatarFigure.View
	avatarView.SetLayoutAnimated(0, ix, iy, iw, ih)
	avatarFigure.setWidth(iw & "px")
	avatarFigure.setHeight(ih & "px")
	avatarFigure.setCenterOnParent(False)
	avatarFigure.Base_Resize(iw, ih)
	If EnsureImageLoaded Then
		avatarFigure.setAvatarBitmap(mImageBitmap, Null)
		avatarFigure.setBackgroundColor(xui.Color_Transparent)
	Else
		avatarFigure.setAvatar("")
		avatarFigure.setBackgroundColor(B4XDaisyVariants.GetTokenColor("--color-base-200", xui.Color_RGB(233, 238, 245)))
	End If
	pnlOverlay.SetLayoutAnimated(0, 0, 0, w, h)
	If OverlayMode Then
		pnlOverlay.Visible = True
		pnlOverlay.Color = B4XDaisyVariants.SetAlpha(xui.Color_Black, 184)
	Else
		pnlOverlay.Visible = False
		pnlOverlay.Color = xui.Color_Transparent
	End If
End Sub

Private Sub LayoutBody(Width As Int, Height As Int, Pad As Int, Gap As Int)
	Dim w As Int = Max(1dip, Width)
	Dim contentW As Int = Max(1dip, w - (Pad * 2))
	Dim hasActionsRow As Boolean = mActionsVisible And (partActions.getContainer.NumberOfViews > 0)
	Dim actionsH As Int = IIf(hasActionsRow, MeasureActionsHeight(partActions.getContainer, contentW, partActions.GapDip, partActions.Wrap), 0)
	pnlActions.Visible = hasActionsRow
	Dim hasTitle As Boolean = mTitleVisible And (mTitle.Trim.Length > 0)
	Dim titleH As Int = 0
	If hasTitle Then
		titleH = Max(22dip, MeasureTitleHeight(partTitle.TextSize, contentW))
	End If
	Dim contentH As Int = MeasureContentHeight(pnlContent, contentW)
	Dim y As Int = Pad
	If hasTitle Then
		pnlTitle.Visible = True
		pnlTitle.SetLayoutAnimated(0, Pad, y, contentW, titleH)
		partTitle.SetLayoutAnimated(0, 0, 0, contentW, titleH)
		y = y + titleH
		If contentH > 0 Or hasActionsRow Then y = y + Gap
	Else
		pnlTitle.Visible = False
		pnlTitle.SetLayoutAnimated(0, 0, 0, 0, 0)
		partTitle.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
	pnlContent.SetLayoutAnimated(0, Pad, y, contentW, contentH)
	If contentH > 0 Then y = y + contentH
	If hasActionsRow And (hasTitle Or contentH > 0) Then y = y + Gap
	If hasActionsRow Then
		pnlActions.SetLayoutAnimated(0, Pad, y, contentW, actionsH)
		partActions.SetLayoutAnimated(0, 0, 0, contentW, actionsH)
		partActions.Relayout
	Else
		pnlActions.SetLayoutAnimated(0, 0, 0, 0, 0)
		partActions.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
End Sub

Private Sub MeasureContentHeight(Container As B4XView, AvailableWidth As Int) As Int
	If Container.IsInitialized = False Then Return 0
	Dim maxBottom As Int = 0
	For i = 0 To Container.NumberOfViews - 1
		Dim v As B4XView = Container.GetView(i)
		If v.Visible = False Then Continue
		If v.Left = 0 And v.Width < = 1dip Then
			v.SetLayoutAnimated(0, 0, v.Top, AvailableWidth, v.Height)
		End If
		maxBottom = Max(maxBottom, v.Top + v.Height)
	Next
	Return Max(0, maxBottom)
End Sub

Private Sub MeasureActionsHeight(Container As B4XView, AvailableWidth As Int, GapDip As Int, Wrap As Boolean) As Int
	If Container.IsInitialized = False Then Return 0
	Dim rowW As Int = 0
	Dim rowH As Int = 0
	Dim totalH As Int = 0
	Dim hasRow As Boolean = False
	For i = 0 To Container.NumberOfViews - 1
		Dim v As B4XView = Container.GetView(i)
		If v.Visible = False Then Continue
		Dim vw As Int = Max(1dip, Min(AvailableWidth, v.Width))
		Dim vh As Int = Max(1dip, v.Height)
		Dim addW As Int = vw
		If hasRow Then addW = addW + GapDip
		If Wrap And hasRow And rowW + addW > AvailableWidth Then
			totalH = totalH + rowH + GapDip
			rowW = vw
			rowH = vh
		Else
			rowW = rowW + addW
			rowH = Max(rowH, vh)
		End If
		hasRow = True
	Next
	If hasRow Then totalH = totalH + rowH
	Return Max(0, totalH)
End Sub

Private Sub ApplyAutoHeightIfNeeded(CurrentWidth As Int, CurrentHeight As Int)
	If mBase.IsInitialized = False Then Return
	If mApplyingAutoHeight Then Return
	If IsAutoHeightSpec = False Then Return
	Dim desired As Int = ComputeAutoHeight(CurrentWidth)
	If Abs(desired - CurrentHeight) < = 1dip Then Return
	mApplyingAutoHeight = True
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, Max(1dip, CurrentWidth), Max(1dip, desired))
	mApplyingAutoHeight = False
	Base_Resize(CurrentWidth, desired)
End Sub

Private Sub IsAutoHeightSpec As Boolean
	Dim s As String = IIf(mHeight = Null, "", mHeight.ToLowerCase.Trim)
	Return s = "" Or s = "auto"
End Sub

Private Sub MeasureTitleHeight(FontSize As Float, ContentWidth As Int) As Int
	Try
		Dim lbl As Label = partTitle.getLabel
		If lbl.IsInitialized Then
			Dim tu As StringUtils
			Dim savedW As Int = lbl.Width
			lbl.Width = Max(1dip, ContentWidth)
			Dim h As Int = tu.MeasureMultilineTextHeight(lbl, mTitle)
			lbl.Width = savedW
			If h > 0 Then Return Max(1dip, h)
		End If
	Catch
	End Try  'ignore
	Return Max(1dip, Ceil(FontSize * 1.8) + 4dip)
End Sub

Private Sub ComputeAutoHeight(CardWidth As Int) As Int
	Dim w As Int = Max(1dip, CardWidth)
	Dim mode As String = EffectiveLayout(w)
	partBody.Size = mSize
	partTitle.Size = mSize
	Dim pad As Int = partBody.PaddingDip
	Dim gap As Int = partBody.GapDip
	Dim hasImg As Boolean = EnsureImageLoaded
	Dim showFigure As Boolean = mImageVisible And (mode <> "none") And hasImg
	Dim bodyW As Int = w
	If mode = "side" Then
		Dim fw As Int = IIf(showFigure, Max(110dip, Round(w * 0.42)), 0)
		bodyW = Max(1dip, w - fw)
	End If
	Dim bodyH As Int = MeasureBodyRequiredHeight(bodyW, pad, gap)
	Select Case mode
		Case "top", "bottom"
			Dim figH As Int = IIf(showFigure, Max(96dip, Round(w * 0.62)), 0)
			Return Max(1dip, figH + bodyH)
		Case "side"
			Dim minFigureH As Int = IIf(showFigure, 96dip, 0)
			Return Max(1dip, Max(bodyH, minFigureH))
		Case "overlay"
			Dim overlayH As Int = IIf(showFigure, Max(96dip, Round(w * 0.62)), 0)
			Return Max(1dip, Max(bodyH, overlayH))
		Case Else
			Return Max(1dip, bodyH)
	End Select
End Sub

Private Sub MeasureBodyRequiredHeight(Width As Int, Pad As Int, Gap As Int) As Int
	Dim w As Int = Max(1dip, Width)
	Dim contentW As Int = Max(1dip, w - (Pad * 2))
	Dim hasActionsRow As Boolean = mActionsVisible And (partActions.getContainer.NumberOfViews > 0)
	Dim actionsH As Int = IIf(hasActionsRow, MeasureActionsHeight(partActions.getContainer, contentW, partActions.GapDip, partActions.Wrap), 0)
	Dim hasTitle As Boolean = mTitleVisible And (mTitle.Trim.Length > 0)
	Dim titleH As Int = 0
	If hasTitle Then
		titleH = Max(22dip, MeasureTitleHeight(partTitle.TextSize, contentW))
	End If
	Dim contentH As Int = MeasureContentHeight(pnlContent, contentW)
	Dim y As Int = Pad
	If hasTitle Then
		y = y + titleH
		If contentH > 0 Or hasActionsRow Then y = y + Gap
	End If
	If contentH > 0 Then y = y + contentH
	If hasActionsRow Then
		If hasTitle Or contentH > 0 Then y = y + Gap
		y = y + actionsH
	End If
	y = y + Pad
	Return Max(1dip, y)
End Sub

Private Sub ResolveImageWidthDip(ContainerWidth As Int) As Int
	Dim token As String = ResolveImageToken("w-", mImageWidth)
	Dim cw As Int = Max(1dip, ContainerWidth)
	Dim s As String = IIf(token = Null, "", token.ToLowerCase.Trim)
	If s = "" Or s = "w-full" Or s = "full" Or s = "100%" Then Return cw
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(token, cw))
End Sub

Private Sub ResolveImageHeightDip(ContainerHeight As Int) As Int
	Dim token As String = ResolveImageToken("h-", mImageHeight)
	Dim ch As Int = Max(1dip, ContainerHeight)
	Dim s As String = IIf(token = Null, "", token.ToLowerCase.Trim)
	If s = "" Or s = "h-full" Or s = "full" Or s = "100%" Then Return ch
	If s = "auto" Then Return ch
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(token, ch))
End Sub

Private Sub ResolveImageToken(Prefix As String, DefaultToken As String) As String
	Dim token As String = DefaultToken
	Dim cls As String = NormalizeClassList(mImageClasses)
	If cls.Length = 0 Then Return token
	Dim tokens() As String = Regex.Split("\s+", cls)
	For Each t As String In tokens
		If t.StartsWith(Prefix) Then token = t
	Next
	Return token
End Sub

Private Sub NormalizeClassList(Value As String) As String
	If Value = Null Then Return ""
	Return Regex.Replace("\s+", Value.ToLowerCase.Trim, " ")
End Sub

Private Sub NormalizeHeightSpec(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.Trim)
	If s.Length = 0 Then Return "auto"
	Return s
End Sub

Private Sub EffectiveLayout(CurrentWidth As Int) As String
	Return mLayoutMode
End Sub

Private Sub ResolveRadius As Float
	If mRounded = "theme" Then Return B4XDaisyVariants.GetRadiusBoxDip(12dip)
	Return B4XDaisyVariants.ResolveRoundedDip(mRounded, B4XDaisyVariants.GetRadiusBoxDip(12dip))
End Sub

Private Sub EnsureImageLoaded As Boolean
	If mImageBitmap.IsInitialized Then Return True
	If mImagePath = Null Or mImagePath.Trim.Length = 0 Then Return False
	mImageBitmap = LoadBitmapFromPath(mImagePath)
	Return mImageBitmap.IsInitialized
End Sub

Private Sub LoadBitmapFromPath(Path As String) As B4XBitmap
	Dim empty As B4XBitmap
	If Path = Null Then Return empty
	Dim p As String = Path.Trim
	If p.Length = 0 Then Return empty
	Try
		Dim slash1 As Int = p.LastIndexOf("/")
		Dim slash2 As Int = p.LastIndexOf("\")
		Dim slash As Int = Max(slash1, slash2)
		If slash > = 0 Then
			Dim dir As String = p.SubString2(0, slash)
			Dim fn As String = p.SubString(slash + 1)
			If dir.Length > 0 And fn.Length > 0 And File.Exists(dir, fn) Then Return xui.LoadBitmap(dir, fn)
		End If
		If File.Exists(File.DirAssets, p) Then Return xui.LoadBitmap(File.DirAssets, p)
	Catch
	End Try  'ignore
	Return empty
End Sub

Private Sub SizePad(Token As String) As Int
	Select Case Token
		Case "xs": Return 8dip
		Case "sm": Return 16dip
		Case "lg": Return 32dip
		Case "xl": Return 40dip
		Case Else: Return 24dip
	End Select
End Sub

Private Sub SizeTitleSp(Token As String) As Float
	Select Case Token
		Case "xs": Return 14
		Case "sm": Return 16
		Case "lg": Return 20
		Case "xl": Return 22
		Case Else: Return 18
	End Select
End Sub

Private Sub SizeBodySp(Token As String) As Float
	Select Case Token
		Case "xs": Return 11
		Case "sm": Return 12
		Case "lg": Return 16
		Case "xl": Return 18
		Case Else: Return 14
	End Select
End Sub

Private Sub NormalizeStyle(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.ToLowerCase.Trim)
	If s = "border" Or s = "dash" Then Return s
	Return "none"
End Sub

Private Sub NormalizeLayout(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.ToLowerCase.Trim)
	If s = "top" Or s = "bottom" Or s = "side" Or s = "overlay" Or s = "none" Then Return s
	Return "top"
End Sub

Private Sub NormalizeRounded(Value As String) As String
	Dim s As String = IIf(Value = Null, "", Value.ToLowerCase.Trim)
	Select Case s
		Case "", "theme": Return "theme"
		Case "rounded-none", "none": Return "rounded-none"
		Case "rounded-sm", "sm": Return "rounded-sm"
		Case "rounded", "default": Return "rounded"
		Case "rounded-md", "md": Return "rounded-md"
		Case "rounded-lg", "lg": Return "rounded-lg"
		Case "rounded-xl", "xl": Return "rounded-xl"
		Case "rounded-2xl", "2xl": Return "rounded-2xl"
		Case "rounded-3xl", "3xl": Return "rounded-3xl"
		Case "rounded-full", "full": Return "rounded-full"
		Case Else: Return "theme"
	End Select
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = IIf(Width > 0, Width, ResolveAutoWidth(Parent))
	Dim useAutoHeight As Boolean = (Height < = 0 And IsAutoHeightSpec)
	Dim h As Int
	If Height > 0 Then
		h = Height
	Else If useAutoHeight Then
		h = 1dip
	Else
		h = ResolveAutoHeight(Parent, w)
	End If
	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.SetLayoutAnimated(0, 0, 0, Max(1dip, w), Max(1dip, h))
		DesignerCreateView(b, Null, CreateMap())
	End If
	If mBase.Parent.IsInitialized Then mBase.RemoveViewFromParent
	Parent.AddView(mBase, Left, Top, Max(1dip, w), Max(1dip, h))
	Refresh
	Return mBase
End Sub

Private Sub ResolveAutoWidth(Parent As B4XView) As Int
	Dim s As String = IIf(mWidth = Null, "", mWidth.ToLowerCase.Trim)
	If s = "w-full" Or s = "full" Or s = "100%" Or s = "w-screen" Or s = "screen" Then
		If Parent.IsInitialized And Parent.Width > 0 Then Return Max(1dip, Parent.Width)
	End If
	Dim fallback As Int = IIf(Parent.IsInitialized And Parent.Width > 0, Parent.Width, 320dip)
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mWidth, fallback))
End Sub

Private Sub ResolveAutoHeight(Parent As B4XView, CurrentWidth As Int) As Int
	Dim s As String = IIf(mHeight = Null, "", mHeight.ToLowerCase.Trim)
	If s = "" Or s = "auto" Then
		Return Max(1dip, ComputeAutoHeight(CurrentWidth))
	End If
	If s = "h-full" Or s = "full" Or s = "100%" Or s = "h-screen" Or s = "screen" Then
		If Parent.IsInitialized And Parent.Height > 0 Then Return Max(1dip, Parent.Height)
	End If
	Dim fallback As Int = IIf(Parent.IsInitialized And Parent.Height > 0, Parent.Height, 360dip)
	Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mHeight, fallback))
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub GetActualHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

Public Sub GetActualWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(Duration, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Base_Resize(Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getFigureContainer As B4XView
	Return pnlFigure
End Sub

Public Sub getCardBody As B4XView
	Return pnlContent
End Sub

Public Sub getBodyPartContainer As B4XView
	Return pnlBody
End Sub

Public Sub getTitleContainer As B4XView
	Return partTitle.getContainer
End Sub

Public Sub getCardTitle As B4XView
	Return partTitle.getExtrasContainer
End Sub

Public Sub getCardActions As B4XView
	Return partActions.getContainer
End Sub

Public Sub getContainer As B4XView
	Return mBase
End Sub

Public Sub getBodyContainer As B4XView
	Return pnlContent
End Sub

Public Sub getTitleExtrasContainer As B4XView
	Return partTitle.getExtrasContainer
End Sub

Public Sub getActionsContainer As B4XView
	Return partActions.getContainer
End Sub

''' Adds a B4XDaisyButton to the card actions footer.
''' The button is created inside the actions slot automatically — no external AddToParent needed.
''' Usage: card.AddAction(btn)
''' Note: Call card.Refresh after adding all actions to trigger layout.
Public Sub AddAction(btn As B4XDaisyButton)
    If mBase.IsInitialized = False Then Return
    If btn.mBase.IsInitialized = False Or btn.mBase.Width < 1dip Then
        btn.AddToParent(partActions.getContainer, 0, 0, 80dip, 32dip)
    Else
        If btn.mBase.Parent <> partActions.getContainer Then
            partActions.getContainer.AddView(btn.mBase, 0, 0, btn.mBase.Width, btn.mBase.Height)
        End If
    End If
End Sub

''' Returns the number of action buttons currently in the footer.
Public Sub getActionsCount As Int
    Return partActions.getContainer.NumberOfViews
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub setTitle(Value As String)
	mTitle = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTitle As String
	Return mTitle
End Sub

Public Sub setHeight(Value As String)
	mHeight = NormalizeHeightSpec(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getHeight As String
	Return mHeight
End Sub

Public Sub setImagePath(Value As String)
	mImagePath = Value
	mImageBitmap = LoadBitmapFromPath(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setImageWidth(Value As String)
	mImageWidth = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getImageWidth As String
	Return mImageWidth
End Sub

Public Sub setImageHeight(Value As String)
	mImageHeight = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getImageHeight As String
	Return mImageHeight
End Sub

Public Sub setImageClasses(Value As String)
	mImageClasses = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getImageClasses As String
	Return mImageClasses
End Sub

Public Sub SetImage(Image As B4XBitmap)
	mImageBitmap = Image
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub ClearImage
	Dim e As B4XBitmap
	mImageBitmap = e
	mImagePath = ""
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getSize As String
	Return mSize
End Sub

Public Sub setStyle(Value As String)
	mCardStyle = NormalizeStyle(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setPlaceItemsCenter(Value As Boolean)
	mPlaceItemsCenter = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getPlaceItemsCenter As Boolean
	Return mPlaceItemsCenter
End Sub

Public Sub ShowTitle
	mTitleVisible = True
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub HideTitle
	mTitleVisible = False
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub ShowActions
	mActionsVisible = True
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub HideActions
	mActionsVisible = False
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub ShowImage
	mImageVisible = True
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub HideImage
	mImageVisible = False
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub setVariant(Value As String)
	mVariant = B4XDaisyVariants.NormalizeVariant(Value)
	mBackColor = 0
	mTextColor = 0
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVariant As String
	Return mVariant
End Sub

Public Sub setLayoutMode(Value As String)
	mLayoutMode = NormalizeLayout(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setRounded(Value As String)
	mRounded = NormalizeRounded(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setShadow(Value As String)
	mShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setBackgroundColor(Value As Int)
	mBackColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getBackgroundColor As Int
	Return mBackColor
End Sub

Public Sub setTextColor(Value As Int)
	mTextColor = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getTextColor As Int
	Return mTextColor
End Sub

Public Sub setBackgroundColorVariant(VariantName As String)
	mBackColor = B4XDaisyVariants.ResolveBackgroundColorVariant(VariantName, mBackColor)
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub setTextColorVariant(VariantName As String)
	mTextColor = B4XDaisyVariants.ResolveTextColorVariant(VariantName, mTextColor)
	If mBase.IsInitialized Then Refresh
End Sub

Private Sub surface_Click
	If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
		CallSub2(mCallBack, mEventName & "_Click", mTag)
	End If
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyCanvasSpinner.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: 100dip, Description: Width/height of the spinner (px, %, dip, etc).
#DesignerProperty: Key: Color1, DisplayName: Primary Color, FieldType: Color, DefaultValue: 0xFF3FC3EE, Description: First ring color.
#DesignerProperty: Key: Color2, DisplayName: Secondary Color, FieldType: Color, DefaultValue: 0xFFF27474, Description: Second ring color.
#DesignerProperty: Key: Color3, DisplayName: Tertiary Color, FieldType: Color, DefaultValue: 0xFFF8BB86, Description: Third ring color.
#DesignerProperty: Key: StrokeWidth, DisplayName: Stroke Width, FieldType: String, DefaultValue: 4dip, Description: Ring border thickness.
#DesignerProperty: Key: OverlayColor, DisplayName: Overlay Color, FieldType: Color, DefaultValue: 0xFFFFFFFF, Description: Backdrop color when overlay is shown.
#DesignerProperty: Key: OverlayOpacity, DisplayName: Overlay Opacity, FieldType: Float, DefaultValue: 0.0, Description: Backdrop opacity from 0.0 (transparent) to 1.0 (opaque).
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Shows or hides the spinner.

#IgnoreWarnings:12,9
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private timer As Timer
    Private angle1 As Float, angle2 As Float, angle3 As Float

    ' nested panels/canvases for each ring
    Private p1 As B4XView, p2 As B4XView, p3 As B4XView
    Private c1 As B4XCanvas, c2 As B4XCanvas, c3 As B4XCanvas
    Private canvasesInitialized As Boolean

    ' properties
    Private mSize As String = "100dip"
    Private mColor1 As Int = 0xFF3FC3EE
    Private mColor2 As Int = 0xFFF27474
    Private mColor3 As Int = 0xFFF8BB86
    Private mStroke As Float = 4dip
    Private mOverlayColor As Int = 0xFFFFFFFF
    Private mOverlayOpacity As Float = 0.0
    Private mVisible As Boolean = True
    Private mCallBack As Object
    Private mEventName As String
    Private mFullScreenLoader As Boolean = False
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
    mBase = xui.CreatePanel("")
    mBase.SetLayoutAnimated(0, 0, 0, 100dip, 100dip)
    timer.Initialize("t", 16)
    ' create three overlay panels
    p1 = xui.CreatePanel("") : p1.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    p2 = xui.CreatePanel("") : p2.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    p3 = xui.CreatePanel("") : p3.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    mBase.AddView(p1,0,0,0,0)
    mBase.AddView(p2,0,0,0,0)
    mBase.AddView(p3,0,0,0,0)
    ' canvases will be initialized once panels have valid size
    canvasesInitialized = False
End Sub

Public Sub DesignerCreateView (Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent
    timer.Initialize("t", 16)
    ' nested panels
    p1 = xui.CreatePanel("") : p2 = xui.CreatePanel("") : p3 = xui.CreatePanel("")
    p1.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    p2.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    p3.SetColorAndBorder(xui.Color_Transparent,0,0,0)
    mBase.AddView(p1,0,0,0,0)
    mBase.AddView(p2,0,0,0,0)
    mBase.AddView(p3,0,0,0,0)
    ' canvases will be initialized later when resized
    canvasesInitialized = False
    ' apply designer props
    mSize = B4XDaisyVariants.GetPropString(Props, "Size", mSize)
    mColor1 = B4XDaisyVariants.GetPropColor(Props, "Color1", mColor1)
    mColor2 = B4XDaisyVariants.GetPropColor(Props, "Color2", mColor2)
    mColor3 = B4XDaisyVariants.GetPropColor(Props, "Color3", mColor3)
    mStroke = B4XDaisyVariants.GetPropFloat(Props, "StrokeWidth", mStroke)
    mOverlayColor = B4XDaisyVariants.GetPropColor(Props, "OverlayColor", mOverlayColor)
    mOverlayOpacity = Max(0.0, Min(1.0, B4XDaisyVariants.GetPropFloat(Props, "OverlayOpacity", mOverlayOpacity)))
    mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
    Base_Resize(mBase.Width, mBase.Height)
    ' apply visibility state from designer
    mBase.Visible = mVisible
    If timer <> Null Then
        If timer.IsInitialized Then timer.Enabled = mVisible
    End If
End Sub

Public Sub GetComputedHeight As Int
	If mBase = Null Then Return 0
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent = Null Or Parent.IsInitialized = False Then Return mBase
    If mBase = Null Or mBase.IsInitialized = False Then Return mBase
    mFullScreenLoader = False
    If Width <= 0 Then Width = mBase.Width
    If Height <= 0 Then Height = mBase.Height
    Parent.AddView(mBase, Left, Top, Width, Height)
    ApplyOverlayColor
    ResizePanels(Width, Height)
    Return mBase
End Sub

' Attaches spinner full-screen to the given view and expands to its size
Public Sub AttachTo(Target As B4XView) As B4XView
    If Target = Null Or Target.IsInitialized = False Then Return mBase
    If mBase = Null Or mBase.IsInitialized = False Then Return mBase
    mFullScreenLoader = True
    Target.AddView(mBase, 0, 0, Target.Width, Target.Height)
    ApplyOverlayColor
    ResizePanels(Target.Width, Target.Height)
    Return mBase
End Sub

Public Sub Base_Resize (Width As Double, Height As Double)
    If mBase = Null Or mBase.IsInitialized = False Then Return
    ResizePanels(Width, Height)
    Draw
End Sub

' convenience method for external callers (page/overlay) to resize & layout
Public Sub Resize(Width As Int, Height As Int)
    If mBase = Null Or mBase.IsInitialized = False Then Return
    mBase.SetLayoutAnimated(0, 0, 0, Width, Height)
    ResizePanels(Width, Height)
    Draw
End Sub

' Utility for adding a child to the spinner (not common but mirror overlay API)
Public Sub AddChild(View As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
    If mBase <> Null And mBase.IsInitialized Then
        mBase.AddView(View, Left, Top, Width, Height)
    End If
End Sub

Private Sub t_Tick
    ' loader-1.css durations:
    ' Ring 1 (Center/Outer): 2s = 360deg / (2000ms / 16ms) = 2.88 deg/tick
    ' Ring 2 (Middle): 3s = 360deg / (3000ms / 16ms) = 1.92 deg/tick
    ' Ring 3 (Inner): 1.5s = 360deg / (1500ms / 16ms) = 3.84 deg/tick
    
    angle1 = (angle1 + 2.88) Mod 360
    angle2 = (angle2 + 1.92) Mod 360
    angle3 = (angle3 + 3.84) Mod 360
    
    ' Rotate panels clockwise (parity with CSS spin keyframe)
    p1.Rotation = angle1
    p2.Rotation = angle2
    p3.Rotation = angle3
End Sub

Private Sub Draw
    ' loader-1.css: 3 concentric rings, only specific arcs colored, each spins at different speed
    If canvasesInitialized = False Then Return
    c1.ClearRect(c1.TargetRect)
    c2.ClearRect(c2.TargetRect)
    c3.ClearRect(c3.TargetRect)
    
    Dim avail As Float = Min(c1.TargetRect.Width, c1.TargetRect.Height)
    Dim rBase As Float = Max(0, (avail - mStroke) / 2)
    Dim cx As Float = c1.TargetRect.Width / 2
    Dim cy As Float = c1.TargetRect.Height / 2
    
    ' To prevent overlap, we space the concentric rings by the stroke width plus a small gap
    Dim ringGap As Float = mStroke * 1.3
    Dim r1 As Float = rBase
    Dim r2 As Float = Max(0, rBase - ringGap)
    Dim r3 As Float = Max(0, rBase - 2 * ringGap)
    
    ' Ring 1: Outer, first color, two 90deg segments (top and bottom)
    DrawLoaderRing(c1, cx, cy, r1, mStroke, mColor1, 90, 0)
    DrawLoaderRing(c1, cx, cy, r1, mStroke, mColor1, 90, 180)
    
    ' Ring 2: Middle, second color, one 90deg segment (top)
    DrawLoaderRing(c2, cx, cy, r2, mStroke, mColor2, 90, 0)
    
    ' Ring 3: Inner, third color, one 90deg segment (top)
    DrawLoaderRing(c3, cx, cy, r3, mStroke, mColor3, 90, 0)
    
    c1.Invalidate
    c2.Invalidate
    c3.Invalidate
End Sub

' Draws a ring portion (CSS border-color effect)
Private Sub DrawLoaderRing(cvs As B4XCanvas, cx As Float, cy As Float, radius As Float, stroke As Float, color As Int, sweepAngle As Float, offsetAngle As Float)
    Dim startAngle As Float = -90 - (sweepAngle / 2) + offsetAngle
    
    ' Draw subtle background track (10% opacity) for depth
    Dim trackColor As Int = xui.Color_ARGB(25, GetRGB(color, 1), GetRGB(color, 2), GetRGB(color, 3))
    Dim trackP As B4XPath = CreateCleanArcPath(cx, cy, radius, 0, 360)
    cvs.DrawPath(trackP, trackColor, False, stroke)
    
    ' Draw foreground rotating/colored arc
    Dim path As B4XPath = CreateCleanArcPath(cx, cy, radius, startAngle, sweepAngle)
    cvs.DrawPath(path, color, False, stroke)
    
    ' Round caps to match the smooth look of the GIF
    Dim startX As Float = cx + radius * CosD(startAngle)
    Dim startY As Float = cy + radius * SinD(startAngle)
    Dim endAngle As Float = startAngle + sweepAngle
    Dim endX As Float = cx + radius * CosD(endAngle)
    Dim endY As Float = cy + radius * SinD(endAngle)
    cvs.DrawCircle(startX, startY, stroke/2, color, True, 0)
    cvs.DrawCircle(endX, endY, stroke/2, color, True, 0)
End Sub

Public Sub Show(Target As B4XView)
    If mBase = Null Or mBase.IsInitialized = False Then Return
    If Target <> Null And Target.IsInitialized Then
        Dim isAttached As Boolean = False
        Try
            If mBase.Parent = Target Then isAttached = True
        Catch
            Log("B4XDaisyCanvasSpinner.Show: " & LastException.Message)
        End Try
        If isAttached = False Then
            mBase.RemoveViewFromParent
            Target.AddView(mBase, 0, 0, Target.Width, Target.Height)
        End If
        mFullScreenLoader = True
        Resize(Target.Width, Target.Height)
    End If
    ApplyOverlayColor
    mBase.Visible = True
    Dim jo As JavaObject = mBase
    jo.RunMethod("bringToFront", Null)
    If canvasesInitialized Then Draw
    If timer <> Null Then
        If timer.IsInitialized Then timer.Enabled = True
    End If
End Sub

Public Sub Hide
    If timer <> Null Then
        If timer.IsInitialized Then timer.Enabled = False
    End If
    If mBase = Null Or mBase.IsInitialized = False Then Return
    mBase.Color = xui.Color_Transparent
    mBase.Visible = False
End Sub

Public Sub getVisible As Boolean
    If mBase <> Null And mBase.IsInitialized Then Return mBase.Visible
    Return False
End Sub

Public Sub setVisible(b As Boolean)
    If mBase = Null Then Return
    If b Then Show(Null) Else Hide
End Sub

Public Sub getSize As String
    Return mSize
End Sub
Public Sub setSize(s As String)
    mSize = s
    If mBase <> Null And mBase.IsInitialized Then
        mBase.SetLayoutAnimated(0, 0, 0, B4XDaisyVariants.TailwindSizeToDip(s, mBase.Width), B4XDaisyVariants.TailwindSizeToDip(s, mBase.Height))
        ResizePanels(mBase.Width, mBase.Height)
        Draw
    End If
End Sub

' ---- additional property accessors ----
Public Sub getColor1 As Int
    Return mColor1
End Sub
Public Sub setColor1(c As Int)
    mColor1 = c
    If canvasesInitialized Then Draw
End Sub

Public Sub getColor2 As Int
    Return mColor2
End Sub
Public Sub setColor2(c As Int)
    mColor2 = c
    If canvasesInitialized Then Draw
End Sub

Public Sub getColor3 As Int
    Return mColor3
End Sub
Public Sub setColor3(c As Int)
    mColor3 = c
    If canvasesInitialized Then Draw
End Sub

Public Sub getStrokeWidth As Float
    Return mStroke
End Sub
Public Sub setStrokeWidth(s As Float)
    mStroke = s
    If canvasesInitialized Then Draw
End Sub

Public Sub getOverlayColor As Int
    Return mOverlayColor
End Sub
Public Sub setOverlayColor(c As Int)
    mOverlayColor = c
    If getVisible Then ApplyOverlayColor
End Sub

Public Sub getOverlayOpacity As Float
    Return mOverlayOpacity
End Sub
Public Sub setOverlayOpacity(o As Float)
    mOverlayOpacity = Max(0.0, Min(1.0, o))
    If getVisible Then ApplyOverlayColor
End Sub' helper to reposition the three panels when size changes
Private Sub ResizePanels(Width As Int, Height As Int)
    Dim side As Int = Min(Width, Height)
    If mFullScreenLoader Then
        side = Min(Width, Height) * 0.5
    End If
    Dim left As Int = (Width - side) / 2
    Dim top As Int = (Height - side) / 2
    ' To ensure 100% perfect concentricity, all panels must have the exact same size and center.
    ' We control the ring offsets purely via the radius in the Draw method.
    p1.SetLayoutAnimated(0, left, top, side, side)
    p2.SetLayoutAnimated(0, left, top, side, side)
    p3.SetLayoutAnimated(0, left, top, side, side)
    
    ' initialize canvases once when panels have dimensions
    If canvasesInitialized = False And side > 0 Then
        c1.Initialize(p1)
        c2.Initialize(p2)
        c3.Initialize(p3)
        canvasesInitialized = True
    End If
    c1.Resize(side, side)
    c2.Resize(side, side)
    c3.Resize(side, side)
    ' always redraw after resizing (or after first initialization)
    If canvasesInitialized Then Draw
End Sub

Private Sub ApplyOverlayColor
    If mBase <> Null And mBase.IsInitialized Then
        Dim alpha As Int = Max(0, Min(255, mOverlayOpacity * 255))
        mBase.Color = xui.Color_ARGB(alpha, GetRGB(mOverlayColor, 1), GetRGB(mOverlayColor, 2), GetRGB(mOverlayColor, 3))
    End If
End Sub

' more setters/getters could be added as needed

' Helper to extract RGB channels
Private Sub GetRGB(Color As Int, Channel As Int) As Int
	If Channel = 1 Then Return Bit.And(Bit.ShiftRight(Color, 16), 0xFF) ' Red
	If Channel = 2 Then Return Bit.And(Bit.ShiftRight(Color, 8), 0xFF)  ' Green
	Return Bit.And(Color, 0xFF) ' Blue
End Sub

Private Sub CreateCleanArcPath(cx As Float, cy As Float, radius As Float, startAngle As Float, sweepAngle As Float) As B4XPath
	#If B4A
	Dim nativePath As JavaObject
	nativePath.InitializeNewInstance("android.graphics.Path", Null)
	Dim rectF As JavaObject
	rectF.InitializeNewInstance("android.graphics.RectF", Array(cx - radius, cy - radius, cx + radius, cy + radius))
	nativePath.RunMethod("addArc", Array(rectF, startAngle, sweepAngle))
	Dim path As B4XPath = nativePath
	Return path
	#Else
	Dim path As B4XPath
	path.InitializeArc(cx, cy, radius, startAngle, sweepAngle)
	Return path
	#End If
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyButton.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#Event: Click (Tag As Object)
#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: Button, Description: Button label.
'#DesignerProperty: Key: Class, DisplayName: Class, FieldType: String, DefaultValue: btn, Description: Daisy class tokens (for example: btn btn-primary btn-outline).
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: default, List: default|neutral|primary|secondary|accent|info|success|warning|error|none, Description: Semantic color variant.
#DesignerProperty: Key: Style, DisplayName: Style, FieldType: String, DefaultValue: solid, List: solid|soft|outline|dash|ghost|link, Description: Daisy button style.
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Daisy button size token.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Border radius token.
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Tailwind padding utility tokens (For example: px-3 py-1).
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Tailwind margin utility tokens.
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: 40px, Description: Tailwind/CSS width token (For example: auto, 40px, w-40, [12rem]).
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: auto, Description: Tailwind/CSS height token (For example: auto, 40px, h-10, [3rem]).
#DesignerProperty: Key: IconName, DisplayName: Icon Name, FieldType: String, DefaultValue:, Description: SVG icon asset file name.
#DesignerProperty: Key: IconColor, DisplayName: Icon Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Optional icon color override.
#DesignerProperty: Key: Wide, DisplayName: Wide, FieldType: Boolean, DefaultValue: False, Description: Applies btn-wide behavior.
#DesignerProperty: Key: Block, DisplayName: Block, FieldType: Boolean, DefaultValue: False, Description: Applies btn-block behavior.
#DesignerProperty: Key: Square, DisplayName: Square, FieldType: Boolean, DefaultValue: False, Description: Applies btn-square behavior.
#DesignerProperty: Key: Circle, DisplayName: Circle, FieldType: Boolean, DefaultValue: False, Description: Applies btn-circle behavior.
#DesignerProperty: Key: Active, DisplayName: Active, FieldType: Boolean, DefaultValue: False, Description: Applies btn-active behavior.
#DesignerProperty: Key: Disabled, DisplayName: Disabled, FieldType: Boolean, DefaultValue: False, Description: Applies disabled behavior.
#DesignerProperty: Key: Loading, DisplayName: Loading, FieldType: Boolean, DefaultValue: False, Description: Shows loading indicator.
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override background color.
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override text color.
#DesignerProperty: Key: BorderColor, DisplayName: Border Color, FieldType: Color, DefaultValue: 0x00FFFFFF, Description: Override border color.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show Or hide component.
#DesignerProperty: Key: TextAlignment, DisplayName: Text Alignment, FieldType: String, DefaultValue: CENTER, List: CENTER|LEFT|RIGHT, Description: Horizontal alignment of button text.
#DesignerProperty: Key: ButtonSizeDip, DisplayName: Button Size (dip), FieldType: Int, DefaultValue: 0, MinRange: 0, Description: Explicit button extent in dip (circle/square/content height). 0 = auto from Size token.
#DesignerProperty: Key: IconSize, DisplayName: Icon Size (dip), FieldType: Int, DefaultValue: 0, MinRange: 0, Description: Explicit icon size in dip. 0 = auto from Size token.

#IgnoreWarnings:12,9

Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object

    Private lblContent As B4XView
    Private iconComp As B4XDaisySvgIcon
    Private iconView As B4XView
    Private loadingComp As B4XDaisyLoading
    Private loadingView As B4XView
    Private pnlMeasure As B4XView
    Private cvsMeasure As B4XCanvas

    Private mText As String ="Button"
    Private mClassTokens As String ="btn"
    Private mShadow As String ="xs"
    Private mVariant As String ="default"
    Private mStyle As String ="solid"
    Private mSize As String ="md"
    Private mRounded As String ="theme"
    Private mPadding As String =""
    Private mMargin As String =""
    Private mWidth As String ="40px"
    Private mHeight As String ="auto"
    Private mIconName As String =""
    Private mIconColor As Int = xui.Color_Transparent
    ' Explicit overrides (0 = auto/derive from Size token). Used by the FAB and
    ' other containers that need exact dip sizing independent of the Size token.
    Private miButtonExtentDip As Int = 0
    Private miIconSizeDip As Int = 0

    Private mWide As Boolean = False
    Private mBlock As Boolean = False
    Private mSquare As Boolean = False
    Private mCircle As Boolean = False
    Private mActive As Boolean = False
    Private mDisabled As Boolean = False
    Private mLoading As Boolean = False

    Private mBackgroundColor As Int = xui.Color_Transparent
    Private mTextColor As Int = xui.Color_Transparent
    Private mBorderColor As Int = xui.Color_Transparent
    Private mVisible As Boolean = True
    Private mTextAlignment As String = "CENTER"
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me

    Dim t As Label
    t.Initialize("part")
    lblContent = t
    ' Content label gravity is applied by ApplyContentGravity after mTextAlignment
    ' has been resolved from Props (below). This honors both the value stored via
    ' setTextAlignment before AddToParent and the visual-designer Props value.
    lblContent.Color = xui.Color_Transparent
    Dim lContent As Label = lblContent
    lContent.SingleLine = True
    EnsureContentSingleLine
    mBase.AddView(lblContent, 0, 0, mBase.Width, mBase.Height)

    Dim icon As B4XDaisySvgIcon
    icon.Initialize(Me,"btnicon")
    iconComp = icon
    iconView = iconComp.AddToParent(mBase, 0, 0, 1dip, 1dip)
    iconComp.SetClickable(False)
    iconView.Visible = False

    Dim ld As B4XDaisyLoading
    ld.Initialize(Me,"btnloading")
    loadingComp = ld
    loadingView = loadingComp.AddToParent(mBase, 0, 0, 1dip, 1dip)
    loadingComp.SetClickable(False)
    loadingView.Visible = False

    Dim p As Panel
    p.Initialize("")
    pnlMeasure = p
    mBase.AddView(pnlMeasure, 0, 0, 120dip, 40dip)
    pnlMeasure.Visible = False
    cvsMeasure.Initialize(pnlMeasure)

    mText = B4XDaisyVariants.GetPropString(Props,"Text", mText)
    mClassTokens = B4XDaisyVariants.GetPropString(Props,"Class", mClassTokens)
    mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props,"Variant", mVariant))
    mStyle = B4XDaisyVariants.NormalizeStyle(B4XDaisyVariants.GetPropString(Props,"Style", mStyle))
    mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props,"Size", mSize))
    mRounded = B4XDaisyVariants.GetPropString(Props,"Rounded", mRounded)
    mPadding = B4XDaisyVariants.GetPropString(Props,"Padding", mPadding)
    mMargin = B4XDaisyVariants.GetPropString(Props,"Margin", mMargin)
    mWidth = NormalizeLayoutSpec(B4XDaisyVariants.GetPropString(Props,"Width", mWidth),"40px")
    mHeight = NormalizeLayoutSpec(B4XDaisyVariants.GetPropString(Props,"Height", mHeight),"auto")
    mIconName = B4XDaisyVariants.GetPropString(Props,"IconName", mIconName)
    mIconColor = B4XDaisyVariants.GetPropColor(Props,"IconColor", mIconColor)
    mWide = B4XDaisyVariants.GetPropBool(Props,"Wide", mWide)
    mBlock = B4XDaisyVariants.GetPropBool(Props,"Block", mBlock)
    mSquare = B4XDaisyVariants.GetPropBool(Props,"Square", mSquare)
    mCircle = B4XDaisyVariants.GetPropBool(Props,"Circle", mCircle)
    mActive = B4XDaisyVariants.GetPropBool(Props,"Active", mActive)
    mDisabled = B4XDaisyVariants.GetPropBool(Props,"Disabled", mDisabled)
    mLoading = B4XDaisyVariants.GetPropBool(Props,"Loading", mLoading)
    mBackgroundColor = B4XDaisyVariants.GetPropColor(Props,"BackgroundColor", mBackgroundColor)
    mTextColor = B4XDaisyVariants.GetPropColor(Props,"TextColor", mTextColor)
    mBorderColor = B4XDaisyVariants.GetPropColor(Props,"BorderColor", mBorderColor)
    mVisible = B4XDaisyVariants.GetPropBool(Props,"Visible", mVisible)
    mTextAlignment = B4XDaisyVariants.GetPropString(Props,"TextAlignment", mTextAlignment)
    miButtonExtentDip = Max(0, B4XDaisyVariants.GetPropInt(Props, "ButtonSizeDip", miButtonExtentDip))
    miIconSizeDip = Max(0, B4XDaisyVariants.GetPropInt(Props, "IconSize", miIconSizeDip))
    ApplyContentGravity

    ParseClassTokens(mClassTokens)
    Refresh
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    Dim empty As B4XView
    If Parent.IsInitialized = False Then Return empty

    If mBase.IsInitialized = False Then
        Dim p As Panel
        p.Initialize("mBase")
        DesignerCreateView(p, Null, BuildRuntimeProps)
    End If

    Dim mg As Map = ResolveMargin
    Left = Left + mg.Get("l")
    Top = Top + mg.Get("t")
    Width = Width - mg.Get("l") - mg.Get("r")
    Height = Height - mg.Get("t") - mg.Get("b")

    Dim controlSize As Int = ResolveButtonHeightDip(mSize)
    If Width <= 0 Then Width = ResolveConfiguredWidthDip(controlSize)
    If mBlock = False Then Width = Max(Width, EstimateContentWidthDip)
    If Height <= 0 Then Height = ResolveConfiguredHeightDip(controlSize)
    If mBlock Then
        Dim available As Int = Parent.Width - Left - mg.Get("r")
        If available > controlSize Then
            Width = Max(Width, available)
        Else
            Width = Max(Width, EstimateContentWidthDip)
        End If
    Else If mWide Then
        Width = ResolveWideWidthDip(Max(Width, EstimateContentWidthDip), Left, Parent, mg, controlSize)
    End If

    Parent.AddView(mBase, Left, Top, Width, Height)
    Base_Resize(mBase.Width, mBase.Height)
    Return mBase
End Sub

Private Sub BuildRuntimeProps As Map
    Return CreateMap( _
    "Text": mText, _
    "Class": mClassTokens, _
    "Variant": mVariant, _
    "Style": mStyle, _
    "Size": mSize, _
    "Rounded": mRounded, _
    "Padding": mPadding, _
    "Margin": mMargin, _
    "Width": mWidth, _
    "Height": mHeight, _
    "IconName": mIconName, _
    "IconColor": mIconColor, _
    "Wide": mWide, _
    "Block": mBlock, _
    "Square": mSquare, _
    "Circle": mCircle, _
    "Active": mActive, _
    "Disabled": mDisabled, _
    "Loading": mLoading, _
    "BackgroundColor": mBackgroundColor, _
    "TextColor": mTextColor, _
    "BorderColor": mBorderColor, _
    "Visible": mVisible, _
    "TextAlignment": mTextAlignment, _
    "ButtonSizeDip": miButtonExtentDip, _
    "IconSize": miIconSizeDip)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
    If mBase.IsInitialized = False Then Return

    Dim targetW As Int = Max(1dip, Width)
    Dim controlSize As Int = ResolveButtonHeightDip(mSize)
    Dim targetH As Int
    If IsAutoLayoutSpec(mHeight) Then
        targetH = Max(controlSize, Height)
    Else
        targetH = Max(1dip, Height)
    End If
    Dim targetLeft As Int = mBase.Left
    Dim mg As Map = ResolveMargin

    If mSquare Or mCircle Then
        targetW = controlSize
        targetH = controlSize
    Else
        targetW = Max(targetW, controlSize)
        targetW = Max(targetW, EstimateContentWidthDip)
        If mBlock And mBase.Parent.IsInitialized Then
            Dim available As Int = mBase.Parent.Width - targetLeft - mg.Get("r")
            If available > controlSize Then
                targetW = Max(targetW, available)
            End If
        Else If mWide Then
            targetW = ResolveWideWidthDip(targetW, targetLeft, mBase.Parent, mg, controlSize)
        End If
    End If

    If targetW <> Width Or targetH <> Height Or targetLeft <> mBase.Left Then
        mBase.SetLayoutAnimated(0, targetLeft, mBase.Top, targetW, targetH)
        Width = targetW
        Height = targetH
    End If

    Dim p As Map = ResolvePadding
    Dim l As Int = p.Get("l")
    Dim t As Int = p.Get("t")
    Dim r As Int = p.Get("r")
    Dim b As Int = p.Get("b")

    Dim contentW As Int = Max(1dip, Width - l - r)
    Dim contentH As Int = Max(1dip, Height - t - b)

    Dim indicatorW As Int = Max(12dip, ResolveIndicatorSizeDip)
    Dim gap As Int = 6dip
    Dim hasText As Boolean = (mText.Trim.Length > 0)
    Dim hasIcon As Boolean = (mIconName.Trim.Length > 0)

    lblContent.Visible = hasText

    If mLoading Then
        loadingView.Visible = True
        iconView.Visible = False
        If hasText = False Then
            loadingView.SetLayoutAnimated(0, l + (contentW - indicatorW) / 2, t + (contentH - indicatorW) / 2, indicatorW, indicatorW)
            lblContent.SetLayoutAnimated(0, l, t, contentW, contentH)
        Else
            Dim tw As Int = MeasureTextWidthDip(mText, xui.CreateDefaultBoldFont(lblContent.TextSize))
            Dim textX As Int
            Dim spinnerX As Int
            Dim alignLoad As String = mTextAlignment.ToUpperCase
            If alignLoad = "LEFT" Then
                spinnerX = l
                textX = l + indicatorW + gap
            Else If alignLoad = "RIGHT" Then
                textX = l + Max(0dip, contentW - tw)
                spinnerX = Max(l, textX - indicatorW - gap)
            Else
                textX = l + (contentW - tw) / 2
                spinnerX = Max(l, textX - indicatorW - gap)
            End If
            loadingView.SetLayoutAnimated(0, spinnerX, t + (contentH - indicatorW) / 2, indicatorW, indicatorW)
            lblContent.SetLayoutAnimated(0, textX, t, Max(1dip, tw), contentH)
        End If
        If loadingComp.IsInitialized Then loadingComp.Base_Resize(loadingView.Width, loadingView.Height)
        iconView.SetLayoutAnimated(0, 0, 0, 1dip, 1dip)
    Else If hasIcon Then
        loadingView.Visible = False
        iconView.Visible = True
        If hasText = False Then
            iconView.SetLayoutAnimated(0, l + (contentW - indicatorW) / 2, t + (contentH - indicatorW) / 2, indicatorW, indicatorW)
            lblContent.SetLayoutAnimated(0, l, t, contentW, contentH)
        Else
            Dim tw2 As Int = MeasureTextWidthDip(mText, xui.CreateDefaultBoldFont(lblContent.TextSize))
            Dim totalW2 As Int = indicatorW + gap + tw2
            Dim startX2 As Int
            Dim alignIcon As String = mTextAlignment.ToUpperCase
            If alignIcon = "LEFT" Then
                startX2 = l
            Else If alignIcon = "RIGHT" Then
                startX2 = l + Max(0dip, contentW - totalW2)
            Else
                startX2 = l + Max(0dip, (contentW - totalW2) / 2)
            End If
            iconView.SetLayoutAnimated(0, startX2, t + (contentH - indicatorW) / 2, indicatorW, indicatorW)
            lblContent.SetLayoutAnimated(0, startX2 + indicatorW + gap, t, Max(1dip, tw2), contentH)
        End If
        If iconComp.IsInitialized Then
            iconComp.setWidth(iconView.Width)
            iconComp.setHeight(iconView.Height)
            iconComp.ResizeToParent(iconView)
        End If
        loadingView.SetLayoutAnimated(0, 0, 0, 1dip, 1dip)
    Else
        loadingView.Visible = False
        iconView.Visible = False
        loadingView.SetLayoutAnimated(0, 0, 0, 1dip, 1dip)
        iconView.SetLayoutAnimated(0, 0, 0, 1dip, 1dip)
        lblContent.SetLayoutAnimated(0, l, t, contentW, contentH)
    End If
    'Explicitly set the background drawable's bounds to the resolved layout dimensions
    'and invalidate the outline.  This is necessary for buttons inside initially-hidden
    'containers (e.g. modals with Visible=False): Android's drawBackground never runs
    'while a view is hidden, so GradientDrawable.getBounds() stays at 0,0,0,0.
    'GradientDrawable.getOutline() returns empty when getBounds() is empty, which
    'causes the shadow renderer to fall back to the view's rectangular BOUNDS outline
    '? producing a square shadow regardless of the button's corner radius.
    'By setting the drawable bounds here (after we know the final layout size) and
    're-invalidating the outline, the rounded-rect shadow is correct on first draw.
    #If B4A
        If targetW > 0 And targetH > 0 Then
            Try
                Dim joMB As JavaObject = mBase
                Dim bgDraw As JavaObject = joMB.RunMethod("getBackground", Null)
                If bgDraw.IsInitialized Then
                    bgDraw.RunMethod("setBounds", Array(0, 0, targetW, targetH))
                    joMB.RunMethod("invalidateOutline", Null)
                End If
            Catch
                Log("B4XDaisyButton.Base_Resize: " & LastException.Message)
            End Try
        End If
    #End If
End Sub

Public Sub Refresh
    If mBase.IsInitialized = False Then Return

    Dim baseContent As Int = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55))
    Dim base100 As Int = B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_White)
    Dim base200 As Int = B4XDaisyVariants.GetTokenColor("--color-base-200", xui.Color_RGB(229, 231, 235))
    Dim primaryBack As Int = B4XDaisyVariants.ResolveBackgroundColorVariant("primary", xui.Color_RGB(59, 130, 246))

    Dim variantBack As Int = ResolveVariantBackColor(base200)
    Dim variantText As Int = ResolveVariantTextColor(baseContent)

    Dim bg As Int = variantBack
    Dim fg As Int = variantText
    Dim br As Int = B4XDaisyVariants.Blend(bg, xui.Color_Black, 0.10)

    Select Case B4XDaisyVariants.NormalizeStyle(mStyle)
        Case "soft"
            bg = B4XDaisyVariants.Blend(base100, variantBack, 0.08)
            br = B4XDaisyVariants.Blend(base100, variantBack, 0.10)
            If NormalizeVariant(mVariant) ="default" Or NormalizeVariant(mVariant) ="none" Then
                fg = baseContent
            Else
                fg = variantBack
            End If
        Case "outline","dash"
            bg = xui.Color_Transparent
            br = ResolveOutlineColor(baseContent, variantBack)
            fg = br
        Case "ghost"
            bg = xui.Color_Transparent
            br = xui.Color_Transparent
            fg = ResolveOutlineColor(baseContent, variantBack)
        Case "link"
            bg = xui.Color_Transparent
            br = xui.Color_Transparent
            If NormalizeVariant(mVariant) ="default" Or NormalizeVariant(mVariant) ="none" Then
                fg = primaryBack
            Else
                fg = variantBack
            End If
        Case Else
            'solid default
    End Select

    If NormalizeVariant(mVariant) ="warning" And B4XDaisyVariants.NormalizeStyle(mStyle) ="solid" Then
        fg = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55))
    End If

    If mActive And bg <> xui.Color_Transparent Then
        bg = B4XDaisyVariants.Blend(bg, xui.Color_Black, 0.07)
        br = B4XDaisyVariants.Blend(br, xui.Color_Black, 0.07)
    End If

    If mDisabled Then
        Dim faded As Int = ToAlphaColor(baseContent, 51)
        fg = faded
        If B4XDaisyVariants.NormalizeStyle(mStyle) <>"ghost" And B4XDaisyVariants.NormalizeStyle(mStyle) <>"link" Then
            bg = ToAlphaColor(baseContent, 26)
            br = xui.Color_Transparent
        Else
            bg = xui.Color_Transparent
            br = xui.Color_Transparent
        End If
        mBase.Enabled = False
    Else
        mBase.Enabled = True
    End If

    If mBackgroundColor <> xui.Color_Transparent Then bg = mBackgroundColor
    If mTextColor <> xui.Color_Transparent Then fg = mTextColor
    If mBorderColor <> xui.Color_Transparent Then br = mBorderColor

    lblContent.Text = FlattenTextSingleLine(mText)
    lblContent.TextColor = fg
    lblContent.TextSize = ResolveFontSizeDip(mSize)

    Dim l As Label = lblContent
    l.Typeface = Typeface.DEFAULT_BOLD
    l.SingleLine = True
    EnsureContentSingleLine

    If iconComp.IsInitialized Then
        If mIconName.Trim.Length > 0 Then
            Dim preserveIconColors As Boolean = IsTransparentColor(mIconColor)
            iconComp.setSvgAsset(mIconName)
            iconComp.setPreserveOriginalColors(preserveIconColors)
            iconComp.setColor(IIf(preserveIconColors, fg, mIconColor))
            iconComp.setRoundedBox(False)
        End If
    End If
    If loadingComp.IsInitialized Then
        loadingComp.setStyle("spinner")
        loadingComp.setSize(mSize)
        loadingComp.setColor(fg)
        loadingComp.setVisible(mLoading)
    End If

    Dim radius As Int = ResolveRadiusDip(IIf(mCircle,"rounded-full", mRounded))

    Dim borderW As Int = ResolveBorderDip
    Select Case B4XDaisyVariants.NormalizeStyle(mStyle)
        Case "ghost","link"
            If mBorderColor <> xui.Color_Transparent Then
                borderW = Max(1dip, borderW)  ' Honor explicit border color even on ghost/link
            Else
                borderW = 0
            End If
        Case Else
            borderW = Max(1dip, borderW)
    End Select
    If mDisabled And (B4XDaisyVariants.NormalizeStyle(mStyle) <>"ghost" And B4XDaisyVariants.NormalizeStyle(mStyle) <>"link") Then
        borderW = 0
    End If

    B4XDaisyVariants.ApplyDashedBorder(mBase, bg, borderW, br, radius, mStyle)
    EnsureShadowVisibility
    If mActive Then
        B4XDaisyVariants.ApplyElevation(mBase,"none")
    Else
        B4XDaisyVariants.ApplyElevation(mBase, mShadow)
    End If
    mBase.Visible = mVisible
    Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub ResolveVariantBackColor(DefaultColor As Int) As Int
    Dim v As String = NormalizeVariant(mVariant)
    If v ="default" Or v ="none" Then Return DefaultColor
        Return B4XDaisyVariants.ResolveBackgroundColorVariant(v, DefaultColor)
    End Sub

    Private Sub ResolveVariantTextColor(DefaultColor As Int) As Int
        Dim v As String = NormalizeVariant(mVariant)
        If v ="default" Or v ="none" Then Return DefaultColor
            Return B4XDaisyVariants.ResolveTextColorVariant(v, DefaultColor)
        End Sub

        Private Sub ResolveOutlineColor(BaseContent As Int, VariantBack As Int) As Int
            Dim v As String = NormalizeVariant(mVariant)
            If v ="default" Or v ="none" Then Return BaseContent
                Return VariantBack
            End Sub

            Private Sub ToAlphaColor(BaseColor As Int, Alpha As Int) As Int
                Dim a As Int = Max(0, Min(255, Alpha))
                Dim r As Int = Bit.And(Bit.ShiftRight(BaseColor, 16), 0xFF)
                Dim g As Int = Bit.And(Bit.ShiftRight(BaseColor, 8), 0xFF)
                Dim b As Int = Bit.And(BaseColor, 0xFF)
                Return xui.Color_ARGB(a, r, g, b)
            End Sub

            Private Sub ParseClassTokens(TokensText As String)
                If TokensText = Null Then Return
                Dim raw As String = TokensText.Trim
                If raw.Length = 0 Then Return

                Dim tokens() As String = Regex.Split("\s+", raw)
                For Each tokenRaw As String In tokens
                    Dim token As String = tokenRaw.ToLowerCase.Trim
                    If token.Length = 0 Then Continue

                    Select Case token
                        Case "btn","button"
                        Case "btn-neutral","neutral"
                            mVariant ="neutral"
                        Case "btn-primary","primary"
                            mVariant ="primary"
                        Case "btn-secondary","secondary"
                            mVariant ="secondary"
                        Case "btn-accent","accent"
                            mVariant ="accent"
                        Case "btn-info","info"
                            mVariant ="info"
                        Case "btn-success","success"
                            mVariant ="success"
                        Case "btn-warning","warning"
                            mVariant ="warning"
                        Case "btn-error","error"
                            mVariant ="error"
                        Case "btn-soft","soft"
                            mStyle ="soft"
                        Case "btn-outline","outline"
                            mStyle ="outline"
                        Case "btn-dash","dash"
                            mStyle ="dash"
                        Case "btn-ghost","ghost"
                            mStyle ="ghost"
                        Case "btn-link","link"
                            mStyle ="link"
                        Case "btn-xs","xs"
                            mSize ="xs"
                        Case "btn-sm","sm"
                            mSize ="sm"
                        Case "btn-md","md"
                            mSize ="md"
                        Case "btn-lg","lg"
                            mSize ="lg"
                        Case "btn-xl","xl"
                            mSize ="xl"
                        Case "btn-wide","wide"
                            mWide = True
                        Case "btn-block","block"
                            mBlock = True
                        Case "btn-square","square"
                            mSquare = True
                            mCircle = False
                        Case "btn-circle","circle"
                            mCircle = True
                            mSquare = False
                            mRounded ="rounded-full"
                        Case "btn-active","active"
                            mActive = True
                        Case "btn-disabled","disabled"
                            mDisabled = True
                        Case "loading","loading-spinner"
                            mLoading = True
                        Case "shadow-none","shadow-xs","shadow-sm","shadow-md","shadow-lg","shadow-xl","shadow-2xl","shadow"
                            mShadow = B4XDaisyVariants.NormalizeShadow(token)
                        Case Else
                            If token.StartsWith("rounded") Then
                                mRounded = token
                            Else If token.StartsWith("shadow-") Then
                                mShadow = B4XDaisyVariants.NormalizeShadow(token)
                            Else If token.StartsWith("p-") Or token.StartsWith("px-") Or token.StartsWith("py-") Or token.StartsWith("pt-") Or token.StartsWith("pr-") Or token.StartsWith("pb-") Or token.StartsWith("pl-") Then
                                If mPadding.Length = 0 Then
                                    mPadding = token
                                Else
                                    mPadding = mPadding &" " & token
                                End If
                            Else If token.StartsWith("m-") Or token.StartsWith("mx-") Or token.StartsWith("my-") Or token.StartsWith("mt-") Or token.StartsWith("mr-") Or token.StartsWith("mb-") Or token.StartsWith("ml-") Then
                                If mMargin.Length = 0 Then
                                    mMargin = token
                                Else
                                    mMargin = mMargin &" " & token
                                End If
                            Else If token.StartsWith("bg-") Then
                                mBackgroundColor = B4XDaisyVariants.ResolveBackgroundColorVariant(token, mBackgroundColor)
                            Else If token.StartsWith("text-") Then
                                mTextColor = B4XDaisyVariants.ResolveTextColorVariant(token, mTextColor)
                            Else If token.StartsWith("border-") Then
                                mBorderColor = B4XDaisyVariants.ResolveBorderColorVariant(token, mBorderColor)
                            End If
                    End Select
                Next

                mVariant = NormalizeVariant(mVariant)
                mStyle = B4XDaisyVariants.NormalizeStyle(mStyle)
                mSize = B4XDaisyVariants.NormalizeSize(mSize)
            End Sub

            Private Sub NormalizeVariant(Value As String) As String
                If Value = Null Then Return "default"
                Dim v As String = Value.ToLowerCase.Trim
                v = v.Replace("btn-","")
                Select Case v
                    Case "default","none","neutral","primary","secondary","accent","info","success","warning","error"
                        Return v
                    Case Else
                        Return "default"
                End Select
            End Sub

            Private Sub ResolveFontSizeDip(SizeToken As String) As Float
                Select Case B4XDaisyVariants.NormalizeSize(SizeToken)
                    Case "xs"
                        Return 11
                    Case "sm"
                        Return 12
                    Case "md"
                        Return 14
                    Case "lg"
                        Return 18
                    Case "xl"
                        Return 22
                    Case Else
                        Return 14
                End Select
            End Sub

            Private Sub ResolveHorizontalPaddingDip(SizeToken As String) As Int
                Select Case B4XDaisyVariants.NormalizeSize(SizeToken)
                    Case "xs"
                        Return 8dip
                    Case "sm"
                        Return 12dip
                    Case "md"
                        Return 16dip
                    Case "lg"
                        Return 20dip
                    Case "xl"
                        Return 24dip
                    Case Else
                        Return 16dip
                End Select
            End Sub

            Private Sub ResolveButtonHeightDip(SizeToken As String) As Int
                If miButtonExtentDip > 0 Then Return miButtonExtentDip
                Select Case B4XDaisyVariants.NormalizeSize(SizeToken)
                    Case "xs"
                        Return 24dip
                    Case "sm"
                        Return 32dip
                    Case "md"
                        Return 40dip
                    Case "lg"
                        Return 48dip
                    Case "xl"
                        Return 56dip
                    Case Else
                        Return 40dip
                End Select
            End Sub

            Private Sub ResolveBorderDip As Int
                Return Max(0dip, B4XDaisyVariants.GetBorderDip(1dip))
            End Sub

            Private Sub ResolveRadiusDip(RoundedToken As String) As Int
                Dim s As String = IIf(RoundedToken = Null,"theme", RoundedToken.ToLowerCase.Trim)
                Select Case s
                    Case "theme",""
                        Return Max(0dip, B4XDaisyVariants.GetRadiusFieldDip(8dip))
                    Case Else
                        Return Max(0dip, B4XDaisyVariants.TailwindBorderRadiusToDip(s, B4XDaisyVariants.GetRadiusFieldDip(8dip)))
                End Select
            End Sub

            Private Sub ResolvePadding As Map
                Dim px As Int = ResolveHorizontalPaddingDip(mSize)
                Dim py As Int = 0dip
                
                ' Reduce horizontal padding for square/circle buttons to ensure text fits
                If mSquare Or mCircle Then
                    Select Case B4XDaisyVariants.NormalizeSize(mSize)
                        Case "xs"
                            px = 4dip
                        Case "sm"
                            px = 6dip
                        Case "md"
                            px = 8dip
                        Case "lg"
                            px = 10dip
                        Case "xl"
                            px = 12dip
                        Case Else
                            px = 8dip
                    End Select
                End If
                
                Dim left As Int = px
                Dim top As Int = py
                Dim right As Int = px
                Dim bottom As Int = py

                If mPadding.Trim.Length > 0 Then
                    Dim tokens() As String = Regex.Split("\s+", mPadding.Trim)
                    For Each tok As String In tokens
                        Dim t As String = tok.ToLowerCase.Trim
                        If t.Length = 0 Then Continue
                        If t.StartsWith("p-") Then
                            Dim v As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(2), 0)
                            left = v: right = v: top = v: bottom = v
                        Else If t.StartsWith("px-") Then
                            Dim v2 As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), left)
                            left = v2: right = v2
                        Else If t.StartsWith("py-") Then
                            Dim v3 As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), top)
                            top = v3: bottom = v3
                        Else If t.StartsWith("pt-") Then
                            top = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), top)
                        Else If t.StartsWith("pr-") Then
                            right = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), right)
                        Else If t.StartsWith("pb-") Then
                            bottom = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), bottom)
                        Else If t.StartsWith("pl-") Then
                            left = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), left)
                        End If
                    Next
                End If

                Return CreateMap("l": Max(0dip, left),"t": Max(0dip, top),"r": Max(0dip, right),"b": Max(0dip, bottom))
            End Sub

            Private Sub ResolveMargin As Map
                Dim left As Int = 0dip
                Dim top As Int = 0dip
                Dim right As Int = 0dip
                Dim bottom As Int = 0dip

                If mMargin.Trim.Length > 0 Then
                    Dim tokens() As String = Regex.Split("\s+", mMargin.Trim)
                    For Each tok As String In tokens
                        Dim t As String = tok.ToLowerCase.Trim
                        If t.Length = 0 Then Continue
                        If t.StartsWith("m-") Then
                            Dim v As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(2), 0)
                            left = v: right = v: top = v: bottom = v
                        Else If t.StartsWith("mx-") Then
                            Dim v2 As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), left)
                            left = v2: right = v2
                        Else If t.StartsWith("my-") Then
                            Dim v3 As Int = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), top)
                            top = v3: bottom = v3
                        Else If t.StartsWith("mt-") Then
                            top = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), top)
                        Else If t.StartsWith("mr-") Then
                            right = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), right)
                        Else If t.StartsWith("mb-") Then
                            bottom = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), bottom)
                        Else If t.StartsWith("ml-") Then
                            left = B4XDaisyVariants.TailwindSpacingToDip(t.SubString(3), left)
                        End If
                    Next
                End If

                Return CreateMap("l": Max(0dip, left),"t": Max(0dip, top),"r": Max(0dip, right),"b": Max(0dip, bottom))
            End Sub

            Public Sub GetEstimateContentWidth As Int
                Return EstimateContentWidthDip
            End Sub

            Private Sub EstimateContentWidthDip As Int
                Dim f As B4XFont = xui.CreateDefaultBoldFont(ResolveFontSizeDip(mSize))
                Dim w As Int = MeasureTextWidthDip(mText, f)
                Dim px As Int = ResolveHorizontalPaddingDip(mSize)
                Dim total As Int = w + (px * 2)
                If mLoading Or mIconName.Trim.Length > 0 Then total = total + ResolveIndicatorSizeDip + 6dip
                If mSquare Or mCircle Then Return ResolveButtonHeightDip(mSize)
                If mWide Then total = Max(total, 256dip)
                Return Max(ResolveButtonHeightDip(mSize), total)
            End Sub

            Private Sub ResolveIndicatorSizeDip As Int
                If miIconSizeDip > 0 Then Return miIconSizeDip
                Select Case B4XDaisyVariants.NormalizeSize(mSize)
                    Case "xs"
                        Return 12dip
                    Case "sm"
                        Return 14dip
                    Case "md"
                        Return 16dip
                    Case "lg"
                        Return 18dip
                    Case "xl"
                        Return 20dip
                    Case Else
                        Return 16dip
                End Select
            End Sub

            Private Sub ResolveWideWidthDip(PreferredWidth As Int, Left As Int, Parent As B4XView, Margin As Map, MinWidth As Int) As Int
                'Daisy btn-wide maps to max-w-64 (16rem). Use fixed 16rem width.
                Return Max(MinWidth, 256dip)
            End Sub

            Private Sub ResolveConfiguredWidthDip(DefaultWidth As Int) As Int
                If IsAutoLayoutSpec(mWidth) Then Return Max(1dip, DefaultWidth)
                Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mWidth, DefaultWidth))
            End Sub

            Private Sub ResolveConfiguredHeightDip(DefaultHeight As Int) As Int
                If IsAutoLayoutSpec(mHeight) Then Return Max(1dip, DefaultHeight)
                Return Max(1dip, B4XDaisyVariants.TailwindSizeToDip(mHeight, DefaultHeight))
            End Sub

            Private Sub IsAutoLayoutSpec(Value As String) As Boolean
                Dim s As String = IIf(Value = Null,"", Value.ToLowerCase.Trim)
                Return s ="auto"
            End Sub

            Private Sub NormalizeLayoutSpec(Value As String, DefaultValue As String) As String
                Dim s As String = IIf(Value = Null,"", Value.Trim)
                If s.Length = 0 Then Return DefaultValue
                Return s
            End Sub

            Private Sub IsTransparentColor(Value As Int) As Boolean
                Return Bit.And(Bit.ShiftRight(Value, 24), 0xFF) = 0
            End Sub

            Private Sub MeasureTextWidthDip(Text As String, Font As B4XFont) As Int
                Dim t As String = Text
                If t = Null Or t.Trim.Length = 0 Then Return 16dip
                If pnlMeasure.IsInitialized = False Then
                    Return Max(16dip, Ceil(t.Trim.Length * Font.Size * 0.62) + 2dip)
                End If
                Dim r As B4XRect = cvsMeasure.MeasureText(t.Trim, Font)
                Return Ceil(r.Width) + 8dip
            End Sub

            Private Sub FlattenTextSingleLine(Value As String) As String
                Dim s As String = IIf(Value = Null,"", Value)
                s = s.Replace(CRLF," ")
                s = s.Replace(Chr(10)," ")
                s = s.Replace(Chr(13)," ")
                Return s
            End Sub

            Private Sub EnsureContentSingleLine
                #If B4A
                    Try
                        Dim jo As JavaObject = lblContent
                        jo.RunMethod("setSingleLine", Array(True))
                        jo.RunMethod("setMaxLines", Array(1))
                        jo.RunMethod("setHorizontallyScrolling", Array(True))
                        jo.RunMethod("setFocusable", Array(False))
                        jo.RunMethod("setFocusableInTouchMode", Array(False))
                        Dim truncateAt As JavaObject
                        truncateAt.InitializeStatic("android.text.TextUtils$TruncateAt")
                        jo.RunMethod("setEllipsize", Array(truncateAt.GetField("END")))
                    Catch
                    End Try  'ignore
                #End If
            End Sub

            Private Sub EnsureShadowVisibility
                'use shared helper in Variants
                B4XDaisyVariants.DisableClippingChain(mBase, 4)
            End Sub

            Public Sub setText(Value As String)
                mText = IIf(Value = Null,"", Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getText As String
                Return mText
            End Sub

            Public Sub setClass(Value As String)
                mClassTokens = IIf(Value = Null,"", Value)
                ParseClassTokens(mClassTokens)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getClass As String
                Return mClassTokens
            End Sub

            Public Sub setVariant(Value As String)
                mVariant = NormalizeVariant(Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getVariant As String
                Return mVariant
            End Sub

            Public Sub setStyle(Value As String)
                mStyle = B4XDaisyVariants.NormalizeStyle(Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getStyle As String
                Return mStyle
            End Sub

            Public Sub setSize(Value As String)
                mSize = B4XDaisyVariants.NormalizeSize(Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getSize As String
                Return mSize
            End Sub

            ' Explicit button extent (circle/square/content height) in dip.
            ' 0 = auto from the Size token. Lets containers like the FAB force an
            ' exact button size that the token scale cannot reach.
            Public Sub setButtonSizeDip(Value As Int)
                miButtonExtentDip = Max(0, Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getButtonSizeDip As Int
                Return miButtonExtentDip
            End Sub

            ' Explicit icon size in dip. 0 = auto from the Size token.
            Public Sub setIconSize(Value As Int)
                miIconSizeDip = Max(0, Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getIconSize As Int
                Return miIconSizeDip
            End Sub

            Public Sub setRounded(Value As String)
                mRounded = IIf(Value = Null,"theme", Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getRounded As String
                Return mRounded
            End Sub

            Public Sub setPadding(Value As String)
                mPadding = IIf(Value = Null,"", Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getPadding As String
                Return mPadding
            End Sub

            Public Sub setMargin(Value As String)
                mMargin = IIf(Value = Null,"", Value)
                If mBase.IsInitialized = False Then Return
                Base_Resize(mBase.Width, mBase.Height)
            End Sub

            Public Sub getMargin As String
                Return mMargin
            End Sub

            ' Shadow / elevation level applied to the button (idle state).
            ' Accepted: "none","xs","sm","md","lg","xl","2xl" (also "shadow-*").
            ' Default is "xs". Set to "none" to remove the elevation.
            Public Sub setShadow(Value As String)
                mShadow = B4XDaisyVariants.NormalizeShadow(Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getShadow As String
                Return mShadow
            End Sub

            Public Sub setWidth(Value As String)
                mWidth = NormalizeLayoutSpec(Value,"40px")
                If mBase.IsInitialized = False Then Return
                Base_Resize(mBase.Width, mBase.Height)
            End Sub

            Public Sub getWidth As String
                Return mWidth
            End Sub

            Public Sub setHeight(Value As String)
                mHeight = NormalizeLayoutSpec(Value,"auto")
                If mBase.IsInitialized = False Then Return
                Base_Resize(mBase.Width, mBase.Height)
            End Sub

            Public Sub getHeight As String
                Return mHeight
            End Sub

            Public Sub setIconName(Value As String)
                mIconName = IIf(Value = Null,"", Value)
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getIconName As String
                Return mIconName
            End Sub

            Public Sub setIconColor(Value As Int)
                mIconColor = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getIconColor As Int
                Return mIconColor
            End Sub

            Public Sub setWide(Value As Boolean)
                mWide = Value
                If mBase.IsInitialized = False Then Return
                Base_Resize(mBase.Width, mBase.Height)
            End Sub

            Public Sub getWide As Boolean
                Return mWide
            End Sub

            Public Sub setBlock(Value As Boolean)
                mBlock = Value
                If mBase.IsInitialized = False Then Return
                Base_Resize(mBase.Width, mBase.Height)
            End Sub

            Public Sub getBlock As Boolean
                Return mBlock
            End Sub

            Public Sub setSquare(Value As Boolean)
                mSquare = Value
                If Value Then mCircle = False
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getSquare As Boolean
                Return mSquare
            End Sub

            Public Sub setCircle(Value As Boolean)
                mCircle = Value
                If Value Then
                    mSquare = False
                    mRounded ="rounded-full"
                End If
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getCircle As Boolean
                Return mCircle
            End Sub

            Public Sub setActive(Value As Boolean)
                mActive = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getActive As Boolean
                Return mActive
            End Sub

            Public Sub setDisabled(Value As Boolean)
                mDisabled = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getDisabled As Boolean
                Return mDisabled
            End Sub

            Public Sub setEnabled(Value As Boolean)
                setDisabled(Not(Value))
            End Sub

            Public Sub getEnabled As Boolean
                Return Not(getDisabled)
            End Sub

            Public Sub setLoading(Value As Boolean)
                mLoading = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getLoading As Boolean
                Return mLoading
            End Sub

            Public Sub setBackgroundColor(Value As Int)
                mBackgroundColor = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getBackgroundColor As Int
                Return mBackgroundColor
            End Sub

            Public Sub setTextColor(Value As Int)
                mTextColor = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getTextColor As Int
                Return mTextColor
            End Sub

            Public Sub setBorderColor(Value As Int)
                mBorderColor = Value
                If mBase.IsInitialized = False Then Return
                Refresh
            End Sub

            Public Sub getBorderColor As Int
                Return mBorderColor
            End Sub

            Public Sub setVisible(Value As Boolean)
                mVisible = Value
                If mBase.IsInitialized = False Then Return
                mBase.Visible = mVisible
            End Sub

            Public Sub getVisible As Boolean
                Return mVisible
            End Sub

            Public Sub setTextAlignment(Value As String)
                mTextAlignment = IIf(Value = Null, "CENTER", Value.ToUpperCase)
                If mBase.IsInitialized = False Then Return
                ApplyContentGravity
                Refresh
            End Sub

            Public Sub getTextAlignment As String
                Return mTextAlignment
            End Sub

            ' Applies the content label gravity so text is vertically centered on Android
            ' for any horizontal alignment (LEFT/CENTER/RIGHT). Always ORs in
            ' Gravity.CENTER_VERTICAL (0x00000010) on B4A because B4XView.SetTextAlignment
            ' does not reliably produce CENTER_VERTICAL on a native Android TextView.
            Private Sub ApplyContentGravity
                If lblContent.IsInitialized = False Then Return
                Dim align As String = mTextAlignment.ToUpperCase
                #If B4A
                    Dim jo As JavaObject = lblContent
                    Dim horiz As Int
                    Select Case align
                        Case "LEFT"
                            horiz = 0x00000003   ' Gravity.LEFT
                        Case "RIGHT"
                            horiz = 0x00000005   ' Gravity.RIGHT
                        Case Else
                            horiz = 0x00000001   ' Gravity.CENTER_HORIZONTAL
                    End Select
                    jo.RunMethod("setGravity", Array(Bit.Or(horiz, 0x00000010))) ' | Gravity.CENTER_VERTICAL
                #Else
                    lblContent.SetTextAlignment(align, "CENTER")
                #End If
            End Sub

            Public Sub setTag(Value As Object)
                mTag = Value
            End Sub

            Public Sub getTag As Object
                Return mTag
            End Sub

            Public Sub getView As B4XView
                Return mBase
            End Sub

            Public Sub GetComputedHeight As Int
                If mBase.IsInitialized = False Then Return 0
                Return mBase.Height
            End Sub

            Public Sub RemoveViewFromParent
                If mBase.IsInitialized Then mBase.RemoveViewFromParent
            End Sub
            ''' <summary>
            ''' Requests or clears keyboard/touch focus on the button.
            ''' B4XDaisyButton is a Panel-based custom view which is not focusable by default,
            ''' so the base view is made focusable (and focusable-in-touch-mode) before focus
            ''' is requested. Pass False to clear focus.
            ''' </summary>
            Public Sub setFocus(Value As Boolean)
                If mBase.IsInitialized = False Then Return
                #If B4A
                    Dim jo As JavaObject = mBase
                    If Value Then
                        jo.RunMethod("setFocusable", Array(True))
                        jo.RunMethod("setFocusableInTouchMode", Array(True))
                        jo.RunMethod("requestFocus", Null)
                    Else
                        jo.RunMethod("clearFocus", Null)
                    End If
                #End If
            End Sub

            ''' <summary>
            ''' Returns True if the button currently has focus.
            ''' </summary>
            Public Sub getIsFocused As Boolean
                If mBase.IsInitialized = False Then Return False
                #If B4A
                    Dim jo As JavaObject = mBase
                    Return jo.RunMethod("hasFocus", Null)
                #Else
                    Return False
                #End If
            End Sub

            ''' <summary>
            ''' Convenience method that requests focus on the button (equivalent to setFocus(True)).
            ''' </summary>
            Public Sub RequestFocus
                setFocus(True)
            End Sub

            Private Sub mBase_Click
                RaiseClick
            End Sub

            Private Sub part_Click
                RaiseClick
            End Sub

            Private Sub RaiseClick
                If mDisabled Then Return
                Dim payload As Object = mTag
        		Dim args As List
                args.Initialize
                If payload = Null Then
				Else
					 args.Add(payload)
				End If
                B4XDaisyVariants.RaiseEventIfSubExists(mCallBack, mEventName & "_Click", args)
            End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyBreadcrumbs.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9

#Region Events
#Event: ItemClick (ItemId As String)
#End Region

#Region Designer Properties
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: Enables breadcrumb interaction.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Shows or hides the component.
#DesignerProperty: Key: TextSize, DisplayName: Text Size, FieldType: String, DefaultValue: text-sm, List: text-sm|text-base|text-lg|text-xl, Description: Tailwind text size token used by all crumbs.
#DesignerProperty: Key: CurrentIndex, DisplayName: Current Index, FieldType: Int, DefaultValue: -1, Description: Active breadcrumb index. -1 uses the last item.
#DesignerProperty: Key: RTL, DisplayName: Right-To-Left, FieldType: Boolean, DefaultValue: False, Description: Flips chevron direction for RTL languages.
#End Region

#Region Variables
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object

    Private mHScroll As HorizontalScrollView
    Private xvScroll As B4XView
    Private mItemsPanel As B4XView

    Private mbEnabled As Boolean = True
    Private mbVisible As Boolean = True
    Private msTextSize As String = "text-sm"
    Private miCurrentIndex As Int = -1
    Private mbRTL As Boolean = False

    Private mItems As List
    Private miContentWidth As Int = 0
    Private miContentHeight As Int = 0

    Private miPaddingYDip As Int = 8dip
    Private miItemGapDip As Int = 8dip
    Private miIconSizeDip As Int = 16dip
    Private miSeparatorSizeDip As Int = 6dip
    Private miSeparatorMarginStartDip As Int = 8dip
    Private miSeparatorMarginEndDip As Int = 12dip
    Private miTextVerticalBufferDip As Int = 6dip
    Private mfTextScaleBase As Float = 14
    Private mfMinScaleFactor As Float = 0.75
    Private miMinSeparatorSizeDip As Int = 4dip
End Sub
#End Region

#Region Initialization
''' <summary>
''' Initializes the component.
''' </summary>
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
    mItems.Initialize
End Sub

''' <summary>
''' Creates the component programmatically.
''' </summary>
Public Sub CreateView(Width As Int, Height As Int) As B4XView
    Dim p As Panel
    p.Initialize("mBase")
    Dim b As B4XView = p
    b.Color = xui.Color_Transparent
    b.SetLayoutAnimated(0, 0, 0, Width, Height)
    DesignerCreateView(b, Null, BuildRuntimeProps)
    Return mBase
End Sub

''' <summary>
''' Designer entry point.
''' </summary>
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    mTag = mBase.Tag
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent

    BuildHostStructure
    ApplyDesignerProps(Props)
    Refresh
End Sub
#End Region

#Region Public API
''' <summary>
''' Returns the public base view.
''' </summary>
Public Sub getView As B4XView
    Return mBase
End Sub

''' <summary>
''' Adds the component to a parent B4XView.
''' </summary>
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    If mBase.IsInitialized = False Then
        Dim p As Panel
        p.Initialize("mBase")
        Dim b As B4XView = p
        b.Color = xui.Color_Transparent
        b.SetLayoutAnimated(0, 0, 0, Max(1dip, Width), Max(1dip, Height))
        DesignerCreateView(b, Null, BuildRuntimeProps)
    End If
    Parent.AddView(mBase, Left, Top, Max(1dip, Width), Max(1dip, Height))
    Base_Resize(mBase.Width, mBase.Height)
    Return mBase
End Sub

''' <summary>
''' Refreshes theme-aware colors and redraws the component.
''' </summary>
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Rebuilds the breadcrumb layout.
''' </summary>
Public Sub Refresh
    If mBase.IsInitialized = False Then Return
    If mItemsPanel.IsInitialized = False Then Return

    mBase.Visible = mbVisible
    mBase.Enabled = mbEnabled
    ' Disabled: dim the breadcrumb trail so it reads as inactive.
    mBase.Alpha = IIf(mbEnabled, 1.0, 0.3)
    xvScroll.Visible = mbVisible
    xvScroll.Enabled = mbEnabled
    mBase.Color = xui.Color_Transparent
    B4XDaisyVariants.DisableClippingRecursive(mBase)

    RenderItems(Max(1dip, mBase.Width), Max(1dip, mBase.Height))
End Sub

''' <summary>
''' Returns the currently rendered component height.
''' </summary>
Public Sub GetComputedHeight As Int
    If miContentHeight > 0 Then Return miContentHeight
    If mBase.IsInitialized = False Then Return 1dip
    Return mBase.Height
End Sub

''' <summary>
''' Replaces the current breadcrumb items.
''' </summary>
Public Sub SetItems(Items As List)
    mItems = NormalizeItemsList(Items)
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Returns a copy of the breadcrumb items list.
''' </summary>
Public Sub getItems As List
    Return NormalizeItemsList(mItems)
End Sub

''' <summary>
''' Removes all breadcrumb items.
''' </summary>
Public Sub Clear
    mItems.Initialize
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Adds a breadcrumb item.
''' </summary>
Public Sub AddItem(Id As String, Text As String, IconPath As String, Clickable As Boolean)
    If mItems.IsInitialized = False Then mItems.Initialize
    mItems.Add(CreateNormalizedItem(Id, Text, IconPath, Clickable, True))
    mItems = NormalizeItemsList(mItems)
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Sets the enabled state.
''' </summary>
Public Sub setEnabled(Value As Boolean)
    mbEnabled = Value
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Gets the enabled state.
''' </summary>
Public Sub getEnabled As Boolean
    Return mbEnabled
End Sub

''' <summary>
''' Sets the visible state.
''' </summary>
Public Sub setVisible(Value As Boolean)
    mbVisible = Value
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Gets the visible state.
''' </summary>
Public Sub getVisible As Boolean
    Return mbVisible
End Sub

''' <summary>
''' Sets the breadcrumb text size token.
''' </summary>
Public Sub setTextSize(Value As String)
    msTextSize = NormalizeTextSizeToken(Value)
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Gets the breadcrumb text size token.
''' </summary>
Public Sub getTextSize As String
    Return msTextSize
End Sub

''' <summary>
''' Sets the active breadcrumb index. -1 uses the last item.
''' </summary>
Public Sub setCurrentIndex(Value As Int)
    miCurrentIndex = Value
    If mBase.IsInitialized = False Then Return
    Refresh
End Sub

''' <summary>
''' Gets the active breadcrumb index.
''' </summary>
Public Sub getCurrentIndex As Int
    Return miCurrentIndex
End Sub

''' <summary>
''' Sets the component tag.
''' </summary>
Public Sub setTag(Value As Object)
    mTag = Value
End Sub

''' <summary>
''' Gets the component tag.
''' </summary>
Public Sub getTag As Object
    Return mTag
End Sub

''' <summary>
''' Removes the component from its parent.
''' </summary>
Public Sub RemoveViewFromParent
    If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mBase.IsInitialized = False Then Return
    If xvScroll.IsInitialized = False Then Return
    xvScroll.SetLayoutAnimated(0, 0, 0, Max(1dip, Width), Max(1dip, Height))
    RenderItems(Max(1dip, Width), Max(1dip, Height))
End Sub

Private Sub crumb_Click
    If mbEnabled = False Then Return
    Dim senderView As B4XView = Sender
    If senderView.Tag = Null Then Return
    Dim meta As Map = senderView.Tag
    If meta.GetDefault("clickable", False) = False Then Return

    Dim itemId As String = "" & meta.GetDefault("id", "")

    If xui.SubExists(mCallBack, mEventName & "_ItemClick", 1) Then
        CallSub2(mCallBack, mEventName & "_ItemClick", itemId)
    End If
End Sub
#End Region

#Region Rendering
' DaisyUI token support map for parity validation:
' block breadcrumbs cursor-pointer flex gap-2 h-1.5 h-4 hover:underline inline-flex items-center
' max-w-full max-w-xs me-3 min-h-min ms-2 opacity-40 outline-hidden overflow-x-auto py-2
' stroke-current text-sm w-1.5 w-4 whitespace-nowrap
Private Sub BuildHostStructure
    Dim hsv As HorizontalScrollView
    hsv.Initialize(0, "")
    mHScroll = hsv
    xvScroll = mHScroll
    Dim jo As JavaObject = mHScroll
    jo.RunMethod("setHorizontalScrollBarEnabled", Array(False))
    jo.RunMethod("setVerticalScrollBarEnabled", Array(False))
    mBase.AddView(xvScroll, 0, 0, Max(1dip, mBase.Width), Max(1dip, mBase.Height))
    mHScroll.Panel.Color = xui.Color_Transparent

    Dim p As Panel
    p.Initialize("")
    mItemsPanel = p
    mItemsPanel.Color = xui.Color_Transparent
    mHScroll.Panel.AddView(mItemsPanel, 0, 0, Max(1dip, mBase.Width), Max(1dip, mBase.Height))
End Sub

Private Sub ApplyDesignerProps(Props As Map)
    mbEnabled = B4XDaisyVariants.GetPropBool(Props, "Enabled", mbEnabled)
    mbVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mbVisible)
    msTextSize = NormalizeTextSizeToken(B4XDaisyVariants.GetPropString(Props, "TextSize", msTextSize))
    miCurrentIndex = B4XDaisyVariants.GetPropInt(Props, "CurrentIndex", miCurrentIndex)
    mbRTL = B4XDaisyVariants.GetPropBool(Props, "RTL", mbRTL)
End Sub

Private Sub BuildRuntimeProps As Map
    Return CreateMap( _
        "Enabled": mbEnabled, _
        "Visible": mbVisible, _
        "TextSize": msTextSize, _
        "CurrentIndex": miCurrentIndex)
End Sub

Private Sub RenderItems(AvailableWidth As Int, AvailableHeight As Int)
    If mItemsPanel.IsInitialized = False Then Return
    If mItems.IsInitialized = False Then Return
    mItemsPanel.RemoveAllViews

    Dim fontSize As Float = B4XDaisyVariants.ResolveTextSizeDip(msTextSize)
    Dim tm As Map = B4XDaisyVariants.TailwindTextMetrics(msTextSize, fontSize, fontSize * 1.4)
    fontSize = tm.GetDefault("font_size", fontSize)
    Dim iconSizeDip As Int = ScaleDimensionByTextSize(miIconSizeDip, fontSize, False)
    Dim separatorSizeDip As Int = ScaleDimensionByTextSize(miSeparatorSizeDip, fontSize, True)
    Dim itemGapDip As Int = ScaleSpacingByTextSize(miItemGapDip, fontSize)
    Dim separatorMarginStartDip As Int = ScaleSpacingByTextSize(miSeparatorMarginStartDip, fontSize)
    Dim separatorMarginEndDip As Int = ScaleSpacingByTextSize(miSeparatorMarginEndDip, fontSize)
    Dim lineHeightPx As Int = Ceil(tm.GetDefault("line_height_px", fontSize * 1.4))
    Dim labelHeight As Int = Max(lineHeightPx, MeasureSingleLineTextHeight(fontSize)) + 4dip
    Dim itemHeight As Int = Max(iconSizeDip, labelHeight)
    Dim baseContentColor As Int = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(31, 41, 55))
    Dim separatorColor As Int = ApplyAlpha(baseContentColor, 0.4)
    Dim currentIndexResolved As Int = ResolveCurrentIndex

    Dim currentLeft As Int = 0
    Dim contentHeight As Int = itemHeight + (miPaddingYDip * 2)

    For i = 0 To mItems.Size - 1
        Dim item As Map = mItems.Get(i)

        If i > 0 Then
            Dim separatorView As B4XView = CreateSeparatorView(separatorColor, separatorSizeDip, separatorMarginStartDip, separatorMarginEndDip)
            mItemsPanel.AddView(separatorView, currentLeft, Round((contentHeight - separatorView.Height) / 2), separatorView.Width, separatorView.Height)
            currentLeft = currentLeft + separatorView.Width
        End If

        Dim crumbView As B4XView = CreateCrumbView(item, i, currentIndexResolved, fontSize, itemHeight, labelHeight, baseContentColor, iconSizeDip, itemGapDip)
        Dim crumbTop As Int = Round((contentHeight - crumbView.Height) / 2)
        mItemsPanel.AddView(crumbView, currentLeft, crumbTop, crumbView.Width, crumbView.Height)
        currentLeft = currentLeft + crumbView.Width
    Next

    miContentWidth = Max(currentLeft, AvailableWidth)
    miContentHeight = Max(1dip, contentHeight)

    xvScroll.SetLayoutAnimated(0, 0, 0, AvailableWidth, Max(miContentHeight, AvailableHeight))
    mHScroll.Panel.Width = miContentWidth
    mHScroll.Panel.Height = Max(miContentHeight, AvailableHeight)
    mItemsPanel.SetLayoutAnimated(0, 0, 0, miContentWidth, contentHeight)
End Sub

Private Sub CreateCrumbView(Item As Map, Index As Int, CurrentIndexResolved As Int, FontSize As Float, ItemHeight As Int, LabelHeight As Int, TextColor As Int, IconSizeDip As Int, ItemGapDip As Int) As B4XView
    Dim p As Panel
    p.Initialize("crumb")
    Dim crumb As B4XView = p
    crumb.Color = xui.Color_Transparent

    Dim textValue As String = Item.GetDefault("Text", "")
    Dim iconPath As String = Item.GetDefault("IconPath", "")
    Dim clickable As Boolean = ResolveItemClickable(Item, Index, CurrentIndexResolved)
    crumb.Tag = CreateMap( _
        "id": Item.GetDefault("Id", ""), _
        "clickable": clickable)
    crumb.Enabled = clickable

    Dim currentLeft As Int = 0

    If iconPath.Length > 0 Then
        Dim iconComp As B4XDaisySvgIcon
        iconComp.Initialize(Me, "")
        Dim iconView As B4XView = iconComp.AddToParent(crumb, 0, 0, IconSizeDip, IconSizeDip)
        iconComp.SetClickable(False)
        iconComp.SvgAsset = iconPath
        iconComp.Color = TextColor
        iconView.SetLayoutAnimated(0, 0, Round((ItemHeight - IconSizeDip) / 2), IconSizeDip, IconSizeDip)
        currentLeft = currentLeft + IconSizeDip + ItemGapDip
    End If

    Dim l As Label
    l.Initialize("")
    Dim xlbl As B4XView = l
    xlbl.Color = xui.Color_Transparent
    xlbl.Text = textValue
    xlbl.TextColor = TextColor
    xlbl.TextSize = FontSize
    xlbl.SetTextAlignment("CENTER", "LEFT")
    Dim nativeLabel As Label = xlbl
    nativeLabel.SingleLine = True
    Dim joLbl As JavaObject = nativeLabel
    joLbl.RunMethod("setIncludeFontPadding", Array(False))
    Dim textTop As Int = Round((itemHeight - LabelHeight) / 2)
    crumb.AddView(xlbl, currentLeft, textTop, Max(1dip, MeasureTextWidth(textValue, FontSize)), Max(1dip, LabelHeight))

    Dim crumbWidth As Int = currentLeft + xlbl.Width
    crumb.SetLayoutAnimated(0, 0, 0, Max(1dip, crumbWidth), Max(1dip, ItemHeight))
    Return crumb
End Sub

Private Sub CreateSeparatorView(ColorValue As Int, SeparatorSizeDip As Int, SeparatorMarginStartDip As Int, SeparatorMarginEndDip As Int) As B4XView
    Dim totalWidth As Int = SeparatorMarginStartDip + SeparatorSizeDip + SeparatorMarginEndDip
    Dim totalHeight As Int = Max(SeparatorSizeDip, 8dip)

    Dim p As Panel
    p.Initialize("")
    Dim host As B4XView = p
    host.Color = xui.Color_Transparent
    host.SetLayoutAnimated(0, 0, 0, totalWidth, totalHeight)

    Dim pnlChevron As Panel
    pnlChevron.Initialize("")
    Dim chevron As B4XView = pnlChevron
    chevron.Color = xui.Color_Transparent
    host.AddView(chevron, SeparatorMarginStartDip, Round((totalHeight - SeparatorSizeDip) / 2), SeparatorSizeDip, SeparatorSizeDip)

    Dim cvs As B4XCanvas
    cvs.Initialize(chevron)
    cvs.ClearRect(cvs.TargetRect)
    Dim s As Int = SeparatorSizeDip
    If mbRTL Then
        ' RTL: chevron points left (<)
        cvs.DrawLine(s - 1dip, 0, 1dip, s / 2, ColorValue, 1dip)
        cvs.DrawLine(1dip, s / 2, s - 1dip, s, ColorValue, 1dip)
    Else
        ' LTR: chevron points right (>)
        cvs.DrawLine(1dip, 0, s - 1dip, s / 2, ColorValue, 1dip)
        cvs.DrawLine(s - 1dip, s / 2, 1dip, s, ColorValue, 1dip)
    End If
    cvs.Invalidate
    cvs.Release

    Return host
End Sub
#End Region

#Region Item Helpers
Private Sub NormalizeItemsList(Source As List) As List
    Dim nextItems As List
    nextItems.Initialize
    If Source.IsInitialized = False Then Return nextItems

    For Each rawItem As Object In Source
        If rawItem Is Map Then
            Dim item As Map = rawItem
            Dim idValue As String = ""
            If item.ContainsKey("Id") Then idValue = "" & item.Get("Id")

            Dim textValue As String = ""
            If item.ContainsKey("Text") Then textValue = "" & item.Get("Text")
            If textValue.Trim.Length = 0 Then Continue

            Dim iconPath As String = ""
            If item.ContainsKey("IconPath") Then iconPath = "" & item.Get("IconPath")

            Dim clickableValue As Boolean = False
            Dim hasClickable As Boolean = False
            If item.ContainsKey("Clickable") Then
                clickableValue = item.Get("Clickable")
                hasClickable = True
            End If

            nextItems.Add(CreateNormalizedItem(idValue, textValue, iconPath, clickableValue, hasClickable))
        End If
    Next

    Return EnsureUniqueItemIds(nextItems)
End Sub

Private Sub CreateNormalizedItem(IdValue As String, TextValue As String, IconPath As String, Clickable As Boolean, HasClickable As Boolean) As Map
    Dim normalized As Map
    normalized.Initialize
    normalized.Put("Id", NormalizeItemId(IdValue, TextValue))
    normalized.Put("Text", TextValue.Trim)
    normalized.Put("IconPath", ResolveIconPath(IconPath))
    normalized.Put("Clickable", Clickable)
    normalized.Put("HasClickable", HasClickable)
    Return normalized
End Sub

Private Sub EnsureUniqueItemIds(Source As List) As List
    Dim nextItems As List
    nextItems.Initialize
    Dim seenIds As Map
    seenIds.Initialize

    For i = 0 To Source.Size - 1
        Dim item As Map = Source.Get(i)
        Dim baseId As String = NormalizeItemId("" & item.GetDefault("Id", ""), "" & item.GetDefault("Text", ""))
        Dim resolvedId As String = baseId
        Dim suffix As Int = 2

        Do While seenIds.ContainsKey(resolvedId)
            resolvedId = baseId & "-" & suffix
            suffix = suffix + 1
        Loop

        seenIds.Put(resolvedId, True)
        nextItems.Add(CreateNormalizedItem( _
            resolvedId, _
            "" & item.GetDefault("Text", ""), _
            "" & item.GetDefault("IconPath", ""), _
            item.GetDefault("Clickable", False), _
            item.GetDefault("HasClickable", False)))
    Next

    Return nextItems
End Sub

Private Sub NormalizeItemId(IdValue As String, TextValue As String) As String
    Dim resolvedId As String = ""
    If IdValue <> Null Then resolvedId = IdValue.Trim
    If resolvedId.Length = 0 Then
        If TextValue <> Null Then resolvedId = TextValue.Trim
    End If
    If resolvedId.Length = 0 Then resolvedId = "breadcrumb-item"
    resolvedId = resolvedId.Replace(" ", "-")
    Return resolvedId.ToLowerCase
End Sub

Private Sub ResolveItemClickable(Item As Map, Index As Int, CurrentIndexResolved As Int) As Boolean
    If Item.GetDefault("HasClickable", False) Then Return Item.GetDefault("Clickable", False) And mbEnabled
    Return (Index < CurrentIndexResolved) And mbEnabled
End Sub

Private Sub ResolveCurrentIndex As Int
    If mItems.IsInitialized = False Or mItems.Size = 0 Then Return -1
    If miCurrentIndex < 0 Or miCurrentIndex >= mItems.Size Then Return mItems.Size - 1
    Return miCurrentIndex
End Sub

Private Sub ScaleDimensionByTextSize(BaseSizeDip As Int, FontSize As Float, IsSeparator As Boolean) As Int
    Dim scaleFactor As Float = Max(mfMinScaleFactor, FontSize / mfTextScaleBase)
    Dim scaledSize As Int = Max(1dip, Round(BaseSizeDip * scaleFactor))
    If IsSeparator Then Return Max(miMinSeparatorSizeDip, scaledSize)
    Return scaledSize
End Sub

Private Sub ScaleSpacingByTextSize(BaseSizeDip As Int, FontSize As Float) As Int
    Dim scaleFactor As Float = Max(mfMinScaleFactor, FontSize / mfTextScaleBase)
    Return Max(1dip, Round(BaseSizeDip * scaleFactor))
End Sub

Private Sub NormalizeTextSizeToken(Value As String) As String
    If Value = Null Then Return "text-sm"
    Dim token As String = Value.ToLowerCase.Trim
    If token.Length = 0 Then Return "text-sm"
    Select Case token
        Case "sm"
            Return "text-sm"
        Case "md", "base"
            Return "text-base"
        Case "lg"
            Return "text-lg"
        Case "xl"
            Return "text-xl"
    End Select
    If token.StartsWith("text-") Then Return token
    Return "text-sm"
End Sub

Private Sub ResolveIconPath(Value As String) As String
    If Value = Null Then Return ""
    Dim trimmed As String = Value.Trim
    If trimmed.Length = 0 Then Return ""
    Return B4XDaisyVariants.ResolveAssetImage(trimmed, "")
End Sub

Private Sub MeasureTextWidth(TextValue As String, FontSize As Float) As Int
    Return B4XDaisyVariants.MeasureTextWidthSafe(TextValue, FontSize, Typeface.DEFAULT, 4dip)
End Sub

Private Sub MeasureSingleLineTextHeight(FontSize As Float) As Int
    ' Use a probe with an ascender and a descender to size single-line labels safely.
    ' extraPad uses 0.5x font size to ensure descenders (g, y, p, j) are never clipped
    ' when setIncludeFontPadding(false) removes the default Android bottom cushion.
    Dim probe As String = "Ag"
    Dim extraPad As Int = Max(miTextVerticalBufferDip, Ceil(FontSize * 0.5))
    Try
        Return B4XDaisyVariants.MeasureTextHeightSafe(probe, FontSize, Null, 2000dip, extraPad)
    Catch
        ' Fall back to the same padding model when canvas measurement is unavailable.
    End Try			'ignore
    Return Max(1dip, Ceil(FontSize * 1.3) + extraPad)
End Sub

Private Sub ApplyAlpha(ColorValue As Int, Alpha01 As Float) As Int
    Dim alphaValue As Int = Max(0, Min(255, Round(255 * Alpha01)))
    Dim redValue As Int = Bit.And(Bit.UnsignedShiftRight(ColorValue, 16), 0xFF)
    Dim greenValue As Int = Bit.And(Bit.UnsignedShiftRight(ColorValue, 8), 0xFF)
    Dim blueValue As Int = Bit.And(ColorValue, 0xFF)
    Return Bit.Or(Bit.ShiftLeft(alphaValue, 24), Bit.Or(Bit.ShiftLeft(redValue, 16), Bit.Or(Bit.ShiftLeft(greenValue, 8), blueValue)))
End Sub
#End Region

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyBoxModel.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=StaticCode
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9
'Shared CSS-like box model helpers for Daisy components.

Sub Process_Globals
	Private DefaultSpacingScalePx As Map
	Private Const AUTO_MARGIN_SENTINEL As Float = -9999999
End Sub

Private Sub EnsureDefaultSpacingScale
	If DefaultSpacingScalePx.IsInitialized Then Return
	DefaultSpacingScalePx.Initialize
	DefaultSpacingScalePx.Put("0", 0)
	DefaultSpacingScalePx.Put("px", 1)
	DefaultSpacingScalePx.Put("0.5", 2)
	DefaultSpacingScalePx.Put("1", 4)
	DefaultSpacingScalePx.Put("1.5", 6)
	DefaultSpacingScalePx.Put("2", 8)
	DefaultSpacingScalePx.Put("2.5", 10)
	DefaultSpacingScalePx.Put("3", 12)
	DefaultSpacingScalePx.Put("3.5", 14)
	DefaultSpacingScalePx.Put("4", 16)
	DefaultSpacingScalePx.Put("5", 20)
	DefaultSpacingScalePx.Put("6", 24)
	DefaultSpacingScalePx.Put("7", 28)
	DefaultSpacingScalePx.Put("8", 32)
	DefaultSpacingScalePx.Put("9", 36)
	DefaultSpacingScalePx.Put("10", 40)
	DefaultSpacingScalePx.Put("11", 44)
	DefaultSpacingScalePx.Put("12", 48)
	DefaultSpacingScalePx.Put("14", 56)
	DefaultSpacingScalePx.Put("16", 64)
	DefaultSpacingScalePx.Put("20", 80)
	DefaultSpacingScalePx.Put("24", 96)
	DefaultSpacingScalePx.Put("28", 112)
	DefaultSpacingScalePx.Put("32", 128)
	DefaultSpacingScalePx.Put("36", 144)
	DefaultSpacingScalePx.Put("40", 160)
	DefaultSpacingScalePx.Put("44", 176)
	DefaultSpacingScalePx.Put("48", 192)
	DefaultSpacingScalePx.Put("52", 208)
	DefaultSpacingScalePx.Put("56", 224)
	DefaultSpacingScalePx.Put("60", 240)
	DefaultSpacingScalePx.Put("64", 256)
	DefaultSpacingScalePx.Put("72", 288)
	DefaultSpacingScalePx.Put("80", 320)
	DefaultSpacingScalePx.Put("96", 384)
End Sub

Public Sub GetDefaultSpacingScale As Map
	EnsureDefaultSpacingScale
	Dim out As Map
	out.Initialize
	For Each k As String In DefaultSpacingScalePx.Keys
		out.Put(k, DefaultSpacingScalePx.Get(k))
	Next
	Return out
End Sub

Public Sub TailwindSpacingToDip(Value As Object, DefaultDip As Float) As Float
	If Value = Null Then Return Max(0, DefaultDip)
	EnsureDefaultSpacingScale

	Dim raw As String = Value
	If raw = Null Then Return Max(0, DefaultDip)
	Dim s As String = raw.ToLowerCase.Trim
	If s.Length = 0 Then Return Max(0, DefaultDip)

	'Accept utility-style tokens and keep only the final spacing spec part.
	Dim p As Int = s.LastIndexOf("-")
	If p > = 0 And p < s.Length - 1 Then s = s.SubString(p + 1)
	If s.StartsWith("[") And s.EndsWith("]") And s.Length > 2 Then
		s = s.SubString2(1, s.Length - 1).Trim
	End If

	If DefaultSpacingScalePx.ContainsKey(s) Then
		Dim px As Float = DefaultSpacingScalePx.Get(s)
		Return px * 1dip
	End If
	Return Max(0, B4XDaisyVariants.TailwindSizeToDip(Value, DefaultDip))
End Sub

Public Sub CreateDefaultModel As Map
	Dim m As Map
	m.Initialize
	m.Put("margin_left", 0)
	m.Put("margin_top", 0)
	m.Put("margin_right", 0)
	m.Put("margin_bottom", 0)
	m.Put("padding_left", 0)
	m.Put("padding_top", 0)
	m.Put("padding_right", 0)
	m.Put("padding_bottom", 0)
	m.Put("border_width", 0)
	m.Put("radius", 0)
	m.Put("radius_tl", -1)
	m.Put("radius_tr", -1)
	m.Put("radius_br", -1)
	m.Put("radius_bl", -1)
	m.Put("min_width", 0)
	m.Put("min_height", 0)
	m.Put("max_width", -1)
	m.Put("max_height", -1)
	m.Put("box_sizing", "border-box")
	Return m
End Sub

Public Sub ResolveLength(Value As Object, ParentSize As Float, DefaultDip As Float) As Float
	Dim base As Float = IIf(ParentSize > 0, ParentSize, DefaultDip)
	Return Max(0, TailwindSpacingToDip(Value, base))
End Sub

Public Sub ApplyPaddingUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean
	If Model.IsInitialized = False Then Return False
	If Utility = Null Then Return False
	Dim u As String = Utility.ToLowerCase.Trim
	If u.Length = 0 Then Return False

	Dim key As String = ""
	Dim valueToken As String = ""
	Dim p As Int = u.IndexOf("-")
	If p < = 0 Or p > = u.Length - 1 Then Return False
	key = u.SubString2(0, p)
	valueToken = u.SubString(p + 1)
	Dim v As Float = TailwindSpacingToDip(valueToken, 0dip)

	Select Case key
		Case "p"
			Model.Put("padding_left", v)
			Model.Put("padding_top", v)
			Model.Put("padding_right", v)
			Model.Put("padding_bottom", v)
		Case "px"
			Model.Put("padding_left", v)
			Model.Put("padding_right", v)
		Case "py"
			Model.Put("padding_top", v)
			Model.Put("padding_bottom", v)
		Case "pt"
			Model.Put("padding_top", v)
		Case "pr"
			Model.Put("padding_right", v)
		Case "pb"
			Model.Put("padding_bottom", v)
		Case "pl"
			Model.Put("padding_left", v)
		Case "ps"
			If IsRtl Then
				Model.Put("padding_right", v)
			Else
				Model.Put("padding_left", v)
			End If
		Case "pe"
			If IsRtl Then
				Model.Put("padding_left", v)
			Else
				Model.Put("padding_right", v)
			End If
		Case Else
			Return False
	End Select
	Return True
End Sub

Public Sub ApplyPaddingUtilities(Model As Map, Utilities As String, IsRtl As Boolean)
	If Model.IsInitialized = False Then Return
	If Utilities = Null Then Return
	Dim raw As String = Utilities.Trim
	If raw.Length = 0 Then Return
	Dim parts() As String = Regex.Split("\s+", raw)
	For Each token As String In parts
		ApplyPaddingUtility(Model, token, IsRtl)
	Next
End Sub

Public Sub ApplyMarginUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean
	If Model.IsInitialized = False Then Return False
	If Utility = Null Then Return False
	Dim u As String = Utility.ToLowerCase.Trim
	If u.Length = 0 Then Return False
	Dim isNegative As Boolean = False
	If u.StartsWith("-") Then
		isNegative = True
		u = u.SubString(1).Trim
		If u.Length = 0 Then Return False
	End If

	Dim key As String = ""
	Dim valueToken As String = ""
	Dim p As Int = u.IndexOf("-")
	If p < = 0 Or p > = u.Length - 1 Then Return False
	key = u.SubString2(0, p)
	valueToken = u.SubString(p + 1)
	Dim isAuto As Boolean = (valueToken = "auto")
	If isAuto And isNegative Then Return False
	Dim v As Float = IIf(isAuto, AUTO_MARGIN_SENTINEL, TailwindSpacingToDip(valueToken, 0dip))
	If isNegative Then v = -v

	Select Case key
		Case "m"
			Model.Put("margin_left", v)
			Model.Put("margin_top", v)
			Model.Put("margin_right", v)
			Model.Put("margin_bottom", v)
		Case "mx"
			Model.Put("margin_left", v)
			Model.Put("margin_right", v)
		Case "my"
			Model.Put("margin_top", v)
			Model.Put("margin_bottom", v)
		Case "mt"
			Model.Put("margin_top", v)
		Case "mr"
			Model.Put("margin_right", v)
		Case "mb"
			Model.Put("margin_bottom", v)
		Case "ml"
			Model.Put("margin_left", v)
		Case "ms"
			If IsRtl Then
				Model.Put("margin_right", v)
			Else
				Model.Put("margin_left", v)
			End If
		Case "me"
			If IsRtl Then
				Model.Put("margin_left", v)
			Else
				Model.Put("margin_right", v)
			End If
		Case Else
			Return False
	End Select
	Return True
End Sub

Public Sub ApplyMarginUtilities(Model As Map, Utilities As String, IsRtl As Boolean)
	If Model.IsInitialized = False Then Return
	If Utilities = Null Then Return
	Dim raw As String = Utilities.Trim
	If raw.Length = 0 Then Return
	Dim parts() As String = Regex.Split("\s+", raw)
	For Each token As String In parts
		ApplyMarginUtility(Model, token, IsRtl)
	Next
End Sub

Public Sub ApplyRadiusUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean
	If Model.IsInitialized = False Then Return False
	If Utility = Null Then Return False
	Dim u As String = Utility.ToLowerCase.Trim
	If u.Length = 0 Then Return False

	Dim value As Float = ResolveRadiusUtilityValueDip(u)
	If value < 0 Then Return False

	Select Case u
		Case "rounded-box", "rounded-field", "rounded-selector"
			SetRadiusAll(Model, value)
		Case "rounded-t-box", "rounded-t-field", "rounded-t-selector"
			SetRadiusTop(Model, value)
		Case "rounded-b-box", "rounded-b-field", "rounded-b-selector"
			SetRadiusBottom(Model, value)
		Case "rounded-l-box", "rounded-l-field", "rounded-l-selector"
			SetRadiusLeft(Model, value)
		Case "rounded-r-box", "rounded-r-field", "rounded-r-selector"
			SetRadiusRight(Model, value)
		Case "rounded-tl-box", "rounded-tl-field", "rounded-tl-selector"
			Model.Put("radius_tl", value)
		Case "rounded-tr-box", "rounded-tr-field", "rounded-tr-selector"
			Model.Put("radius_tr", value)
		Case "rounded-br-box", "rounded-br-field", "rounded-br-selector"
			Model.Put("radius_br", value)
		Case "rounded-bl-box", "rounded-bl-field", "rounded-bl-selector"
			Model.Put("radius_bl", value)
		Case Else
			Return False
	End Select
	Model.Put("radius", ResolveEffectiveRadius(Model, value))
	Return True
End Sub

Public Sub ApplyRadiusUtilities(Model As Map, Utilities As String, IsRtl As Boolean)
	If Model.IsInitialized = False Then Return
	If Utilities = Null Then Return
	Dim raw As String = Utilities.Trim
	If raw.Length = 0 Then Return
	Dim parts() As String = Regex.Split("\s+", raw)
	For Each token As String In parts
		ApplyRadiusUtility(Model, token, IsRtl)
	Next
End Sub

Public Sub GetCornerRadius(Model As Map, Corner As String, Fallback As Float) As Float
	If Model.IsInitialized = False Then Return Max(0, Fallback)
	Dim key As String = "radius_" & Corner.ToLowerCase.Trim
	Dim v As Float = GetFloat(Model, key, -1)
	If v > = 0 Then Return v
	Return Max(0, GetFloat(Model, "radius", Fallback))
End Sub

Public Sub ResolveOuterRect(HostRect As B4XRect, Model As Map) As B4XRect
	Dim l As Float = HostRect.Left + GetMarginValue(Model, "margin_left")
	Dim t As Float = HostRect.Top + GetMarginValue(Model, "margin_top")
	Dim rr As Float = HostRect.Right - GetMarginValue(Model, "margin_right")
	Dim bb As Float = HostRect.Bottom - GetMarginValue(Model, "margin_bottom")
	Dim r As B4XRect
	r.Initialize(l, t, rr, bb)
	Return NormalizeRect(r)
End Sub

Public Sub ResolveBorderRect(OuterRect As B4XRect, Model As Map) As B4XRect
	Return NormalizeRect(OuterRect)
End Sub

Public Sub ResolvePaddingRect(BorderRect As B4XRect, Model As Map) As B4XRect
	Dim bw As Float = Max(0, GetFloat(Model, "border_width", 0))
	Return InsetRect(BorderRect, bw, bw, bw, bw)
End Sub

Public Sub ResolveContentRect(BorderRect As B4XRect, Model As Map) As B4XRect
	Dim r As B4XRect = ResolvePaddingRect(BorderRect, Model)
	Return InsetRect(r, Max(0, GetFloat(Model, "padding_left", 0)), Max(0, GetFloat(Model, "padding_top", 0)), Max(0, GetFloat(Model, "padding_right", 0)), Max(0, GetFloat(Model, "padding_bottom", 0)))
End Sub

Public Sub ExpandContentWidth(ContentWidth As Float, Model As Map) As Float
	Dim w As Float = Max(0, ContentWidth)
	w = w + Max(0, GetFloat(Model, "padding_left", 0)) + Max(0, GetFloat(Model, "padding_right", 0))
	w = w + Max(0, GetFloat(Model, "border_width", 0)) * 2
	w = w + GetMarginValue(Model, "margin_left") + GetMarginValue(Model, "margin_right")
	Dim minW As Float = GetFloat(Model, "min_width", 0)
	Dim maxW As Float = GetFloat(Model, "max_width", -1)
	If minW > 0 Then w = Max(w, minW)
	If maxW > 0 Then w = Min(w, maxW)
	Return w
End Sub

Public Sub ExpandContentHeight(ContentHeight As Float, Model As Map) As Float
	Dim h As Float = Max(0, ContentHeight)
	h = h + Max(0, GetFloat(Model, "padding_top", 0)) + Max(0, GetFloat(Model, "padding_bottom", 0))
	h = h + Max(0, GetFloat(Model, "border_width", 0)) * 2
	h = h + GetMarginValue(Model, "margin_top") + GetMarginValue(Model, "margin_bottom")
	Dim minH As Float = GetFloat(Model, "min_height", 0)
	Dim maxH As Float = GetFloat(Model, "max_height", -1)
	If minH > 0 Then h = Max(h, minH)
	If maxH > 0 Then h = Min(h, maxH)
	Return h
End Sub

Public Sub ToLocalRect(AbsoluteRect As B4XRect, OriginRect As B4XRect) As B4XRect
	Dim r As B4XRect
	r.Initialize(AbsoluteRect.Left - OriginRect.Left, AbsoluteRect.Top - OriginRect.Top, AbsoluteRect.Right - OriginRect.Left, AbsoluteRect.Bottom - OriginRect.Top)
	Return NormalizeRect(r)
End Sub

Private Sub InsetRect(SourceRect As B4XRect, LeftInset As Float, TopInset As Float, RightInset As Float, BottomInset As Float) As B4XRect
	Dim r As B4XRect
	Dim l As Float = SourceRect.Left + Max(0, LeftInset)
	Dim t As Float = SourceRect.Top + Max(0, TopInset)
	Dim rr As Float = SourceRect.Right - Max(0, RightInset)
	Dim bb As Float = SourceRect.Bottom - Max(0, BottomInset)
	If rr < l Then rr = l
	If bb < t Then bb = t
	r.Initialize(l, t, rr, bb)
	Return r
End Sub

Private Sub NormalizeRect(SourceRect As B4XRect) As B4XRect
	Dim r As B4XRect
	Dim l As Float = SourceRect.Left
	Dim t As Float = SourceRect.Top
	Dim rr As Float = Max(SourceRect.Right, l)
	Dim bb As Float = Max(SourceRect.Bottom, t)
	r.Initialize(l, t, rr, bb)
	Return r
End Sub

Private Sub GetFloat(Model As Map, Key As String, DefaultValue As Float) As Float
	If Model.IsInitialized = False Then Return DefaultValue
	If Model.ContainsKey(Key) = False Then Return DefaultValue
	Dim o As Object = Model.Get(Key)
	If o = Null Then Return DefaultValue
	Return o
End Sub

Private Sub GetMarginValue(Model As Map, Key As String) As Float
	Dim v As Float = GetFloat(Model, Key, 0)
	If v = AUTO_MARGIN_SENTINEL Then Return 0
	Return v
End Sub

Private Sub ResolveRadiusUtilityValueDip(Utility As String) As Float
	Select Case Utility
		Case "rounded-box", "rounded-t-box", "rounded-b-box", "rounded-l-box", "rounded-r-box", "rounded-tl-box", "rounded-tr-box", "rounded-br-box", "rounded-bl-box"
			Return B4XDaisyVariants.GetRadiusBoxDip(8dip)
		Case "rounded-field", "rounded-t-field", "rounded-b-field", "rounded-l-field", "rounded-r-field", "rounded-tl-field", "rounded-tr-field", "rounded-br-field", "rounded-bl-field"
			Return B4XDaisyVariants.GetRadiusFieldDip(6dip)
		Case "rounded-selector", "rounded-t-selector", "rounded-b-selector", "rounded-l-selector", "rounded-r-selector", "rounded-tl-selector", "rounded-tr-selector", "rounded-br-selector", "rounded-bl-selector"
			Return B4XDaisyVariants.GetRadiusSelectorDip(4dip)
		Case Else
			Return -1
	End Select
End Sub

Private Sub SetRadiusAll(Model As Map, Value As Float)
	Model.Put("radius", Value)
	Model.Put("radius_tl", Value)
	Model.Put("radius_tr", Value)
	Model.Put("radius_br", Value)
	Model.Put("radius_bl", Value)
End Sub

Private Sub SetRadiusTop(Model As Map, Value As Float)
	Model.Put("radius_tl", Value)
	Model.Put("radius_tr", Value)
End Sub

Private Sub SetRadiusBottom(Model As Map, Value As Float)
	Model.Put("radius_bl", Value)
	Model.Put("radius_br", Value)
End Sub

Private Sub SetRadiusLeft(Model As Map, Value As Float)
	Model.Put("radius_tl", Value)
	Model.Put("radius_bl", Value)
End Sub

Private Sub SetRadiusRight(Model As Map, Value As Float)
	Model.Put("radius_tr", Value)
	Model.Put("radius_br", Value)
End Sub

Private Sub ResolveEffectiveRadius(Model As Map, DefaultValue As Float) As Float
	Dim tl As Float = GetFloat(Model, "radius_tl", -1)
	Dim tr As Float = GetFloat(Model, "radius_tr", -1)
	Dim br As Float = GetFloat(Model, "radius_br", -1)
	Dim bl As Float = GetFloat(Model, "radius_bl", -1)
	If tl > = 0 And tr > = 0 And br > = 0 And bl > = 0 And tl = tr And tr = br And br = bl Then Return tl
	Return Max(0, GetFloat(Model, "radius", DefaultValue))
End Sub

---

## B4XDaisyBadgeGroupSelect.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Event: ItemChanged (Item As Map)
#Event: FocusChanged (HasFocus As Boolean)
#Event: Changed (SelectedIds As List)

#DesignerProperty: Key: Legend, DisplayName: Legend, FieldType: String, DefaultValue: Select options, Description: Fieldset legend text
#DesignerProperty: Key: LegendSize, DisplayName: Legend Size, FieldType: String, DefaultValue: text-sm, List: text-xs|text-sm|text-base|text-lg|text-xl, Description: Tailwind-like text size token for legend
#DesignerProperty: Key: LegendBold, DisplayName: Legend Bold, FieldType: Boolean, DefaultValue: False, Description: Render the fieldset legend caption in bold
#DesignerProperty: Key: LabelAbove, DisplayName: Label Above, FieldType: Boolean, DefaultValue: False, Description: If True, the legend text is displayed as a label above the border box
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Optional accent variant for border tint
#DesignerProperty: Key: BorderStyle, DisplayName: Border Style, FieldType: String, DefaultValue: outlined, List: outlined|ghost|inset, Description: Border visual style
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: Int, DefaultValue: 16, Description: Inner content padding in dip (p-4)
#DesignerProperty: Key: AutoHeight, DisplayName: Auto Height, FieldType: Boolean, DefaultValue: True, Description: Automatically grow to fit added content
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius mode
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: True, Description: Use box radius for container
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl, Description: Elevation shadow level
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Background color (0 = default bg-base-200)
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Legend text color (0 = use theme token)
#DesignerProperty: Key: BorderColor, DisplayName: Border Color, FieldType: Color, DefaultValue: 0x00000000, Description: Border color override (0 = default border-base-300)
#DesignerProperty: Key: BorderSize, DisplayName: Border Size, FieldType: Int, DefaultValue: 1, Description: Border width in dip
#DesignerProperty: Key: InputBorder, DisplayName: Input Border, FieldType: Boolean, DefaultValue: False, Description: When True, apply B4XDaisyInput border color and width to the fieldset
#DesignerProperty: Key: Required, DisplayName: Required, FieldType: Boolean, DefaultValue: False, Description: Whether at least one badge must be selected.
#DesignerProperty: Key: HintText, DisplayName: Hint Text, FieldType: String, DefaultValue:, Description: Helper text displayed below the group.
#DesignerProperty: Key: ErrorText, DisplayName: Error Text, FieldType: String, DefaultValue:, Description: Error text displayed below the group when validation fails.
#DesignerProperty: Key: BadgeSelectionMode, DisplayName: Badge Selection Mode, FieldType: String, DefaultValue: multi, List: single|multi, Description: Single allows one checked badge, multi allows many
#DesignerProperty: Key: BadgeSize, DisplayName: Badge Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Badge size token
#DesignerProperty: Key: BadgeHeight, DisplayName: Badge Height, FieldType: String, DefaultValue: 8, Description: Badge height token (tailwind/css size)
#DesignerProperty: Key: BadgeColor, DisplayName: Badge Color, FieldType: String, DefaultValue: neutral, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Default (unchecked) badge color variant
#DesignerProperty: Key: BadgeCheckedColor, DisplayName: Badge Checked Color, FieldType: Color, DefaultValue: 0x00000000, Description: Checked badge background color (0 uses success)
#DesignerProperty: Key: BadgeCheckedTextColor, DisplayName: Badge Checked Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Checked badge text color (0 uses success text/white fallback)
#DesignerProperty: Key: Gap, DisplayName: Gap, FieldType: Int, DefaultValue: 8, Description: Horizontal gap between badges in dip
#DesignerProperty: Key: RowGap, DisplayName: Row Gap, FieldType: Int, DefaultValue: 8, Description: Vertical gap between badge rows in dip

' ELI5: This is our toy box where we keep all important variables.
#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object

	Private msLegend As String = "Select options"
	Private msLegendSize As String = "text-sm"
	Private mbLegendBold As Boolean = False
	Private mbLabelAbove As Boolean = False
	Private msVariant As String = "none"
	Private msBorderStyle As String = "outlined"
	Private miPadding As Int = 16
	Private mbAutoHeight As Boolean = True
	Private msRounded As String = "theme"
	Private mbRoundedBox As Boolean = True
	Private msShadow As String = "none"
	Private mcBackgroundColor As Int = 0
	Private mcTextColor As Int = 0
	Private mcBorderColor As Int = 0
	Private miBorderSize As Int = 1
	Private mbInputBorder As Boolean = False

	Private msBadgeSelectionMode As String = "multi"
	Private msItemsSpec As String = ""

	Private msBadgeSize As String = "md"
	Private msBadgeHeight As String = "8"
	Private msBadgeColor As String = "neutral"
	Private msBadgeStyle As String = "solid"
	Private mcBadgeCheckedColor As Int = 0
	Private mcBadgeCheckedTextColor As Int = 0
	Private miGap As Int = 8
	Private miRowGap As Int = 8

	Private FieldsetComp As B4XDaisyFieldset
	Private FieldsetView As B4XView
	Private BadgeHost As B4XView

	Private BadgeDefs As List          ' List(Map("id":String, "text":String))
	Private BadgeOrder As List         ' List(String)
	Private BadgeById As Map           ' id -> B4XDaisyBadge
	Private BadgeViewById As Map       ' id -> B4XView
	Private BadgeTextById As Map       ' id -> text
	Private SelectedLookup As Map      ' id -> True

	Private mbRequired As Boolean = False
        Private mbValidationTouched As Boolean = False
        Private mbProgrammaticError As Boolean = False
	Private msHintText As String = ""
	Private msErrorText As String = ""
	Private pnlHintText As B4XView
	Private lblHintText As B4XView

	Private mInternalSelectionChange As Boolean = False
End Sub

' ELI5: This wakes up the component and remembers who to call back.
Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	BadgeDefs.Initialize
	BadgeOrder.Initialize
	BadgeById.Initialize
	BadgeViewById.Initialize
	BadgeTextById.Initialize
	SelectedLookup.Initialize
End Sub

' ELI5: This builds the visual parts when the screen designer places this control.
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	mBase.RemoveAllViews

	EnsureFieldset
	ApplyDesignerProps(Props)
	ApplyFieldsetStyle
	BuildDefinitionsFromSpec(msItemsSpec)
	RecreateBadgeViews
	Refresh
End Sub

' ELI5: This makes a new badge group view in code with a size you choose.
Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub

' ELI5: This places the badge group inside another view and sizes it.
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty

	If mBase.IsInitialized = False Then
		Dim p As Panel
		p.Initialize("")
		Dim b As B4XView = p
		b.Color = xui.Color_Transparent
		b.SetLayoutAnimated(0, 0, 0, Max(1dip, Width), Max(1dip, Height))
		Dim props As Map
		props.Initialize
		props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Max(1dip, Width)))
		props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Max(1dip, Height)))
		Dim dummy As Label
		DesignerCreateView(b, dummy, props)
	End If

	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	If mbAutoHeight Then h = Max(h, FieldsetView.Height)
	Parent.AddView(mBase, Left, Top, w, h)
	Base_Resize(w, h)
	Return mBase
End Sub

' ELI5: This is a helper that forwards to AddToParent with the same values.
Public Sub AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Return AddToParent(Parent, Left, Top, Width, Height)
End Sub

' ELI5: This gives you the main view so you can use it elsewhere.
Public Sub View As B4XView
	Dim empty As B4XView
	If mBase.IsInitialized = False Then Return empty
	Return mBase
End Sub

' ELI5: This tells us if all visual pieces are created and ready.
Public Sub IsReady As Boolean
	Return mBase.IsInitialized And FieldsetView.IsInitialized And BadgeHost.IsInitialized
End Sub

' ELI5: When size changes, this asks the control to redraw neatly.
Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This redraws styles and layout so everything stays in sync.
Public Sub Refresh
	If mBase.IsInitialized = False Then Return
	ApplyFieldsetStyle
	RebuildLayout
End Sub

' ELI5: This makes sure the inner fieldset and badge host exist.
Private Sub EnsureFieldset
	If FieldsetView.IsInitialized Then Return

	Dim fs As B4XDaisyFieldset
	fs.Initialize(Me, "badgegroupfieldset")
	FieldsetComp = fs
	FieldsetView = FieldsetComp.AddToParent(mBase, 0, 0, Max(1dip, mBase.Width), Max(1dip, mBase.Height))

	Dim pHost As Panel
	pHost.Initialize("")
	BadgeHost = pHost
	BadgeHost.Color = xui.Color_Transparent
	FieldsetComp.AddContentView(BadgeHost, 0, 0, 1dip, 1dip)

	' Hint/error text panel below the fieldset
	Dim pHT As Panel
	pHT.Initialize("")
	pnlHintText = pHT
	pnlHintText.Color = xui.Color_Transparent
	mBase.AddView(pnlHintText, 0, 0, 1dip, 1dip)
	Dim lHT As Label
	lHT.Initialize("")
	lblHintText = lHT
	lblHintText.Color = xui.Color_Transparent
	lblHintText.SetTextAlignment("TOP", "LEFT")
	lblHintText.As(Label).SingleLine = False
	pnlHintText.AddView(lblHintText, 0, 0, 1dip, 1dip)
End Sub

' ELI5: This reads designer settings and stores them in our variables.
Private Sub ApplyDesignerProps(Props As Map)
	msLegend = B4XDaisyVariants.GetPropString(Props, "Legend", msLegend)
	' Fieldset props should stay raw here; Fieldset does its own normalization/calculation.
	msLegendSize = B4XDaisyVariants.GetPropString(Props, "LegendSize", msLegendSize)
	mbLegendBold = B4XDaisyVariants.GetPropBool(Props, "LegendBold", mbLegendBold)
	mbLabelAbove = B4XDaisyVariants.GetPropBool(Props, "LabelAbove", mbLabelAbove)
	msVariant = B4XDaisyVariants.GetPropString(Props, "Variant", msVariant)
	msBorderStyle = B4XDaisyVariants.GetPropString(Props, "BorderStyle", msBorderStyle)
	miPadding = B4XDaisyVariants.GetPropInt(Props, "Padding", miPadding)
	mbAutoHeight = B4XDaisyVariants.GetPropBool(Props, "AutoHeight", mbAutoHeight)
	msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
	mbRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mbRoundedBox)
	msShadow = B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow)
	mcBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mcBackgroundColor)
	mcTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mcTextColor)
	mcBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mcBorderColor)
	miBorderSize = B4XDaisyVariants.GetPropInt(Props, "BorderSize", miBorderSize)
	mbInputBorder = B4XDaisyVariants.GetPropBool(Props, "InputBorder", mbInputBorder)

	msBadgeSelectionMode = B4XDaisyVariants.NormalizeSelectionMode(B4XDaisyVariants.GetPropString(Props, "BadgeSelectionMode", msBadgeSelectionMode))
	msItemsSpec = B4XDaisyVariants.GetPropString(Props, "Items", msItemsSpec)

	' Badge props must stay raw here; each Badge handles its own normalization/parsing.
	msBadgeSize = B4XDaisyVariants.GetPropString(Props, "BadgeSize", msBadgeSize)
	msBadgeHeight = B4XDaisyVariants.GetPropString(Props, "BadgeHeight", msBadgeHeight)
	msBadgeColor = B4XDaisyVariants.GetPropString(Props, "BadgeColor", msBadgeColor)
	mcBadgeCheckedColor = B4XDaisyVariants.GetPropColor(Props, "BadgeCheckedColor", mcBadgeCheckedColor)
	mcBadgeCheckedTextColor = B4XDaisyVariants.GetPropColor(Props, "BadgeCheckedTextColor", mcBadgeCheckedTextColor)
	miGap = Max(0, B4XDaisyVariants.GetPropInt(Props, "Gap", miGap))
	miRowGap = Max(0, B4XDaisyVariants.GetPropInt(Props, "RowGap", miRowGap))
	mbRequired = B4XDaisyVariants.GetPropBool(Props, "Required", mbRequired)
	msHintText = B4XDaisyVariants.GetPropString(Props, "HintText", msHintText)
	msErrorText = B4XDaisyVariants.GetPropString(Props, "ErrorText", msErrorText)
End Sub

' ELI5: This sends our stored fieldset style settings to the fieldset control.

Private Sub MeasureHintHeight(Width As Int) As Int
	Dim displayedHint As String = msHintText
	If msErrorText.Length > 0 Then displayedHint = msErrorText
	If displayedHint.Length = 0 Then Return 0
	Dim txtSize As Float = B4XDaisyVariants.ResolveTextSizeDip("text-xs")
	Return B4XDaisyVariants.MeasureTextHeightSafe(displayedHint, txtSize, Typeface.DEFAULT, Max(1dip, Width - 4dip), 4dip)
End Sub

Private Sub LayoutHintPanel(Width As Int, Top As Int, Gap As Int)
	If pnlHintText.IsInitialized = False Then Return
	Dim errH As Int = MeasureHintHeight(Width)
	If errH > 0 Then
		Dim displayedHint As String = msHintText
		Dim isError As Boolean = False
		If msErrorText.Length > 0 Then
			displayedHint = msErrorText
			isError = True
		End If
		pnlHintText.Visible = True
		pnlHintText.SetLayoutAnimated(0, 0, Top + Gap, Width, errH)
		lblHintText.SetLayoutAnimated(0, 0, 0, Width, errH)
		lblHintText.Text = displayedHint
		Dim txtSize As Float = B4XDaisyVariants.ResolveTextSizeDip("text-xs")
		lblHintText.TextSize = txtSize
		If isError Then
			lblHintText.TextColor = B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444)
		Else
			Dim muted As Int = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_DarkGray)
			lblHintText.TextColor = B4XDaisyVariants.SetAlpha(muted, 170)
		End If
		lblHintText.SetTextAlignment("TOP", "LEFT")
	Else
		pnlHintText.Visible = False
		pnlHintText.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
End Sub

Private Sub ApplyFieldsetStyle
	If FieldsetComp.IsInitialized = False Then Return
	FieldsetComp.BeginUpdate
	FieldsetComp.setLegend(msLegend)
	FieldsetComp.setTextColor(mcTextColor)
	FieldsetComp.setLegendSize(msLegendSize)
	FieldsetComp.setLegendBold(mbLegendBold)
	FieldsetComp.setLabelAbove(mbLabelAbove)
	FieldsetComp.setRequired(mbRequired)
	FieldsetComp.setVariant(msVariant)
	FieldsetComp.setAutoHeight(mbAutoHeight)
	FieldsetComp.setPadding(miPadding)
	FieldsetComp.setRounded(msRounded)
	FieldsetComp.setRoundedBox(mbRoundedBox)
	FieldsetComp.setBorderStyle(msBorderStyle)
	FieldsetComp.setBorderSize(miBorderSize)
	FieldsetComp.setInputBorder(mbInputBorder)
	FieldsetComp.setShadow(msShadow)
	FieldsetComp.setBackgroundColor(mcBackgroundColor)
	If msErrorText.Length > 0 Then
		FieldsetComp.setBorderColor(B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444))
	Else
		FieldsetComp.setBorderColor(mcBorderColor)
	End If
	FieldsetComp.EndUpdate
End Sub

' ELI5: This recalculates sizes and positions for fieldset and badge area.
Private Sub RebuildLayout
	If IsReady = False Then Return

	Dim w As Int = Max(1dip, mBase.Width)
	Dim initialH As Int = Max(1dip, mBase.Height)
	FieldsetView.SetLayoutAnimated(0, 0, 0, w, initialH)
	FieldsetComp.Refresh

	Dim contentPanel As B4XView = FieldsetComp.GetContentPanel
	Dim contentW As Int = Max(1dip, contentPanel.Width)

	Dim hostH As Int = LayoutBadges(contentW)
	BadgeHost.SetLayoutAnimated(0, 0, 0, contentW, hostH)
	FieldsetComp.Refresh

	Dim fieldsetH As Int = Max(1dip, FieldsetView.Height)
	Dim gap As Int = 4dip
	Dim errH As Int = MeasureHintHeight(w)
	Dim targetH As Int = fieldsetH
	If errH > 0 Then targetH = targetH + gap + errH
	FieldsetView.SetLayoutAnimated(0, 0, 0, w, fieldsetH)
	LayoutHintPanel(w, fieldsetH, gap)

	If mbAutoHeight Then
		If Abs(mBase.Height - targetH) > 1dip Then
			mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, targetH)
		End If
	End If
End Sub

' ELI5: This wraps badges into rows, like placing stickers left to right.
Private Sub LayoutBadges(ContentW As Int) As Int
	If BadgeHost.IsInitialized = False Then Return 1dip
	Dim x As Int = 0
	Dim y As Int = 0
	Dim rowH As Int = 0
	Dim gapDip As Int = miGap * 1dip
	Dim rowGapDip As Int = miRowGap * 1dip

	For Each id As String In BadgeOrder
		If BadgeViewById.ContainsKey(id) = False Then Continue
		Dim v As B4XView = BadgeViewById.Get(id)
		Dim b As B4XDaisyBadge = BadgeById.Get(id)
		Dim bw As Int = Max(1dip, v.Width)
		Dim bh As Int = Max(1dip, v.Height)

		If x > 0 And x + bw > ContentW Then
			x = 0
			y = y + rowH + rowGapDip
			rowH = 0
		End If

		v.SetLayoutAnimated(0, x, y, bw, bh)
		b.Base_Resize(bw, bh)
		x = x + bw + gapDip
		rowH = Max(rowH, bh)
	Next

	If BadgeOrder.Size = 0 Then Return 1dip
	Return Max(1dip, y + rowH)
End Sub

' ELI5: This clears old badges and builds fresh badge views from item data.
Private Sub RecreateBadgeViews
	If BadgeHost.IsInitialized = False Then Return
	BadgeHost.RemoveAllViews
	BadgeOrder.Clear
	BadgeById.Clear
	BadgeViewById.Clear
	BadgeTextById.Clear

	NormalizeSelectionAgainstDefinitions

	For i = 0 To BadgeDefs.Size - 1
		Dim def As Map = BadgeDefs.Get(i)
		Dim id As String = def.GetDefault("id", "")
		Dim txt As String = def.GetDefault("text", "")
		If id.Length = 0 Then Continue
		' Badge visuals are global source-of-truth for this component.
		Dim startChecked As Boolean = SelectedLookup.ContainsKey(id)
		If def.ContainsKey("checked") Then startChecked = ResolveBoolValue(def.Get("checked"), startChecked)
		If startChecked Then
			SelectedLookup.Put(id, True)
		Else
			SelectedLookup.Remove(id)
		End If

		Dim chip As B4XDaisyBadge
		chip.Initialize(Me, "chip")
		chip.setId(id)
		chip.setTag(id)
		chip.setToggle(True)
		chip.setText(txt)
		chip.setSize(msBadgeSize)
		chip.setHeight(msBadgeHeight)
		chip.setRounded("rounded-full")
		chip.setStyle(msBadgeStyle)
		chip.setVariant(msBadgeColor)
		chip.setShadow("none")
		chip.setCheckedColor(mcBadgeCheckedColor)
		chip.setCheckedTextColor(mcBadgeCheckedTextColor)
		chip.setChecked(startChecked)
		'
		Dim chipView As B4XView = chip.AddToParent(BadgeHost, 0, 0, 0, 0)
		BadgeOrder.Add(id)
		BadgeById.Put(id, chip)
		BadgeViewById.Put(id, chipView)
		BadgeTextById.Put(id, txt)
	Next

	ApplySelectionModeConstraints(False)
	RebuildLayout
End Sub

' ELI5: This removes selected ids that no longer exist in items.
Private Sub NormalizeSelectionAgainstDefinitions
	Dim keep As Map
	keep.Initialize
	For i = 0 To BadgeDefs.Size - 1
		Dim d As Map = BadgeDefs.Get(i)
		Dim id As String = d.GetDefault("id", "")
		If id.Length > 0 Then keep.Put(id, True)
	Next

	Dim keysToRemove As List
	keysToRemove.Initialize
	For Each k As String In SelectedLookup.Keys
		If keep.ContainsKey(k) = False Then keysToRemove.Add(k)
	Next
	For Each k As String In keysToRemove
		SelectedLookup.Remove(k)
	Next
End Sub

' ELI5: This runs when a badge is tapped and updates selection rules.
Private Sub chip_FocusChanged(HasFocus As Boolean)
        ' Route focus changes through public validation hooks:
        ' clear transient errors on focus, re-validate on blur.
        If HasFocus Then
                ReceiveFocus
        Else
                Blur
        End If
        If xui.SubExists(mCallBack, mEventName & "_FocusChanged", 1) Then
                CallSub2(mCallBack, mEventName & "_FocusChanged", HasFocus)
        End If
End Sub

Private Sub chip_Checked(Id As String, Checked As Boolean)
	If mInternalSelectionChange Then Return
	If Id = Null Then Return
	Dim key As String = Id.Trim
	If key.Length = 0 Then Return
	If BadgeById.ContainsKey(key) = False Then Return

	If msBadgeSelectionMode = "single" And Checked Then
		mInternalSelectionChange = True
		For Each otherId As String In BadgeOrder
			If otherId = key Then Continue
			If BadgeById.ContainsKey(otherId) = False Then Continue
			Dim otherChip As B4XDaisyBadge = BadgeById.Get(otherId)
			If otherChip.getChecked Then otherChip.setChecked(False)
			SelectedLookup.Remove(otherId)
		Next
		mInternalSelectionChange = False
	End If

	If Checked Then
		SelectedLookup.Put(key, True)
	Else
		SelectedLookup.Remove(key)
	End If

	If msBadgeSelectionMode = "single" Then ApplySelectionModeConstraints(False)
	RebuildLayout
	RaiseItemChanged(key, Checked)
	RaiseChanged
End Sub

' ELI5: This tells your page which single badge changed.
Private Sub RaiseItemChanged(Id As String, Checked As Boolean)
	If xui.SubExists(mCallBack, mEventName & "_ItemChanged", 1) = False Then Return
	Dim item As Map
	item.Initialize
	item.Put("id", Id)
	item.Put("text", BadgeTextById.GetDefault(Id, ""))
	item.Put("checked", Checked)
	CallSub2(mCallBack, mEventName & "_ItemChanged", item)
End Sub

' ELI5: This tells your page the full selected-id list changed.
Private Sub RaiseChanged
	If xui.SubExists(mCallBack, mEventName & "_Changed", 1) = False Then Return
	CallSub2(mCallBack, mEventName & "_Changed", GetSelectedIds)
End Sub

' ELI5: This gets the first found key value from an item map.
Private Sub GetDefValue(Def As Map, Keys() As String, DefaultValue As Object) As Object
	If Def.IsInitialized = False Then Return DefaultValue
	For i = 0 To Keys.Length - 1
		Dim k As String = Keys(i)
		If Def.ContainsKey(k) Then Return Def.Get(k)
	Next
	Return DefaultValue
End Sub

' ELI5: This gets one item value as text with a safe fallback.
Private Sub GetDefString(Def As Map, Keys() As String, DefaultValue As String) As String
	Dim raw As Object = GetDefValue(Def, Keys, DefaultValue)
	If raw = Null Then Return DefaultValue
	Dim s As String = raw
	s = s.Trim
	If s.Length = 0 Then Return DefaultValue
	Return s
End Sub

' ELI5: This reads yes/no values from numbers, text, or booleans.
Private Sub ResolveBoolValue(Value As Object, DefaultValue As Boolean) As Boolean
	If Value = Null Then Return DefaultValue
	If Value Is Boolean Then Return Value
	If IsNumber(Value) Then Return (Value <> 0)
	Dim s As String = Value
	s = s.ToLowerCase.Trim
	Select Case s
		Case "true", "1", "yes", "y", "on"
			Return True
		Case "false", "0", "no", "n", "off", ""
			Return False
		Case Else
			Return DefaultValue
	End Select
End Sub

' ELI5: This updates each visible badge checked state from our selected map.
Private Sub ApplySelectionToVisibleBadges
	For Each id As String In BadgeOrder
		If BadgeById.ContainsKey(id) = False Then Continue
		Dim chip As B4XDaisyBadge = BadgeById.Get(id)
		chip.setChecked(SelectedLookup.ContainsKey(id))
	Next
	RebuildLayout
End Sub

' ELI5: This enforces single-select mode so only one badge stays selected.
Private Sub ApplySelectionModeConstraints(RaiseEvents As Boolean)
	If msBadgeSelectionMode <> "single" Then Return

	Dim firstSelected As String = ""
	For Each id As String In BadgeOrder
		If SelectedLookup.ContainsKey(id) Then
			firstSelected = id
			Exit
		End If
	Next

	mInternalSelectionChange = True
	For Each id As String In BadgeOrder
		Dim shouldBeChecked As Boolean = (firstSelected.Length > 0 And id = firstSelected)
		If shouldBeChecked Then
			SelectedLookup.Put(id, True)
		Else
			SelectedLookup.Remove(id)
		End If
		If BadgeById.ContainsKey(id) Then
			Dim chip As B4XDaisyBadge = BadgeById.Get(id)
			chip.setChecked(shouldBeChecked)
		End If
	Next
	mInternalSelectionChange = False
	RebuildLayout

	If RaiseEvents Then RaiseChanged
End Sub

' ELI5: This turns a pipe-and-colon text spec into badge item definitions.
Private Sub BuildDefinitionsFromSpec(Spec As String)
	BadgeDefs.Clear
	If Spec = Null Then Return
	Dim src As String = Spec.Trim
	If src.Length = 0 Then Return

	Dim parts() As String = Regex.Split("\|", src)
	For i = 0 To parts.Length - 1
		Dim token As String = parts(i).Trim
		If token.Length = 0 Then Continue

		Dim id As String = ""
		Dim txt As String = ""
		Dim p As Int = token.IndexOf(":")
		If p >= 0 Then
			id = token.SubString2(0, p).Trim
			txt = token.SubString(p + 1).Trim
		Else
			txt = token
		End If
		If txt.Length = 0 Then txt = "Item " & (i + 1)
		If id.Length = 0 Then id = BuildFallbackId(txt, i + 1)
		id = EnsureUniqueId(id)

		BadgeDefs.Add(CreateMap("id": id, "text": txt))
	Next
End Sub

' ELI5: This makes a safe id from text when an id is missing.
Private Sub BuildFallbackId(Text As String, Index As Int) As String
	Dim src As String = IIf(Text = Null, "", Text.ToLowerCase.Trim)
	If src.Length = 0 Then Return "item-" & Index
	src = src.Replace(" ", "-")
	src = src.Replace(":", "-")
	src = src.Replace("|", "-")
	src = src.Replace(",", "-")
	src = src.Replace("/", "-")
	src = src.Replace("\", "-")
	Do While src.Contains("--")
		src = src.Replace("--", "-")
	Loop
	If src.StartsWith("-") Then src = src.SubString(1)
	If src.EndsWith("-") Then src = src.SubString2(0, src.Length - 1)
	If src.Length = 0 Then src = "item-" & Index
	Return src
End Sub

' ELI5: This makes sure an id is unique by adding -2, -3, and so on.
Private Sub EnsureUniqueId(Value As String) As String
	Dim baseId As String = IIf(Value = Null, "", Value.Trim)
	If baseId.Length = 0 Then baseId = "item"
	Dim candidate As String = baseId
	Dim n As Int = 2
	Do While DefinitionContainsId(candidate)
		candidate = baseId & "-" & n
		n = n + 1
	Loop
	Return candidate
End Sub

' ELI5: This checks if an id already exists in current item definitions.
Private Sub DefinitionContainsId(Id As String) As Boolean
	For i = 0 To BadgeDefs.Size - 1
		Dim d As Map = BadgeDefs.Get(i)
		If d.GetDefault("id", "") = Id Then Return True
	Next
	Return False
End Sub

' ELI5: This finds where an item id sits in the definition list.
Private Sub FindDefinitionIndex(Id As String) As Int
	For i = 0 To BadgeDefs.Size - 1
		Dim d As Map = BadgeDefs.Get(i)
		If d.GetDefault("id", "") = Id Then Return i
	Next
	Return -1
End Sub


' ELI5: This turns a dip size into a px string for props.

'========================
' Items API
'========================

' ELI5: This adds or updates one badge item in our data list.
Public Sub AddBadgeItem(Id As String, Text As String)
	Dim idNorm As String = IIf(Id = Null, "", Id.Trim)
	Dim txt As String = IIf(Text = Null, "", Text.Trim)
	If txt.Length = 0 Then txt = idNorm
	If txt.Length = 0 Then txt = "Item " & (BadgeDefs.Size + 1)
	If idNorm.Length = 0 Then idNorm = BuildFallbackId(txt, BadgeDefs.Size + 1)

	Dim idx As Int = FindDefinitionIndex(idNorm)
	If idx >= 0 Then
		Dim existing As Map = BadgeDefs.Get(idx)
		existing.Put("text", txt)
		BadgeDefs.Set(idx, existing)
	Else
		Dim uniqueId As String = EnsureUniqueId(idNorm)
		BadgeDefs.Add(CreateMap("id": uniqueId, "text": txt))
	End If
	If mBase.IsInitialized Then RecreateBadgeViews
End Sub

' ELI5: This removes one badge item and its selection.
Public Sub RemoveBadgeItem(Id As String)
	If Id = Null Then Return
	Dim key As String = Id.Trim
	If key.Length = 0 Then Return
	Dim idx As Int = FindDefinitionIndex(key)
	If idx < 0 Then Return
	BadgeDefs.RemoveAt(idx)
	SelectedLookup.Remove(key)
	If mBase.IsInitialized Then RecreateBadgeViews
End Sub

' ELI5: This removes all badge items and clears selection.
Public Sub ClearBadgeItems
	BadgeDefs.Clear
	SelectedLookup.Clear
	If mBase.IsInitialized Then RecreateBadgeViews
End Sub

' ELI5: This accepts runtime items (Map/List/spec text) and rebuilds badges.
Public Sub setItems(Items As Object)
	BadgeDefs.Clear
	SelectedLookup.Clear
	If Items = Null Then
		If mBase.IsInitialized Then RecreateBadgeViews
		Return
	End If

	If Items Is Map Then
		BuildDefinitionsFromMap(Items)
	Else If Items Is List Then
		BuildDefinitionsFromList(Items)
	Else
		Dim s As String = Items
		BuildDefinitionsFromSpec(s)
	End If

	If mBase.IsInitialized Then RecreateBadgeViews
End Sub

' ELI5: This reads key-value pairs and makes badge definitions from them.
Private Sub BuildDefinitionsFromMap(Items As Map)
	If Items.IsInitialized = False Then Return
	Dim keys As List = Items.Keys
	For i = 0 To keys.Size - 1
		Dim rawKey As Object = keys.Get(i)
		Dim rawValue As Object = Items.Get(rawKey)
		Dim id As String = IIf(rawKey = Null, "", rawKey)
		id = id.Trim
		If rawValue Is Map Then
			Dim it As Map = rawValue
			Dim def As Map = CloneMap(it)
			If id.Length > 0 And def.ContainsKey("id") = False Then def.Put("id", id)
			If def.ContainsKey("text") = False And def.ContainsKey("label") Then def.Put("text", def.Get("label"))
			AddDefinitionFromMap(def, i + 1)
		Else
			Dim txt As String = IIf(rawValue = Null, "", rawValue)
			AddDefinitionItem(id, txt, i + 1)
		End If
	Next
End Sub

' ELI5: This reads list items and makes badge definitions from them.
Private Sub BuildDefinitionsFromList(Items As List)
	If Items.IsInitialized = False Then Return
	For i = 0 To Items.Size - 1
		Dim raw As Object = Items.Get(i)
		Dim id As String = ""
		Dim txt As String = ""
		If raw Is Map Then
			Dim it As Map = raw
			Dim def As Map = CloneMap(it)
			If def.ContainsKey("text") = False And def.ContainsKey("label") Then def.Put("text", def.Get("label"))
			AddDefinitionFromMap(def, i + 1)
		Else
			Dim s As String = raw
			s = s.Trim
			Dim p As Int = s.IndexOf(":")
			If p >= 0 Then
				id = s.SubString2(0, p).Trim
				txt = s.SubString(p + 1).Trim
			Else
				txt = s
			End If
		End If

		If raw Is Map Then
			' Map entries were already handled above.
		Else
			AddDefinitionItem(id, txt, i + 1)
		End If
	Next
End Sub

' ELI5: This normalizes one item and adds it with a unique id.
Private Sub AddDefinitionItem(Id As String, Text As String, Index As Int)
	Dim def As Map
	def.Initialize
	def.Put("id", Id)
	def.Put("text", Text)
	AddDefinitionFromMap(def, Index)
End Sub

' ELI5: This normalizes one item map and keeps extra item settings too.
Private Sub AddDefinitionFromMap(Source As Map, Index As Int)
	Dim def As Map = CloneMap(Source)
	Dim idNorm As String = GetDefString(def, Array As String("id"), "")
	Dim txt As String = GetDefString(def, Array As String("text", "label"), "")
	idNorm = idNorm.Trim
	If txt.Length = 0 Then txt = idNorm
	If txt.Length = 0 Then txt = "Item " & Index
	If idNorm.Length = 0 Then idNorm = BuildFallbackId(txt, Index)
	If DefinitionContainsId(idNorm) Then idNorm = EnsureUniqueId(idNorm)
	def.Put("id", idNorm)
	def.Put("text", txt)
	BadgeDefs.Add(def)
	If def.ContainsKey("checked") Then
		If ResolveBoolValue(def.Get("checked"), False) Then
			SelectedLookup.Put(idNorm, True)
		Else
			SelectedLookup.Remove(idNorm)
		End If
	End If
End Sub

' ELI5: This copies a map so we can safely edit our own version.
Private Sub CloneMap(Source As Map) As Map
	Dim out As Map
	out.Initialize
	If Source.IsInitialized = False Then Return out
	For Each k As Object In Source.Keys
		out.Put(k, Source.Get(k))
	Next
	Return out
End Sub

' ELI5: This returns all badge item definitions as a list of maps.
Public Sub getItems As List
	Dim out As List
	out.Initialize
	For i = 0 To BadgeDefs.Size - 1
		Dim src As Map = BadgeDefs.Get(i)
		out.Add(CloneMap(src))
	Next
	Return out
End Sub

' ELI5: This stores text spec items and rebuilds badges from it.
Public Sub setItemsSpec(Value As String)
	msItemsSpec = IIf(Value = Null, "", Value.Trim)
	BuildDefinitionsFromSpec(msItemsSpec)
	If mBase.IsInitialized Then RecreateBadgeViews
End Sub

' ELI5: This returns the text spec used for item creation.
Public Sub getItemsSpec As String
	Return msItemsSpec
End Sub

'========================
' Selection API
'========================

' ELI5: This sets single or multi selection behavior.
Public Sub setBadgeSelectionMode(Value As String)
	msBadgeSelectionMode = B4XDaisyVariants.NormalizeSelectionMode(Value)
	ApplySelectionModeConstraints(True)
End Sub

' ELI5: This returns current selection behavior mode.
Public Sub getBadgeSelectionMode As String
	Return msBadgeSelectionMode
End Sub

' ELI5: This sets which badges are selected, then updates visuals/events.
Private Sub setSelectedIds(Ids As List)
	SelectedLookup.Clear
	If Ids.IsInitialized = False Then
		ApplySelectionModeConstraints(False)
		ApplySelectionToVisibleBadges
		RaiseChanged
		Return
	End If

	If msBadgeSelectionMode = "single" Then
		For Each rawId As Object In Ids
			Dim id As String = rawId
			If BadgeById.ContainsKey(id) Or FindDefinitionIndex(id) >= 0 Then
				SelectedLookup.Put(id, True)
				Exit
			End If
		Next
	Else
		For Each rawId As Object In Ids
			Dim id As String = rawId
			If BadgeById.ContainsKey(id) Or FindDefinitionIndex(id) >= 0 Then
				SelectedLookup.Put(id, True)
			End If
		Next
	End If

	ApplySelectionModeConstraints(False)
	ApplySelectionToVisibleBadges
	RaiseChanged
End Sub

' ELI5: This returns selected badge ids in display order.
Private Sub getSelectedIds As List
	Dim ids As List
	ids.Initialize
	For Each id As String In BadgeOrder
		If SelectedLookup.ContainsKey(id) Then ids.Add(id)
	Next
	Return ids
End Sub

' ELI5: This sets selected badge ids from a ; separated string.
Public Sub setSelected(Value As String)
	Dim ids As List
	ids.Initialize
	If Value <> Null Then
		Dim src As String = Value.Trim
		If src.Length > 0 Then
			Dim parts() As String = Regex.Split(";", src)
			For i = 0 To parts.Length - 1
				Dim id As String = parts(i).Trim
				If id.Length = 0 Then Continue
				If ids.IndexOf(id) = -1 Then ids.Add(id)
			Next
		End If
	End If
	setSelectedIds(ids)
End Sub

' ELI5: This returns selected badge ids as a ; separated string.
Public Sub getSelected As String
	Dim ids As List = getSelectedIds
	If ids.IsInitialized = False Or ids.Size = 0 Then Return ""
	Dim sb As StringBuilder
	sb.Initialize
	For i = 0 To ids.Size - 1
		If i > 0 Then sb.Append(";")
		sb.Append(ids.Get(i))
	Next
	Return sb.ToString
End Sub

''' <summary>
''' Sets checked badge ids from a semicolon-delimited string (e.g. "ui;api;ops").
''' Mirrors the Checked API on B4XDaisyCheckboxGroup / B4XDaisyToggleGroup.
''' </summary>
Public Sub setChecked(CheckedIds As String)
	setSelected(CheckedIds)
End Sub

''' <summary>
''' Gets checked badge ids as a semicolon-delimited string. Returns "" if none.
''' Mirrors the Checked API on B4XDaisyCheckboxGroup / B4XDaisyToggleGroup.
''' </summary>
Public Sub getChecked As String
	Return getSelected
End Sub
' ELI5: This checks whether one badge id is currently selected.
Public Sub IsItemSelected(Id As String) As Boolean
	If Id = Null Then Return False
	Return SelectedLookup.ContainsKey(Id.Trim)
End Sub

' ELI5: This checks or unchecks an individual badge by its id.
Public Sub SetItemChecked(Id As String, Checked As Boolean)
	If Id = Null Then Return
	Dim key As String = Id.Trim
	If key.Length = 0 Then Return
	If Checked Then
		SelectedLookup.Put(key, True)
	Else
		SelectedLookup.Remove(key)
	End If
	ApplySelectionModeConstraints(False)
	ApplySelectionToVisibleBadges
	RaiseChanged
End Sub

' ELI5: This checks an individual badge by its id.
Public Sub CheckItem(Id As String)
	SetItemChecked(Id, True)
End Sub

' ELI5: This unchecks an individual badge by its id.
Public Sub UncheckItem(Id As String)
	SetItemChecked(Id, False)
End Sub

' ELI5: This unchecks every badge and raises selection changed.
Public Sub ClearSelection
	SelectedLookup.Clear
	ApplySelectionToVisibleBadges
	RaiseChanged
End Sub

'========================
' Fieldset API
'========================

' ELI5: This sets whether the legend is displayed above the border box.
Public Sub setLabelAbove(Value As Boolean)
	mbLabelAbove = Value
	If mBase.IsInitialized Then Refresh
End Sub

' ELI5: This returns whether label above mode is enabled.
Public Sub getLabelAbove As Boolean
	Return mbLabelAbove
End Sub

' ELI5: This enables Input-style border when True.
Public Sub setInputBorder(Value As Boolean)
	mbInputBorder = Value
	If mBase.IsInitialized Then Refresh
End Sub

' ELI5: This returns whether input border mode is active.
Public Sub getInputBorder As Boolean
	Return mbInputBorder
End Sub

' ELI5: This sets the legend setting.
Public Sub setLegend(Value As String)
	msLegend = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current legend setting.
Public Sub getLegend As String
	Return msLegend
End Sub

' ELI5: This sets the legend size setting.
Public Sub setLegendSize(Value As String)
	msLegendSize = B4XDaisyVariants.NormalizeLegendSize(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current legend size setting.
Public Sub getLegendSize As String
	Return msLegendSize
End Sub

' ELI5: This sets whether the legend caption is rendered in bold.
Public Sub setLegendBold(Value As Boolean)
	mbLegendBold = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns whether the legend caption is rendered in bold.
Public Sub getLegendBold As Boolean
	Return mbLegendBold
End Sub

' ELI5: This sets the auto height setting.
Public Sub setAutoHeight(Value As Boolean)
	mbAutoHeight = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current auto height setting.
Public Sub getAutoHeight As Boolean
	Return mbAutoHeight
End Sub

' ELI5: This sets the variant setting.
Public Sub setVariant(Value As String)
	msVariant = B4XDaisyVariants.NormalizeVariant(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current variant setting.
Public Sub getVariant As String
	Return msVariant
End Sub

' ELI5: This sets the border style setting.
Public Sub setBorderStyle(Value As String)
	msBorderStyle = B4XDaisyVariants.NormalizeFieldsetBorderStyle(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current border style setting.
Public Sub getBorderStyle As String
	Return msBorderStyle
End Sub

' ELI5: This sets the padding setting.
Public Sub setPadding(Value As Int)
	miPadding = Max(0, Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current padding setting.
Public Sub getPadding As Int
	Return miPadding
End Sub

' ELI5: This sets the rounded setting.
Public Sub setRounded(Value As String)
	msRounded = IIf(Value = Null, "theme", Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the rounded mode token.
Public Sub getRounded As String
	Return msRounded
End Sub

' ELI5: This tells you if rounded is active (not rounded-none).
Public Sub isRounded As Boolean
	Return B4XDaisyVariants.NormalizeRounded(msRounded) <> "rounded-none"
End Sub

' ELI5: This sets the rounded box setting.
Public Sub setRoundedBox(Value As Boolean)
	mbRoundedBox = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This tells you if rounded box is true right now.
Public Sub isRoundedBox As Boolean
	Return mbRoundedBox
End Sub

' ELI5: This sets the shadow setting.
Public Sub setShadow(Value As String)
	msShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current shadow setting.
Public Sub getShadow As String
	Return msShadow
End Sub

' ELI5: This sets the background color setting.
Public Sub setBackgroundColor(Value As Object)
	mcBackgroundColor = B4XDaisyVariants.ResolveColorValue(Value, mcBackgroundColor)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current background color setting.
Public Sub getBackgroundColor As Int
	Return mcBackgroundColor
End Sub

' ELI5: This sets the text color setting.
Public Sub setTextColor(Value As Object)
	mcTextColor = B4XDaisyVariants.ResolveColorValue(Value, mcTextColor)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current text color setting.
Public Sub getTextColor As Int
	Return mcTextColor
End Sub

' ELI5: This sets the border color setting.
Public Sub setBorderColor(Value As Object)
	mcBorderColor = B4XDaisyVariants.ResolveColorValue(Value, mcBorderColor)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current border color setting.
Public Sub getBorderColor As Int
	Return mcBorderColor
End Sub

' ELI5: This sets the border size setting.
Public Sub setBorderSize(Value As Int)
	miBorderSize = Max(0, Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

' ELI5: This returns the current border size setting.
Public Sub getBorderSize As Int
	Return miBorderSize
End Sub

'========================
' Badge style API
'========================

' ELI5: This sets the badge size setting.
Public Sub setBadgeSize(Value As String)
	msBadgeSize = Value
End Sub

' ELI5: This returns the current badge size setting.
Public Sub getBadgeSize As String
	Return msBadgeSize
End Sub

' ELI5: This sets the badge height setting.
Public Sub setBadgeHeight(Value As String)
	msBadgeHeight = Value
End Sub

' ELI5: This returns the current badge height setting.
Public Sub getBadgeHeight As String
	Return msBadgeHeight
End Sub

' ELI5: This sets the badge color setting.
Public Sub setBadgeColor(Value As String)
	msBadgeColor = Value
End Sub

' ELI5: This returns the current badge color setting.
Public Sub getBadgeColor As String
	Return msBadgeColor
End Sub

' ELI5: This sets the badge style setting.
Public Sub setBadgeStyle(Value As String)
	msBadgeStyle = B4XDaisyVariants.NormalizeBadgeStyle(Value)
End Sub

' ELI5: This returns the current badge style setting.
Public Sub getBadgeStyle As String
	Return msBadgeStyle
End Sub

' ELI5: This sets the badge checked color setting.
Public Sub setBadgeCheckedColor(Value As Object)
	mcBadgeCheckedColor = B4XDaisyVariants.ResolveColorValue(Value, mcBadgeCheckedColor)
End Sub

' ELI5: This returns the current badge checked color setting.
Public Sub getBadgeCheckedColor As Int
	Return mcBadgeCheckedColor
End Sub

' ELI5: This sets the badge checked text color setting.
Public Sub setBadgeCheckedTextColor(Value As Object)
	mcBadgeCheckedTextColor = B4XDaisyVariants.ResolveColorValue(Value, mcBadgeCheckedTextColor)
End Sub

' ELI5: This returns the current badge checked text color setting.
Public Sub getBadgeCheckedTextColor As Int
	Return mcBadgeCheckedTextColor
End Sub

' ELI5: This sets the gap setting.
Public Sub setGap(Value As Int)
	miGap = Max(0, Value)
End Sub

' ELI5: This returns the current gap setting.
Public Sub getGap As Int
	Return miGap
End Sub

' ELI5: This sets the row gap setting.
Public Sub setRowGap(Value As Int)
	miRowGap = Max(0, Value)
End Sub

' ELI5: This returns the current row gap setting.
Public Sub getRowGap As Int
	Return miRowGap
End Sub

'========================
' Common API
'========================

' ELI5: This returns your custom tag object stored on this component.
Public Sub getTag As Object
	Return mTag
End Sub

' ELI5: This stores your custom tag object on this component.

' Required / error validation helpers
Public Sub setRequired(Value As Boolean)
	mbRequired = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getRequired As Boolean
	Return mbRequired
End Sub

Public Sub setErrorText(Value As String)
	If Value = Null Then Value = ""
	msErrorText = Value.Trim
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getErrorText As String
	Return msErrorText
End Sub

Public Sub setHintText(Value As String)
	If Value = Null Then Value = ""
	msHintText = Value
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getHintText As String
	Return msHintText
End Sub

Public Sub ShowError(ErrorMessage As String)
        msErrorText = ErrorMessage
        mbProgrammaticError = True
        If mBase.IsInitialized Then Refresh
End Sub

Public Sub ClearError
        msErrorText = ""
        mbProgrammaticError = False
        mbValidationTouched = False
        If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIsValid As Boolean
	Return (msErrorText.Length = 0)
End Sub

Public Sub Validate As Boolean
	If mbRequired = False Then
                mbProgrammaticError = False
		ClearError
		Return True
	End If
	If SelectedLookup.Size > 0 Then
                mbProgrammaticError = False
		ClearError
		Return True
	End If
	Dim msg As String = msErrorText
	If msg.Length = 0 Then msg = "This field is required."
        mbValidationTouched = True
	ShowError(msg)
	Return False
End Sub

''' <summary>
''' Called when the badge group receives focus. Clears any transient
''' validation error visual so the user can interact without a distracting red border.
''' </summary>
Public Sub ReceiveFocus
        If msErrorText.Length > 0 And mbProgrammaticError = False Then
                msErrorText = ""
                If mBase.IsInitialized Then Refresh
        End If
End Sub

''' <summary>
''' Called when the component loses focus.
''' </summary>
Public Sub Blur
    ' No state changes on blur; skip Refresh to avoid a full re-layout on every focus loss.
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
End Sub


Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyBadge.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Event: Click (Tag As Object)
#Event: CloseClick (Tag As Object)
#Event: Checked (Id As String, Checked As Boolean)

#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: fit-content, Description: Tailwind size token, CSS size, or fit-content
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-6, Description: Tailwind size token or CSS size (eg h-6, 24px, 1.5rem)
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, List: xs|sm|md|lg|xl, Description: Badge size token
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Daisy variant used for colors
#DesignerProperty: Key: BadgeStyle, DisplayName: Style, FieldType: String, DefaultValue: solid, List: solid|soft|outline|dash|ghost, Description: Badge visual style
#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: Badge, Description: Text displayed inside badge
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Tailwind spacing utilities (eg px-2, py-1, p-1.5)
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Tailwind spacing utilities (eg m-1, mx-2)
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Show or hide badge view
#DesignerProperty: Key: AvatarVisible, DisplayName: Avatar Visible, FieldType: Boolean, DefaultValue: False, Description: Show avatar inside badge
#DesignerProperty: Key: AvatarImage, DisplayName: Avatar Image, FieldType: String, DefaultValue:, Description: Avatar image path from assets or full path
#DesignerProperty: Key: AvatarText, DisplayName: Avatar Text, FieldType: String, DefaultValue:, Description: Avatar placeholder text when image is empty
#DesignerProperty: Key: AvatarPosition, DisplayName: Avatar Position, FieldType: String, DefaultValue: left, List: left|right, Description: Avatar placement relative to text
#DesignerProperty: Key: IconAsset, DisplayName: Icon Asset, FieldType: String, DefaultValue:, Description: SVG asset used for left icon
#DesignerProperty: Key: Toggle, DisplayName: Toggle, FieldType: Boolean, DefaultValue: False, Description: Enables checked/unchecked toggle behavior
#DesignerProperty: Key: Checked, DisplayName: Checked, FieldType: Boolean, DefaultValue: False, Description: Current checked state (effective when Toggle is true)
#DesignerProperty: Key: CheckedColor, DisplayName: Checked Color, FieldType: Color, DefaultValue: 0x00000000, Description: Background color used when checked (0 uses variant fallback)
#DesignerProperty: Key: CheckedTextColor, DisplayName: Checked Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Text/icon color used when checked (0 uses variant fallback)
#DesignerProperty: Key: ID, DisplayName: ID, FieldType: String, DefaultValue:, Description: Optional chip identifier returned in checked event
#DesignerProperty: Key: Closable, DisplayName: Closable, FieldType: Boolean, DefaultValue: False, Description: Show close icon on the right side
#DesignerProperty: Key: CloseIconAsset, DisplayName: Close Icon Asset, FieldType: String, DefaultValue: xmark-solid.svg, Description: SVG asset used for close icon
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Corner radius mode
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: True, Description: Use selector radius from active theme
#DesignerProperty: Key: CapValue, DisplayName: Cap Value, FieldType: Int, DefaultValue: 99, Description: Numeric cap - values above this display as cap+ (0 disables capping)
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation shadow level
#DesignerProperty: Key: Clickable, DisplayName: Clickable, FieldType: Boolean, DefaultValue: True, Description: When False, touch events pass through to parent (useful inside clickable list rows)

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object
	Private mWidth As Float = 88dip
	Private mHeight As Float = 24dip
	Private mWidthExplicit As Boolean = False
	Private mHeightExplicit As Boolean = False
	Private mFitContentWidth As Boolean = True
	Private mSize As String = "md"
	Private mVariant As String = "none"
	Private mBadgeStyle As String = "solid"
	Private mText As String = "Badge"
	Private mTextCentered As Boolean = False
	Private mPadding As String = ""
	Private mMargin As String = ""
	Private mVisible As Boolean = True
	Private mRounded As String = "theme"
	Private mRoundedBox As Boolean = True
	Private mShadow As String = "none"

	Private mAvatarVisible As Boolean = False
	Private mAvatarImage As String = ""
	Private mAvatarText As String = ""
	Private mAvatarPosition As String = "left"
	Private mIconAsset As String = ""
	Private mToggle As Boolean = False
	Private mChecked As Boolean = False
	Private mCheckedColor As Int = 0
	Private mCheckedTextColor As Int = 0
	Private mId As String = ""
	Private mClosable As Boolean = False
	Private mCloseIconAsset As String = "xmark-solid.svg"
	Private mCapValue As Int = 99
	Private mClickable As Boolean = True

	Private mBackgroundColor As Int = 0
	Private mTextColor As Int = 0
	Private mBorderColor As Int = 0

	Private Surface As B4XView
	Private lblText As B4XView
	Private AvatarComp As B4XDaisyAvatar
	Private AvatarView As B4XView
	Private LeftIconComp As B4XDaisySvgIcon
	Private LeftIconView As B4XView
	Private CloseHost As B4XView
	Private CloseIconComp As B4XDaisySvgIcon
	Private CloseIconView As B4XView
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map = BuildRuntimeProps(Width, Height)
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub



Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	

	Dim pSurface As Panel
	pSurface.Initialize("surface")
	Surface = pSurface
	Surface.Color = xui.Color_Transparent
	mBase.AddView(Surface, 0, 0, mBase.Width, mBase.Height)

	Dim pText As Label
	pText.Initialize("")
	pText.SingleLine = True
	lblText = pText
	lblText.Color = xui.Color_Transparent
	lblText.SetTextAlignment("CENTER", "LEFT")
	Surface.AddView(lblText, 0, 0, 1dip, 1dip)
	

	Dim av As B4XDaisyAvatar
	av.Initialize(Me, "badgeavatar")
	AvatarComp = av
	AvatarView = AvatarComp.AddToParent(Surface, 0, 0, 1dip, 1dip)
	AvatarView.Visible = False

	Dim li As B4XDaisySvgIcon
	li.Initialize(Me, "badgeicon")
	LeftIconComp = li
	LeftIconView = LeftIconComp.AddToParent(Surface, 0, 0, 1dip, 1dip)
	LeftIconView.Visible = False

	Dim pClose As Panel
	pClose.Initialize("closebtn")
	CloseHost = pClose
	CloseHost.Color = xui.Color_Transparent
	Surface.AddView(CloseHost, 0, 0, 1dip, 1dip)
	CloseHost.Visible = False

	Dim ci As B4XDaisySvgIcon
	ci.Initialize(Me, "closeicon")
	CloseIconComp = ci
	CloseIconView = CloseIconComp.AddToParent(CloseHost, 0, 0, 1dip, 1dip)
	CloseIconView.Visible = False

	ApplyDesignerProps(Props)
	' Apply clickable state to native views
	If mClickable = False Then
		#If B4A
		Dim joClickable As JavaObject = mBase
		joClickable.RunMethod("setClickable", Array(False))
		If Surface.IsInitialized Then
			Dim joS As JavaObject = Surface
			joS.RunMethod("setClickable", Array(False))
		End If
		If CloseHost.IsInitialized Then
			Dim joC As JavaObject = CloseHost
			joC.RunMethod("setClickable", Array(False))
		End If
		#Else If B4J
		mBase.Enabled = False
		If Surface.IsInitialized Then Surface.Enabled = False
		If CloseHost.IsInitialized Then CloseHost.Enabled = False
		#Else If B4i
		mBase.Enabled = False
		If Surface.IsInitialized Then Surface.Enabled = False
		If CloseHost.IsInitialized Then CloseHost.Enabled = False
		#End If
		' Propagate to child components
		If AvatarComp.IsInitialized Then AvatarComp.setClickable(False)
		If LeftIconComp.IsInitialized Then LeftIconComp.setClickable(False)
		If CloseIconComp.IsInitialized Then CloseIconComp.setClickable(False)
	End If
	mBase.Visible = mVisible
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mHeightExplicit = Props.ContainsKey("Height")
	Dim widthSpec As String = B4XDaisyVariants.GetPropString(Props, "Width", "fit-content").ToLowerCase.Trim
	If widthSpec = "" Or widthSpec = "fit-content" Or widthSpec = "auto" Then
		mFitContentWidth = True
		mWidthExplicit = False
	Else
		mFitContentWidth = False
		mWidthExplicit = True
		mWidth = Max(24dip, B4XDaisyVariants.GetPropSizeDip(Props, "Width", "fit-content"))
	End If
		mHeight = Max(16dip, B4XDaisyVariants.GetPropSizeDip(Props, "Height", "h-6"))
	mText = B4XDaisyVariants.GetPropString(Props, "Text", mText)
	mVariant = B4XDaisyVariants.NormalizeVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	mSize = B4XDaisyVariants.NormalizeSize(B4XDaisyVariants.GetPropString(Props, "Size", mSize))
	mRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", mRounded))
	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mVisible)
	mClosable = B4XDaisyVariants.GetPropBool(Props, "Closable", mClosable)
	mToggle = B4XDaisyVariants.GetPropBool(Props, "Toggle", mToggle)
	mChecked = B4XDaisyVariants.GetPropBool(Props, "Checked", mChecked)
	' mOutline = B4XDaisyVariants.GetPropBool(Props, "Outline", False) ' This line was not in the original code, so it's commented out or removed.
	' mSoft = B4XDaisyVariants.GetPropBool(Props, "Soft", False) ' This line was not in the original code, so it's commented out or removed.
	mIconAsset = B4XDaisyVariants.GetPropString(Props, "IconAsset", mIconAsset)
	mAvatarPosition = B4XDaisyVariants.GetPropString(Props, "AvatarPosition", mAvatarPosition)
	mAvatarVisible = B4XDaisyVariants.GetPropBool(Props, "AvatarVisible", mAvatarVisible)
	mAvatarImage = B4XDaisyVariants.GetPropString(Props, "AvatarImage", mAvatarImage).Trim ' Kept original mAvatarImage as it was not replaced by mAvatarAsset
	mAvatarText = B4XDaisyVariants.GetPropString(Props, "AvatarText", mAvatarText) ' Kept original mAvatarText as it was not replaced by mAvatarAsset
	mMargin = B4XDaisyVariants.GetPropString(Props, "Margin", mMargin)
	mPadding = B4XDaisyVariants.GetPropString(Props, "Padding", mPadding)
	mTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mTextColor)
	mBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mBackgroundColor)
	mBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mBorderColor)
	' ApplyDesignerSpacing(mPadding, mMargin) ' This line was not in the original code, so it's commented out or removed.

	mBadgeStyle = B4XDaisyVariants.NormalizeBadgeStyle(B4XDaisyVariants.GetPropString(Props, "BadgeStyle", mBadgeStyle)) ' Moved from earlier in the block
	mTextCentered = B4XDaisyVariants.GetPropBool(Props, "TextCentered", mTextCentered) ' Moved from earlier in the block
	mRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mRoundedBox) ' Kept original
	mCheckedColor = Props.GetDefault("CheckedColor", mCheckedColor)
	mCheckedTextColor = Props.GetDefault("CheckedTextColor", mCheckedTextColor)
	mId = B4XDaisyVariants.GetPropString(Props, "ID", mId) ' Kept original
	mCloseIconAsset = B4XDaisyVariants.GetPropString(Props, "CloseIconAsset", mCloseIconAsset).Trim
	If mCloseIconAsset.Length = 0 Then mCloseIconAsset = "xmark-solid.svg"
	mCapValue = B4XDaisyVariants.GetPropInt(Props, "CapValue", mCapValue)
	mClickable = B4XDaisyVariants.GetPropBool(Props, "Clickable", mClickable)

	mBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mBackgroundColor)
	mTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mTextColor)
	mBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mBorderColor)

	If mHeightExplicit = False Then
		Dim sz As Map = ResolveSizeSpec
		mHeight = Max(16dip, sz.GetDefault("height", 24dip))
	End If
	If mFitContentWidth Then
		mWidth = Max(1dip, EstimatePreferredWidth)
	End If
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	If Surface.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	If mFitContentWidth Then w = Max(w, Max(1dip, EstimatePreferredWidth))
	Dim h As Int = Max(1dip, Height)
	If mFitContentWidth And Abs(mBase.Width - w) > 1dip Then
		mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, w, h)
	End If
	Dim box As Map = BuildBoxModel
	Dim host As B4XRect
	host.Initialize(0, 0, w, h)
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Surface.SetLayoutAnimated(0, outerRect.Left, outerRect.Top, outerRect.Width, outerRect.Height)
	ApplyVisualStyle(box)
	Dim contentLocalRect As B4XRect = ResolveContentLocalRect(outerRect, box)
	LayoutContent(contentLocalRect)
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim targetW As Int = Width
	Dim targetH As Int = Height
	If targetW <= 0 Then targetW = Max(1dip, EstimatePreferredWidth)
	If targetH <= 0 Then targetH = Max(16dip, mHeight)
	Dim w As Int = Max(1dip, targetW)
	Dim h As Int = Max(1dip, targetH)

	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, w, h)

	Dim props As Map = BuildRuntimeProps(w, h)

	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Dim finalW As Int = w
	If mFitContentWidth Then finalW = Max(finalW, EstimatePreferredWidth)
	mBase.SetLayoutAnimated(0, 0, 0, finalW, h)
	Parent.AddView(mBase, Left, Top, finalW, h)
	Return mBase
End Sub

Private Sub BuildRuntimeProps(Width As Int, Height As Int) As Map
	' Keep runtime state when view is recreated through DesignerCreateView.
	Dim props As Map
	props.Initialize
	If mFitContentWidth Then
		props.Put("Width", "fit-content")
	Else
		props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	End If
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	props.Put("Size", mSize)
	props.Put("Variant", mVariant)
	props.Put("BadgeStyle", mBadgeStyle)
	props.Put("Text", mText)
	props.Put("TextCentered", mTextCentered)
	props.Put("Padding", mPadding)
	props.Put("Margin", mMargin)
	props.Put("Visible", mVisible)
	props.Put("Rounded", mRounded)
	props.Put("RoundedBox", mRoundedBox)
	props.Put("Shadow", mShadow)
	props.Put("AvatarVisible", mAvatarVisible)
	props.Put("AvatarImage", mAvatarImage)
	props.Put("AvatarText", mAvatarText)
	props.Put("AvatarPosition", mAvatarPosition)
	props.Put("IconAsset", mIconAsset)
	props.Put("Toggle", mToggle)
	props.Put("Checked", mChecked)
	props.Put("CheckedColor", mCheckedColor)
	props.Put("CheckedTextColor", mCheckedTextColor)
	props.Put("ID", mId)
	props.Put("Closable", mClosable)
	props.Put("CloseIconAsset", mCloseIconAsset)
	props.Put("CapValue", mCapValue)
	props.Put("BackgroundColor", mBackgroundColor)
	props.Put("TextColor", mTextColor)
	props.Put("BorderColor", mBorderColor)
	Return props
End Sub

Public Sub AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Return AddToParent(Parent, Left, Top, Width, Height)
End Sub

Public Sub View As B4XView
	Dim empty As B4XView
	If mBase.IsInitialized = False Then Return empty
	Return mBase
End Sub

Public Sub IsReady As Boolean
	Return mBase.IsInitialized And Surface.IsInitialized And mBase.Width > 0 And mBase.Height > 0
End Sub







Private Sub HasLeadingElement As Boolean
	Dim avatarOnLeft As Boolean = mAvatarVisible And (mAvatarPosition <> "right")
	Dim hasLeftIcon As Boolean = (mIconAsset.Length > 0) Or (mToggle And mChecked)
	Return avatarOnLeft Or hasLeftIcon
End Sub

Private Sub HasTrailingElement As Boolean
	Dim hasClose As Boolean = (mToggle = False) And mClosable
	Dim avatarOnRight As Boolean = mAvatarVisible And (mAvatarPosition = "right")
	Return hasClose Or avatarOnRight
End Sub

Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	Dim sizeMap As Map = ResolveSizeSpec
	Dim sizeDip As Float = sizeMap.GetDefault("height", 24dip)
	If mHeightExplicit Then sizeDip = Max(16dip, mHeight)
	Dim bw As Float = Max(0, B4XDaisyVariants.GetBorderDip(1dip))
	Dim px As Float = Max(0, (sizeDip / 2) - bw)
	Dim py As Float = 0dip
	' Bleed leading/trailing inner components (avatar/icon/close) toward the badge edge
	' with a small inset instead of the pill text padding, mirroring ion-chip slot margins.
	Dim edgeInset As Float = Max(2dip, Round(sizeDip / 10))
	Dim padLeft As Float = px
	Dim padRight As Float = px
	If HasLeadingElement Then padLeft = edgeInset
	If HasTrailingElement Then padRight = edgeInset
	box.Put("padding_left", padLeft)
	box.Put("padding_right", padRight)
	box.Put("padding_top", py)
	box.Put("padding_bottom", py)
	box.Put("border_width", bw)
	box.Put("radius", ResolveBadgeRadiusDip(sizeDip))
	ApplySpacingSpecToBox(box, mPadding, mMargin)
	Return box
End Sub

Private Sub ApplySpacingSpecToBox(Box As Map, PaddingSpec As String, MarginSpec As String)
	Dim rtl As Boolean = False
	Dim p As String = IIf(PaddingSpec = Null, "", PaddingSpec.Trim)
	Dim m As String = IIf(MarginSpec = Null, "", MarginSpec.Trim)
	If p.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(p, Array As String("p-", "px-", "py-", "pt-", "pr-", "pb-", "pl-", "ps-", "pe-")) Then
			B4XDaisyBoxModel.ApplyPaddingUtilities(Box, p, rtl)
		Else
			Dim pv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(p, 0dip)
			Box.Put("padding_left", pv)
			Box.Put("padding_right", pv)
			Box.Put("padding_top", pv)
			Box.Put("padding_bottom", pv)
		End If
	End If
	If m.Length > 0 Then
		If B4XDaisyVariants.ContainsAny(m, Array As String("m-", "mx-", "my-", "mt-", "mr-", "mb-", "ml-", "ms-", "me-", "-m-", "-mx-", "-my-", "-mt-", "-mr-", "-mb-", "-ml-", "-ms-", "-me-")) Then
			B4XDaisyBoxModel.ApplyMarginUtilities(Box, m, rtl)
		Else
			Dim mv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(m, 0dip)
			Box.Put("margin_left", mv)
			Box.Put("margin_right", mv)
			Box.Put("margin_top", mv)
			Box.Put("margin_bottom", mv)
		End If
	End If
End Sub

Private Sub ResolveContentLocalRect(OuterRect As B4XRect, Box As Map) As B4XRect
	Dim borderRect As B4XRect = B4XDaisyBoxModel.ResolveBorderRect(OuterRect, Box)
	Dim contentAbs As B4XRect = B4XDaisyBoxModel.ResolveContentRect(borderRect, Box)
	Return B4XDaisyBoxModel.ToLocalRect(contentAbs, OuterRect)
End Sub

Private Sub ResolveSizeSpec As Map
	Dim key As String = B4XDaisyVariants.NormalizeSize(mSize)
	Select Case key
		Case "xs"
			Return CreateMap("height": 16dip, "font": 10, "pad_h": 6dip, "pad_v": 1dip, "gap": 4dip, "avatar": 12dip, "icon": 10dip, "close": 10dip)
		Case "sm"
			Return CreateMap("height": 20dip, "font": 12, "pad_h": 7dip, "pad_v": 2dip, "gap": 4dip, "avatar": 14dip, "icon": 11dip, "close": 11dip)
		Case "lg"
			Return CreateMap("height": 28dip, "font": 16, "pad_h": 10dip, "pad_v": 3dip, "gap": 6dip, "avatar": 18dip, "icon": 14dip, "close": 14dip)
		Case "xl"
			Return CreateMap("height": 32dip, "font": 18, "pad_h": 12dip, "pad_v": 4dip, "gap": 6dip, "avatar": 20dip, "icon": 16dip, "close": 16dip)
		Case Else
			Return CreateMap("height": 24dip, "font": 14, "pad_h": 8dip, "pad_v": 2dip, "gap": 5dip, "avatar": 16dip, "icon": 12dip, "close": 12dip)
	End Select
End Sub

Private Sub LayoutContent(ContentRect As B4XRect)
	Dim sizeMap As Map = ResolveSizeSpec
	Dim xColors As Map = ResolveVisualColors
	Dim textColor As Int = xColors.Get("text")
	Dim textValue As String = ResolveDisplayText

	Dim gap As Int = Max(2dip, sizeMap.GetDefault("gap", 5dip))
	Dim avatarSize As Int = Max(10dip, sizeMap.GetDefault("avatar", 14dip))
	Dim iconSize As Int = Max(8dip, sizeMap.GetDefault("icon", 11dip))
	Dim closeSize As Int = Max(8dip, sizeMap.GetDefault("close", 11dip))
	Dim fontSize As Float = Max(9, sizeMap.GetDefault("font", 14))

	lblText.Font = xui.CreateDefaultFont(fontSize)
	lblText.Text = textValue
	lblText.TextColor = textColor
	lblText.SetTextAlignment("CENTER", "LEFT")

	Dim leftX As Int = ContentRect.Left
	Dim rightX As Int = ContentRect.Right
	Dim centerY As Int = ContentRect.Top + (ContentRect.Height / 2)

	Dim hasAvatar As Boolean = mAvatarVisible
	Dim avatarOnRight As Boolean = hasAvatar And (mAvatarPosition = "right")
	Dim avatarOnLeft As Boolean = hasAvatar And (mAvatarPosition <> "right")
	If hasAvatar Then
		ConfigureAvatar(avatarSize)
		AvatarView.Visible = True
	Else
		AvatarView.Visible = False
		AvatarView.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If

	If avatarOnLeft Then
		ConfigureAvatar(avatarSize)
		AvatarView.SetLayoutAnimated(0, leftX, centerY - (avatarSize / 2), avatarSize, avatarSize)
		leftX = leftX + avatarSize + gap
	End If

	Dim hasToggleIcon As Boolean = mToggle And mChecked
	Dim leftIconAsset As String = IIf(hasToggleIcon, "check-solid.svg", mIconAsset)
	Dim hasLeftIcon As Boolean = leftIconAsset.Trim.Length > 0
	If hasLeftIcon Then
		LeftIconView.Visible = True
		LeftIconView.SetLayoutAnimated(0, leftX, centerY - (iconSize / 2), iconSize, iconSize)
		ConfigureLeftIcon(iconSize, textColor, leftIconAsset)
		leftX = leftX + iconSize + gap
	Else
		LeftIconView.Visible = False
		LeftIconView.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If

	Dim hasClose As Boolean = (mToggle = False) And mClosable
	Dim hasRightIcon As Boolean = hasClose
	If hasRightIcon Then
		Dim hostSize As Int = closeSize + 2dip
		CloseHost.Visible = True
		CloseHost.SetLayoutAnimated(0, rightX - hostSize, centerY - (hostSize / 2), hostSize, hostSize)
		ConfigureCloseIcon(closeSize, textColor)
		rightX = rightX - hostSize - gap
	Else
		CloseHost.Visible = False
		CloseHost.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If

	If avatarOnRight Then
		AvatarView.SetLayoutAnimated(0, rightX - avatarSize, centerY - (avatarSize / 2), avatarSize, avatarSize)
		rightX = rightX - avatarSize - gap
	End If

	Dim textOnlyContent As Boolean = (hasAvatar = False) And (hasLeftIcon = False) And (hasRightIcon = False)
	If mTextCentered Or textOnlyContent Then
		lblText.SetTextAlignment("CENTER", "CENTER")
	Else
		lblText.SetTextAlignment("CENTER", "LEFT")
	End If

	Dim textW As Int = Max(1dip, rightX - leftX)
	Dim textH As Int = Max(1dip, ContentRect.Height)
	lblText.SetLayoutAnimated(0, leftX, centerY - (textH / 2), textW, textH)
End Sub

Private Sub ConfigureAvatar(AvatarSize As Int)
	If AvatarComp.IsInitialized = False Then Return
	AvatarComp.setVariant(mVariant)
	AvatarComp.setShadow("none")
	AvatarComp.setAvatarMask("circle")
	AvatarComp.setAvatarSize(B4XDaisyVariants.ResolvePxSizeSpec(AvatarSize))
	If mAvatarImage.Length > 0 Then
		AvatarComp.setAvatarType("image")
		AvatarComp.setAvatar(mAvatarImage)
	Else
		AvatarComp.setAvatarType("text")
		AvatarComp.setAvatar("")
		AvatarComp.setPlaceHolder(mAvatarText)
	End If
End Sub

Private Sub ConfigureLeftIcon(IconSize As Int, IconColor As Int, Asset As String)
	If LeftIconComp.IsInitialized = False Then Return
	Dim iconAsset As String = IIf(Asset = Null, "", Asset.Trim)
	If iconAsset.Length = 0 Then Return
	LeftIconComp.setSvgAsset(iconAsset)
	LeftIconComp.setPreserveOriginalColors(False)
	LeftIconComp.setColor(IconColor)
	LeftIconComp.setSize(B4XDaisyVariants.ResolvePxSizeSpec(IconSize))
	LeftIconComp.ResizeToParent(LeftIconView)
End Sub

Private Sub ConfigureCloseIcon(IconSize As Int, IconColor As Int)
	If CloseIconComp.IsInitialized = False Then Return
	Dim iconAsset As String = IIf(mCloseIconAsset = Null, "", mCloseIconAsset.Trim)
	If iconAsset.Length = 0 Then iconAsset = "xmark-solid.svg"
	CloseIconComp.setSvgAsset(iconAsset)
	CloseIconComp.setPreserveOriginalColors(False)
	CloseIconComp.setColor(IconColor)
	CloseIconComp.setSize(B4XDaisyVariants.ResolvePxSizeSpec(IconSize))
	CloseIconView.Visible = True
	CloseIconView.SetLayoutAnimated(0, 1dip, 1dip, Max(1dip, CloseHost.Width - 2dip), Max(1dip, CloseHost.Height - 2dip))
	CloseIconComp.ResizeToParent(CloseIconView)
End Sub

Private Sub ApplyVisualStyle(Box As Map)
	If Surface.IsInitialized = False Then Return
	Dim xColors As Map = ResolveVisualColors
	Dim bg As Int = xColors.Get("back")
	Dim border As Int = xColors.Get("border")
	Dim bw As Int = Max(0, Box.GetDefault("border_width", 0))
	Dim radius As Float = Max(0, Box.GetDefault("radius", 0))
	B4XDaisyVariants.ApplyDashedBorder(Surface, bg, bw, border, radius, mBadgeStyle)
	ApplyShadow
End Sub

Private Sub ResolveVisualColors As Map
	Dim palette As Map = B4XDaisyVariants.GetVariantPalette
	Dim tokens As Map = B4XDaisyVariants.GetActiveTokens
	Dim base100 As Int = tokens.GetDefault("--color-base-100", xui.Color_White)
	Dim base200 As Int = tokens.GetDefault("--color-base-200", xui.Color_RGB(232, 234, 237))
	Dim baseContent As Int = tokens.GetDefault("--color-base-content", xui.Color_RGB(63, 64, 77))
	Dim neutralBack As Int = tokens.GetDefault("--color-neutral", xui.Color_RGB(63, 64, 77))
	Dim neutralContent As Int = tokens.GetDefault("--color-neutral-content", xui.Color_RGB(248, 248, 249))

	Dim variantKey As String = B4XDaisyVariants.NormalizeVariant(mVariant)
	Dim accentBack As Int = B4XDaisyVariants.ResolveVariantColor(palette, variantKey, "back", neutralBack)
	Dim accentText As Int = B4XDaisyVariants.ResolveVariantColor(palette, variantKey, "text", neutralContent)

	Dim back As Int
	Dim text As Int
	Dim border As Int

	Select Case mBadgeStyle
		Case "ghost"
			back = base200
			text = baseContent
			border = base200
		Case "soft"
			If variantKey = "none" Then
				back = B4XDaisyVariants.Blend(baseContent, base100, 0.92)
				text = baseContent
				border = B4XDaisyVariants.Blend(baseContent, base100, 0.9)
			Else
				back = B4XDaisyVariants.Blend(accentBack, base100, 0.92)
				text = accentBack
				border = B4XDaisyVariants.Blend(accentBack, base100, 0.9)
			End If
		Case "outline", "dash"
			back = xui.Color_Transparent
			If variantKey = "none" Then
				text = baseContent
				border = text
			Else If variantKey = "neutral" Then
				text = baseContent
				border = accentBack
			Else
				text = accentBack
				border = accentBack
			End If
		Case Else
			If variantKey = "none" Then
				back = base100
				text = baseContent
				border = base200
			Else
				back = accentBack
				text = accentText
				border = accentBack
			End If
	End Select

	If mToggle And mChecked Then
		Dim checkedBackFallback As Int = B4XDaisyVariants.ResolveVariantColor(palette, "success", "back", accentBack)
		Dim checkedTextFallback As Int = B4XDaisyVariants.ResolveVariantColor(palette, "success", "text", accentText)
		back = IIf(mCheckedColor <> 0, mCheckedColor, checkedBackFallback)
		text = IIf(mCheckedTextColor <> 0, mCheckedTextColor, checkedTextFallback)
		border = back
	End If

	If mBackgroundColor <> 0 Then back = mBackgroundColor
	If mTextColor <> 0 Then text = mTextColor
	If mBorderColor <> 0 Then border = mBorderColor
	Return CreateMap("back": back, "text": text, "border": border)
End Sub

Private Sub ApplyShadow
	B4XDaisyVariants.ApplyElevation(Surface, mShadow)
End Sub

Private Sub EstimatePreferredWidth As Int
	' Empty badge = perfect circle: width must equal height so radius=height/2 gives a circle.
	If IsEmptyBadge Then Return Max(16dip, mHeight)
	Dim sizeMap As Map = ResolveSizeSpec
	Dim f As Float = Max(9, sizeMap.GetDefault("font", 14))
	Dim gap As Int = Max(2dip, sizeMap.GetDefault("gap", 5dip))
	Dim textW As Int = MeasureSingleLineWidth(ResolveDisplayText, f)
	Dim avatarW As Int = IIf(mAvatarVisible, Max(10dip, sizeMap.GetDefault("avatar", 14dip)) + gap, 0)
	Dim hasLeftIcon As Boolean = (mIconAsset.Length > 0) Or (mToggle And mChecked)
	Dim iconW As Int = IIf(hasLeftIcon, Max(8dip, sizeMap.GetDefault("icon", 11dip)) + gap, 0)
	Dim hasRightIcon As Boolean = (mToggle = False) And mClosable
	Dim rightIconW As Int = IIf(hasRightIcon, Max(8dip, sizeMap.GetDefault("close", 11dip)) + 2dip + gap, 0)
	Dim contentW As Int = Max(0, textW + avatarW + iconW + rightIconW)
	Dim box As Map = BuildBoxModel
	Return Max(1dip, Ceil(B4XDaisyBoxModel.ExpandContentWidth(contentW, box)))
End Sub

Private Sub MeasureSingleLineWidth(Text As String, FontSize As Float) As Int
	If Text.Length = 0 Then Return 0
	If lblText.IsInitialized Then
		Try
			Dim l As Label = lblText
			Return B4XDaisyVariants.MeasureTextWidthSafe(Text, FontSize, l.Typeface, 2dip)
		Catch
		End Try 'ignore
	End If
	' Fallback used before views are attached (e.g. AddToParent pre-creation path).
	Return Max(1dip, Ceil(Text.Length * FontSize * 0.62) + 2dip)
End Sub


Private Sub ResolveBadgeRadiusDip(SizeDip As Float) As Float
	Dim mode As String = B4XDaisyVariants.NormalizeRounded(mRounded)
	If mode = "theme" Then
		If mRoundedBox = False Then Return 0
		If IsEmptyBadge Then Return SizeDip / 2
		Return B4XDaisyVariants.GetRadiusSelectorDip(4dip)
	End If
	Select Case mode
		Case "rounded-none"
			Return 0
		Case "rounded-sm"
			Return Min(SizeDip / 2, 2dip)
		Case "rounded"
			Return Min(SizeDip / 2, 4dip)
		Case "rounded-md"
			Return Min(SizeDip / 2, 6dip)
		Case "rounded-lg"
			Return Min(SizeDip / 2, 8dip)
		Case "rounded-xl"
			Return Min(SizeDip / 2, 12dip)
		Case "rounded-2xl"
			Return Min(SizeDip / 2, 16dip)
		Case "rounded-3xl"
			Return Min(SizeDip / 2, 24dip)
		Case "rounded-full"
			Return SizeDip / 2
		Case Else
			Return B4XDaisyVariants.GetRadiusSelectorDip(4dip)
	End Select
End Sub

Private Sub IsEmptyBadge As Boolean
	If mAvatarVisible Then Return False
	If mIconAsset.Trim.Length > 0 Then Return False
	If mClosable Then Return False
	If mToggle And mChecked Then Return False
	If B4XDaisyVariants.NormalizeSingleLineText(mText).Length > 0 Then Return False
	Return True
End Sub








Private Sub ResolveDisplayText As String
	Dim raw As String = B4XDaisyVariants.NormalizeSingleLineText(mText)
	If mCapValue <= 0 Then Return raw
	If raw.Length = 0 Then Return raw
	If IsNumber(raw) Then
		Dim n As Int = raw
		If n >= mCapValue + 1 Then
			Return mCapValue & "+"
		End If
	End If
	Return raw
End Sub

Private Sub Refresh

	If mBase.IsInitialized = False Then Return
	If mFitContentWidth Then
		Dim targetW As Int = Max(1dip, EstimatePreferredWidth)
		Dim targetH As Int = Max(1dip, mBase.Height)
		mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, targetW, targetH)
	End If
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub surface_Click
	If mClickable = False Then Return
	If mToggle Then
		ToggleCheckedFromInteraction
	Else If mClosable Then
		HandleCloseTap
	Else
		RaiseClick
	End If
End Sub

Private Sub closebtn_Click
	If mClickable = False Then Return
	If mToggle Then
		ToggleCheckedFromInteraction
	Else
		HandleCloseTap
	End If
End Sub

Private Sub HandleCloseTap
	If mClosable = False Then Return
	setVisible(False)
	RaiseCloseClick
End Sub

Private Sub ToggleCheckedFromInteraction
	If mToggle = False Then Return
	mChecked = Not(mChecked)
	Refresh
	RaiseCheckedEvent
End Sub

Private Sub RaiseClick
	Dim payload As Object = mTag
	If payload = Null Then payload = mBase
	If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
		CallSub2(mCallBack, mEventName & "_Click", payload)
	Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
		CallSub(mCallBack, mEventName & "_Click")
	End If
End Sub

Private Sub RaiseCloseClick
	Dim payload As Object = mTag
	If payload = Null Then payload = mBase
	If xui.SubExists(mCallBack, mEventName & "_CloseClick", 1) Then
		CallSub2(mCallBack, mEventName & "_CloseClick", payload)
	Else If xui.SubExists(mCallBack, mEventName & "_CloseClick", 0) Then
		CallSub(mCallBack, mEventName & "_CloseClick")
	End If
End Sub

Private Sub RaiseCheckedEvent
	Dim idValue As String = mId
	If xui.SubExists(mCallBack, mEventName & "_Checked", 2) Then
		CallSub3(mCallBack, mEventName & "_Checked", idValue, mChecked)
	Else If xui.SubExists(mCallBack, mEventName & "_CheckedChanged", 2) Then
		CallSub3(mCallBack, mEventName & "_CheckedChanged", idValue, mChecked)
	End If
End Sub

'========================
' Public API
'========================

Public Sub setWidth(Value As Object)
	mFitContentWidth = False
	mWidth = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveWidthBase(mBase, mWidth)))
	mWidthExplicit = True
	If mBase.IsInitialized = False Then Return
	Dim targetH As Int = Max(1dip, mBase.Height)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, mWidth, targetH)
	Base_Resize(mWidth, targetH)
End Sub

Public Sub getWidth As Float
	Return mWidth
End Sub

Public Sub setHeight(Value As Object)
	mHeight = Max(16dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveHeightBase(mBase, mHeight)))
	mHeightExplicit = True
	If mBase.IsInitialized = False Then Return
	Dim targetW As Int = Max(1dip, mBase.Width)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, targetW, mHeight)
	Base_Resize(targetW, mHeight)
End Sub

Public Sub getHeight As Float
	Return mHeight
End Sub

Public Sub setSize(Value As String)
	mSize = B4XDaisyVariants.NormalizeSize(Value)
	If mHeightExplicit = False Then
		Dim sz As Map = ResolveSizeSpec
		mHeight = Max(16dip, sz.GetDefault("height", 24dip))
	End If
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getSize As String
	Return mSize
End Sub

Public Sub setVariant(Value As String)
	mVariant = B4XDaisyVariants.NormalizeVariant(Value)
	' Reset manual colors to ensure variant colors take full effect. (Last one wins)
	mBackgroundColor = 0
	mTextColor = 0
	mBorderColor = 0
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getVariant As String
	Return mVariant
End Sub

Public Sub setBadgeStyle(Value As String)
	mBadgeStyle = B4XDaisyVariants.NormalizeStyle(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBadgeStyle As String
	Return mBadgeStyle
End Sub

Public Sub setStyle(Value As String)
	setBadgeStyle(Value)
End Sub

Public Sub getStyle As String
	Return mBadgeStyle
End Sub

Public Sub setText(Value As String)
	If Value = Null Then Value = ""
	mText = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getText As String
	Return mText
End Sub

Public Sub setTextCentered(Value As Boolean)
	mTextCentered = Value
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getTextCentered As Boolean
	Return mTextCentered
End Sub

Public Sub setPadding(Value As String)
	mPadding = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getPadding As String
	Return mPadding
End Sub

Public Sub setMargin(Value As String)
	mMargin = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getMargin As String
	Return mMargin
End Sub

Public Sub setVisible(Value As Boolean)
	mVisible = Value
	If mBase.IsInitialized = False Then Return
	mBase.Visible = mVisible
End Sub

Public Sub getVisible As Boolean
	Return mVisible
End Sub

Public Sub setRounded(Value As String)
	mRounded = B4XDaisyVariants.NormalizeRounded(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getRounded As String
	Return mRounded
End Sub

Public Sub setRoundedBox(Value As Boolean)
	mRoundedBox = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getRoundedBox As Boolean
	Return mRoundedBox
End Sub

Public Sub setShadow(Value As String)
	mShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getShadow As String
	Return mShadow
End Sub

Public Sub setAvatarVisible(Value As Boolean)
	mAvatarVisible = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getAvatarVisible As Boolean
	Return mAvatarVisible
End Sub

Public Sub setAvatarImage(Value As String)
	If Value = Null Then Value = ""
	mAvatarImage = Value.Trim
	If mAvatarImage.Length > 0 Then mAvatarVisible = True
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getAvatarImage As String
	Return mAvatarImage
End Sub

Public Sub setAvatarText(Value As String)
	If Value = Null Then Value = ""
	mAvatarText = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getAvatarText As String
	Return mAvatarText
End Sub

Public Sub setAvatarPosition(Value As String)
	mAvatarPosition = B4XDaisyVariants.NormalizeAvatarPosition(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getAvatarPosition As String
	Return mAvatarPosition
End Sub

Public Sub setIconAsset(Value As String)
	If Value = Null Then Value = ""
	mIconAsset = Value.Trim
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getIconAsset As String
	Return mIconAsset
End Sub

Public Sub setToggle(Value As Boolean)
	mToggle = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getToggle As Boolean
	Return mToggle
End Sub

Public Sub setChecked(Value As Boolean)
	mChecked = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getChecked As Boolean
	Return mChecked
End Sub

Public Sub setCheckedColor(Value As Int)
	mCheckedColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getCheckedColor As Int
	Return mCheckedColor
End Sub

Public Sub setCheckedTextColor(Value As Int)
	mCheckedTextColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getCheckedTextColor As Int
	Return mCheckedTextColor
End Sub

Public Sub setId(Value As String)
	If Value = Null Then Value = ""
	mId = Value
End Sub

Public Sub getId As String
	Return mId
End Sub

Public Sub setClosable(Value As Boolean)
	mClosable = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getClosable As Boolean
	Return mClosable
End Sub

Public Sub setCloseIconAsset(Value As String)
	If Value = Null Then Value = ""
	mCloseIconAsset = Value.Trim
	If mCloseIconAsset.Length = 0 Then mCloseIconAsset = "xmark-solid.svg"
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getCloseIconAsset As String
	Return mCloseIconAsset
End Sub


Public Sub setCapValue(Value As Int)
	mCapValue = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getCapValue As Int
	Return mCapValue
End Sub

Public Sub setValue(Value As Int)
	mText = "" & Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getValue As Int
	Try
		Dim n As Int = mText.Trim
		Return n
	Catch
		Log("B4XDaisyBadge.getValue: " & LastException.Message)
	End Try
	Return 0
End Sub

' Increment the badge value by 1. Only works when mText is a valid integer.
Public Sub increment As Int
	Return incrementBy(1)
End Sub

' Increment the badge value by the given amount. Only works when mText is a valid integer.
Public Sub incrementBy(Amount As Int) As Int
	Try
		Dim n As Int = mText.Trim
		n = n + Amount
		mText = "" & n
		If mBase.IsInitialized Then Refresh
		Return n
	Catch
		Log("B4XDaisyBadge.incrementBy: " & LastException.Message)
	End Try
	Return getValue
End Sub

' Decrement the badge value by 1 (minimum 0). Only works when mText is a valid integer.
Public Sub decrement As Int
	Return decrementBy(1)
End Sub

' Decrement the badge value by the given amount (minimum 0). Only works when mText is a valid integer.
Public Sub decrementBy(Amount As Int) As Int
	Try
		Dim n As Int = mText.Trim
		n = n - Amount
		If n < 0 Then n = 0
		mText = "" & n
		If mBase.IsInitialized Then Refresh
		Return n
	Catch
		Log("B4XDaisyBadge.decrementBy: " & LastException.Message)
	End Try
	Return getValue
End Sub
Public Sub setBackgroundColor(Value As Int)
	mBackgroundColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBackgroundColor As Int
	Return mBackgroundColor
End Sub


Public Sub setBackgroundColorVariant(VariantName As String)
	Dim xColors As Map = ResolveVisualColors
	Dim fallback As Int = xColors.GetDefault("back", mBackgroundColor)
	Dim c As Int = B4XDaisyVariants.ResolveBackgroundColorVariantFromPalette(B4XDaisyVariants.GetVariantPalette, VariantName, fallback)
	setBackgroundColor(c)
End Sub

Public Sub setTextColor(Value As Int)
	mTextColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getTextColor As Int
	Return mTextColor
End Sub

Public Sub setTextColorVariant(VariantName As String)
	Dim xColors As Map = ResolveVisualColors
	Dim fallback As Int = xColors.GetDefault("text", mTextColor)
	Dim c As Int = B4XDaisyVariants.ResolveTextColorVariantFromPalette(B4XDaisyVariants.GetVariantPalette, VariantName, fallback)
	setTextColor(c)
End Sub

Public Sub setBorderColor(Value As Int)
	mBorderColor = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderColor As Int
	Return mBorderColor
End Sub

Public Sub setBorderColorVariant(VariantName As String)
	Dim xColors As Map = ResolveVisualColors
	Dim fallback As Int = xColors.GetDefault("border", mBorderColor)
	Dim c As Int = B4XDaisyVariants.ResolveBorderColorVariantFromPalette(B4XDaisyVariants.GetVariantPalette, VariantName, fallback)
	setBorderColor(c)
End Sub



Public Sub setTag(Value As Object)
	mTag = Value
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

''' Sets whether the badge intercepts touch events.
''' When False, touches pass through to the parent view (e.g. a list row).
''' When True, the badge handles clicks normally via the Click event.
Public Sub setClickable(Value As Boolean)
	mClickable = Value
	#If B4A
	If mBase.IsInitialized Then
		Dim joBase As JavaObject = mBase
		joBase.RunMethod("setClickable", Array(Value))
		If Surface.IsInitialized Then
			Dim joS As JavaObject = Surface
			joS.RunMethod("setClickable", Array(Value))
		End If
		If CloseHost.IsInitialized Then
			Dim joC As JavaObject = CloseHost
			joC.RunMethod("setClickable", Array(Value))
		End If
	End If
	#Else If B4J
	If mBase.IsInitialized Then
		mBase.Enabled = Value
		If Surface.IsInitialized Then Surface.Enabled = Value
		If CloseHost.IsInitialized Then CloseHost.Enabled = Value
	End If
	#Else If B4i
	If mBase.IsInitialized Then
		mBase.Enabled = Value
		If Surface.IsInitialized Then Surface.Enabled = Value
		If CloseHost.IsInitialized Then CloseHost.Enabled = Value
	End If
	#End If
	' Propagate to child components
	If AvatarComp.IsInitialized Then AvatarComp.setClickable(Value)
	If LeftIconComp.IsInitialized Then LeftIconComp.setClickable(Value)
	If CloseIconComp.IsInitialized Then CloseIconComp.setClickable(Value)
End Sub

''' Gets whether the badge is clickable.
Public Sub getClickable As Boolean
	Return mClickable
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

#End Region

---

## B4XDaisyAvatarGroup.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: full, Description: Tailwind size token or CSS size (eg full, 72, 320px, 20rem)
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-12, Description: Tailwind size token or CSS size (eg h-12, 80px, 5rem)
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Padding utility/value for group content (eg p-2, px-3, 2)
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Margin utility/value for group host insets (eg m-2, mx-1.5, 1)
#DesignerProperty: Key: Spacing, DisplayName: Spacing, FieldType: String, DefaultValue: -space-x-6, Description: Overlap or gap utility (eg -space-x-6, space-x-4)
#DesignerProperty: Key: AvatarSize, DisplayName: Avatar Size, FieldType: String, DefaultValue: 12, Description: Tailwind size for avatars (e.g. 12, 16, 24)
#DesignerProperty: Key: LimitTo, DisplayName: Limit To, FieldType: Int, DefaultValue: 5, Description: Max avatars shown before overflow placeholder (+N)

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mCallBack As Object
	Private mEventName As String
	Private mTag As Object

	Private mWidth As Float = 160dip
	Private mHeight As Float = 48dip
	Private mWidthExplicit As Boolean = False
	Private mHeightExplicit As Boolean = False
	Private mPadding As String = ""
	Private mMargin As String = ""
	Private mSpacing As String = "-space-x-6"
	Private mAvatarSize As Object = "12"
	Private mLimitTo As Int = 5
	Private Items As List
	Private OverflowAvatar As B4XDaisyAvatar
	Private mOverflowCount As Int = 0
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	Items.Initialize
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent
	ApplyDesignerProps(Props)
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, w, h)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(w))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(h))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Parent.AddView(mBase, Left, Top, w, h)
	Return mBase
End Sub


Public Sub View As B4XView
	Dim empty As B4XView
	If mBase.IsInitialized = False Then Return empty
	Return mBase
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub







Private Sub ApplyDesignerProps(Props As Map)
mWidth = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(B4XDaisyVariants.GetPropString(Props, "Width", "full"), B4XDaisyVariants.ResolveWidthBase(mBase, mWidth)))
	mHeight = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(B4XDaisyVariants.GetPropString(Props, "Height", "h-12"), B4XDaisyVariants.ResolveHeightBase(mBase, mHeight)))
	mWidthExplicit = Props.ContainsKey("Width")
	mHeightExplicit = Props.ContainsKey("Height")
	mPadding = B4XDaisyVariants.GetPropString(Props, "Padding", mPadding)
	mMargin = B4XDaisyVariants.GetPropString(Props, "Margin", mMargin)
	mSpacing = B4XDaisyVariants.GetPropString(Props, "Spacing", mSpacing)
	mAvatarSize = Props.GetDefault("AvatarSize", mAvatarSize)
	mLimitTo = Max(0, B4XDaisyVariants.GetPropInt(Props, "LimitTo", mLimitTo))
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub



Public Sub AddAvatar(Avatar As B4XDaisyAvatar) As Int
	Dim v As B4XView = Avatar.View
	If v.IsInitialized = False Then Return -1
	
	' --- Limit-aware overflow logic ---
	' If adding this avatar would exceed the limit (and limit > 0), create or update the overflow placeholder.
	If mLimitTo > 0 And Items.Size >= mLimitTo Then
		mOverflowCount = mOverflowCount + 1
		If OverflowAvatar.IsInitialized = False Then
			CreateOverflowPlaceholder
		Else
			OverflowAvatar.SetPlaceHolder("+" & mOverflowCount)
		End If
		Return -1
	End If
	
	' --- Normal add (slot available or no limit) ---
	ApplyGroupStyle(Avatar)
	Dim tag As Object = Avatar.getAvatarTag
	Dim idx As Int = AddAvatarViewInternal(v, tag, Avatar)
	Return idx
End Sub

Private Sub ApplyGroupStyle(Avatar As B4XDaisyAvatar)
	' DaisyUI avatar-group spec: circle mask + 2px base-100 ring + 0 offset
	Dim ringW As Float = 2dip
	Dim ringColor As Int = ResolveBorderColor
	' Order matters: Size and Mask first, then manual ring overrides
	Avatar.setAvatarSize(mAvatarSize)
	Avatar.SetAvatarMask("rounded-full")
	Avatar.setRingWidth(ringW)
	Avatar.setRingColor(ringColor)
	Avatar.setRingOffset(0)
End Sub

Private Sub CreateOverflowPlaceholder
	' Build the "+N" pill avatar and add it as a regular item in the Items list.
	OverflowAvatar.Initialize(mCallBack, "overflow_placeholder")
	OverflowAvatar.CreateView(32dip, 32dip) ' Initial size, will be reset by Relayout/ApplyGroupStyle
	OverflowAvatar.SetAvatarType("text")
	OverflowAvatar.SetPlaceHolder("+" & mOverflowCount)
	OverflowAvatar.SetVariant("neutral")
	OverflowAvatar.SetBackgroundColorVariant("neutral")
	OverflowAvatar.SetTextColorVariant("neutral-content")
	OverflowAvatar.TextSize = "text-sm"
	
	' Ensure ring is enabled explicitly before applying group style
	OverflowAvatar.SetRingWidth(2dip)
	ApplyGroupStyle(OverflowAvatar)
	
	Dim v As B4XView = OverflowAvatar.View
	Dim tag As Object = OverflowAvatar.getAvatarTag
	AddAvatarViewInternal(v, tag, OverflowAvatar)
End Sub

Public Sub AddAvatarView(ChildView As B4XView, Tag As Object) As Int
	Return AddAvatarViewInternal(ChildView, Tag, Null)
End Sub

Private Sub AddAvatarViewInternal(ChildView As B4XView, Tag As Object, AvatarObj As Object) As Int
	If ChildView.IsInitialized = False Then Return -1
	Dim item As Map = CreateMap("view": ChildView, "tag": Tag, "avatar": AvatarObj)
	Items.Add(item)
	If mBase.IsInitialized Then
		If ChildView.Parent.IsInitialized Then
			Dim parentView As B4XView = ChildView.Parent
			If parentView.IsInitialized And parentView <> mBase Then ChildView.RemoveViewFromParent
		End If
		If ChildView.Parent.IsInitialized = False Then mBase.AddView(ChildView, 0, 0, Max(1dip, ChildView.Width), Max(1dip, ChildView.Height))
		Relayout
	End If
	Return Items.Size - 1
End Sub

Public Sub Clear
	For i = 0 To Items.Size - 1
		Dim it As Map = Items.Get(i)
		Dim v As B4XView = it.Get("view")
		If v.IsInitialized Then v.RemoveViewFromParent
	Next
	Items.Clear
	mOverflowCount = 0
	Dim emptyAvatar As B4XDaisyAvatar
	OverflowAvatar = emptyAvatar
End Sub

Public Sub getCount As Int
	Return Items.Size
End Sub

Public Sub setWidth(Value As Object)
	mWidth = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveWidthBase(mBase, mWidth)))
	mWidthExplicit = True
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, mWidth, Max(1dip, mBase.Height))
	Relayout
End Sub

Public Sub getWidth As Float
	Return mWidth
End Sub

Public Sub setHeight(Value As Object)
	mHeight = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveHeightBase(mBase, mHeight)))
	mHeightExplicit = True
	If mBase.IsInitialized = False Then Return
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, Max(1dip, mBase.Width), mHeight)
	Relayout
End Sub

Public Sub getHeight As Float
	Return mHeight
End Sub

Public Sub setPadding(Value As String)
	mPadding = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub getPadding As String
	Return mPadding
End Sub

Public Sub setMargin(Value As String)
	mMargin = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub getMargin As String
	Return mMargin
End Sub

Public Sub setSpacing(Value As String)
	mSpacing = IIf(Value = Null, "", Value)
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub getSpacing As String
	Return mSpacing
End Sub



Public Sub applyActiveTheme
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub setAvatarSize(Value As Object)
	mAvatarSize = Value
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub getAvatarSize As Object
	Return mAvatarSize
End Sub

Public Sub setLimitTo(Value As Int)
	mLimitTo = Max(1, Value)
	If mBase.IsInitialized = False Then Return
	Relayout
End Sub

Public Sub getLimitTo As Int
	Return mLimitTo
End Sub



Private Sub Relayout
	If mBase.IsInitialized = False Then Return
	Dim box As Map = BuildBoxModel
	Dim host As B4XRect
	host.Initialize(0, 0, Max(1dip, mBase.Width), Max(1dip, mBase.Height))
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Dim contentRect As B4XRect = B4XDaisyBoxModel.ResolveContentRect(outerRect, box)
	Dim borderColor As Int = ResolveBorderColor
	Dim x As Int = contentRect.Left
	Dim h As Int = Max(1dip, contentRect.Height)
	Dim yTop As Int = contentRect.Top
	
	' Resolve avatar size for positioning only
	Dim resolvedSize As Float = Max(16dip, B4XDaisyVariants.TailwindSizeToDip(mAvatarSize, h))
	
	' Parse spacing string (e.g. "-space-x-6" -> -24dip, "space-x-4" -> 16dip)
	Dim spacingVal As Float = 0
	Dim spc As String = mSpacing.ToLowerCase.Trim
	If spc.Length > 0 Then
		Dim isNeg As Boolean = spc.StartsWith("-")
		If isNeg Then spc = spc.SubString(1).Trim
		If spc.StartsWith("space-x-") Then
			Dim token As String = spc.SubString("space-x-".Length)
			spacingVal = B4XDaisyBoxModel.TailwindSpacingToDip(token, 0dip)
			If isNeg Then spacingVal = -spacingVal
		End If
	End If
	
	' Position every item ? all items in the list are visible (overflow is already a regular item)
	For i = 0 To Items.Size - 1
		Dim item As Map = Items.Get(i)
		Dim v As B4XView = item.Get("view")
		If v.IsInitialized = False Then Continue
		
		If v.Parent.IsInitialized = False Then mBase.AddView(v, 0, 0, Max(1dip, v.Width), Max(1dip, v.Height))
		
		' Container size matches resolvedSize
		Dim w As Int = resolvedSize
		Dim vh As Int = resolvedSize
		Dim y As Int = yTop + Max(0, (h - vh) / 2)
		
		v.SetLayoutAnimated(0, x, y, w, vh)
		ApplyAvatarItemStyle(item, borderColor)
		
		x = x + w + spacingVal
	Next
	
	' Enforce left-to-right overlaps (Z-Index): later items draw on top.
	For i = 0 To Items.Size - 1
		Dim item As Map = Items.Get(i)
		Dim v As B4XView = item.Get("view")
		If v.IsInitialized And v.Parent.IsInitialized Then
			v.BringToFront
		End If
	Next
End Sub



Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	ApplySpacingSpecToBox(box, mPadding, mMargin)
	Return box
End Sub

Private Sub ApplySpacingSpecToBox(Box As Map, PaddingSpec As String, MarginSpec As String)
	Dim rtl As Boolean = False
	Dim p As String = IIf(PaddingSpec = Null, "", PaddingSpec.Trim)
	Dim m As String = IIf(MarginSpec = Null, "", MarginSpec.Trim)
	If p.Length > 0 Then
		If p.Contains("-") Then
			B4XDaisyBoxModel.ApplyPaddingUtilities(Box, p, rtl)
		Else
			Dim pv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(p, 0dip)
			Box.Put("padding_left", pv)
			Box.Put("padding_top", pv)
			Box.Put("padding_right", pv)
			Box.Put("padding_bottom", pv)
		End If
	End If
	If m.Length > 0 Then
		If m.Contains("-") Then
			B4XDaisyBoxModel.ApplyMarginUtilities(Box, m, rtl)
		Else
			Dim mv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(m, 0dip)
			Box.Put("margin_left", mv)
			Box.Put("margin_top", mv)
			Box.Put("margin_right", mv)
			Box.Put("margin_bottom", mv)
		End If
	End If
End Sub

Private Sub ApplyAvatarItemStyle(Item As Map, BorderColor As Int)
	Dim v As B4XView = Item.Get("view")
	If v.IsInitialized = False Then Return
	
	Dim avatarObj As Object = Item.GetDefault("avatar", Null)
	If avatarObj <> Null Then
		' Styles (mask, ring, offset) already applied in AddAvatar.
		' Here we only trigger the internal redraw to match the container size set by Relayout.
		If xui.SubExists(avatarObj, "Base_Resize", 2) Then
			CallSub3(avatarObj, "Base_Resize", v.Width, v.Height)
		Else If xui.SubExists(avatarObj, "ResizeToParent", 1) Then
			CallSub2(avatarObj, "ResizeToParent", v)
		End If
	Else
		' Fallback to OS native border for raw non-Avatar views inside the group
		Dim r As Float = Min(v.Width, v.Height) / 2
		v.SetColorAndBorder(xui.Color_Transparent, 2dip, BorderColor, r)
	End If
End Sub

Private Sub ResolveBorderColor As Int
	Return B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_White)
End Sub





Public Sub setTag(Value As Object)
	mTag = Value
	If mBase.IsInitialized = False Then Return
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyAvatar.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Event: Click

#DesignerProperty: Key: Image, DisplayName: Image Path, FieldType: String, DefaultValue:, Description: Full image path on device
#DesignerProperty: Key: Mask, DisplayName: Mask, FieldType: String, DefaultValue: circle, List: circle|square|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full|squircle|decagon|diamond|heart|hexagon|hexagon-2|pentagon|star|star-2|triangle|triangle-2|triangle-3|triangle-4|half-1|half-2, Description: Avatar mask shape
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: False, Description: Use rounded box mask (radius: 4dip). Overrides mask setting.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation shadow level (Tailwind/Daisy scale)
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|neutral|primary|secondary|accent|info|success|warning|error, Description: Variant used for placeholder and status colors
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: w-10, Description: Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem)
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-10, Description: Tailwind size token or CSS size (eg h-12, 80px, 4em, 5rem)
#DesignerProperty: Key: AvatarType, DisplayName: Avatar Type, FieldType: String, DefaultValue: image, List: image|svg|text, Description: Content type rendered inside avatar
#DesignerProperty: Key: PlaceHolder, DisplayName: Placeholder, FieldType: String, DefaultValue:, Description: Placeholder text for text type (and image/svg fallback)
#DesignerProperty: Key: TextSize, DisplayName: Text Size, FieldType: String, DefaultValue: text-sm, Description: Placeholder text size token (eg text-sm, text-lg). Empty = auto-fit
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: Color, DefaultValue: 0x00000000, Description: Placeholder text color (0 = theme base-content)
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: Color, DefaultValue: 0x00000000, Description: Placeholder background color (0 = variant/theme fallback)
#DesignerProperty: Key: Padding, DisplayName: Padding, FieldType: String, DefaultValue:, Description: Padding utility/value for avatar drawing area (eg p-2, px-1, 2)
#DesignerProperty: Key: Margin, DisplayName: Margin, FieldType: String, DefaultValue:, Description: Margin utility/value for avatar host insets (eg m-2, mx-1.5, 1)
#DesignerProperty: Key: CenterOnParent, DisplayName: Center On Parent, FieldType: Boolean, DefaultValue: True, Description: Center avatar inside parent bounds
#DesignerProperty: Key: ChatImage, DisplayName: Chat Image Mode, FieldType: Boolean, DefaultValue: False, Description: Apply chat-image rendering defaults (shared with chat bubble usage)
#DesignerProperty: Key: Status, DisplayName: Status, FieldType: String, DefaultValue: none, List: none|online|offline, Description: Online indicator status
#DesignerProperty: Key: ShowOnline, DisplayName: Show Online Indicator, FieldType: Boolean, DefaultValue: False, Description: Show online/offline indicator dot
#DesignerProperty: Key: UseVariantStatusColors, DisplayName: Use Variant Colors, FieldType: Boolean, DefaultValue: False, Description: Derive online/offline colors from current variant (default keeps success green / gray)
#DesignerProperty: Key: OnlineColor, DisplayName: Online Color, FieldType: Color, DefaultValue: 0x00000000, Description: Override online color (0 means auto)
#DesignerProperty: Key: OfflineColor, DisplayName: Offline Color, FieldType: Color, DefaultValue: 0x00000000, Description: Override offline color (0 means auto)
#DesignerProperty: Key: RingColor, DisplayName: Ring Color, FieldType: Color, DefaultValue: 0x00000000, Description: Ring color override (0 means auto by variant)
#DesignerProperty: Key: RingWidth, DisplayName: Ring Width, FieldType: Int, DefaultValue: 0, Description: Ring stroke width in dip
#DesignerProperty: Key: RingOffset, DisplayName: Ring Offset, FieldType: Int, DefaultValue: 0, Description: Space between image and ring in dip
#DesignerProperty: Key: Clickable, DisplayName: Clickable, FieldType: Boolean, DefaultValue: True, Description: When False, touch events pass through to parent (useful inside clickable list rows)
#DesignerProperty: Key: ResizeMode, DisplayName: Resize Mode, FieldType: String, DefaultValue: FILL_NO_DISTORTIONS, List: FIT|FILL|FILL_NO_DISTORTIONS|NONE, Description: Aspect scaling mode (similar to B4XImageView ResizeMode)
#DesignerProperty: Key: BlurRadius, DisplayName: Blur Radius, FieldType: Int, DefaultValue: 0, Description: Image blur level (0 = disabled, 1 to 25 = scale-down blur factor)
#DesignerProperty: Key: Glass, DisplayName: Glass Effect, FieldType: Boolean, DefaultValue: False, Description: Enable translucent glass shine and highlight borders

#IgnoreWarnings:12,9
Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object
	Private msVariant As String = "none"

	Private mWidth As Float = 40dip
	Private mHeight As Float = 40dip
	Private mWidthToken As String = "w-10"
	Private mHeightToken As String = "h-10"
	Private mWidthExplicit As Boolean = False
	Private mHeightExplicit As Boolean = False
	Private msAvatarType As String = "image"
	Private PlaceholderText As String = ""
	Private PlaceholderTextSize As String = "text-sm"
	Private PlaceholderTextColor As Int = 0
	Private PlaceholderBackgroundColor As Int = 0
	Private mPadding As String = ""
	Private mMargin As String = ""
	Private mbCenterOnParent As Boolean = True
	Private mbChatImage As Boolean = False
	Private AvatarMask As String = "circle"
	Private mRoundedBox As Boolean = False
	Private AvatarPath As String = ""
	Private AvatarBmp As B4XBitmap
	Private AvatarTag As Object

	Private AvatarStatus As String = "none" 'none|online|offline
	Private OnlineIndicatorVisible As Boolean = False
	Private mbUseVariantStatusColors As Boolean = False
	Private AvatarOnlineColor As Int = 0xFF2ECC71
	Private AvatarOfflineColor As Int = 0xFFB4B4B4
	Private CustomOnlineColor As Int = 0
	Private CustomOfflineColor As Int = 0

	Private AvatarBorderColor As Int = 0
	Private AvatarBorderWidth As Float = 0dip
	Private mcRingColor As Int = 0
	Private mfRingOffset As Float = 0dip
	Private mClickable As Boolean = True
	Private msShadow As String = "none"

	Private msResizeMode As String = "FILL_NO_DISTORTIONS"
	Private miBlurRadius As Int = 0
	Private mbGlass As Boolean = False

	Private VariantPalette As Map
	Private DefaultVariantPalette As Map

	Private ivAvatar As B4XView
	Private AvatarCvs As B4XCanvas
	Private AvatarCanvasReady As Boolean = False
	Private LastUpscaleWarningKey As String = ""
	Private mImage As B4XDaisyImage
	Private ivIndicator As Panel
	Private IndicatorCvs As B4XCanvas
	Private IndicatorCvsReady As Boolean = False
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	InitializePalette
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, w, h)
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(w))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(h))
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Parent.AddView(mBase, Left, Top, w, h)
	Return mBase
End Sub

Public Sub View As B4XView
	Dim empty As B4XView
	If mBase.IsInitialized = False Then Return empty
	Return mBase
End Sub

Public Sub getView As B4XView
	Return mBase
End Sub

Public Sub GetActualHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

Public Sub GetActualWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized = False Then Return
	mBase.AddView(ChildView, Left, Top, Width, Height)
End Sub

Sub IsReady As Boolean
	Return mBase.IsInitialized And ivAvatar.IsInitialized And mBase.Width > 0 And mBase.Height > 0
End Sub

Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	Dim pAvatar As Panel
	pAvatar.Initialize("ivAvatar")
	ivAvatar = pAvatar
	ivAvatar.Color = xui.Color_Transparent
	mBase.AddView(ivAvatar, 0, 0, mWidth, mHeight)

	mImage.Initialize(Me, "mImage")
	mImage.AddToParent(ivAvatar, 0, 0, ivAvatar.Width, ivAvatar.Height)
	mImage.mBase.Visible = False

	' Overlay panel for the online/offline indicator — always above mImage
	ivIndicator.Initialize("")
	Dim ivInd As B4XView = ivIndicator
	ivInd.Color = xui.Color_Transparent
	ivAvatar.AddView(ivInd, 0, 0, ivAvatar.Width, ivAvatar.Height)
	ivInd.BringToFront

	InitializePalette
	ApplyDesignerProps(Props)
	' Apply clickable state to native views
	If mClickable = False Then
		#If B4A
		Dim joBase2 As JavaObject = mBase
		joBase2.RunMethod("setClickable", Array(False))
		If ivAvatar.IsInitialized Then
			Dim joIv2 As JavaObject = ivAvatar
			joIv2.RunMethod("setClickable", Array(False))
		End If
		#Else If B4J
		mBase.Enabled = False
		If ivAvatar.IsInitialized Then ivAvatar.Enabled = False
		#Else If B4i
		mBase.Enabled = False
		If ivAvatar.IsInitialized Then ivAvatar.Enabled = False
		#End If
	End If
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	If mBase.IsInitialized = False Or ivAvatar.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)

	Dim box As Map = BuildBoxModel
	Dim host As B4XRect
	host.Initialize(0, 0, w, h)
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Dim contentRect As B4XRect = B4XDaisyBoxModel.ResolveContentRect(outerRect, box)
	Dim availW As Int = Max(1dip, contentRect.Width)
	Dim availH As Int = Max(1dip, contentRect.Height)
	Dim drawW As Int = Max(1dip, mWidth)
	Dim drawH As Int = Max(1dip, mHeight)
	If drawW > availW Then drawW = availW
	If drawH > availH Then drawH = availH
	Dim x As Int = contentRect.Left
	Dim y As Int = contentRect.Top
	If mbCenterOnParent Then
		x = contentRect.Left + (availW - drawW) / 2
		y = contentRect.Top + (availH - drawH) / 2
	End If
	ivAvatar.SetLayoutAnimated(0, x, y, drawW, drawH)
	DrawAvatar
End Sub

'Convenience helper: resize avatar internals to match a parent view size.
Public Sub ResizeToParent(b4xV As B4XView)
	If b4xV.IsInitialized = False Then Return
	Base_Resize(b4xV.Width, b4xV.Height)
End Sub







Private Sub ApplyDesignerProps(Props As Map)
	setVariant(B4XDaisyVariants.GetPropString(Props, "Variant", msVariant))
	setShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow))

	mWidth = Max(1dip, B4XDaisyVariants.GetPropSizeDip(Props, "Width", "w-10"))
	mHeight = Max(1dip, B4XDaisyVariants.GetPropSizeDip(Props, "Height", "h-10"))
	mWidthExplicit = Props.IsInitialized And Props.ContainsKey("Width")
	mHeightExplicit = Props.IsInitialized And Props.ContainsKey("Height")
	'mPlaceholderAutoColor = B4XDaisyVariants.GetPropBool(Props, "PlaceholderAutoColor", True) ' Not used in this class
	'mRadius = Max(0dip, B4XDaisyVariants.GetPropFloat(Props, "Radius", 0) * 1dip) ' Not used in this class

	msAvatarType = NormalizeAvatarType(B4XDaisyVariants.GetPropString(Props, "AvatarType", msAvatarType))
	PlaceholderText = B4XDaisyVariants.GetPropString(Props, "PlaceHolder", PlaceholderText)
	Dim tsVal As String = B4XDaisyVariants.GetPropString(Props, "TextSize", PlaceholderTextSize)
	If tsVal.Length > 0 Then PlaceholderTextSize = tsVal
	PlaceholderTextColor = B4XDaisyVariants.GetPropInt(Props, "TextColor", PlaceholderTextColor)
	PlaceholderBackgroundColor = B4XDaisyVariants.GetPropInt(Props, "BackgroundColor", PlaceholderBackgroundColor)
	mPadding = B4XDaisyVariants.GetPropString(Props, "Padding", mPadding)
	mMargin = B4XDaisyVariants.GetPropString(Props, "Margin", mMargin)
	mbCenterOnParent = B4XDaisyVariants.GetPropBool(Props, "CenterOnParent", mbCenterOnParent)
	mbChatImage = B4XDaisyVariants.GetPropBool(Props, "ChatImage", mbChatImage)
	'Compatibility aliases (older key names).
	If Props.ContainsKey("AvatarWidth") Then mWidthExplicit = True
	If Props.ContainsKey("AvatarHeight") Then mHeightExplicit = True
	If Props.ContainsKey("AvatarSize") Then
		mWidthExplicit = True
		mHeightExplicit = True
	End If

	setAvatarMask(B4XDaisyVariants.GetPropString(Props, "Mask", B4XDaisyVariants.GetPropString(Props, "AvatarMask", AvatarMask)))
	mRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mRoundedBox)
	setAvatarStatus(B4XDaisyVariants.GetPropString(Props, "Status", AvatarStatus))
	setShowOnline(B4XDaisyVariants.GetPropBool(Props, "ShowOnline", OnlineIndicatorVisible))
	mbUseVariantStatusColors = B4XDaisyVariants.GetPropBool(Props, "UseVariantStatusColors", mbUseVariantStatusColors)
	mClickable = B4XDaisyVariants.GetPropBool(Props, "Clickable", mClickable)

	CustomOnlineColor = B4XDaisyVariants.GetPropInt(Props, "OnlineColor", CustomOnlineColor)
	CustomOfflineColor = B4XDaisyVariants.GetPropInt(Props, "OfflineColor", CustomOfflineColor)
	If CustomOnlineColor <> 0 Then AvatarOnlineColor = CustomOnlineColor
	If CustomOfflineColor <> 0 Then AvatarOfflineColor = CustomOfflineColor
	mcRingColor = B4XDaisyVariants.GetPropInt(Props, "RingColor", mcRingColor)
	AvatarBorderWidth = Max(0, B4XDaisyVariants.GetPropDip(Props, "RingWidth", AvatarBorderWidth))
	mfRingOffset = Max(0, B4XDaisyVariants.GetPropDip(Props, "RingOffset", mfRingOffset))
	'Compatibility with earlier internal naming.
	If Props.ContainsKey("AvatarBorderWidth") Then AvatarBorderWidth = Max(0, B4XDaisyVariants.GetPropDip(Props, "AvatarBorderWidth", AvatarBorderWidth))
	If Props.ContainsKey("AvatarBorderInset") Then mfRingOffset = Max(0, B4XDaisyVariants.GetPropDip(Props, "AvatarBorderInset", mfRingOffset))

	setAvatar(B4XDaisyVariants.GetPropString(Props, "Image", B4XDaisyVariants.GetPropString(Props, "Avatar", "")))
	msResizeMode = B4XDaisyVariants.GetPropString(Props, "ResizeMode", msResizeMode)
	If msResizeMode.Trim = "" Then msResizeMode = "FILL_NO_DISTORTIONS"
	miBlurRadius = B4XDaisyVariants.GetPropInt(Props, "BlurRadius", miBlurRadius)
	mbGlass = B4XDaisyVariants.GetPropBool(Props, "Glass", mbGlass)
End Sub







Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	ApplySpacingSpecToBox(box, mPadding, mMargin)
	Return box
End Sub

Private Sub ApplySpacingSpecToBox(Box As Map, PaddingSpec As String, MarginSpec As String)
	Dim rtl As Boolean = False
	Dim p As String = ""
	If PaddingSpec <> Null Then p = PaddingSpec.Trim
	Dim m As String = ""
	If MarginSpec <> Null Then m = MarginSpec.Trim
	If p.Length > 0 Then
		If p.Contains("-") Then
			B4XDaisyBoxModel.ApplyPaddingUtilities(Box, p, rtl)
		Else
			Dim pv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(p, 0dip)
			Box.Put("padding_left", pv)
			Box.Put("padding_top", pv)
			Box.Put("padding_right", pv)
			Box.Put("padding_bottom", pv)
		End If
	End If
	If m.Length > 0 Then
		If m.Contains("-") Then
			B4XDaisyBoxModel.ApplyMarginUtilities(Box, m, rtl)
		Else
			Dim mv As Float = B4XDaisyBoxModel.TailwindSpacingToDip(m, 0dip)
			Box.Put("margin_left", mv)
			Box.Put("margin_top", mv)
			Box.Put("margin_right", mv)
			Box.Put("margin_bottom", mv)
		End If
	End If
End Sub





Public Sub setVariant(Value As String)
	msVariant = B4XDaisyVariants.NormalizeVariant(Value)
	' Reset manual overrides to ensure variant colors take full effect.
	PlaceholderBackgroundColor = 0
	PlaceholderTextColor = 0
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getVariant As String
	Return msVariant
End Sub

Public Sub setShadow(Value As String)
	msShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getShadow As String
	Return msShadow
End Sub

Public Sub setResizeMode(Value As String)
	If Value = Null Or Value.Trim = "" Then Value = "FILL_NO_DISTORTIONS"
	msResizeMode = Value.ToUpperCase.Trim
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getResizeMode As String
	Return msResizeMode
End Sub

Public Sub setBlurRadius(Value As Int)
	miBlurRadius = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getBlurRadius As Int
	Return miBlurRadius
End Sub

Public Sub setGlass(Value As Boolean)
	mbGlass = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getGlass As Boolean
	Return mbGlass
End Sub

Public Sub setChatImage(Value As Boolean)
	mbChatImage = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getChatImage As Boolean
	Return mbChatImage
End Sub

Public Sub setVariantPalette(Palette As Map)
	If Palette.IsInitialized Then
		VariantPalette = Palette
	Else
		InitializePalette
		VariantPalette = DefaultVariantPalette
	End If
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getVariantPalette As Map
	InitializePalette
	Return VariantPalette
End Sub

Public Sub applyActiveTheme
	InitializePalette
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setUseVariantStatusColors(Enabled As Boolean)
	mbUseVariantStatusColors = Enabled
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getUseVariantStatusColors As Boolean
	Return mbUseVariantStatusColors
End Sub

Public Sub setAvatar(Path As String)
	Dim empty As B4XBitmap
	If Path = Null Then
		AvatarPath = ""
		AvatarBmp = empty
		LastUpscaleWarningKey = ""
		If ivAvatar.IsInitialized Then DrawAvatar
		Return
	End If
	AvatarPath = Path.Trim
	If AvatarPath.Length = 0 Then
		AvatarBmp = empty
		LastUpscaleWarningKey = ""
		If ivAvatar.IsInitialized Then DrawAvatar
		Return
	End If
	Try
		Dim resolved As String = B4XDaisyVariants.ResolveAssetImage(AvatarPath, "")
		If resolved.Length > 0 Then
			AvatarBmp = xui.LoadBitmap(File.DirAssets, resolved)
		Else
			AvatarBmp = empty
		End If
	Catch
		AvatarBmp = empty
	End Try
	LastUpscaleWarningKey = ""
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatar As String
	Return AvatarPath
End Sub

Public Sub setImage(Path As String)
	If Path = Null Then
		AvatarPath = ""
	Else
		AvatarPath = Path.Trim
	End If
	If mBase.IsInitialized = False Then Return
	setAvatar(AvatarPath)
End Sub

Public Sub getImage As String
	Return AvatarPath
End Sub

Public Sub setMask(Value As String)
	AvatarMask = B4XDaisyVariants.NormalizeMask(Value)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getMask As String
	Return getAvatarMask
End Sub

Public Sub setAvatarBitmap(bmp As B4XBitmap, Tag As Object)
	AvatarBmp = bmp
	AvatarTag = Tag
	AvatarPath = ""
	LastUpscaleWarningKey = ""
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatarTag As Object
	Return AvatarTag
End Sub

Public Sub setTag(Value As Object)
	mTag = Value
	If mBase.IsInitialized = False Then Return
End Sub

Public Sub getTag As Object
	Return mTag
End Sub

Public Sub setAvatarStatus(Mode As String)
	If Mode = Null Then Mode = "none"
	Dim m As String = Mode.ToLowerCase
	If m <> "online" And m <> "offline" Then m = "none"
	AvatarStatus = m
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setStatus(Mode As String)
	If Mode = Null Then Mode = "none"
	Dim m As String = Mode.ToLowerCase
	If m <> "online" And m <> "offline" Then m = "none"
	AvatarStatus = m
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatarStatus As String
	Return AvatarStatus
End Sub

Public Sub getStatus As String
	Return AvatarStatus
End Sub

Public Sub setAvatarStatusColors(OnlineColor As Int, OfflineColor As Int)
	If OnlineColor <> 0 Then
		CustomOnlineColor = OnlineColor
		AvatarOnlineColor = OnlineColor
	End If
	If OfflineColor <> 0 Then
		CustomOfflineColor = OfflineColor
		AvatarOfflineColor = OfflineColor
	End If
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

'Helper for online-only status color updates without touching offline color.
Public Sub setAvatarOnlineColor(OnlineColor As Int)
	If OnlineColor = 0 Then Return
	CustomOnlineColor = OnlineColor
	AvatarOnlineColor = OnlineColor
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getOnlineColor As Int
	Return ResolveOnlineColor
End Sub

Public Sub setOfflineColor(OfflineColor As Int)
	If OfflineColor = 0 Then Return
	CustomOfflineColor = OfflineColor
	AvatarOfflineColor = OfflineColor
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getOfflineColor As Int
	Return ResolveOfflineColor
End Sub

Public Sub setAvatarOnlineColorVariant(VariantName As String)
	Dim c As Int = B4XDaisyVariants.ResolveOnlineColor(VariantName, ResolveOnlineColor)
	setAvatarOnlineColor(c)
End Sub

'Short alias for setAvatarOnlineColor.
Public Sub setOnlineColor(OnlineColor As Int)
	If OnlineColor = 0 Then Return
	CustomOnlineColor = OnlineColor
	AvatarOnlineColor = OnlineColor
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setOnlineColorVariant(VariantName As String)
	setAvatarOnlineColorVariant(VariantName)
End Sub

Public Sub getAvatarOnlineColor As Int
	Return ResolveOnlineColor
End Sub

Public Sub getAvatarOfflineColor As Int
	Return ResolveOfflineColor
End Sub

Public Sub setShowOnline(Show As Boolean)
	OnlineIndicatorVisible = Show
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getShowOnline As Boolean
	Return OnlineIndicatorVisible
End Sub

Public Sub setAvatarType(Value As String)
	msAvatarType = NormalizeAvatarType(Value)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatarType As String
	Return msAvatarType
End Sub

Public Sub setPlaceHolder(Value As String)
	If Value = Null Then Value = ""
	PlaceholderText = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getPlaceHolder As String
	Return PlaceholderText
End Sub

Public Sub setTextColor(Value As Int)
	PlaceholderTextColor = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getTextColor As Int
	Return PlaceholderTextColor
End Sub

Public Sub setTextSize(Value As String)
	If Value = Null Then Value = ""
	PlaceholderTextSize = Value.Trim
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getTextSize As String
	Return PlaceholderTextSize
End Sub

Public Sub setTextColorVariant(VariantName As String)
	InitializePalette
	Dim c As Int = B4XDaisyVariants.ResolveTextColorVariantFromPalette(ActivePalette, VariantName, PlaceholderTextColor)
	setTextColor(c)
End Sub

Public Sub setBackgroundColor(Value As Int)
	PlaceholderBackgroundColor = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getBackgroundColor As Int
	Return PlaceholderBackgroundColor
End Sub

Public Sub setBackgroundColorVariant(VariantName As String)
	InitializePalette
	Dim c As Int = B4XDaisyVariants.ResolveBackgroundColorVariantFromPalette(ActivePalette, VariantName, PlaceholderBackgroundColor)
	setBackgroundColor(c)
End Sub

Public Sub setAvatarMask(MaskName As String)
	AvatarMask = B4XDaisyVariants.NormalizeMask(MaskName)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getAvatarMask As String
	Return AvatarMask
End Sub

Public Sub setRoundedBox(Value As Boolean)
	mRoundedBox = Value
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getRoundedBox As Boolean
	Return mRoundedBox
End Sub

Public Sub setGlobalMask(MaskName As String)
	setAvatarMask(MaskName)
End Sub

Public Sub setAvatarSize(Size As Object)
	If Size <> Null Then
		mWidthToken = Size
		mHeightToken = Size
	End If
	Dim v As Float
	If IsNumber(Size) And (Not(Size Is String)) Then
		v = Size
	Else
		v = B4XDaisyVariants.TailwindSizeToDip(Size, Min(mWidth, mHeight))
	End If
	mWidth = Max(16dip, v)
	mHeight = Max(16dip, v)

	mWidthExplicit = True
	mHeightExplicit = True
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub setWidth(Value As Object)
	If Value <> Null Then mWidthToken = Value
	Dim v As Float
	If IsNumber(Value) And (Not(Value Is String)) Then
		v = Value
	Else
		v = B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveWidthBase(mBase, mWidth))
	End If
	mWidth = Max(16dip, v)

	mWidthExplicit = True
	If mBase.IsInitialized = False Then Return
	ApplyRuntimeHostSize
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub setHeight(Value As Object)
	If Value <> Null Then mHeightToken = Value
	Dim v As Float
	If IsNumber(Value) And (Not(Value Is String)) Then
		v = Value
	Else
		v = B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveHeightBase(mBase, mHeight))
	End If
	mHeight = Max(16dip, v)

	mHeightExplicit = True
	If mBase.IsInitialized = False Then Return
	ApplyRuntimeHostSize
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub setPadding(Value As String)
	If Value = Null Then mPadding = "" Else mPadding = Value.Trim
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getPadding As String
	Return mPadding
End Sub

Public Sub setMargin(Value As String)
	If Value = Null Then mMargin = "" Else mMargin = Value.Trim
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getMargin As String
	Return mMargin
End Sub

Public Sub setCenterOnParent(Value As Boolean)
	mbCenterOnParent = Value
	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getCenterOnParent As Boolean
	Return mbCenterOnParent
End Sub

Public Sub getWidth As Float
	Return mWidth
End Sub

Public Sub getHeight As Float
	Return mHeight
End Sub

'Compatibility wrappers for previous API names.
Public Sub setAvatarWidth(Value As Object)
	setWidth(Value)
End Sub

Public Sub setAvatarHeight(Value As Object)
	setHeight(Value)
End Sub

Public Sub getAvatarWidth As Float
	Return getWidth
End Sub

Public Sub getAvatarHeight As Float
	Return getHeight
End Sub

Private Sub ApplyRuntimeHostSize
	If mBase.IsInitialized = False Then Return
	Dim targetW As Int = Max(1dip, mBase.Width)
	Dim targetH As Int = Max(1dip, mBase.Height)
	If mWidthExplicit Then targetW = Max(1dip, Round(mWidth))
	If mHeightExplicit Then targetH = Max(1dip, Round(mHeight))
	If targetW <> mBase.Width Or targetH <> mBase.Height Then
		mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, targetW, targetH)
	End If
End Sub

Public Sub setAvatarBorder(Color As Int, Width As Float)
	AvatarBorderColor = Color
	AvatarBorderWidth = Max(0, Width)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setAvatarBorderInset(Inset As Float)
	setRingOffset(Inset)
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub setRingColor(Color As Int)
	mcRingColor = Color
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getRingColor As Int
	Return mcRingColor
End Sub

'Convenience API: pass a Daisy variant token (eg "primary") for ring color.
Public Sub setRingColorVariant(VariantName As String)
	Dim c As Int = B4XDaisyVariants.ResolveVariantColor(ActivePalette, VariantName, "back", 0)
	If c = 0 Then c = B4XDaisyVariants.ResolveVariantColor(B4XDaisyVariants.DefaultPalette, VariantName, "back", 0)
	If c = 0 Then c = B4XDaisyVariants.ResolveVariantColor(ActivePalette, VariantName, "muted", AvatarBorderColor)
	setRingColor(c)
End Sub

Public Sub setRingWidth(Width As Float)
	AvatarBorderWidth = Max(0, Width)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getRingWidth As Float
	Return AvatarBorderWidth
End Sub

Public Sub setRingOffset(Offset As Float)
	mfRingOffset = Max(0, Offset)
	If mBase.IsInitialized = False Then Return
	If ivAvatar.IsInitialized Then DrawAvatar
End Sub

Public Sub getRingOffset As Float
	Return mfRingOffset
End Sub

Private Sub InitializePalette
	If DefaultVariantPalette.IsInitialized = False Then DefaultVariantPalette = B4XDaisyVariants.DefaultPalette
	If VariantPalette.IsInitialized = False Then VariantPalette = DefaultVariantPalette
End Sub

Private Sub ActivePalette As Map
	If VariantPalette.IsInitialized Then Return VariantPalette
	InitializePalette
	Return DefaultVariantPalette
End Sub

Private Sub ResolveOnlineColor As Int
	Dim c As Int = 0
	If mbUseVariantStatusColors Then
		c = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "back", 0)
	End If
	If c = 0 Then c = B4XDaisyVariants.GetTokenColor("--color-success", 0xFF2ECC71)
	If CustomOnlineColor <> 0 Then c = CustomOnlineColor
	Return c
End Sub

Private Sub ResolveOfflineColor As Int
	Dim c As Int = 0
	If mbUseVariantStatusColors Then
		c = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "muted", 0)
	End If
	If c = 0 Then c = B4XDaisyVariants.GetTokenColor("--color-base-300", 0xFFB4B4B4)
	If CustomOfflineColor <> 0 Then c = CustomOfflineColor
	Return c
End Sub

Private Sub ResolveEffectiveMask As String
	If mRoundedBox = False Then Return AvatarMask
	' RoundedBox=True: use the theme's --radius-box value, mapped to the nearest mask token.
	Dim r As Float = B4XDaisyVariants.GetRadiusBoxDip(8dip)
	If r <= 0 Then Return "rounded-none"
	If r < 3dip Then Return "rounded-sm"
	If r < 6dip Then Return "rounded"
	If r < 10dip Then Return "rounded-md"
	If r < 14dip Then Return "rounded-lg"
	If r < 18dip Then Return "rounded-xl"
	If r < 22dip Then Return "rounded-2xl"
	Return "rounded-3xl"
End Sub

Private Sub ResolveRingColor As Int
	If mcRingColor <> 0 Then Return mcRingColor
	Dim c As Int = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "back", 0)
	If c = 0 Then c = B4XDaisyVariants.ResolveVariantColor(B4XDaisyVariants.DefaultPalette, msVariant, "back", 0)
	If c = 0 Then c = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "muted", ResolveAvatarOutlineColor)
	Return c
End Sub

Private Sub ResolveAvatarOutlineColor As Int
	If AvatarBorderColor <> 0 Then Return AvatarBorderColor
	Return B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_RGB(245, 245, 245))
End Sub

Private Sub ResolveAvatarContentRect(Width As Float, Height As Float) As B4XRect
	Dim r As B4XRect
	r.Initialize(0, 0, Width, Height)
	Dim spec As Map = B4XDaisyVariants.ResolveShadowSpec(msShadow)
	Dim leftPadPx As Float = 0
	Dim topPadPx As Float = 0
	Dim rightPadPx As Float = 0
	Dim bottomPadPx As Float = 0

	Dim layers() As String = Array As String("1", "2")
	For Each layer As String In layers
		Dim alpha As Double = B4XDaisyVariants.GetPropFloat(spec, "alpha" & layer, 0)
		If alpha <= 0 Then Continue
		Dim y As Float = B4XDaisyVariants.GetPropFloat(spec, "y" & layer, 0)
		Dim blur As Float = Max(0, B4XDaisyVariants.GetPropFloat(spec, "blur" & layer, 0))
		Dim spread As Float = B4XDaisyVariants.GetPropFloat(spec, "spread" & layer, 0)
		Dim expansion As Float = Max(0, spread) + (blur * 0.6)
		leftPadPx = Max(leftPadPx, expansion)
		rightPadPx = Max(rightPadPx, expansion)
		topPadPx = Max(topPadPx, expansion + Max(0, -y))
		bottomPadPx = Max(bottomPadPx, expansion + Max(0, y))
	Next

	Dim leftPad As Float = leftPadPx * 1dip
	Dim topPad As Float = topPadPx * 1dip
	Dim rightPad As Float = rightPadPx * 1dip
	Dim bottomPad As Float = bottomPadPx * 1dip

	If leftPad + rightPad > Width - 2dip Then
		Dim capX As Float = Max(0, (Width - 2dip) / 2)
		leftPad = Min(leftPad, capX)
		rightPad = Min(rightPad, capX)
	End If
	If topPad + bottomPad > Height - 2dip Then
		Dim capY As Float = Max(0, (Height - 2dip) / 2)
		topPad = Min(topPad, capY)
		bottomPad = Min(bottomPad, capY)
	End If

	r.Initialize(leftPad, topPad, Width - rightPad, Height - bottomPad)
	Return r
End Sub

Private Sub DrawAvatarShadow(BaseRect As B4XRect, MaskName As String)
	Dim spec As Map = B4XDaisyVariants.ResolveShadowSpec(msShadow)
	DrawShadowLayer(BaseRect, MaskName, B4XDaisyVariants.GetPropFloat(spec, "y1", 0), B4XDaisyVariants.GetPropFloat(spec, "blur1", 0), B4XDaisyVariants.GetPropFloat(spec, "spread1", 0), B4XDaisyVariants.GetPropFloat(spec, "alpha1", 0))
	DrawShadowLayer(BaseRect, MaskName, B4XDaisyVariants.GetPropFloat(spec, "y2", 0), B4XDaisyVariants.GetPropFloat(spec, "blur2", 0), B4XDaisyVariants.GetPropFloat(spec, "spread2", 0), B4XDaisyVariants.GetPropFloat(spec, "alpha2", 0))
End Sub

Private Sub DrawShadowLayer(BaseRect As B4XRect, MaskName As String, OffsetYPx As Float, BlurPx As Float, SpreadPx As Float, Alpha As Double)
	If Alpha <= 0 Then Return

	Dim y As Float = OffsetYPx * 1dip
	Dim blur As Float = Max(0, BlurPx) * 1dip
	Dim spread As Float = SpreadPx * 1dip
	Dim expansion As Float = Max(0, spread) + (blur * 0.45)
	Dim alphaInt As Int = Max(0, Min(255, Alpha * 255))
	If alphaInt <= 0 Then Return

	Dim shadowRect As B4XRect
	shadowRect.Initialize(BaseRect.Left - expansion, BaseRect.Top + y - expansion, BaseRect.Right + expansion, BaseRect.Bottom + y + expansion)
	Dim shadowPath As B4XPath = B4XDaisyVariants.CreateMaskPathInRect(shadowRect, MaskName)
	AvatarCvs.DrawPath(shadowPath, xui.Color_ARGB(alphaInt, 0, 0, 0), True, 0)

	'Second pass to mimic blur falloff in utility shadows.
	If blur > 0.5dip Then
		Dim extra As Float = blur * 0.35
		Dim shadowRect2 As B4XRect
		shadowRect2.Initialize(shadowRect.Left - extra, shadowRect.Top - extra, shadowRect.Right + extra, shadowRect.Bottom + extra)
		Dim alpha2 As Int = Max(0, Min(255, alphaInt * 0.4))
		If alpha2 > 0 Then
			Dim shadowPath2 As B4XPath = B4XDaisyVariants.CreateMaskPathInRect(shadowRect2, MaskName)
			AvatarCvs.DrawPath(shadowPath2, xui.Color_ARGB(alpha2, 0, 0, 0), True, 0)
		End If
	End If
End Sub

Private Sub DrawAvatar
	If ivAvatar.IsInitialized = False Or ivAvatar.Visible = False Then Return
	Dim aw As Int = Max(1dip, ivAvatar.Width)
	Dim ah As Int = Max(1dip, ivAvatar.Height)
	If aw <= 0 Or ah <= 0 Then Return
	
	Dim effectiveMask As String = ResolveEffectiveMask

	If AvatarCanvasReady Then AvatarCvs.Release
	AvatarCvs.Initialize(ivAvatar)
	AvatarCanvasReady = True
	#If B4A
	Dim jo As JavaObject = AvatarCvs
	Dim cvsJO As JavaObject = jo.GetField("cvs")
	Dim paint As JavaObject = cvsJO.GetField("paint")
	paint.RunMethod("setFilterBitmap", Array(True))
	paint.RunMethod("setAntiAlias", Array(True))
	#End If
	AvatarCvs.ClearRect(AvatarCvs.TargetRect)

	Dim full As B4XRect
	If mbChatImage Then
		full.Initialize(0, 0, aw, ah)
	Else
		full = ResolveAvatarContentRect(aw, ah)
		If full.Width <= 2dip Or full.Height <= 2dip Then full.Initialize(0, 0, aw, ah)
	End If
	Dim imageRect As B4XRect
	Dim imagePath As B4XPath
	Dim ringPath As B4XPath
	Dim shadowRect As B4XRect
	Dim shadowMask As String = effectiveMask
	Dim effectiveStatus As String = AvatarStatus
	If OnlineIndicatorVisible = False Then effectiveStatus = "none"

	Dim statusColor As Int = 0
	If effectiveStatus = "online" Then
		statusColor = ResolveOnlineColor
	Else If effectiveStatus = "offline" Then
		statusColor = ResolveOfflineColor
	End If

	Dim resolvedRingColor As Int
	Dim outlineColor As Int = ResolveAvatarOutlineColor
	If mbChatImage Then
		resolvedRingColor = IIf(statusColor <> 0, statusColor, outlineColor)
	Else
		resolvedRingColor = ResolveRingColor
	End If
	Dim ringW As Float = Max(0, AvatarBorderWidth)
	'Chat-image behavior: online status should always surface a visible ring.
	If mbChatImage And effectiveStatus = "online" And ringW <= 0 Then
		ringW = Max(1dip, 1dip)
	End If
	Dim gapW As Float = Max(0, mfRingOffset)
	Dim ringRadius As Float = -1

	If effectiveMask = "circle" Or effectiveMask = "rounded-full" Then
		Dim s As Float = Min(full.Width, full.Height)
		Dim ox As Float = full.Left + (full.Width - s) / 2
		Dim oy As Float = full.Top + (full.Height - s) / 2
		Dim cx0 As Float = ox + s / 2
		Dim cy0 As Float = oy + s / 2
		Dim outerR As Float = s / 2 - 0.5dip
		Dim reserved As Float = IIf(ringW > 0, gapW + ringW, 0)
		Dim avatarR As Float = outerR - reserved
		If avatarR < 3dip Then avatarR = Max(3dip, outerR * 0.75)
		imageRect.Initialize(cx0 - avatarR, cy0 - avatarR, cx0 + avatarR, cy0 + avatarR)
		imagePath.InitializeOval(imageRect)
		If ringW > 0 Then ringRadius = avatarR + gapW + ringW / 2
		shadowRect.Initialize(cx0 - outerR, cy0 - outerR, cx0 + outerR, cy0 + outerR)
		shadowMask = "circle"
	Else
		Dim reserved2 As Float = IIf(ringW > 0, gapW + ringW, 0)
		If mbChatImage Then
			imageRect = full
			imagePath = B4XDaisyVariants.CreateMaskPathRect(aw, ah, effectiveMask)
		Else
			imageRect = InsetRectSafe(full, reserved2, 6dip)
			imagePath = B4XDaisyVariants.CreateMaskPathInRect(imageRect, effectiveMask)
		End If
		shadowRect = full
		If ringW > 0 Then
			If mbChatImage Then
				ringPath = imagePath
			Else
				ringPath = B4XDaisyVariants.CreateMaskPathInRect(full, effectiveMask)
			End If
		End If
	End If

	If mbChatImage = False And B4XDaisyVariants.NormalizeShadow(msShadow) <> "none" Then
		DrawAvatarShadow(shadowRect, shadowMask)
	End If

	Dim canDrawBitmap As Boolean = AvatarBmp.IsInitialized And msAvatarType = "image"

	Dim isSimpleShape As Boolean = False
	If effectiveMask = "circle" Or effectiveMask = "square" Or effectiveMask = "rounded-none" Or _
	   effectiveMask = "rounded-sm" Or effectiveMask = "rounded" Or effectiveMask = "rounded-md" Or _
	   effectiveMask = "rounded-lg" Or effectiveMask = "rounded-xl" Or _
	   effectiveMask = "rounded-2xl" Or effectiveMask = "rounded-3xl" Or effectiveMask = "rounded-full" Then
		isSimpleShape = True
	End If

	If mImage.IsInitialized Then
		If canDrawBitmap And isSimpleShape Then
			mImage.mBase.Visible = True
			Dim imgX As Int = imageRect.Left
			Dim imgY As Int = imageRect.Top
			Dim imgW As Int = Max(1dip, imageRect.Width)
			Dim imgH As Int = Max(1dip, imageRect.Height)
			mImage.mBase.SetLayoutAnimated(0, imgX, imgY, imgW, imgH)
			mImage.Base_Resize(imgW, imgH)
			mImage.setResizeMode(msResizeMode)
			
			If effectiveMask = "circle" Or effectiveMask = "rounded-full" Then
				mImage.setRoundedImage(True)
			Else
				mImage.setRoundedImage(False)
				Dim rad As Int = 0
				Select Case effectiveMask
					Case "rounded-sm": rad = 2dip
					Case "rounded": rad = 4dip
					Case "rounded-md": rad = 6dip
					Case "rounded-lg": rad = 8dip
					Case "rounded-xl": rad = 12dip
					Case "rounded-2xl": rad = 16dip
					Case "rounded-3xl": rad = 24dip
				End Select
				mImage.setCornersRadius(rad)
			End If
			
			Dim drawBmp As B4XBitmap = AvatarBmp
			If miBlurRadius > 0 Then
				drawBmp = BlurImage(AvatarBmp, miBlurRadius)
			End If
			mImage.setBitmap(drawBmp)
		Else
			mImage.mBase.Visible = False
		End If
	End If

	If canDrawBitmap And isSimpleShape = False Then
		Dim drawBmp As B4XBitmap = AvatarBmp
		If miBlurRadius > 0 Then
			drawBmp = BlurImage(AvatarBmp, miBlurRadius)
		End If
		Dim bmpRect As B4XRect = ResolveBitmapDestRect(drawBmp, imageRect, msResizeMode)
		
		' 1. Create a transparent mask bitmap of the target size (aw x ah)
		Dim maskBmp As B4XBitmap
		Dim maskCvs As B4XCanvas
		Dim pnlMask As Panel
		pnlMask.Initialize("")
		Dim maskView As B4XView = pnlMask
		maskView.SetLayoutAnimated(0, 0, 0, aw, ah)
		maskCvs.Initialize(maskView)
		
		' Draw the path on the mask canvas in solid black with anti-aliasing
		#If B4A
		Dim joMask As JavaObject = maskCvs
		Dim cvsJOMask As JavaObject = joMask.GetField("cvs")
		Dim paintMask As JavaObject = cvsJOMask.GetField("paint")
		paintMask.RunMethod("setAntiAlias", Array(True))
		#End If
		maskCvs.DrawPath(imagePath, xui.Color_Black, True, 0)
		maskCvs.Invalidate
		maskBmp = maskCvs.CreateBitmap
		maskCvs.Release
		
		' 2. Create a transparent image bitmap of the target size (aw x ah)
		Dim imgBmp As B4XBitmap
		Dim imgCvs As B4XCanvas
		Dim pnlImg As Panel
		pnlImg.Initialize("")
		Dim imgView As B4XView = pnlImg
		imgView.SetLayoutAnimated(0, 0, 0, aw, ah)
		imgCvs.Initialize(imgView)
		
		' Resize the drawBmp to the exact destination size first using bilinear filtering.
		' This prevents the pixelation/aliasing that occurs when downscaling large images on Android's Canvas.
		Dim scaledBmp As B4XBitmap = drawBmp.Resize(Max(1, bmpRect.Width), Max(1, bmpRect.Height), False)
		
		#If B4A
		Dim joImg As JavaObject = imgCvs
		Dim cvsJOImg As JavaObject = joImg.GetField("cvs")
		Dim paintImg As JavaObject = cvsJOImg.GetField("paint")
		paintImg.RunMethod("setFilterBitmap", Array(True))
		paintImg.RunMethod("setAntiAlias", Array(True))
		#End If
		imgCvs.DrawBitmap(scaledBmp, bmpRect)
		
		' Draw glass overlay on the temporary image canvas so that it also gets masked perfectly
		If mbGlass Then
			Dim glassBgColor As Int = xui.Color_ARGB(35, 255, 255, 255)
			imgCvs.DrawRect(imageRect, glassBgColor, True, 0)
			imgCvs.DrawPath(imagePath, xui.Color_ARGB(100, 255, 255, 255), False, 1.5dip)
		End If
		
		imgCvs.Invalidate
		imgBmp = imgCvs.CreateBitmap
		imgCvs.Release
		
		' 3. Blend them using BitmapCreatorEffectsExt
		Dim bce As BitmapCreatorEffectsExt
		bce.Initialize
		Dim maskedBmp As B4XBitmap = bce.DrawThroughMask(imgBmp, maskBmp)
		
		' 4. Draw the final anti-aliased bitmap onto the main canvas (without clipping)
		AvatarCvs.DrawBitmap(maskedBmp, AvatarCvs.TargetRect)
	Else
		' Standard clipped drawing for placeholders
		AvatarCvs.ClipPath(imagePath)
		Dim placeholder As Int = ResolvePlaceholderBackColor
		AvatarCvs.DrawRect(imageRect, placeholder, True, 0)
		Dim phText As String = ResolvePlaceholderText
		If phText.Length > 0 Then
			Dim autoSize As Float = Max(10, Min(imageRect.Width, imageRect.Height) * 0.36)
			Dim fontSize As Float = autoSize
			If PlaceholderTextSize.Length > 0 Then
				Dim tm As Map = B4XDaisyVariants.TailwindTextMetrics(PlaceholderTextSize, autoSize, autoSize * 1.2)
				fontSize = tm.GetDefault("font_size", autoSize)
			End If
			Dim f As B4XFont = xui.CreateDefaultBoldFont(fontSize)
			Dim textRect As B4XRect = AvatarCvs.MeasureText(phText, f)
			Dim tx As Float = imageRect.CenterX
			Dim ty As Float = imageRect.CenterY - (textRect.Height / 2) - textRect.Top
			AvatarCvs.DrawText(phText, tx, ty, f, ResolvePlaceholderTextColor, "CENTER")
		End If
		If mbGlass Then
			Dim glassBgColor As Int = xui.Color_ARGB(35, 255, 255, 255)
			AvatarCvs.DrawRect(imageRect, glassBgColor, True, 0)
			AvatarCvs.DrawPath(imagePath, xui.Color_ARGB(100, 255, 255, 255), False, 1.5dip)
		End If
		AvatarCvs.RemoveClip
	End If

	If ringW > 0 Then
		If AvatarMask = "circle" Or AvatarMask = "rounded-full" Then
			If ringRadius > 0 Then AvatarCvs.DrawCircle(imageRect.CenterX, imageRect.CenterY, ringRadius, resolvedRingColor, False, ringW)
		Else
			AvatarCvs.DrawPath(ringPath, resolvedRingColor, False, ringW)
		End If
	End If

	' Draw online/offline indicator on its own overlay canvas so it renders above mImage
	Dim ivInd As B4XView = ivIndicator
	ivInd.SetLayoutAnimated(0, 0, 0, aw, ah)
	ivInd.BringToFront
	If IndicatorCvsReady Then IndicatorCvs.Release
	IndicatorCvs.Initialize(ivInd)
	IndicatorCvsReady = True
	IndicatorCvs.ClearRect(IndicatorCvs.TargetRect)
	If effectiveStatus <> "none" Then
		' Anchor the status dot to the avatar's outer box (not the ring-inset imageRect)
		' so it sits at the corner consistently, matching DaisyUI .avatar-online:before
		' (top:7%; right:7%; width/height:15% of the box). A ring no longer pulls it inward.
		Dim dd As Float = Min(aw, ah)
		Dim d As Float = Max(6dip, dd * 0.15)
		Dim r As Float = d / 2
		Dim cx As Float = aw - (dd * 0.07) - r
		Dim cy As Float = (dd * 0.07) + r
		IndicatorCvs.DrawCircle(cx, cy, r + 2dip, outlineColor, True, 0)
		IndicatorCvs.DrawCircle(cx, cy, r, statusColor, True, 0)
	End If
	IndicatorCvs.Invalidate

	AvatarCvs.Invalidate
End Sub

Private Sub NormalizeAvatarType(Value As String) As String
	If Value = Null Then Return "image"
	Dim v As String = Value.ToLowerCase.Trim
	Select Case v
		Case "image", "svg", "text"
			Return v
		Case Else
			Return "image"
	End Select
End Sub

Private Sub ResolvePlaceholderBackColor As Int
	If PlaceholderBackgroundColor <> 0 Then Return PlaceholderBackgroundColor
	Dim c As Int = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "back", 0)
	If c <> 0 Then Return c
	Return B4XDaisyVariants.GetTokenColor("--color-base-200", xui.Color_RGB(220, 220, 220))
End Sub

Private Sub ResolvePlaceholderTextColor As Int
	If PlaceholderTextColor <> 0 Then Return PlaceholderTextColor
	Dim c As Int = B4XDaisyVariants.ResolveVariantColor(ActivePalette, msVariant, "text", 0)
	If c <> 0 Then Return c
	Return B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_RGB(55, 65, 81))
End Sub

Private Sub ResolvePlaceholderText As String
	Dim t As String = PlaceholderText
	If t <> Null Then t = t.Trim Else t = ""
	If t.Length > 0 Then Return t
	If msAvatarType = "svg" Then Return "SVG"
	Return ""
End Sub

Private Sub AvatarSourceLabel As String
	If AvatarPath <> "" Then Return AvatarPath
	Return "<bitmap>"
End Sub

Private Sub WarnIfUpscaled(SourceLabel As String, Bmp As B4XBitmap, DestRect As B4XRect)
	If Bmp.IsInitialized = False Then Return
	If Bmp.Width <= 0 Or Bmp.Height <= 0 Then Return
	If DestRect.Width <= 0 Or DestRect.Height <= 0 Then Return
	Dim sx As Double = DestRect.Width / Bmp.Width
	Dim sy As Double = DestRect.Height / Bmp.Height
	Dim scaleUp As Double = Max(sx, sy)
	If scaleUp <= 1.05 Then Return
	Dim key As String = SourceLabel & "|" & Bmp.Width & "x" & Bmp.Height & "|" & Round(DestRect.Width) & "x" & Round(DestRect.Height)
	If key = LastUpscaleWarningKey Then Return
	LastUpscaleWarningKey = key
End Sub

Private Sub InsetRectSafe(Source As B4XRect, Inset As Float, MinSize As Float) As B4XRect
	Dim r As B4XRect
	Dim ix As Float = Max(0, Min(Inset, Max(0, (Source.Width - MinSize) / 2)))
	Dim iy As Float = Max(0, Min(Inset, Max(0, (Source.Height - MinSize) / 2)))
	r.Initialize(Source.Left + ix, Source.Top + iy, Source.Right - ix, Source.Bottom - iy)
	Return r
End Sub

Private Sub ResolveBitmapDestRect(Bmp As B4XBitmap, TargetRect As B4XRect, Mode As String) As B4XRect
	Dim outRect As B4XRect
	outRect.Initialize(TargetRect.Left, TargetRect.Top, TargetRect.Right, TargetRect.Bottom)
	If Bmp.IsInitialized = False Then Return outRect
	If Bmp.Width <= 0 Or Bmp.Height <= 0 Then Return outRect
	If TargetRect.Width <= 0 Or TargetRect.Height <= 0 Then Return outRect

	Dim modeUpper As String = Mode.ToUpperCase.Trim
	If modeUpper = "" Then modeUpper = "FILL_NO_DISTORTIONS"
	
	' Check for ChatImageMode override (chat bubbles always use FILL / stretch)
	If mbChatImage Then modeUpper = "FILL"

	Select modeUpper
		Case "FILL"
			Return outRect
		Case "FIT"
			Dim r As Float = Min(TargetRect.Width / Bmp.Width, TargetRect.Height / Bmp.Height)
			Dim w As Float = Bmp.Width * r
			Dim h As Float = Bmp.Height * r
			Dim cx As Float = TargetRect.CenterX
			Dim cy As Float = TargetRect.CenterY
			outRect.Initialize(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)
		Case "FILL_NO_DISTORTIONS"
			Dim r As Float = Max(TargetRect.Width / Bmp.Width, TargetRect.Height / Bmp.Height)
			Dim w As Float = Bmp.Width * r
			Dim h As Float = Bmp.Height * r
			Dim cx As Float = TargetRect.CenterX
			Dim cy As Float = TargetRect.CenterY
			outRect.Initialize(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)
		Case "NONE"
			Dim w As Float = Bmp.Width
			Dim h As Float = Bmp.Height
			Dim cx As Float = TargetRect.CenterX
			Dim cy As Float = TargetRect.CenterY
			outRect.Initialize(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)
		Case Else
			' Default to FILL_NO_DISTORTIONS
			Dim r As Float = Max(TargetRect.Width / Bmp.Width, TargetRect.Height / Bmp.Height)
			Dim w As Float = Bmp.Width * r
			Dim h As Float = Bmp.Height * r
			Dim cx As Float = TargetRect.CenterX
			Dim cy As Float = TargetRect.CenterY
			outRect.Initialize(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)
	End Select
	Return outRect
End Sub

Private Sub ivAvatar_Click
	If mClickable = False Then Return
	If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
		CallSub2(mCallBack, mEventName & "_Click", mTag)
	Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
		CallSub(mCallBack, mEventName & "_Click")
	End If
End Sub
Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

''' Sets whether the avatar intercepts touch events.
''' When False, touches pass through to the parent view (e.g. a list row).
''' When True, the avatar handles clicks normally via the Click event.
Public Sub setClickable(Value As Boolean)
	mClickable = Value
	#If B4A
	If mBase.IsInitialized Then
		Dim joBase As JavaObject = mBase
		joBase.RunMethod("setClickable", Array(Value))
		If ivAvatar.IsInitialized Then
			Dim joIv As JavaObject = ivAvatar
			joIv.RunMethod("setClickable", Array(Value))
		End If
	End If
	#Else If B4J
	If mBase.IsInitialized Then
		mBase.Enabled = Value
		If ivAvatar.IsInitialized Then ivAvatar.Enabled = Value
	End If
	#Else If B4i
	If mBase.IsInitialized Then
		mBase.Enabled = Value
		If ivAvatar.IsInitialized Then ivAvatar.Enabled = Value
	End If
	#End If
End Sub

''' Gets whether the avatar is clickable.
Public Sub getClickable As Boolean
	Return mClickable
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

'Sets a bitmap and sets the gravity to Fill.
Public Sub SetBitmapAndFill (ImageView As B4XView, Bmp As B4XBitmap)
	ImageView.SetBitmap(Bmp)
	Dim iiv As ImageView = ImageView
	#if B4A
	iiv.Gravity = Gravity.FILL
	#Else If B4J
	iiv.PreserveRatio = False
	#else if B4i
	iiv.ContentMode = iiv.MODE_FILL
	#End If
End Sub

Private Sub BlurImage(bmp As B4XBitmap, radius As Int) As B4XBitmap
	If bmp.IsInitialized = False Then Return bmp
	Dim n As Long = DateTime.Now
	
	' Downscale the image to speed up blur and add natural smoothness
	Dim ReduceScale As Float = Max(1.5, radius / 2.5)
	Dim count As Int = Max(2, Min(5, radius / 2))
	
	Dim targetW As Int = Max(16, bmp.Width / ReduceScale)
	Dim targetH As Int = Max(16, bmp.Height / ReduceScale)
	Dim smallBmp As B4XBitmap = bmp.Resize(targetW, targetH, True)
	
	Dim bc As BitmapCreator
	bc.Initialize(smallBmp.Width, smallBmp.Height)
	bc.CopyPixelsFromBitmap(smallBmp)
	
	' Perform box blur passes
	Dim clrs(3) As ARGBColor
	Dim temp As ARGBColor
	Dim m As Int
	For steps = 1 To count
		For y = 0 To bc.mHeight - 1
			For x = 0 To 2
				bc.GetARGB(x, y, clrs(x))
			Next
			SetAvg(bc, 1, y, clrs, temp)
			m = 0
			For x = 2 To bc.mWidth - 2
				bc.GetARGB(x + 1, y, clrs(m))
				m = (m + 1) Mod clrs.Length
				SetAvg(bc, x, y, clrs, temp)
			Next
		Next
		For x = 0 To bc.mWidth - 1
			For y = 0 To 2
				bc.GetARGB(x, y, clrs(y))
			Next
			SetAvg(bc, x, 1, clrs, temp)
			m = 0
			For y = 2 To bc.mHeight - 2
				bc.GetARGB(x, y + 1, clrs(m))
				m = (m + 1) Mod clrs.Length
				SetAvg(bc, x, y, clrs, temp)
			Next
		Next
	Next
	
	Dim blurredBmp As B4XBitmap = bc.Bitmap
	Dim finalBmp As B4XBitmap = blurredBmp.Resize(bmp.Width, bmp.Height, True)
	Return finalBmp
End Sub

Private Sub SetAvg(bc As BitmapCreator, x As Int, y As Int, clrs() As ARGBColor, temp As ARGBColor)
	temp.Initialize
	Dim totalR As Int = 0
	Dim totalG As Int = 0
	Dim totalB As Int = 0
	Dim totalA As Int = 0
	For Each c As ARGBColor In clrs
		totalR = totalR + c.r
		totalG = totalG + c.g
		totalB = totalB + c.b
		totalA = totalA + c.a
	Next
	temp.a = totalA / clrs.Length
	temp.r = totalR / clrs.Length
	temp.g = totalG / clrs.Length
	temp.b = totalB / clrs.Length
	bc.SetARGB(x, y, temp)
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyApp.bas

﻿B4A=true
Group=Default Group
ModulesStructureVersion=1
Type=StaticCode
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9

#Region App
' Shared app-related content will live here.
Sub Process_Globals
    Private SvgTextCache As Map
End Sub

Private Sub EnsureSvgTextCache
    If SvgTextCache.IsInitialized = False Then SvgTextCache.Initialize
End Sub

Public Sub ClearSvgTextCache
    EnsureSvgTextCache
    SvgTextCache.Clear
End Sub

Public Sub GetCachedSvgText(Path As String, DefaultText As String) As String
    Dim key As String = NormalizeSvgCacheKey(Path)
    If key.Length = 0 Then Return DefaultText
    EnsureSvgTextCache
    If SvgTextCache.ContainsKey(key) Then Return SvgTextCache.Get(key)
    Dim raw As String = ReadSvgText(Path)
    If raw.Length = 0 Then Return DefaultText
    SvgTextCache.Put(key, raw)
    Return raw
End Sub

Private Sub NormalizeSvgCacheKey(Path As String) As String
    If Path = Null Then Return ""
    Return Path.Trim
End Sub

Private Sub ReadSvgText(Path As String) As String
    If Path = Null Then Return ""
    Dim p As String = Path.Trim
    If p.Length = 0 Then Return ""

    Try
        Dim slash1 As Int = p.LastIndexOf("/")
        Dim slash2 As Int = p.LastIndexOf("\")
        Dim slash As Int = Max(slash1, slash2)
        Dim fn As String = p
        If slash >= 0 Then
            Dim dir As String = p.SubString2(0, slash)
            fn = p.SubString(slash + 1)
            If dir.Length > 0 And fn.Length > 0 And File.Exists(dir, fn) Then
                Return File.ReadString(dir, fn)
            End If
        End If
        If File.Exists(File.DirAssets, p) Then 
            Return File.ReadString(File.DirAssets, p)
        Else If File.Exists(File.DirAssets, fn) Then
            Return File.ReadString(File.DirAssets, fn)
        End If
    Catch
        Log("B4XDaisyApp.ReadSvgText: " & LastException.Message)
    End Try
    Return ""
End Sub
#End Region

---

## SithasoDaisyUIKit B4X Component API Cheat Sheet

# SithasoDaisyUIKit B4X Component API Cheat Sheet

This document lists all available custom views, their event hooks, designer properties, and public methods. Use this reference when building user interfaces dynamically.

## Table of Contents

- [B4XDaisyAccordion](#b4xdaisyaccordion)
- [B4XDaisyAlert](#b4xdaisyalert)
- [B4XDaisyApp](#b4xdaisyapp)
- [B4XDaisyAvatar](#b4xdaisyavatar)
- [B4XDaisyAvatarGroup](#b4xdaisyavatargroup)
- [B4XDaisyBadge](#b4xdaisybadge)
- [B4XDaisyBadgeGroupSelect](#b4xdaisybadgegroupselect)
- [B4XDaisyBoxModel](#b4xdaisyboxmodel)
- [B4XDaisyBreadcrumbs](#b4xdaisybreadcrumbs)
- [B4XDaisyButton](#b4xdaisybutton)
- [B4XDaisyCanvasSpinner](#b4xdaisycanvasspinner)
- [B4XDaisyCard](#b4xdaisycard)
- [B4XDaisyCardActions](#b4xdaisycardactions)
- [B4XDaisyCardBody](#b4xdaisycardbody)
- [B4XDaisyCardTitle](#b4xdaisycardtitle)
- [B4XDaisyCarousel](#b4xdaisycarousel)
- [B4XDaisyCarouselItem](#b4xdaisycarouselitem)
- [B4XDaisyChat](#b4xdaisychat)
- [B4XDaisyChatBubble](#b4xdaisychatbubble)
- [B4XDaisyCheckbox](#b4xdaisycheckbox)
- [B4XDaisyCheckboxGroup](#b4xdaisycheckboxgroup)
- [B4XDaisyCollapse](#b4xdaisycollapse)
- [B4XDaisyCollapseContent](#b4xdaisycollapsecontent)
- [B4XDaisyCollapseTitle](#b4xdaisycollapsetitle)
- [B4XDaisyCountdown](#b4xdaisycountdown)
- [B4XDaisyCountdownItem](#b4xdaisycountdownitem)
- [B4XDaisyDashboard](#b4xdaisydashboard)
- [B4XDaisyDiff](#b4xdaisydiff)
- [B4XDaisyDivider](#b4xdaisydivider)
- [B4XDaisyDivision](#b4xdaisydivision)
- [B4XDaisyDock](#b4xdaisydock)
- [B4XDaisyDropdown](#b4xdaisydropdown)
- [B4XDaisyFab](#b4xdaisyfab)
- [B4XDaisyFieldset](#b4xdaisyfieldset)
- [B4XDaisyFileHandler](#b4xdaisyfilehandler)
- [B4XDaisyFileInput](#b4xdaisyfileinput)
- [B4XDaisyFilter](#b4xdaisyfilter)
- [B4XDaisyFlexItem](#b4xdaisyflexitem)
- [B4XDaisyFlexLayout](#b4xdaisyflexlayout)
- [B4XDaisyFlexPanel](#b4xdaisyflexpanel)
- [B4XDaisyGrid](#b4xdaisygrid)
- [B4XDaisyHero](#b4xdaisyhero)
- [B4XDaisyHover3d](#b4xdaisyhover3d)
- [B4XDaisyIconButton](#b4xdaisyiconbutton)
- [B4XDaisyImage](#b4xdaisyimage)
- [B4XDaisyIndicator](#b4xdaisyindicator)
- [B4XDaisyInput](#b4xdaisyinput)
- [B4XDaisyKbd](#b4xdaisykbd)
- [B4XDaisyLabel](#b4xdaisylabel)
- [B4XDaisyList](#b4xdaisylist)
- [B4XDaisyLoading](#b4xdaisyloading)
- [B4XDaisyMenu](#b4xdaisymenu)
- [B4XDaisyModal](#b4xdaisymodal)
- [B4XDaisyNavbar](#b4xdaisynavbar)
- [B4XDaisyOverlay](#b4xdaisyoverlay)
- [B4XDaisyPageScroll](#b4xdaisypagescroll)
- [B4XDaisyPagination](#b4xdaisypagination)
- [B4XDaisyProgress](#b4xdaisyprogress)
- [B4XDaisyRadialProgress](#b4xdaisyradialprogress)
- [B4XDaisyRadio](#b4xdaisyradio)
- [B4XDaisyRadioGroup](#b4xdaisyradiogroup)
- [B4XDaisyRange](#b4xdaisyrange)
- [B4XDaisyRating](#b4xdaisyrating)
- [B4XDaisySelect](#b4xdaisyselect)
- [B4XDaisyStack](#b4xdaisystack)
- [B4XDaisyStat](#b4xdaisystat)
- [B4XDaisyStatItem](#b4xdaisystatitem)
- [B4XDaisyStatus](#b4xdaisystatus)
- [B4XDaisySteps](#b4xdaisysteps)
- [B4XDaisySvgIcon](#b4xdaisysvgicon)
- [B4XDaisySwap](#b4xdaisyswap)
- [B4XDaisySweetAlert](#b4xdaisysweetalert)
- [B4XDaisySweetAlertIcon](#b4xdaisysweetalerticon)
- [B4XDaisyTab](#b4xdaisytab)
- [B4XDaisyText](#b4xdaisytext)
- [B4XDaisyTextRotate](#b4xdaisytextrotate)
- [B4XDaisyTimeline](#b4xdaisytimeline)
- [B4XDaisyToast](#b4xdaisytoast)
- [B4XDaisyToggle](#b4xdaisytoggle)
- [B4XDaisyToggleGroup](#b4xdaisytogglegroup)
- [B4XDaisyTooltip](#b4xdaisytooltip)
- [B4XDaisyVariants](#b4xdaisyvariants)
- [B4XDaisyWindow](#b4xdaisywindow)

---

## B4XDaisyAccordion

### Events
- `Change (ActiveTag As Object, Status As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `OpenOnlyOne` | Boolean | `True` | If True, only one collapse can be open at a time. |
| `IconPosition` | String | `right` | Default icon position for all children. |
| `Icon` | String | `arrow` | Expansion indicator icon for all children. |
| `Visible` | Boolean | `True` | Visible state. |
| `SpaceY` | Int | `2` | Vertical gap (in dip) between collapse items. |
| `Shadow` | String | `none` | Elevation level applied to all children. |
| `Rounded` | String | `theme` | Radius mode applied to all children. |
| `GroupName` | String | `` | Explicit group name shared by all child collapses (used for single-open enforcement). Leave empty to auto-generate from component tag. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `AddItem(Item As B4XDaisyCollapse)`
- `HandleChildRequestOpen(RequestedChild As B4XDaisyCollapse)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setOpenOnlyOne(Value As Boolean)`
- `getOpenOnlyOneAs Boolean`
- `setIconPosition(Value As String)`
- `getIconPositionAs String`
- `setIcon(Value As String)`
- `getIconAs String`
- `setSpaceY(Value As Int)`
- `getSpaceYAs Int`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `AddItemBasic(ItemTag As Object, Icon As String, Title As String) As B4XDaisyCollapse`
- `SetItemActive(ItemTag As Object, Value As Boolean)`
- `SetItemTitle(ItemTag As Object, Title As String)`
- `SetItemVariant(ItemTag As Object, Variant As String)`
- `SetItemTitleIcon(ItemTag As Object, IconName As String)`
- `SetItemVisible(ItemTag As Object, Value As Boolean)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyAlert

### Events
- `Click (Tag As Object)`
- `ActionClick (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `full` | Tailwind size token or CSS size (eg full, 72, 320px, 20rem) |
| `Height` | String | `h-12` | Tailwind size token or CSS size (eg h-12, 80px, 5rem) |
| `Variant` | String | `none` | Daisy variant used for alert colors |
| `AlertStyle` | String | `solid` | Alert visual style |
| `Direction` | String | `horizontal` | Horizontal or vertical layout |
| `Title` | String | `` | Optional title text |
| `Text` | String | `12 unread messages. Tap to see.` | Main alert message |
| `Description` | String | `` | Optional secondary description |
| `IconAsset` | String | `` | SVG file name from assets (empty uses variant default icon) |
| `IconSize` | String | `6` | Tailwind size token or CSS size for icon |
| `RoundedBox` | Boolean | `True` | Use rounded corners similar to Daisy rounded-box |
| `BorderWidth` | Int | `1` | Border width in dip |
| `Shadow` | String | `none` | Elevation shadow level |
| `ActionSpacing` | Int | `6` | Spacing in dip between action views |
| `AutoResize` | Boolean | `True` | Automatically resize height to fit content. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ClearActions`
- `GetContentPanelAs B4XView`
- `AddActionButton(Text As String, Tag As Object) As B4XView`
- `IsReadyAs Boolean`
- `GetVisualColorsAs Map`
- `SizeToFit(AvailableWidth As Int)`
- `RaiseActionClick(Tag As Object)`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setAlertStyle(Value As String)`
- `getAlertStyleAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setText(Value As String)`
- `getTextAs String`
- `setMessage(Value As String)`
- `getMessageAs String`
- `setDescription(Value As String)`
- `getDescriptionAs String`
- `setIconVisible(Value As Boolean)`
- `getIconVisibleAs Boolean`
- `setIconAsset(Path As String)`
- `getIconAssetAs String`
- `setIconSize(Value As Object)`
- `getIconSizeAs Float`
- `setIconColor(Value As Object)`
- `getIconColorAs Int`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setBorderWidth(Value As Float)`
- `getBorderWidthAs Float`
- `resetBorderWidthToTheme`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setActionSpacing(Value As Float)`
- `getActionSpacingAs Float`
- `setVariantPalette(Palette As Map)`
- `getVariantPaletteAs Map`
- `applyActiveTheme`
- `setBackgroundColor(Color As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Color As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setBorderColor(Color As Int)`
- `getBorderColorAs Int`
- `setBorderColorVariant(VariantName As String)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyApp

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `ClearSvgTextCache`
- `GetCachedSvgText(Path As String, DefaultText As String) As String`


---

## B4XDaisyAvatar

### Events
- `Click`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Image` | String | `` | Full image path on device |
| `Mask` | String | `circle` | Avatar mask shape |
| `RoundedBox` | Boolean | `False` | Use rounded box mask (radius: 4dip). Overrides mask setting. |
| `Shadow` | String | `none` | Elevation shadow level (Tailwind/Daisy scale) |
| `Variant` | String | `none` | Variant used for placeholder and status colors |
| `Width` | String | `w-10` | Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem) |
| `Height` | String | `h-10` | Tailwind size token or CSS size (eg h-12, 80px, 4em, 5rem) |
| `AvatarType` | String | `image` | Content type rendered inside avatar |
| `PlaceHolder` | String | `` | Placeholder text for text type (and image/svg fallback) |
| `TextSize` | String | `text-sm` | Placeholder text size token (eg text-sm, text-lg). Empty = auto-fit |
| `TextColor` | Color | `0x00000000` | Placeholder text color (0 = theme base-content) |
| `BackgroundColor` | Color | `0x00000000` | Placeholder background color (0 = variant/theme fallback) |
| `Padding` | String | `` | Padding utility/value for avatar drawing area (eg p-2, px-1, 2) |
| `Margin` | String | `` | Margin utility/value for avatar host insets (eg m-2, mx-1.5, 1) |
| `CenterOnParent` | Boolean | `True` | Center avatar inside parent bounds |
| `ChatImage` | Boolean | `False` | Apply chat-image rendering defaults (shared with chat bubble usage) |
| `Status` | String | `none` | Online indicator status |
| `ShowOnline` | Boolean | `False` | Show online/offline indicator dot |
| `UseVariantStatusColors` | Boolean | `False` | Derive online/offline colors from current variant (default keeps success green / gray) |
| `OnlineColor` | Color | `0x00000000` | Override online color (0 means auto) |
| `OfflineColor` | Color | `0x00000000` | Override offline color (0 means auto) |
| `RingColor` | Color | `0x00000000` | Ring color override (0 means auto by variant) |
| `RingWidth` | Int | `0` | Ring stroke width in dip |
| `RingOffset` | Int | `0` | Space between image and ring in dip |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |
| `ResizeMode` | String | `FILL_NO_DISTORTIONS` | Aspect scaling mode (similar to B4XImageView ResizeMode) |
| `BlurRadius` | Int | `0` | Image blur level (0 = disabled, 1 to 25 = scale-down blur factor) |
| `Glass` | Boolean | `False` | Enable translucent glass shine and highlight borders |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `getViewAs B4XView`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ResizeToParent(b4xV As B4XView)`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setResizeMode(Value As String)`
- `getResizeModeAs String`
- `setBlurRadius(Value As Int)`
- `getBlurRadiusAs Int`
- `setGlass(Value As Boolean)`
- `getGlassAs Boolean`
- `setChatImage(Value As Boolean)`
- `getChatImageAs Boolean`
- `setVariantPalette(Palette As Map)`
- `getVariantPaletteAs Map`
- `applyActiveTheme`
- `setUseVariantStatusColors(Enabled As Boolean)`
- `getUseVariantStatusColorsAs Boolean`
- `setAvatar(Path As String)`
- `getAvatarAs String`
- `setImage(Path As String)`
- `getImageAs String`
- `setMask(Value As String)`
- `getMaskAs String`
- `setAvatarBitmap(bmp As B4XBitmap, Tag As Object)`
- `getAvatarTagAs Object`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setAvatarStatus(Mode As String)`
- `setStatus(Mode As String)`
- `getAvatarStatusAs String`
- `getStatusAs String`
- `setAvatarStatusColors(OnlineColor As Int, OfflineColor As Int)`
- `setAvatarOnlineColor(OnlineColor As Int)`
- `getOnlineColorAs Int`
- `setOfflineColor(OfflineColor As Int)`
- `getOfflineColorAs Int`
- `setAvatarOnlineColorVariant(VariantName As String)`
- `setOnlineColor(OnlineColor As Int)`
- `setOnlineColorVariant(VariantName As String)`
- `getAvatarOnlineColorAs Int`
- `getAvatarOfflineColorAs Int`
- `setShowOnline(Show As Boolean)`
- `getShowOnlineAs Boolean`
- `setAvatarType(Value As String)`
- `getAvatarTypeAs String`
- `setPlaceHolder(Value As String)`
- `getPlaceHolderAs String`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setTextColorVariant(VariantName As String)`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setAvatarMask(MaskName As String)`
- `getAvatarMaskAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setGlobalMask(MaskName As String)`
- `setAvatarSize(Size As Object)`
- `setWidth(Value As Object)`
- `setHeight(Value As Object)`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setCenterOnParent(Value As Boolean)`
- `getCenterOnParentAs Boolean`
- `getWidthAs Float`
- `getHeightAs Float`
- `setAvatarWidth(Value As Object)`
- `setAvatarHeight(Value As Object)`
- `getAvatarWidthAs Float`
- `getAvatarHeightAs Float`
- `setAvatarBorder(Color As Int, Width As Float)`
- `setAvatarBorderInset(Inset As Float)`
- `setRingColor(Color As Int)`
- `getRingColorAs Int`
- `setRingColorVariant(VariantName As String)`
- `setRingWidth(Width As Float)`
- `getRingWidthAs Float`
- `setRingOffset(Offset As Float)`
- `getRingOffsetAs Float`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`
- `SetBitmapAndFill(ImageView As B4XView, Bmp As B4XBitmap)`


---

## B4XDaisyAvatarGroup

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `full` | Tailwind size token or CSS size (eg full, 72, 320px, 20rem) |
| `Height` | String | `h-12` | Tailwind size token or CSS size (eg h-12, 80px, 5rem) |
| `Padding` | String | `` | Padding utility/value for group content (eg p-2, px-3, 2) |
| `Margin` | String | `` | Margin utility/value for group host insets (eg m-2, mx-1.5, 1) |
| `Spacing` | String | `-space-x-6` | Overlap or gap utility (eg -space-x-6, space-x-4) |
| `AvatarSize` | String | `12` | Tailwind size for avatars (e.g. 12, 16, 24) |
| `LimitTo` | Int | `5` | Max avatars shown before overflow placeholder (+N) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddAvatar(Avatar As B4XDaisyAvatar) As Int`
- `AddAvatarView(ChildView As B4XView, Tag As Object) As Int`
- `Clear`
- `getCountAs Int`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setSpacing(Value As String)`
- `getSpacingAs String`
- `applyActiveTheme`
- `setAvatarSize(Value As Object)`
- `getAvatarSizeAs Object`
- `setLimitTo(Value As Int)`
- `getLimitToAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyBadge

### Events
- `Click (Tag As Object)`
- `CloseClick (Tag As Object)`
- `Checked (Id As String, Checked As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `fit-content` | Tailwind size token, CSS size, or fit-content |
| `Height` | String | `h-6` | Tailwind size token or CSS size (eg h-6, 24px, 1.5rem) |
| `Size` | String | `md` | Badge size token |
| `Variant` | String | `none` | Daisy variant used for colors |
| `BadgeStyle` | String | `solid` | Badge visual style |
| `Text` | String | `Badge` | Text displayed inside badge |
| `Padding` | String | `` | Tailwind spacing utilities (eg px-2, py-1, p-1.5) |
| `Margin` | String | `` | Tailwind spacing utilities (eg m-1, mx-2) |
| `Visible` | Boolean | `True` | Show or hide badge view |
| `AvatarVisible` | Boolean | `False` | Show avatar inside badge |
| `AvatarImage` | String | `` | Avatar image path from assets or full path |
| `AvatarText` | String | `` | Avatar placeholder text when image is empty |
| `AvatarPosition` | String | `left` | Avatar placement relative to text |
| `IconAsset` | String | `` | SVG asset used for left icon |
| `Toggle` | Boolean | `False` | Enables checked/unchecked toggle behavior |
| `Checked` | Boolean | `False` | Current checked state (effective when Toggle is true) |
| `CheckedColor` | Color | `0x00000000` | Background color used when checked (0 uses variant fallback) |
| `CheckedTextColor` | Color | `0x00000000` | Text/icon color used when checked (0 uses variant fallback) |
| `ID` | String | `` | Optional chip identifier returned in checked event |
| `Closable` | Boolean | `False` | Show close icon on the right side |
| `CloseIconAsset` | String | `xmark-solid.svg` | SVG asset used for close icon |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use selector radius from active theme |
| `CapValue` | Int | `99` | Numeric cap - values above this display as cap+ (0 disables capping) |
| `Shadow` | String | `none` | Elevation shadow level |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setSize(Value As String)`
- `getSizeAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setBadgeStyle(Value As String)`
- `getBadgeStyleAs String`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setText(Value As String)`
- `getTextAs String`
- `setTextCentered(Value As Boolean)`
- `getTextCenteredAs Boolean`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setAvatarVisible(Value As Boolean)`
- `getAvatarVisibleAs Boolean`
- `setAvatarImage(Value As String)`
- `getAvatarImageAs String`
- `setAvatarText(Value As String)`
- `getAvatarTextAs String`
- `setAvatarPosition(Value As String)`
- `getAvatarPositionAs String`
- `setIconAsset(Value As String)`
- `getIconAssetAs String`
- `setToggle(Value As Boolean)`
- `getToggleAs Boolean`
- `setChecked(Value As Boolean)`
- `getCheckedAs Boolean`
- `setCheckedColor(Value As Int)`
- `getCheckedColorAs Int`
- `setCheckedTextColor(Value As Int)`
- `getCheckedTextColorAs Int`
- `setId(Value As String)`
- `getIdAs String`
- `setClosable(Value As Boolean)`
- `getClosableAs Boolean`
- `setCloseIconAsset(Value As String)`
- `getCloseIconAssetAs String`
- `setCapValue(Value As Int)`
- `getCapValueAs Int`
- `setValue(Value As Int)`
- `getValueAs Int`
- `incrementAs Int`
- `incrementBy(Amount As Int) As Int`
- `decrementAs Int`
- `decrementBy(Amount As Int) As Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderColorVariant(VariantName As String)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`


---

## B4XDaisyBadgeGroupSelect

### Events
- `ItemChanged (Item As Map)`
- `FocusChanged (HasFocus As Boolean)`
- `Changed (SelectedIds As List)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Legend` | String | `Select options` | Fieldset legend text |
| `LegendSize` | String | `text-sm` | Tailwind-like text size token for legend |
| `LegendBold` | Boolean | `False` | Render the fieldset legend caption in bold |
| `Variant` | String | `none` | Optional accent variant for border tint |
| `BorderStyle` | String | `outlined` | Border visual style |
| `Padding` | Int | `16` | Inner content padding in dip (p-4) |
| `AutoHeight` | Boolean | `True` | Automatically grow to fit added content |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use box radius for container |
| `Shadow` | String | `none` | Elevation shadow level |
| `BackgroundColor` | Color | `0x00000000` | Background color (0 = default bg-base-200) |
| `TextColor` | Color | `0x00000000` | Legend text color (0 = use theme token) |
| `BorderColor` | Color | `0x00000000` | Border color override (0 = default border-base-300) |
| `BorderSize` | Int | `1` | Border width in dip |
| `BadgeSelectionMode` | String | `multi` | Single allows one checked badge, multi allows many |
| `BadgeSize` | String | `md` | Badge size token |
| `BadgeHeight` | String | `8` | Badge height token (tailwind/css size) |
| `BadgeColor` | String | `neutral` | Default (unchecked) badge color variant |
| `BadgeCheckedColor` | Color | `0x00000000` | Checked badge background color (0 uses success) |
| `BadgeCheckedTextColor` | Color | `0x00000000` | Checked badge text color (0 uses success text/white fallback) |
| `Gap` | Int | `8` | Horizontal gap between badges in dip |
| `RowGap` | Int | `8` | Vertical gap between badge rows in dip |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `AddBadgeItem(Id As String, Text As String)`
- `RemoveBadgeItem(Id As String)`
- `ClearBadgeItems`
- `setItems(Items As Object)`
- `getItemsAs List`
- `setItemsSpec(Value As String)`
- `getItemsSpecAs String`
- `setBadgeSelectionMode(Value As String)`
- `getBadgeSelectionModeAs String`
- `setSelected(Value As String)`
- `getSelectedAs String`
- `setChecked(CheckedIds As String)`
- `getCheckedAs String`
- `IsItemSelected(Id As String) As Boolean`
- `SetItemChecked(Id As String, Checked As Boolean)`
- `CheckItem(Id As String)`
- `UncheckItem(Id As String)`
- `ClearSelection`
- `setLegend(Value As String)`
- `getLegendAs String`
- `setLegendSize(Value As String)`
- `getLegendSizeAs String`
- `setLegendBold(Value As Boolean)`
- `getLegendBoldAs Boolean`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `isRoundedAs Boolean`
- `setRoundedBox(Value As Boolean)`
- `isRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setBackgroundColor(Value As Object)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Object)`
- `getTextColorAs Int`
- `setBorderColor(Value As Object)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setBadgeSize(Value As String)`
- `getBadgeSizeAs String`
- `setBadgeHeight(Value As String)`
- `getBadgeHeightAs String`
- `setBadgeColor(Value As String)`
- `getBadgeColorAs String`
- `setBadgeStyle(Value As String)`
- `getBadgeStyleAs String`
- `setBadgeCheckedColor(Value As Object)`
- `getBadgeCheckedColorAs Int`
- `setBadgeCheckedTextColor(Value As Object)`
- `getBadgeCheckedTextColorAs Int`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setRowGap(Value As Int)`
- `getRowGapAs Int`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `ReceiveFocus`
- `Blur`
- `setTag(Value As Object)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyBoxModel

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `GetDefaultSpacingScaleAs Map`
- `TailwindSpacingToDip(Value As Object, DefaultDip As Float) As Float`
- `CreateDefaultModelAs Map`
- `ResolveLength(Value As Object, ParentSize As Float, DefaultDip As Float) As Float`
- `ApplyPaddingUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean`
- `ApplyPaddingUtilities(Model As Map, Utilities As String, IsRtl As Boolean)`
- `ApplyMarginUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean`
- `ApplyMarginUtilities(Model As Map, Utilities As String, IsRtl As Boolean)`
- `ApplyRadiusUtility(Model As Map, Utility As String, IsRtl As Boolean) As Boolean`
- `ApplyRadiusUtilities(Model As Map, Utilities As String, IsRtl As Boolean)`
- `GetCornerRadius(Model As Map, Corner As String, Fallback As Float) As Float`
- `ResolveOuterRect(HostRect As B4XRect, Model As Map) As B4XRect`
- `ResolveBorderRect(OuterRect As B4XRect, Model As Map) As B4XRect`
- `ResolvePaddingRect(BorderRect As B4XRect, Model As Map) As B4XRect`
- `ResolveContentRect(BorderRect As B4XRect, Model As Map) As B4XRect`
- `ExpandContentWidth(ContentWidth As Float, Model As Map) As Float`
- `ExpandContentHeight(ContentHeight As Float, Model As Map) As Float`
- `ToLocalRect(AbsoluteRect As B4XRect, OriginRect As B4XRect) As B4XRect`


---

## B4XDaisyBreadcrumbs

### Events
- `ItemClick (ItemId As String)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables breadcrumb interaction. |
| `Visible` | Boolean | `True` | Shows or hides the component. |
| `TextSize` | String | `text-sm` | Tailwind text size token used by all crumbs. |
| `CurrentIndex` | Int | `-1` | Active breadcrumb index. -1 uses the last item. |
| `RTL` | Boolean | `False` | Flips chevron direction for RTL languages. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `GetComputedHeightAs Int`
- `SetItems(Items As List)`
- `getItemsAs List`
- `ClearItems`
- `AddItem(Id As String, Text As String, IconPath As String, Clickable As Boolean)`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setCurrentIndex(Value As Int)`
- `getCurrentIndexAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `RemoveViewFromParent`


---

## B4XDaisyButton

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Text` | String | `Button` | Button label. |
| `Class` | String | `btn` | Daisy class tokens (for example: btn btn-primary btn-outline). |
| `Variant` | String | `default` | Semantic color variant. |
| `Style` | String | `solid` | Daisy button style. |
| `Size` | String | `md` | Daisy button size token. |
| `Rounded` | String | `theme` | Border radius token. |
| `Padding` | String | `` | Tailwind padding utility tokens (For example: px-3 py-1). |
| `Margin` | String | `` | Tailwind margin utility tokens. |
| `Width` | String | `40px` | Tailwind/CSS width token (For example: auto, 40px, w-40, [12rem]). |
| `Height` | String | `auto` | Tailwind/CSS height token (For example: auto, 40px, h-10, [3rem]). |
| `IconName` | String | `` | SVG icon asset file name. |
| `IconColor` | Color | `0x00FFFFFF` | Optional icon color override. |
| `Wide` | Boolean | `False` | Applies btn-wide behavior. |
| `Block` | Boolean | `False` | Applies btn-block behavior. |
| `Square` | Boolean | `False` | Applies btn-square behavior. |
| `Circle` | Boolean | `False` | Applies btn-circle behavior. |
| `Active` | Boolean | `False` | Applies btn-active behavior. |
| `Disabled` | Boolean | `False` | Applies disabled behavior. |
| `Loading` | Boolean | `False` | Shows loading indicator. |
| `BackgroundColor` | Color | `0x00FFFFFF` | Override background color. |
| `TextColor` | Color | `0x00FFFFFF` | Override text color. |
| `BorderColor` | Color | `0x00FFFFFF` | Override border color. |
| `Visible` | Boolean | `True` | Show Or hide component. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetEstimateContentWidthAs Int`
- `setText(Value As String)`
- `getTextAs String`
- `setClass(Value As String)`
- `getClassAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setIconName(Value As String)`
- `getIconNameAs String`
- `setIconColor(Value As Int)`
- `getIconColorAs Int`
- `setWide(Value As Boolean)`
- `getWideAs Boolean`
- `setBlock(Value As Boolean)`
- `getBlockAs Boolean`
- `setSquare(Value As Boolean)`
- `getSquareAs Boolean`
- `setCircle(Value As Boolean)`
- `getCircleAs Boolean`
- `setActive(Value As Boolean)`
- `getActiveAs Boolean`
- `setDisabled(Value As Boolean)`
- `getDisabledAs Boolean`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setLoading(Value As Boolean)`
- `getLoadingAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getViewAs B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `setFocus(Value As Boolean)`
- `getIsFocusedAs Boolean`
- `RequestFocus`


---

## B4XDaisyCanvasSpinner

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Size` | String | `100dip` | Width/height of the spinner (px, %, dip, etc). |
| `Color1` | Color | `0xFF3FC3EE` | First ring color. |
| `Color2` | Color | `0xFFF27474` | Second ring color. |
| `Color3` | Color | `0xFFF8BB86` | Third ring color. |
| `StrokeWidth` | String | `4dip` | Ring border thickness. |
| `OverlayColor` | Color | `0xFFFFFFFF` | Backdrop color when overlay is shown. |
| `OverlayOpacity` | Float | `0.0` | Backdrop opacity from 0.0 (transparent) to 1.0 (opaque). |
| `Visible` | Boolean | `True` | Shows or hides the spinner. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `GetComputedHeightAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AttachTo(Target As B4XView) As B4XView`
- `Resize(Width As Int, Height As Int)`
- `AddChild(View As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `Show(Target As B4XView)`
- `Hide`
- `getVisibleAs Boolean`
- `setVisible(b As Boolean)`
- `getSizeAs String`
- `setSize(s As String)`
- `getColor1As Int`
- `setColor1(c As Int)`
- `getColor2As Int`
- `setColor2(c As Int)`
- `getColor3As Int`
- `setColor3(c As Int)`
- `getStrokeWidthAs Float`
- `setStrokeWidth(s As Float)`
- `getOverlayColorAs Int`
- `setOverlayColor(c As Int)`
- `getOverlayOpacityAs Float`
- `setOverlayOpacity(o As Float)`


---

## B4XDaisyCard

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-full` | Card width token. |
| `Height` | String | `auto` | Card height token (auto, h-64, h-[500px], etc). |
| `Title` | String | `Card Title` | Title text. |
| `ImagePath` | String | `` | Asset Or full path. |
| `ImageWidth` | String | `w-full` | Image width token. |
| `ImageHeight` | String | `h-full` | Image height token. |
| `ImageClasses` | String | `` | Extra image utility classes. |
| `Size` | String | `md` | Size token. |
| `Style` | String | `none` | Border style. |
| `Variant` | String | `none` | Semantic variant For full-card background/text colors. |
| `LayoutMode` | String | `top` | Figure placement. |
| `BackgroundColor` | Color | `0x00000000` | Explicit card background color override (0 uses theme token). |
| `TextColor` | Color | `0x00000000` | Explicit text color override For all content inside card body (0 uses theme token). |
| `PlaceItemsCenter` | Boolean | `False` | Centers title/actions content similar To place-items-center. |
| `Rounded` | String | `theme` | Radius mode. |
| `Shadow` | String | `sm` | Elevation level. |
| `Visible` | Boolean | `True` | Show Or hide card. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `setTop(Value As Int)`
- `getFigureContainerAs B4XView`
- `getCardBodyAs B4XView`
- `getBodyPartContainerAs B4XView`
- `getTitleContainerAs B4XView`
- `getCardTitleAs B4XView`
- `getCardActionsAs B4XView`
- `getContainerAs B4XView`
- `getBodyContainerAs B4XView`
- `getTitleExtrasContainerAs B4XView`
- `getActionsContainerAs B4XView`
- `AddAction(btn As B4XDaisyButton)`
- `getActionsCountAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setImagePath(Value As String)`
- `setImageWidth(Value As String)`
- `getImageWidthAs String`
- `setImageHeight(Value As String)`
- `getImageHeightAs String`
- `setImageClasses(Value As String)`
- `getImageClassesAs String`
- `SetImage(Image As B4XBitmap)`
- `ClearImage`
- `setSize(Value As String)`
- `getSizeAs String`
- `setStyle(Value As String)`
- `setPlaceItemsCenter(Value As Boolean)`
- `getPlaceItemsCenterAs Boolean`
- `ShowTitle`
- `HideTitle`
- `ShowActions`
- `HideActions`
- `ShowImage`
- `HideImage`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setLayoutMode(Value As String)`
- `setRounded(Value As String)`
- `setShadow(Value As String)`
- `setVisible(Value As Boolean)`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColorVariant(VariantName As String)`


---

## B4XDaisyCardActions

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `GapDip` | Int | `8` | Gap between action items. |
| `Wrap` | Boolean | `True` | Wrap action items when row is full. |
| `Justify` | String | `start` | Horizontal row alignment. |
| `Visible` | Boolean | `True` | Show Or hide actions. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `Relayout`
- `setGapDip(Value As Int)`
- `getGapDipAs Int`
- `setWrap(Value As Boolean)`
- `getWrapAs Boolean`
- `setJustify(Value As String)`
- `getJustifyAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getContainerAs B4XView`


---

## B4XDaisyCardBody

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Size` | String | `md` | Body size token. |
| `Height` | String | `auto` | Body height token (auto, h-32, h-[120px], etc). |
| `Visible` | Boolean | `True` | Show or hide body container. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setSize(Value As String)`
- `getSizeAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `getPaddingDipAs Int`
- `getGapDipAs Int`
- `getBodyTextSizeAs Float`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getContainerAs B4XView`


---

## B4XDaisyCardTitle

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Text` | String | `Card Title` | Title text. |
| `Size` | String | `md` | Title size token. |
| `Centered` | Boolean | `False` | Center align title text. |
| `Gap` | Int | `8` | Horizontal gap between title text and extra components. |
| `SingleLine` | Boolean | `False` | Prevent text wrapping. |
| `Ellipsize` | String | `none` | Truncate with ellipsis when text overflows. |
| `Visible` | Boolean | `True` | Show or hide title. |
| `AutoResize` | Boolean | `True` | Automatically resize height to fit text and extra components. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setText(Value As String)`
- `getTextAs String`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setSize(Value As String)`
- `getSizeAs String`
- `setCentered(Value As Boolean)`
- `getCenteredAs Boolean`
- `setGapDip(Value As Int)`
- `getGapDipAs Int`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setSingleLine(Value As Boolean)`
- `getSingleLineAs Boolean`
- `setEllipsize(Value As String)`
- `getEllipsizeAs String`
- `getTextSizeAs Float`
- `getLabelAs B4XView`
- `getExtrasContainerAs B4XView`
- `getContainerAs B4XView`
- `Relayout`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`


---

## B4XDaisyCarousel

### Events
- `Click (Tag As Object)`
- `PageChanged (Index As Int)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Orientation` | String | `horizontal` | Carousel scroll orientation. |
| `Snap` | String | `start` | Carousel snapping behavior. |
| `Rounded` | String | `theme` | Corner radius variant. |
| `RoundedBox` | Boolean | `False` | Apply the DaisyUI rounded-box semantic corner radius. Takes priority over the Rounded property when True. |
| `Shadow` | String | `none` | Box shadow elevation level. |
| `NavigationButtons` | Boolean | `False` | Show prev/next navigation buttons overlaid on the carousel. |
| `IndicatorButtons` | Boolean | `False` | Show indicator dot buttons overlaid at the bottom of the carousel. |
| `AutoPlay` | Boolean | `False` | Automatically advance slides on a timed interval. |
| `AutoPlayInterval` | Int | `3000` | Milliseconds between auto-advance steps when AutoPlay is enabled. |
| `ItemGap` | Int | `0` | Gap in pixels between carousel items (space-x-N / space-y-N equivalent). |
| `Gap` | String | `` | Space between items as a Tailwind/DaisyUI spacing token: space-x-4, gap-2, 16px etc. Overrides Item Gap when non-empty. |
| `ContentPadding` | Int | `0` | Inner padding in pixels of the scroll area inside the carousel container (p-N equivalent). |
| `Padding` | String | `` | Inner content padding as a Tailwind/DaisyUI spacing token: p-4, p-2, 8px etc. Overrides Content Padding when non-empty. |
| `Width` | String | `w-full` | Width as a Tailwind class: w-full, w-64, w-1/2, w-[300px] etc. |
| `Height` | String | `h-[300px]` | Height as a Tailwind class: h-[300px], h-48, h-full, h-[200px], h-auto (auto-fits tallest item) etc. |
| `BackgroundColor` | String | `` | Background color as a DaisyUI/Tailwind token: neutral, base-200, primary, transparent etc. |
| `IndicatorBackgroundColor` | Color | `0x50000000` | Color of the indicator strip background. |
| `IndicatorActiveColor` | Color | `0xFFFFFFFF` | Color of the active indicator dot. |
| `IndicatorInactiveColor` | Color | `0x78FFFFFF` | Color of the inactive indicator dots. |
| `IndicatorDotSize` | Int | `10` | Size of the indicator dots. |
| `IndicatorDotGap` | Int | `6` | Spacing between indicator dots. |
| `IndicatorOffset` | Int | `0` | Distance to offset the indicator dots strip from its default edge. |
| `Visible` | Boolean | `True` | Visible state. |
| `Enabled` | Boolean | `True` | Enabled state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddItem(Item As B4XDaisyCarouselItem)`
- `RemoveItem(Item As B4XDaisyCarouselItem)`
- `ClearItems`
- `ScrollToItem(Index As Int)`
- `getOrientationAs String`
- `setOrientation(Value As String)`
- `getSnapAs String`
- `setSnap(Value As String)`
- `getRoundedAs String`
- `setRounded(Value As String)`
- `getTagAs Object`
- `setTag(Value As Object)`
- `getItemGapAs Int`
- `setItemGap(Value As Int)`
- `getGapAs String`
- `setGap(Value As String)`
- `getContentPaddingAs Int`
- `setContentPadding(Value As Int)`
- `getPaddingAs String`
- `setPadding(Value As String)`
- `getWidthAs String`
- `setWidth(Value As String)`
- `getHeightAs String`
- `setHeight(Value As String)`
- `getBackgroundColorAs String`
- `setBackgroundColor(Value As String)`
- `getNavigationButtonsAs Boolean`
- `setNavigationButtons(Value As Boolean)`
- `getIndicatorButtonsAs Boolean`
- `setIndicatorButtons(Value As Boolean)`
- `getAutoPlayAs Boolean`
- `setAutoPlay(Value As Boolean)`
- `getAutoPlayIntervalAs Int`
- `setAutoPlayInterval(Value As Int)`
- `StartAutoPlay`
- `StopAutoPlay`
- `getCurrentIndexAs Int`
- `getVisibleAs Boolean`
- `setVisible(Value As Boolean)`
- `getEnabledAs Boolean`
- `setEnabled(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setRoundedBox(Value As Boolean)`
- `getShadowAs String`
- `setShadow(Value As String)`
- `getIndicatorBackgroundColorAs Int`
- `setIndicatorBackgroundColor(Value As Int)`
- `getIndicatorActiveColorAs Int`
- `setIndicatorActiveColor(Value As Int)`
- `getIndicatorInactiveColorAs Int`
- `setIndicatorInactiveColor(Value As Int)`
- `getIndicatorDotSizeAs Int`
- `setIndicatorDotSize(Value As Int)`
- `getIndicatorDotGapAs Int`
- `setIndicatorDotGap(Value As Int)`
- `getIndicatorOffsetAs Int`
- `setIndicatorOffset(Value As Int)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyCarouselItem

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `ItemType` | String | `image` | Type of content to display. |
| `Source` | String | `` | Image file or SVG content/asset. |
| `Snap` | String | `start` | Carousel snapping position. |
| `Rounded` | String | `rounded-none` | Corner radius variant. |
| `Width` | String | `w-full` | Item width as a Tailwind class: w-full, w-1/2, w-64, w-[150px] etc. |
| `Height` | String | `h-full` | Item height as a Tailwind class: h-full, h-48, h-[200px] etc. |
| `ImageWidth` | String | `w-full` | Width of the image/content inside the item frame. w-full = 100% of item width. |
| `ImageHeight` | String | `h-full` | Height of the image/content inside the item frame. h-full = 100% of item height. |
| `ImageResizeMode` | String | `FILL_NO_DISTORTIONS` | How to scale the image within the carousel item frame. |
| `Visible` | Boolean | `True` | Visible state. |
| `Enabled` | Boolean | `True` | Enabled state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getItemTypeAs String`
- `setItemType(Value As String)`
- `getSourceAs String`
- `setSource(Value As String)`
- `getSnapAs String`
- `setSnap(Value As String)`
- `getRoundedAs String`
- `setRounded(Value As String)`
- `getTagAs Object`
- `setTag(Value As Object)`
- `getContainerAs B4XView`
- `getWidthAs String`
- `setWidth(Value As String)`
- `getHeightAs String`
- `setHeight(Value As String)`
- `getImageWidthAs String`
- `setImageWidth(Value As String)`
- `getImageHeightAs String`
- `setImageHeight(Value As String)`
- `getVisibleAs Boolean`
- `setVisible(Value As Boolean)`
- `getEnabledAs Boolean`
- `setEnabled(Value As Boolean)`
- `getImageResizeModeAs String`
- `setImageResizeMode(Value As String)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyChat

### Events
- `AvatarClick (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `AvatarMask` | String | `squircle` | Mask shape used for message avatars |
| `AvatarSize` | Int | `40` | Avatar size in dip for each message row |
| `FromBackgroundColor` | Color | `0xFF4338CA` | Background color for outgoing (from) bubbles |
| `FromTextColor` | Color | `0xFFFFFFFF` | Text color for outgoing (from) bubbles |
| `ToBackgroundColor` | Color | `0xFF0EA5E9` | Background color for incoming (to) bubbles |
| `ToTextColor` | Color | `0xFF082F49` | Text color for incoming (to) bubbles |
| `UseFromToColors` | Boolean | `True` | Use explicit from/to colors instead of theme defaults |
| `Theme` | String | `light` | Theme preset used for default chat colors |
| `DateTimeFormat` | String | `D` | Accepts Java DateFormat or flatpickr tokens (eg H:i, Y-m-d H:i) |
| `UseTimeAgo` | Boolean | `False` | Show relative timestamps (for example, 5m ago) |
| `ShowTimeAgoForToday` | Boolean | `True` | When enabled and UseTimeAgo is true, today's times show as time-ago while older dates use DateTimeFormat |
| `VerticalGap` | Int | `8` | Vertical spacing in dip between message rows |
| `Width` | Int | `0` | Explicit chat width in dip (0 uses base width) |
| `Height` | Int | `0` | Explicit chat height in dip (0 uses base height) |
| `Padding` | String | `` | Tailwind/spacing padding utilities (eg p-2, px-3, 2) |
| `Margin` | String | `` | Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `Resize(Width As Double, Height As Double)`
- `AddToParent(Parent As B4XView)`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ViewAs B4XView`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `Clear`
- `setConversations(Messages As List)`
- `ClearConversations`
- `AppendMessage(Message As Map) As String`
- `AppendMessageAndScroll(Message As Map, Smooth As Boolean) As String`
- `ScrollToMessage(BubbleId As String)`
- `ScrollToTop`
- `ScrollToBottom`
- `SmoothScrollToTop(DurationMs As Int) As ResumableSub`
- `SmoothScrollToBottom(DurationMs As Int) As ResumableSub`
- `SmoothScrollToMessage(BubbleId As String, DurationMs As Int) As ResumableSub`
- `SmoothScrollToPosition(Target As Int, DurationMs As Int) As ResumableSub`
- `getMessageById(BubbleId As String) As Map`
- `getMessage(BubbleId As String) As Map`
- `UpdateMessageById(BubbleId As String, Fields As Map) As Boolean`
- `UpdateMessage(Message As Map) As Boolean`
- `UpdateHeaderById(BubbleId As String, HeaderName As String, HeaderTime As String) As Boolean`
- `UpdateFooterById(BubbleId As String, FooterText As String) As Boolean`
- `UpdateAvatarById(BubbleId As String, AvatarBitmap As B4XBitmap) As Boolean`
- `UpdateOnlineStatusById(BubbleId As String, Status As String, OnlineColor As Int) As Boolean`
- `ReplaceMessageById(BubbleId As String, Message As Map) As Boolean`
- `DeleteMessageById(BubbleId As String) As Boolean`
- `AddMessage(Message As Map, ScrollTo As Boolean) As String`
- `LoadAvatarFilesFromAssets(Files As List)`
- `setAvatarFiles(Files As List)`
- `getAvatarFilesAs List`
- `RandomAvatarFileOrBlank(BlankPct As Int) As String`
- `RandomAvatarStatusAs String`
- `setBubbleAvatarStatusById(BubbleId As String, Mode As String)`
- `getBubbleIdsAs List`
- `setAvatarMask(Mask As String)`
- `getAvatarMaskAs String`
- `setMask(Mask As String)`
- `setAvatarSize(Size As Int)`
- `getAvatarSizeAs Int`
- `setFromBackgroundColor(Color As Int)`
- `getFromBackgroundColorAs Int`
- `setFromTextColor(Color As Int)`
- `getFromTextColorAs Int`
- `setToBackgroundColor(Color As Int)`
- `getToBackgroundColorAs Int`
- `setToTextColor(Color As Int)`
- `getToTextColorAs Int`
- `setFromToColors(FromBack As Int, FromText As Int, ToBack As Int, ToText As Int)`
- `setUseFromToColors(Enabled As Boolean)`
- `getUseFromToColorsAs Boolean`
- `setTheme(Name As String)`
- `getThemeAs String`
- `setDateTimeFormat(Value As String)`
- `getDateTimeFormatAs String`
- `setUseTimeAgo(Enabled As Boolean)`
- `getUseTimeAgoAs Boolean`
- `setShowTimeAgoForToday(Enabled As Boolean)`
- `getShowTimeAgoForTodayAs Boolean`
- `RegisterTheme(Name As String, PaletteMap As Map)`
- `getPaletteAs Map`
- `CreateVariant(BackColor As Int, TextColor As Int) As Map`
- `ShowOnline(Enabled As Boolean)`
- `setOnlineStatusColors(OnlineColor As Int, OfflineColor As Int)`
- `getOnlineStatusColorAs Int`
- `getOfflineStatusColorAs Int`
- `setVerticalGap(Gap As Int)`
- `getVerticalGapAs Int`
- `setWidth(Value As Int)`
- `getWidthAs Int`
- `setHeight(Value As Int)`
- `getHeightAs Int`
- `setSize(Width As Int, Height As Int)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyChatBubble

### Events
- `AvatarClick (Payload As Object)`
- `BubbleClick (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `AvatarMask` | String | `squircle` | Mask shape used for the bubble avatar |
| `AvatarSize` | Int | `40` | Avatar size in dip |
| `Id` | String | `` | Message author id |
| `FromId` | String | `` | Current user id used to resolve outgoing side |
| `Variant` | String | `neutral` | Daisy variant for bubble colors |
| `Side` | String | `start` | Bubble alignment when id-based side is not used |
| `BubbleStyle` | String | `rounded` | Bubble visual style |
| `MaxWidthPercent` | Int | `90` | Maximum bubble width as a percent of row width |
| `UseFromToColors` | Boolean | `False` | Use explicit from/to colors instead of variant defaults |
| `FromBackgroundColor` | Color | `0xFFE5E7EB` | Background color for outgoing (from) bubbles |
| `FromTextColor` | Color | `0xFF111827` | Text color for outgoing (from) bubbles |
| `ToBackgroundColor` | Color | `0xFFDBEAFE` | Background color for incoming (to) bubbles |
| `ToTextColor` | Color | `0xFF1E3A8A` | Text color for incoming (to) bubbles |
| `ShowOnline` | Boolean | `True` | Show avatar online/offline indicator |
| `Padding` | String | `` | Tailwind/spacing padding utilities (eg p-2, px-3, 2) |
| `Margin` | String | `` | Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView)`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ViewAs B4XView`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setId(Value As String)`
- `getIdAs String`
- `setFromId(Value As String)`
- `getFromIdAs String`
- `GetUsedHeightAs Int`
- `setSide(s As String)`
- `getSideAs String`
- `setVariant(v As String)`
- `getVariantAs String`
- `setBubbleStyle(StyleName As String)`
- `SetOutline(Enabled As Boolean, Color As Int, Width As Float)`
- `getBubbleStyleAs String`
- `setMaxWidthPercent(p As Float)`
- `getMaxWidthPercentAs Float`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `SetAvatarVisible(b As Boolean)`
- `SetAvatarBitmap(bmp As B4XBitmap, Tag As Object)`
- `SetAvatarStatus(Mode As String)`
- `SetAvatarStatusColors(OnlineColor As Int, OfflineColor As Int)`
- `GetAvatarOnlineColorAs Int`
- `GetAvatarOfflineColorAs Int`
- `SetAvatarBorder(Color As Int, Width As Float)`
- `SetAvatarBorderInset(Inset As Float)`
- `setAvatarMask(MaskName As String)`
- `getAvatarMaskAs String`
- `SetGlobalMask(MaskName As String)`
- `setAvatarSize(Size As Float)`
- `SetAvatarWidth(Width As Float)`
- `SetAvatarHeight(Height As Float)`
- `GetAvatarWidthAs Float`
- `GetAvatarHeightAs Float`
- `getAvatarSizeAs Float`
- `setShowOnline(Show As Boolean)`
- `getShowOnlineAs Boolean`
- `setFromBackgroundColor(Color As Int)`
- `getFromBackgroundColorAs Int`
- `setFromTextColor(Color As Int)`
- `getFromTextColorAs Int`
- `setToBackgroundColor(Color As Int)`
- `getToBackgroundColorAs Int`
- `setToTextColor(Color As Int)`
- `getToTextColorAs Int`
- `SetFromToColors(FromBack As Int, FromText As Int, ToBack As Int, ToText As Int)`
- `setUseFromToColors(Enabled As Boolean)`
- `getUseFromToColorsAs Boolean`
- `SetVariantPalette(Palette As Map)`
- `GetVariantPaletteAs Map`
- `SetColors(BackOverride As Int, TextOverride As Int, MutedOverride As Int)`
- `SetHeader(Text As String)`
- `SetHeaderTime(Text As String)`
- `SetHeaderParts(NameText As String, TimeText As String)`
- `SetHeaderVisible(b As Boolean)`
- `SetHeaderNameVisible(b As Boolean)`
- `SetHeaderTimeVisible(b As Boolean)`
- `SetFooter(Text As String)`
- `SetFooterVisible(b As Boolean)`
- `SetMessage(Text As String)`
- `SetBubbleVisible(b As Boolean)`
- `SetDebugBorders(Enabled As Boolean)`
- `GetDebugBordersAs Boolean`
- `SetStatus(Mode As String, ExtraText As String)`
- `SetImage(bmp As B4XBitmap, MaxHeight As Int)`
- `SetCustomContent(v As B4XView)`
- `SetContentAll(Header As String, Body As String, Footer As String, SideNow As String, VariantNow As String)`
- `MeasureHeight(AvailableWidth As Int) As Int`
- `RaiseBubbleClick(Tag As Object)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyCheckbox

### Events
- `Checked (Checked As Boolean)`
- `Click (Tag As Object)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `GroupName` | String | `` | Checkbox group name. |
| `Checked` | Boolean | `False` | Checked state. |
| `Indeterminate` | Boolean | `False` | Indeterminate state. |
| `Value` | String | `` | Value assigned to the checkbox. |
| `Text` | String | `` | Label text. |
| `Variant` | String | `none` | Color variant. |
| `Size` | String | `md` | Size variant. |
| `Position` | String | `start` | Position alignment. |
| `Enabled` | Boolean | `True` | Enabled state. |
| `Visible` | Boolean | `True` | Visible state. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `CheckedBackgroundColor` | Color | `0x00FFFFFF` | Override checked background color. |
| `CheckedBorderColor` | Color | `0x00FFFFFF` | Override checked border color. |
| `CheckedTextColor` | Color | `0x00FFFFFF` | Override checked checkmark/text color. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setChecked(Value As Boolean)`
- `getCheckedAs Boolean`
- `setValue(Value As String)`
- `getValueAs String`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `getRoleAs String`
- `setIndeterminate(Value As Boolean)`
- `getIndeterminateAs Boolean`
- `setText(Value As String)`
- `getTextAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setPosition(Value As String)`
- `getPositionAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `getIsValidAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ValidateAs Boolean`
- `setBackgroundColor(Color As Int)`
- `getBackgroundColorAs Int`
- `setBorderColor(Color As Int)`
- `getBorderColorAs Int`
- `setTextColor(Color As Int)`
- `getTextColorAs Int`
- `setCheckedBackgroundColor(Color As Int)`
- `getCheckedBackgroundColorAs Int`
- `setCheckedBorderColor(Color As Int)`
- `getCheckedBorderColorAs Int`
- `setCheckedTextColor(Color As Int)`
- `getCheckedTextColorAs Int`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getComputedHeightAs Int`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisyCheckboxGroup

### Events
- `ItemChanged (id As String, text As String, checked As Boolean)`
- `Changed (SelectedIds As List)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Legend` | String | `Select options` | Fieldset legend text |
| `LegendSize` | String | `theme` | Legend text size token |
| `LegendBold` | Boolean | `False` | Render the fieldset legend caption in bold |
| `Variant` | String | `none` | Optional accent variant for border tint |
| `BorderStyle` | String | `outlined` | Border visual style |
| `Padding` | Int | `16` | Inner content padding in dip |
| `AutoHeight` | Boolean | `True` | Automatically grow to fit added content |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use box radius for container |
| `Shadow` | String | `none` | Elevation shadow level |
| `BackgroundColor` | Color | `0x00000000` | Background color (0 = default bg-base-200) |
| `TextColor` | Color | `0x00000000` | Legend text color (0 = use theme token) |
| `BorderColor` | Color | `0x00000000` | Border color override (0 = default border-base-300) |
| `BorderSize` | Int | `1` | Border width in dip |
| `InputBorder` | Boolean | `False` | When True, apply B4XDaisyInput border color and width to the fieldset |
| `Direction` | String | `vertical` | Items layout direction |
| `Alignment` | String | `start` | Checkbox element check position |
| `CheckboxColor` | String | `neutral` | Default checkbox color variant |
| `CheckboxSize` | String | `md` | Checkbox size token |
| `Gap` | Int | `8` | Gap between elements in dip |
| `RowGap` | Int | `8` | Row gap for wrapped flow mode in dip |
| `Required` | Boolean | `False` | Whether at least one option must be selected. |
| `HintText` | String | `` | Helper text displayed below the group. |
| `ErrorText` | String | `` | Error text displayed below the group when validation fails. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `AddItem(Id As String, Text As String)`
- `RemoveItem(Id As String)`
- `ClearItems`
- `setItems(Items As Map)`
- `getItemsAs Map`
- `setChecked(CheckedIds As String)`
- `getCheckedAs String`
- `SetItemChecked(Id As String, Checked As Boolean)`
- `CheckItem(Id As String)`
- `UncheckItem(Id As String)`
- `IsItemChecked(Id As String) As Boolean`
- `setLegend(Value As String)`
- `getLegendAs String`
- `setLegendSize(Value As String)`
- `getLegendSizeAs String`
- `setLegendBold(Value As Boolean)`
- `getLegendBoldAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setAlignment(Value As String)`
- `getAlignmentAs String`
- `setCheckboxColor(Value As String)`
- `getCheckboxColorAs String`
- `setCheckboxSize(Value As String)`
- `getCheckboxSizeAs String`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setRowGap(Value As Int)`
- `getRowGapAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `ReceiveFocus`
- `Blur`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `isRoundedAs Boolean`
- `setRoundedBox(Value As Boolean)`
- `isRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setInputBorder(Value As Boolean)`
- `getInputBorderAs Boolean`
- `GetComputedHeightAs Int`
- `Release`
- `RemoveViewFromParent`


---

## B4XDaisyCollapse

### Events
- `Click (Tag As Object)`
- `StateChanged (Open As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Open` | Boolean | `False` | Initial expanded state. |
| `Icon` | String | `none` | Expansion indicator icon. |
| `Variant` | String | `none` | Semantic variant. |
| `Rounded` | String | `theme` | Radius mode. |
| `Shadow` | String | `none` | Elevation level. |
| `Visible` | Boolean | `True` | Show or hide component. |
| `TitleText` | String | `Click to expand` | Text shown in the collapse title bar. |
| `TitleVariant` | String | `none` | Semantic color variant applied to the title background. |
| `TitleSize` | String | `text-sm` | Font size token for title text. |
| `TitleIconName` | String | `` | SVG asset filename shown on the left of the title text (e.g. home-solid.svg). |
| `TitleColor` | Color | `0x00000000` | Override title text color (wins over variant and TitleTextColor; 0 = unset). |
| `TitleIconColor` | Color | `0x00000000` | Override title icon color independently (0 = follow text color). |
| `Width` | String | `w-full` | Component width as Tailwind fraction of parent (w-full = fill parent). |
| `BorderWidth` | String | `border` | Tailwind border width utility (e.g. border, border-2, border-0) |
| `BorderStyle` | String | `solid` | The style of the border. |
| `BorderColor` | String | `border-base-300` | Tailwind border color utility (e.g. border-primary, border-base-300) |
| `IconPosition` | String | `right` | Position of the expansion indicator (arrow/plus). |
| `GroupName` | String | `` | Join multiple collapses into an accordion group. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `setOpen(Value As Boolean)`
- `getOpenAs Boolean`
- `Toggle`
- `setIcon(Value As String)`
- `getIconAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTitleText(Value As String)`
- `getTitleTextAs String`
- `setTitleVariant(Value As String)`
- `getTitleVariantAs String`
- `setTitleSize(Value As String)`
- `getTitleSizeAs String`
- `setTitleBackgroundColor(Value As Int)`
- `getTitleBackgroundColorAs Int`
- `setTitleTextColor(Value As Int)`
- `getTitleTextColorAs Int`
- `setTitleIconName(Value As String)`
- `getTitleIconNameAs String`
- `setTitleColor(Value As Int)`
- `getTitleColorAs Int`
- `setTitleIconColor(Value As Int)`
- `getTitleIconColorAs Int`
- `RefreshContent`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setBorderWidth(Value As String)`
- `getBorderWidthAs String`
- `setBorderColor(Value As String)`
- `getBorderColorAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setIconPosition(Value As String)`
- `getIconPositionAs String`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `CollapseTitleAs B4XDaisyCollapseTitle`
- `CollapseContentAs B4XDaisyCollapseContent`
- `getContentViewAs B4XView`


---

## B4XDaisyCollapseContent

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `BackgroundColor` | Color | `0x00000000` | Explicit background color override (0 uses parent/theme). |
| `TextColor` | Color | `0x00000000` | Explicit text color override for child labels (0 uses theme token). |
| `Visible` | Boolean | `True` | Show or hide content. |
| `AutoResize` | Boolean | `True` | Automatically resize height to fit child views. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `Relayout`
- `getContainerAs B4XView`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`


---

## B4XDaisyCollapseTitle

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Text` | String | `Collapse Title` | Title text. |
| `Size` | String | `md` | Title size token. |
| `BackgroundColor` | Color | `0x00000000` | Explicit background color override (0 uses parent/theme). |
| `TextColor` | Color | `0x00000000` | Explicit text color override (0 uses theme token). |
| `IconName` | String | `` | SVG asset filename shown on the left (e.g. home-solid.svg). |
| `Variant` | String | `none` | Semantic color variant � overrides background and text colors. |
| `IconColor` | Color | `0x00000000` | Override icon color independently (0 = follow text color). |
| `Visible` | Boolean | `True` | Show or hide title. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setText(Value As String)`
- `getTextAs String`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setSize(Value As String)`
- `getSizeAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setIconName(Value As String)`
- `getIconNameAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setIconColor(Value As Int)`
- `getIconColorAs Int`


---

## B4XDaisyCountdown

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Orientation` | String | `horizontal` | Layout orientation for the segments. |
| `Gap` | String | `gap-2` | Spacing between segments. |
| `BackgroundColor` | String | `transparent` | Background color for the container. |
| `Border` | Boolean | `False` | Show a border around the container (standard base-300 border). |
| `Rounded` | String | `none` | Corner radius token applied to the container and child items. |
| `Shadow` | String | `none` | Shadow effect applied to child items. |
| `Padding` | String | `p-0` | Inner padding for the container. |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `AddItem(Item As B4XDaisyCountdownItem)`
- `getViewAs B4XView`
- `getIsInitializedAs Boolean`
- `RemoveItem(Item As B4XDaisyCountdownItem)`
- `ClearItems`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getOrientationAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setOrientation(Value As String)`
- `getGapAs String`
- `setGap(Value As String)`
- `getBackgroundColorAs String`
- `setBackgroundColor(Value As String)`
- `getBorderAs Boolean`
- `setBorder(Value As Boolean)`
- `getPaddingAs String`
- `setPadding(Value As String)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyCountdownItem

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Value` | Int | `0` | Current numeric value (0-999). |
| `Digits` | Int | `1` | Minimum number of digits to display. |
| `Label` | String | `` | Text label for this segment (e.g. days, hours). |
| `LabelPosition` | String | `RIGHT` | Where to place the label relative to the number. |
| `Separator` | String | `` | Optional separator string (e.g. ":") shown after the number. |
| `TextSize` | String | `text-base` | Typography size token. |
| `Variant` | String | `none` | DaisyUI semantic color variant. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getIsInitializedAs Boolean`
- `getValueAs Int`
- `setValue(Value As Int)`
- `getDigitsAs Int`
- `setDigits(Value As Int)`
- `getLabelAs String`
- `setLabel(Value As String)`
- `getLabelPositionAs String`
- `setLabelPosition(Value As String)`
- `getSeparatorAs String`
- `setSeparator(Value As String)`
- `getTextSizeAs String`
- `setTextSize(Value As String)`
- `getVariantAs String`
- `setVariant(Value As String)`
- `getRoundedAs String`
- `setRounded(Value As String)`
- `getShadowAs String`
- `setShadow(Value As String)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyDashboard

### Events
- `ButtonClick (ButtonId As String, ButtonDef As Map)`
- `PageChanged (PageIndex As Int, PageCount As Int)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `RowsPerPage` | Int | `6` | Number of grid rows per page. |
| `ColumnsPerPage` | Int | `4` | Number of grid columns per page when Auto Grid is False. |
| `AutoGrid` | Boolean | `False` | Automatically calculate rows and columns from available size. |
| `MinCellWidth` | Int | `72` | Minimum tile width in dip used by Auto Grid. |
| `MinCellHeight` | Int | `100` | Minimum tile height in dip used by Auto Grid. |
| `PagePadding` | Int | `16` | Outer page padding in dip. |
| `CellSpacing` | Int | `12` | Spacing between grid cells horizontally in dip. |
| `CellSpacingY` | Int | `0` | Spacing between grid cells vertically in dip. |
| `ActiveIndicatorColor` | Color | `0xFF3B82F6` | Active page indicator color. |
| `InactiveIndicatorColor` | Color | `0x553B82F6` | Inactive page indicator color. |
| `BackgroundImage` | String | `` | Full image path or asset file name used as dashboard wallpaper. |
| `TextColor` | Color | `0xFFFFFFFF` | Text color for all button labels. |
| `GridTopOffset` | Int | `12` | Extra top offset in dip applied before the first dashboard row. |
| `Width` | String | `100%` | Dashboard width relative to parent. Examples: 100%, 320dip, 300. |
| `Height` | String | `100%` | Dashboard height relative to parent. Examples: 100%, 600dip, 300. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `Resize(Width As Int, Height As Int)`
- `AddToParent(Parent As B4XView)`
- `IsReadyAs Boolean`
- `SetButtons(NewButtons As List)`
- `AddButton(Id As String, Label As String, ImagePath As String, SvgPath As String)`
- `AddButtonWithImagePath(Id As String, Label As String, FullImagePath As String) As Boolean`
- `AddButtonWithSvgPath(Id As String, Label As String, FullSvgPath As String) As Boolean`
- `UpdateButton(ButtonId As String, Updates As Map) As Boolean`
- `RemoveButton(ButtonId As String) As Boolean`
- `UpdateButtonLabel(ButtonId As String, NewLabel As String) As Boolean`
- `UpdateButtonImage(ButtonId As String, NewImagePath As String) As Boolean`
- `UpdateButtonBadge(ButtonId As String, NewBadgeValue As Object) As Boolean`
- `UpdateButtonValue(ButtonId As String, Key As String, Value As Object) As Boolean`
- `ClearButtons`
- `getButtonCountAs Int`
- `getButtonsPerPageAs Int`
- `getPageCountAs Int`
- `getCurrentPageAs Int`
- `SetCurrentPage(Index As Int)`
- `setRowsPerPage(Value As Int)`
- `getRowsPerPageAs Int`
- `setColumnsPerPage(Value As Int)`
- `getColumnsPerPageAs Int`
- `setAutoGrid(Value As Boolean)`
- `getAutoGridAs Boolean`
- `setMinCellWidth(Value As Int)`
- `getMinCellWidthAs Float`
- `setMinCellHeight(Value As Int)`
- `getMinCellHeightAs Float`
- `setPagePadding(Value As Int)`
- `getPagePaddingAs Float`
- `setCellSpacing(Value As Int)`
- `getCellSpacingAs Float`
- `setGridTopOffset(Value As Int)`
- `getGridTopOffsetAs Float`
- `setActiveIndicatorColor(Value As Int)`
- `getActiveIndicatorColorAs Int`
- `setActiveIndicatorColorVariant(VariantName As String)`
- `setInactiveIndicatorColor(Value As Int)`
- `getInactiveIndicatorColorAs Int`
- `setInactiveIndicatorColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setWidth(Value As Object)`
- `getWidthAs String`
- `setHeight(Value As Object)`
- `getHeightAs String`
- `setBackgroundImage(Path As String)`
- `getBackgroundImageAs String`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyDiff

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-full` | Width token or CSS size (for example w-full, 80%, 320px). |
| `Height` | String | `h-[300px]` | Height token or CSS size (for example h-[300px], 200px, 50%). |
| `Rounded` | String | `rounded-xl` | Radius mode. |
| `Shadow` | String | `none` | Elevation level. |
| `Variant` | String | `none` | DaisyUI semantic color variant. |
| `DiffType` | String | `auto` | Rendering mode for diff content. |
| `Position` | String | `0.5` | Split position from 0 to 1. |
| `Image1` | String | `photo-1560717789-0ac7c58ac90a.webp` | Asset file name for first image slot. |
| `Image2` | String | `photo-1560717789-0ac7c58ac90a-blur.webp` | Asset file name for second image slot. |
| `Text1` | String | `DAISY` | Text shown on the first side when DiffType is text. |
| `Text2` | String | `DAISY` | Text shown on the second side when DiffType is text. |
| `TextSize` | String | `text-4xl` | Tailwind text-size token for text mode. |
| `Text1Color` | String | `primary` | Daisy variant applied to text-side 1 (background + text color). |
| `Text2Color` | String | `success` | Daisy variant applied to text-side 2 (background + text color). |
| `Visible` | Boolean | `True` | Visible state. |
| `Enabled` | Boolean | `True` | Enabled state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getItem1ViewAs B4XView`
- `getItem2ViewAs B4XView`
- `setItem1(View As B4XView)`
- `setItem2(View As B4XView)`
- `setPosition(Value As Float)`
- `getPositionAs Float`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setDiffType(Value As String)`
- `getDiffTypeAs String`
- `setImage1(Value As String)`
- `getImage1As String`
- `setImage2(Value As String)`
- `getImage2As String`
- `setText1(Value As String)`
- `getText1As String`
- `setText2(Value As String)`
- `getText2As String`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setText1Color(Value As String)`
- `getText1ColorAs String`
- `setText2Color(Value As String)`
- `getText2ColorAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyDivider

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `` | Optional width token (Tailwind/CSS). Leave empty for direction-based auto sizing. |
| `Height` | String | `` | Optional height token (Tailwind/CSS). Leave empty for direction-based auto sizing. |
| `Direction` | String | `vertical` | vertical = classic divider line; horizontal = side divider. |
| `Placement` | String | `default` | Push text to start / center / end. |
| `Text` | String | `` | Optional divider label. |
| `TextSize` | String | `text-sm` | Tailwind text size token (for example: text-sm, text-lg, text-2xl). |
| `Gap` | String | `4` | Gap between text and lines (Tailwind spacing token or size). |
| `LineThickness` | String | `0.5` | Divider stroke thickness (Tailwind spacing token or size). |
| `Variant` | String | `none` | Daisy semantic divider color variant. |
| `BackgroundColor` | Color | `0x00FFFFFF` | Override divider line color. |
| `TextColor` | Color | `0x00FFFFFF` | Override divider text color. |
| `Padding` | String | `` | Optional padding utility token(s). |
| `Margin` | String | `` | Optional margin utility token(s). Empty uses direction defaults: my-4 (vertical), mx-4 (horizontal). |
| `DebugBorders` | Boolean | `False` | Draw red debug borders around divider parts and text. |
| `Visible` | Boolean | `True` | Show or hide divider. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setPlacement(Value As String)`
- `getPlacementAs String`
- `setText(Value As String)`
- `getTextAs String`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setGap(Value As Object)`
- `getGapAs Float`
- `setLineThickness(Value As Object)`
- `getLineThicknessAs Float`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColorVariant(VariantName As String)`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setDebugBorders(Value As Boolean)`
- `getDebugBordersAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `GetComputedHeightAs Int`
- `GetActualHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyDivision

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-10` | Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem) |
| `Height` | String | `h-10` | Tailwind size token or CSS size (eg h-12, 80px, 4em, 5rem) |
| `Padding` | String | `` | Tailwind/spacing padding utilities (eg p-2, px-3, 2) |
| `Margin` | String | `` | Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1) |
| `BackgroundColor` | Color | `0x00FFFFFF` | Background color of the container. |
| `TextColor` | Color | `0xFF000000` | Color of the text content. |
| `TextSize` | String | `text-sm` | Number in dip or Tailwind token (eg 12, text-sm, text-lg). |
| `Text` | String | `` | Text to display in the container. |
| `RoundedBox` | Boolean | `False` | Apply 16px rounded corners. |
| `Rounded` | String | `none` | Border radius utility. |
| `Shadow` | String | `none` | Shadow depth (elevation). |
| `PlaceContentCenter` | Boolean | `False` | Center content horizontally and vertically. |
| `BorderWidth` | Int | `0` | Border width in dips. |
| `BorderColor` | Color | `0xFF000000` | Border color. |
| `BorderStyle` | String | `solid` | HTML-like border style token. |
| `BorderReliefStrength` | Int | `55` | 0-100 strength for groove/ridge/inset/outset shading. |
| `AutoReliefByStyle` | Boolean | `True` | Use built-in per-style relief presets for groove/ridge/inset/outset. |
| `IsSkeleton` | Boolean | `False` | Show skeleton loading state. |
| `Variant` | String | `none` | DaisyUI semantic color variant. |
| `AutoResize` | Boolean | `False` | Automatically resize height to fit content. Text-only divisions grow like a paragraph (height:auto). Divisions with child views fit the bottom-most child. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `StartAnimation`
- `StopAnimation`
- `setWidth(Value As Object)`
- `getWidthAs Object`
- `setHeight(Value As Object)`
- `getHeightAs Object`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setBackgroundColor(Color As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Color As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setTextSize(Value As Object)`
- `getTextSizeAs Float`
- `setText(Text As String)`
- `getTextAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setPlaceContentCenter(Value As Boolean)`
- `getPlaceContentCenterAs Boolean`
- `setBorderWidth(Value As Int)`
- `getBorderWidthAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderColorVariant(VariantName As String)`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setBorderReliefStrength(Value As Int)`
- `getBorderReliefStrengthAs Int`
- `setAutoReliefByStyle(Value As Boolean)`
- `getAutoReliefByStyleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setIsSkeleton(Value As Boolean)`
- `getIsSkeletonAs Boolean`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyDock

### Events
- `ItemClick (ItemId As String)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Size` | String | `md` | Daisy dock size token. |
| `ActiveIndex` | Int | `1` | Zero-based active item index. Use -1 for no active item. |
| `BackgroundColor` | Color | `0x00000000` | Dock background color (0 = theme base-100). |
| `TextColor` | Color | `0x00000000` | Dock text/icon color (0 = theme base-content). |
| `Shadow` | String | `none` | Elevation shadow level. |
| `Rounded` | String | `none` | Corner radius style. |
| `Width` | String | `w-full` | Tailwind size token or CSS size string. |
| `Height` | String | `auto` | Tailwind size token or CSS size string. auto follows the dock size token. |
| `Enabled` | Boolean | `True` | Enable or disable dock interactions. |
| `Visible` | Boolean | `True` | Show or hide the dock. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddItem(Id As String, Text As String, SvgAssetFile As String) As Int`
- `AddItemWithVariant(Id As String, Text As String, SvgAssetFile As String, VariantName As String) As Int`
- `ClearItems`
- `SetItemTagByIndex(Index As Int, TagValue As Object)`
- `SetItemTag(ItemId As String, TagValue As Object)`
- `SetItemEnabledByIndex(Index As Int, Value As Boolean)`
- `SetItemEnabled(ItemId As String, Value As Boolean)`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `RemoveViewFromParent`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `setSize(Value As String)`
- `getSizeAs String`
- `setActiveIndex(Value As Int)`
- `getActiveIndexAs Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`


---

## B4XDaisyDropdown

### Events
- `Click (Tag As Object)`
- `ItemClick (Tag As Object, Text As String)`
- `SubmenuToggle (Tag As Object, IsOpen As Boolean)`
- `Opened`
- `Closed`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables dropdown interactions. |
| `Visible` | Boolean | `True` | Shows or hides the dropdown. |
| `Open` | Boolean | `False` | Initial open state. |
| `Placement` | String | `start` | Alignment of the popup relative to the trigger. Default start keeps the popup left edge aligned with the target for top/bottom dropdowns. |
| `Direction` | String | `bottom` | Direction used when opening the dropdown. Together with Placement=start, the default behavior is bottom-left on the target unless changed. |
| `HoverOpen` | Boolean | `False` | Hover-style mode. On B4A this falls back to click behavior. |
| `ForceOpen` | Boolean | `False` | Forces the popup to stay open. |
| `ForceClose` | Boolean | `False` | Forces the popup to stay closed. |
| `MenuWidth` | String | `w-52` | Tailwind or CSS width token used for the popup menu. |
| `MenuPadding` | String | `p-2` | Tailwind padding applied to the popup menu. |
| `MenuRounded` | String | `theme` | Rounded token applied to the popup menu. |
| `MenuShadow` | String | `sm` | Shadow level applied to the popup menu. |
| `BringToFront` | Boolean | `True` | Brings the trigger and popup to the front when opened. |
| `MenuBackgroundColor` | Color | `0x00000000` | Optional popup menu background override. |
| `MenuTextColor` | Color | `0x00000000` | Optional popup menu text color override. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `Open`
- `Close`
- `Toggle`
- `AddTitle(Text As String) As Int`
- `AddDividerAs Int`
- `AddItem(TagValue As Object, Text As String) As Int`
- `AddIconItem(TagValue As Object, Text As String, IconName As String) As Int`
- `AddBadgeItem(TagValue As Object, Text As String, BadgeText As String, BadgeVariant As String) As Int`
- `AddIconBadgeItem(TagValue As Object, Text As String, IconName As String, BadgeText As String, BadgeVariant As String) As Int`
- `AddSubmenu(TagValue As Object, Text As String, InitiallyOpen As Boolean) As B4XDaisyMenu`
- `SetItemDisabled(TagValue As Object, Value As Boolean)`
- `getMenuAs B4XDaisyMenu`
- `SetItemActive(TagValue As Object, Value As Boolean)`
- `SetItemText(TagValue As Object, Value As String)`
- `SetItemIcon(TagValue As Object, IconName As String)`
- `SetItemVisible(TagValue As Object, Value As Boolean)`
- `ScrollToItem(TagValue As Object)`
- `SetSubmenuOpen(Index As Int, Value As Boolean)`
- `SetItemBadgeText(TagValue As Object, Value As String)`
- `SetItemBadgeBackgroundColor(TagValue As Object, Color As Int)`
- `SetItemBadgeTextColor(TagValue As Object, Color As Int)`
- `GetPreferredWidthAs Int`
- `GetPreferredHeightAs Int`
- `GetPreferredMenuWidthAs Int`
- `GetPreferredMenuHeightAs Int`
- `AttachTo(Target As B4XView) As B4XView`
- `Detach`
- `UpdateTheme`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `ViewAs B4XView`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setOpen(Value As Boolean)`
- `getOpenAs Boolean`
- `setPlacement(Value As String)`
- `getPlacementAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setHoverOpen(Value As Boolean)`
- `getHoverOpenAs Boolean`
- `setForceOpen(Value As Boolean)`
- `getForceOpenAs Boolean`
- `setForceClose(Value As Boolean)`
- `getForceCloseAs Boolean`
- `setAnchorTarget(Value As B4XView)`
- `getAnchorTargetAs B4XView`
- `getAttachedModeAs Boolean`
- `setMenuWidth(Value As String)`
- `getMenuWidthAs String`
- `setMenuPadding(Value As String)`
- `getMenuPaddingAs String`
- `setMenuRounded(Value As String)`
- `getMenuRoundedAs String`
- `setMenuShadow(Value As String)`
- `getMenuShadowAs String`
- `setBringToFront(Value As Boolean)`
- `getBringToFrontAs Boolean`
- `setMenuBackgroundColor(Value As Int)`
- `getMenuBackgroundColorAs Int`
- `setMenuTextColor(Value As Int)`
- `getMenuTextColorAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`


---

## B4XDaisyFab

### Events
- `Click (Tag As Object)`
- `ActionClick (Index As Int, Tag As Object)`
- `MainActionClick (Tag As Object)`
- `CloseClick (Tag As Object)`
- `Opened`
- `Closed`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enable or disable the FAB. |
| `Visible` | Boolean | `True` | Show or hide the FAB. |
| `Open` | Boolean | `False` | Initial open state. |
| `PlacementMode` | String | `fixed` | How the FAB is positioned. |
| `Placement` | String | `bottom-end` | Fixed placement preset. |
| `AnchorAlignment` | String | `start` | Horizontal alignment used with anchored placement. |
| `OnEdge` | Boolean | `False` | Overlap the active edge by half the trigger size. |
| `OpenMode` | String | `click` | Interaction mode used to open the FAB. |
| `LayoutMode` | String | `vertical` | Layout used when the FAB opens. |
| `Direction` | String | `top` | Expansion direction. |
| `BackdropEnabled` | Boolean | `True` | Show a backdrop and close on outside click. |
| `AutoCloseOnActionClick` | Boolean | `True` | Close after a regular action click. |
| `TriggerText` | String | `F` | Trigger button text. |
| `TriggerVariant` | String | `primary` | Trigger variant. |
| `TriggerStyle` | String | `solid` | Trigger style. |
| `TriggerSize` | String | `lg` | Trigger size. |
| `ChildActionSize` | String | `sm` | Child action button size. |
| `TriggerIconName` | String | `` | Optional trigger icon asset file name. |
| `TriggerCircle` | Boolean | `True` | Use circular trigger button. |
| `UseMainAction` | Boolean | `False` | Replace the trigger with a main action when open. |
| `MainActionText` | String | `M` | Main action button text. |
| `MainActionVariant` | String | `secondary` | Main action variant. |
| `MainActionIconName` | String | `` | Optional main action icon. |
| `UseCloseAction` | Boolean | `False` | Replace the trigger with a close action when open. |
| `CloseActionText` | String | `X` | Close action button text. |
| `CloseActionVariant` | String | `error` | Close action variant. |
| `CloseActionIconName` | String | `` | Optional close action icon. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `Open`
- `Close`
- `Toggle`
- `ClearActions`
- `AddActionEx(Text As String, LabelText As String, Variant As String, Style As String, Size As String, IconName As String, Circle As Boolean, TagValue As Object) As Int`
- `AddAction(TagValue As Object, Variant As String, IconName As String) As Int`
- `AddActionDetailed(Text As String, LabelText As String, Variant As String, IconName As String, TagValue As Object) As Int`
- `SetMainAction(Text As String, LabelText As String, Variant As String, IconName As String, TagValue As Object)`
- `SetCloseAction(Text As String, LabelText As String, Variant As String, IconName As String, TagValue As Object)`
- `GetActionButtonView(Index As Int) As B4XView`
- `SetActionVisible(Index As Int, Value As Boolean)`
- `setAnchorTarget(Value As B4XView)`
- `getAnchorTargetAs B4XView`
- `setAnchorAlignment(Value As String)`
- `getAnchorAlignmentAs String`
- `setAnchorView(Value As B4XView)`
- `getAnchorViewAs B4XView`
- `setOverlayHost(Value As B4XView)`
- `getOverlayHostAs B4XView`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `Resize(Width As Double, Height As Double)`
- `BringToFront`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setOpen(Value As Boolean)`
- `getOpenAs Boolean`
- `setPlacementMode(Value As String)`
- `getPlacementModeAs String`
- `setPlacement(Value As String)`
- `getPlacementAs String`
- `setOnEdge(Value As Boolean)`
- `getOnEdgeAs Boolean`
- `setOpenMode(Value As String)`
- `getOpenModeAs String`
- `setLayoutMode(Value As String)`
- `getLayoutModeAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setBackdropEnabled(Value As Boolean)`
- `getBackdropEnabledAs Boolean`
- `setAutoCloseOnActionClick(Value As Boolean)`
- `getAutoCloseOnActionClickAs Boolean`
- `setTriggerText(Value As String)`
- `getTriggerTextAs String`
- `setTriggerVariant(Value As String)`
- `getTriggerVariantAs String`
- `setTriggerStyle(Value As String)`
- `getTriggerStyleAs String`
- `setTriggerSize(Value As String)`
- `getTriggerSizeAs String`
- `setChildActionSize(Value As String)`
- `getChildActionSizeAs String`
- `setTriggerIconName(Value As String)`
- `getTriggerIconNameAs String`
- `setTriggerCircle(Value As Boolean)`
- `getTriggerCircleAs Boolean`
- `setUseMainAction(Value As Boolean)`
- `getUseMainActionAs Boolean`
- `setMainActionText(Value As String)`
- `getMainActionTextAs String`
- `setMainActionVariant(Value As String)`
- `getMainActionVariantAs String`
- `setMainActionIconName(Value As String)`
- `getMainActionIconNameAs String`
- `setUseCloseAction(Value As Boolean)`
- `getUseCloseActionAs Boolean`
- `setCloseActionText(Value As String)`
- `getCloseActionTextAs String`
- `setCloseActionVariant(Value As String)`
- `getCloseActionVariantAs String`
- `setCloseActionIconName(Value As String)`
- `getCloseActionIconNameAs String`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyFieldset

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Legend` | String | `Legend Caption` | Caption text shown in the fieldset header |
| `LegendSize` | String | `text-sm` | Tailwind-like text size token for legend |
| `LegendBold` | Boolean | `False` | Render the legend caption in bold |
| `Variant` | String | `none` | Optional accent variant for border tint |
| `BorderStyle` | String | `outlined` | Border visual style |
| `Padding` | Int | `16` | Inner content padding in dip (p-4) |
| `AutoHeight` | Boolean | `False` | Automatically grow to fit added content |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use box radius for container |
| `Shadow` | String | `none` | Elevation shadow level |
| `BackgroundColor` | Color | `0x00000000` | Background color (0 = default bg-base-200) |
| `TextColor` | Color | `0x00000000` | Legend text color (0 = use theme token) |
| `BorderColor` | Color | `0x00000000` | Border color override (0 = default border-base-300) |
| `BorderSize` | Int | `1` | Border width in dip |
| `InputBorder` | Boolean | `False` | When True, apply B4XDaisyInput border color and width to the fieldset |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `ApplyDesignerProps(Props As Map)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setLegend(l As String)`
- `getLegendAs String`
- `setLegendSize(s As String)`
- `getLegendSizeAs String`
- `setLegendBold(Value As Boolean)`
- `getLegendBoldAs Boolean`
- `setVariant(v As String)`
- `getVariantAs String`
- `setBorderStyle(s As String)`
- `getBorderStyleAs String`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setBackgroundColor(Value As Object)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Object)`
- `getTextColorAs Int`
- `setBorderColor(Value As Object)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setInputBorder(Value As Boolean)`
- `getInputBorderAs Boolean`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `isRoundedAs Boolean`
- `setRoundedBox(b As Boolean)`
- `isRoundedBoxAs Boolean`
- `setShadow(s As String)`
- `getShadowAs String`
- `GetContentPanelAs B4XView`
- `AddContentView(v As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ClearContent`
- `getTagAs Object`
- `setTag(Value As Object)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyFileHandler

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `Initialize`
- `SaveAs(Source As InputStream, MimeType As String, Title As String) As ResumableSub`
- `LoadAs ResumableSub`
- `CheckForReceivedFilesAs LoadResult`
- `SaveAs(ParentPage As Object, AnchorView As Object, Text As String) As ResumableSub`
- `Load(ParentPage As Object, AnchorView As Object) As ResumableSub`
- `UrlToLoadResult(url As String) As LoadResult`


---

## B4XDaisyFileInput

### Events
- `Click (Tag As Object)`
- `FileSelected (FileName As String)`
- `Cancelled ()`
- `FocusChanged (HasFocus As Boolean)`
- `AppendClick`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `ButtonText` | String | `Choose file` | Label shown on the file selector button (maps to CSS ::file-selector-button text). |
| `Placeholder` | String | `No file chosen` | Text shown in the filename area when no file is selected. |
| `FileName` | String | `` | Currently selected file name displayed in the input. |
| `LabelAbove` | String | `` | Label text shown above the input (maps to fieldset-legend in docs fieldset example). |
| `HintText` | String | `` | Helper text displayed below the input (maps to CSS .label standalone pattern, e.g. Max size 2MB). |
| `ErrorText` | String | `` | Error text displayed below the input in the error color. When non-empty it replaces the hint and turns the border error-colored. |
| `Required` | Boolean | `False` | Whether a file must be selected. |
| `Variant` | String | `none` | DaisyUI color variant applied to the selector button and input border. |
| `Size` | String | `md` | DaisyUI size variant controlling height and font-size (xs/sm intentionally omitted). |
| `Style` | String | `default` | file-input-ghost style (transparent surface, no border). |
| `Radius` | String | `theme` | Corner radius token. |
| `Enabled` | Boolean | `True` | Whether the file input is enabled. |
| `Visible` | Boolean | `True` | Controls view visibility. |
| `BackgroundColor` | Color | `0x00000000` | Override input surface background color. |
| `TextColor` | Color | `0x00000000` | Override filename text color. |
| `PlaceholderColor` | Color | `0x00000000` | Override empty-state filename text color. |
| `ButtonColor` | Color | `0x00000000` | Override selector button background color. |
| `ButtonTextColor` | Color | `0x00000000` | Override selector button text color. |
| `BorderColor` | Color | `0x00000000` | Override input border color. |
| `Padding` | String | `` | Tailwind spacing utility for the filename padding-inline-end (e.g. pe-4, p-4). |
| `Shadow` | String | `none` | Elevation shadow level applied to the input surface. |
| `Typeface` | String | `DEFAULT` | Font family for the filename text. |
| `Gravity` | String | `LEFT` | Horizontal alignment of the filename text. |
| `Alpha` | Float | `1.0` | View opacity from 0 (invisible) to 1 (fully opaque). |
| `Accept` | String | `` | File type filter for the content chooser (e.g. image/*, .pdf, .jpg,.png). Maps to HTML accept attribute. |
| `MaxSize` | Int | `0` | Maximum file size allowed in megabytes. A value of 0 means no limit. |
| `AppendIcon` | String | `` | SVG icon asset filename to display on the right end of the file input (e.g. x-solid-full.svg). |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setAppendIcon(Value As String)`
- `getAppendIconAs String`
- `setButtonText(Value As String)`
- `getButtonTextAs String`
- `setAccept(Value As String)`
- `getAcceptAs String`
- `setFileDate(Value As Long)`
- `getFileDateAs Long`
- `setFileSize(Value As Long)`
- `getFileSizeAs Long`
- `setFileBase64(Value As String)`
- `getFileBase64As String`
- `setMimeType(Value As String)`
- `getMimeTypeAs String`
- `setMaxSize(Value As Int)`
- `getMaxSizeAs Int`
- `getExceedsSizeAs Boolean`
- `getFileInputStreamAs InputStream`
- `getFileBytesAs Byte()`
- `GetBitmapAs B4XBitmap`
- `getExtensionAs String`
- `getIsImageAs Boolean`
- `getIsVideoAs Boolean`
- `getIsPDFAs Boolean`
- `getIsExcelAs Boolean`
- `getIsWordAs Boolean`
- `setPlaceholder(Value As String)`
- `getPlaceholderAs String`
- `setFileName(Value As String)`
- `getFileNameAs String`
- `setLabelAbove(Value As String)`
- `getLabelAboveAs String`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setRadius(Value As String)`
- `getRadiusAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setPlaceholderColor(Value As Int)`
- `getPlaceholderColorAs Int`
- `setButtonColor(Value As Int)`
- `getButtonColorAs Int`
- `setButtonTextColor(Value As Int)`
- `getButtonTextColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setTypeface(Value As String)`
- `getTypefaceAs String`
- `setGravity(Value As String)`
- `getGravityAs String`
- `setAlpha(Value As Float)`
- `getAlphaAs Float`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getViewAs B4XView`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `setTop(Value As Int)`
- `RemoveViewFromParent`
- `Release`
- `GetComputedHeightAs Int`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `Clear`


---

## B4XDaisyFilter

### Events
- `ResetClick`
- `Changed (Keys As List)`
- `ItemChanged (Id As String, Text As String, Checked As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables or disables the component. |
| `Visible` | Boolean | `True` | Shows or hides the component. |
| `Options` | String | `svelte:Svelte` | Comma-separated or pipe-separated key:value pairs. |
| `ActiveKey` | String | `` | Key of the active option. |
| `Rounded` | String | `theme` | Corner radius option. |
| `Variant` | String | `none` | Daisy color variant. |
| `FilterStyle` | String | `solid` | Styling of choices. |
| `Size` | String | `md` | Sizing variant. |
| `ResetPosition` | String | `left` | Position of the reset button. |
| `ResetText` | String | `×` | Text for the reset button. |
| `MultiSelect` | Boolean | `False` | Allow multiple selection like checkboxes. |
| `Orientation` | String | `horizontal` | Layout orientation. |
| `AnimationDuration` | Int | `200` | Layout transition duration in milliseconds (0 to disable). |
| `Width` | String | `w-full` | Sizing width token. |
| `Height` | String | `h-auto` | Sizing height token. |
| `Padding` | String | `` | Spacing padding token. |
| `Margin` | String | `` | Spacing margin token. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setOptions(Value As String)`
- `getOptionsAs String`
- `setActiveKey(Value As String)`
- `getActiveKeyAs String`
- `setOptionsMap(Value As Map)`
- `getOptionsMapAs Map`
- `setOptionsList(Value As List)`
- `getOptionsListAs List`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setFilterStyle(Value As String)`
- `getFilterStyleAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setResetPosition(Value As String)`
- `getResetPositionAs String`
- `setResetText(Value As String)`
- `getResetTextAs String`
- `setMultiSelect(Value As Boolean)`
- `getMultiSelectAs Boolean`
- `setOrientation(Value As String)`
- `getOrientationAs String`
- `setAnimationDuration(Value As Int)`
- `getAnimationDurationAs Int`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setItemChecked(Key As String, Checked As Boolean)`
- `isItemChecked(Key As String) As Boolean`
- `getCheckedKeysAs List`
- `setChecked(CheckedKeys As String)`
- `getCheckedAs String`
- `setItems(Value As Map)`
- `getItemsAs Map`
- `setSelectedIds(Ids As List)`
- `getSelectedIdsAs List`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `RemoveViewFromParent`
- `GetComputedHeightAs Int`


---

## B4XDaisyFlexItem

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `Initialize(Owner As B4XDaisyFlexPanel, v As B4XView)`
- `ResetAs B4XDaisyFlexItem`
- `Grow(Value As Float) As B4XDaisyFlexItem`
- `Shrink(Value As Float) As B4XDaisyFlexItem`
- `Flex(GrowValue As Float, ShrinkValue As Float) As B4XDaisyFlexItem`
- `Flex1As B4XDaisyFlexItem`
- `FlexAutoAs B4XDaisyFlexItem`
- `FlexInitialAs B4XDaisyFlexItem`
- `FlexNoneAs B4XDaisyFlexItem`
- `MinW(Value As Int) As B4XDaisyFlexItem`
- `MaxW(Value As Int) As B4XDaisyFlexItem`
- `MinH(Value As Int) As B4XDaisyFlexItem`
- `MaxH(Value As Int) As B4XDaisyFlexItem`
- `MinSize(W As Int, H As Int) As B4XDaisyFlexItem`
- `MaxSize(W As Int, H As Int) As B4XDaisyFlexItem`
- `Basis(W As Int, H As Int) As B4XDaisyFlexItem`
- `BasisPercent(MainPct As Float, CrossPct As Float) As B4XDaisyFlexItem`
- `Margins(Left As Int, Top As Int, Right As Int, Bottom As Int) As B4XDaisyFlexItem`
- `MarginAll(Value As Int) As B4XDaisyFlexItem`
- `MarginX(Value As Int) As B4XDaisyFlexItem`
- `MarginY(Value As Int) As B4XDaisyFlexItem`
- `AlignSelf(Value As String) As B4XDaisyFlexItem`
- `Order(Value As Int) As B4XDaisyFlexItem`
- `WrapBefore(Value As Boolean) As B4XDaisyFlexItem`
- `ApplyAs B4XDaisyFlexItem`
- `ApplyNoRelayoutAs B4XDaisyFlexItem`
- `ApplyEx(DoRelayout As Boolean) As B4XDaisyFlexItem`
- `getViewAs B4XView`


---

## B4XDaisyFlexLayout

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `Initialize(Container As B4XView)`
- `SetContainer(Container As B4XView)`
- `SetPadding(All As Int)`
- `SetPaddingLTRB(Left As Int, Top As Int, Right As Int, Bottom As Int)`
- `SetGap(X As Int, Y As Int)`
- `SetItemFlexEx(v As B4XView, Grow As Float, Shrink As Float, MinW As Int, MaxW As Int, MinH As Int, MaxH As Int)`
- `SetItemBasis(v As B4XView, BasisW As Int, BasisH As Int)`
- `SetItemBasisPercent(v As B4XView, PercentMain As Float, PercentCross As Float)`
- `ClearItemBasisPercent(v As B4XView)`
- `SetItemMargins(v As B4XView, Left As Int, Top As Int, Right As Int, Bottom As Int)`
- `SetItemAlignSelf(v As B4XView, AlignSelf As String)`
- `SetItemOrder(v As B4XView, OrderValue As Int)`
- `SetItemWrapBefore(v As B4XView, Value As Boolean)`
- `ClearItemMeta(v As B4XView)`
- `ClearAllItemMeta`
- `Relayout`
- `GetContentWidthAs Int`
- `GetContentHeightAs Int`


---

## B4XDaisyFlexPanel

### Events
- `Ready`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Direction` | String | `row` |  |
| `WrapMode` | String | `wrap` |  |
| `GapX` | Int | `8` |  |
| `GapY` | Int | `8` |  |
| `PaddingLeft` | Int | `8` |  |
| `PaddingTop` | Int | `8` |  |
| `PaddingRight` | Int | `8` |  |
| `PaddingBottom` | Int | `8` |  |
| `JustifyContent` | String | `start` |  |
| `AlignItems` | String | `start` |  |
| `AlignContent` | String | `start` |  |
| `AnimateDuration` | Int | `0` |  |
| `AllowShrinkWhenWrap` | Boolean | `False` |  |
| `AutoRelayout` | Boolean | `True` |  |

### Public Methods
- `Initialize`
- `InitForCode(Callback As Object, EventName As String, Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `GetComputedHeightAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getIsInitializedAs Boolean`
- `GetContentPanelAs B4XView`
- `Relayout`
- `GetMeasuredWidthAs Int`
- `GetMeasuredHeightAs Int`
- `SetPadding(All As Int)`
- `SetPaddingLTRB(Left As Int, Top As Int, Right As Int, Bottom As Int)`
- `SetGap(X As Int, Y As Int)`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setWrapMode(Value As String)`
- `getWrapModeAs String`
- `setJustifyContent(Value As String)`
- `getJustifyContentAs String`
- `setAlignItems(Value As String)`
- `getAlignItemsAs String`
- `setAlignContent(Value As String)`
- `getAlignContentAs String`
- `setGapX(Value As Int)`
- `getGapXAs Int`
- `setGapY(Value As Int)`
- `getGapYAs Int`
- `setPaddingLeft(Value As Int)`
- `getPaddingLeftAs Int`
- `setPaddingTop(Value As Int)`
- `getPaddingTopAs Int`
- `setPaddingRight(Value As Int)`
- `getPaddingRightAs Int`
- `setPaddingBottom(Value As Int)`
- `getPaddingBottomAs Int`
- `setAnimateDuration(Value As Int)`
- `getAnimateDurationAs Int`
- `setAllowShrinkWhenWrap(Value As Boolean)`
- `getAllowShrinkWhenWrapAs Boolean`
- `setAutoRelayout(Value As Boolean)`
- `getAutoRelayoutAs Boolean`
- `AddItem(v As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `AddItemEx(v As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XDaisyFlexItem`
- `Item(v As B4XView) As B4XDaisyFlexItem`
- `RemoveItem(v As B4XView)`
- `ClearItems`
- `getNumberOfItemsAs Int`
- `GetItem(Index As Int) As B4XView`
- `SetItemFlexEx(v As B4XView, Grow As Float, Shrink As Float, MinW As Int, MaxW As Int, MinH As Int, MaxH As Int)`
- `SetItemBasis(v As B4XView, BasisW As Int, BasisH As Int)`
- `SetItemBasisPercent(v As B4XView, PercentMain As Float, PercentCross As Float)`
- `ClearItemBasisPercent(v As B4XView)`
- `SetItemMargins(v As B4XView, Left As Int, Top As Int, Right As Int, Bottom As Int)`
- `SetItemAlignSelf(v As B4XView, AlignSelf As String)`
- `SetItemOrder(v As B4XView, OrderValue As Int)`
- `SetItemWrapBefore(v As B4XView, Value As Boolean)`
- `ClearItemMeta(v As B4XView)`
- `ClearAllItemMeta`
- `SetItemFlexEx_NoRelayout(v As B4XView, Grow As Float, Shrink As Float, MinW As Int, MaxW As Int, MinH As Int, MaxH As Int)`
- `SetItemBasis_NoRelayout(v As B4XView, BasisW As Int, BasisH As Int)`
- `SetItemBasisPercent_NoRelayout(v As B4XView, PercentMain As Float, PercentCross As Float)`
- `ClearItemBasisPercent_NoRelayout(v As B4XView)`
- `SetItemMargins_NoRelayout(v As B4XView, Left As Int, Top As Int, Right As Int, Bottom As Int)`
- `SetItemAlignSelf_NoRelayout(v As B4XView, AlignSelf As String)`
- `SetItemOrder_NoRelayout(v As B4XView, OrderValue As Int)`
- `SetItemWrapBefore_NoRelayout(v As B4XView, Value As Boolean)`
- `BeginUpdate`
- `EndUpdate(DoRelayout As Boolean)`


---

## B4XDaisyGrid

### Events
- `LayoutChanged (ContentHeight As Float)`
- `ItemPlaced (Info As Map)`
- `BeforePlace (Key As String, Info As Map)`
- `AfterPlace (Key As String, Info As Map)`
- `LayoutDiff (Changes As List)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `ClassName` | String | `grid grid-cols-1 gap-4` | Tailwind-like grid utility string |
| `Cols` | Int | `1` | Fallback columns when no class token is set |
| `Gap` | String | `4` | Tailwind spacing token or CSS/dip value |
| `GapX` | String | `` | Optional horizontal gap override |
| `GapY` | String | `` | Optional vertical gap override |
| `AutoRows` | String | `minmax(72dip` | Grid auto-rows template |
| `TemplateRows` | String | `` | Explicit row template (e.g., "100dip 1fr 200dip") |
| `Padding` | String | `0` | Padding shorthand |
| `Dense` | Boolean | `False` | Enable dense packing algorithm |
| `Debug` | Boolean | `False` | Enable debug logging |
| `DebugOverlay` | Boolean | `False` | Draw grid lines and labels |
| `AutoRegisterChildrenFromTag` | Boolean | `False` | Register child views using Tag metadata |
| `EmitLayoutDiff` | Boolean | `False` | Raise LayoutDiff event with changed items |
| `DefaultAnimMs` | Int | `0` | Default placement animation in ms |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `getViewAs B4XView`
- `getIsInitializedAs Boolean`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setClassName(ClassText As String)`
- `getClassNameAs String`
- `setCols(Value As Int)`
- `getColsAs Int`
- `setGap(Value As Object)`
- `getGapAs Float`
- `setGapX(Value As Object)`
- `getGapXAs Float`
- `setGapY(Value As Object)`
- `getGapYAs Float`
- `SetGapXY(ValueX As Float, ValueY As Float)`
- `SetAutoRowsTemplate(Template As String)`
- `setAutoRows(Template As String)`
- `getAutoRowsAs String`
- `setTemplateRows(Template As String)`
- `getTemplateRowsAs String`
- `setPadding(Value As Object)`
- `getPaddingAs Float`
- `SetPaddingLTRB(Left As Float, Top As Float, Right As Float, Bottom As Float)`
- `SetBreakpoint(Name As String, MinWidth As Float)`
- `setDense(Value As Boolean)`
- `getDenseAs Boolean`
- `setDebug(Value As Boolean)`
- `getDebugAs Boolean`
- `setDebugOverlay(Value As Boolean)`
- `getDebugOverlayAs Boolean`
- `setAutoRegisterChildrenFromTag(Value As Boolean)`
- `getAutoRegisterChildrenFromTagAs Boolean`
- `setEmitLayoutDiff(Value As Boolean)`
- `getEmitLayoutDiffAs Boolean`
- `setDefaultAnimMs(Value As Int)`
- `getDefaultAnimMsAs Int`
- `AddItem(View As B4XView, ClassText As String) As String`
- `AddItemWithKey(Key As String, View As B4XView, ClassText As String)`
- `UpdateItemClass(Key As String, ClassText As String)`
- `RemoveItem(Key As String)`
- `SetItemVisible(Key As String, Visible As Boolean)`
- `SetItemOrder(Key As String, Order As Int)`
- `SetItemRowSpan(Key As String, RowSpan As Int, Bp As String)`
- `SetItemColSpan(Key As String, ColSpan As Int, Bp As String)`
- `SetItemColStart(Key As String, ColStart As Int, Bp As String)`
- `SetItemRowStart(Key As String, RowStart As Int, Bp As String)`
- `SetItemJustify(Key As String, Value As String, Bp As String)`
- `SetItemAlign(Key As String, Value As String, Bp As String)`
- `SetItemHidden(Key As String, Hidden As Boolean, Bp As String)`
- `GetItemPlacement(Key As String) As GridPlacement`
- `BeginUpdate`
- `EndUpdate`
- `Relayout`
- `RegisterChildrenFromTag(OptionalDefaultClass As String)`
- `GetLayoutSnapshotAs List`
- `DebugDumpSnapshotAs String`
- `GetCollisionDiagnosticsAs List`
- `GetCollisionReportAs String`
- `GetResolvedItemRules(Key As String, Width As Float) As Map`
- `GetResolvedItemRulesNow(Key As String) As Map`
- `GetResolvedContainerRulesNowAs Map`
- `GetItemSpec(Key As String) As GridItemSpec`
- `ApplyItemSpec(Spec As GridItemSpec)`
- `GetAllItemSpecsAs List`
- `ExportLayoutSpecsAs List`
- `ImportLayoutSpecs(Specs As List, IgnoreMissing As Boolean)`
- `ExportLayoutProfile(ProfileName As String) As Map`
- `ImportLayoutProfile(Profile As Map, IgnoreMissing As Boolean)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyHero

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `BackgroundImage` | String | `` | Background image asset name. |
| `Variant` | String | `none` | DaisyUI semantic color variant. |
| `BackgroundColor` | Color | `0xFFF3F4F6` | Hero background color (base-200). |
| `TextColor` | Color | `0xFF000000` | Hero text color. |
| `Rounded` | String | `rounded-none` | Corner radius mode. |
| `RoundedBox` | Boolean | `False` | Use rounded-box radius when Rounded is theme. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `OverlayVisible` | Boolean | `False` | Show/Hide the hero overlay. |
| `OverlayColor` | Color | `0x80000000` | Hero overlay color (with alpha). |
| `Width` | String | `w-full` | Tailwind width class (eg 80, full, 500px). |
| `Height` | String | `h-[320px]` | Tailwind height class (eg 80, screen, 500px). |
| `Direction` | String | `vertical` | Layout direction. |
| `ContentAlignment` | String | `center` | Content alignment. |
| `Gap` | String | `4` | Tailwind gap token (eg 2, 4, 8). |
| `Padding` | String | `4` | Tailwind padding token (eg 4, 8, 12). |
| `Visible` | Boolean | `True` | Visible state. |
| `AutoResize` | Boolean | `False` | Automatically resize height to fit child content. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setBackgroundImage(Value As String)`
- `getBackgroundImageAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColorVariant(Value As String)`
- `getBackgroundColorVariantAs String`
- `setTextColorVariant(Value As String)`
- `getTextColorVariantAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setOverlayVisible(Value As Boolean)`
- `getOverlayVisibleAs Boolean`
- `setOverlayColor(Value As Int)`
- `getOverlayColorAs Int`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setContentAlignment(Value As String)`
- `getContentAlignmentAs String`
- `setGap(Value As String)`
- `getGapAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `GetContentPanelAs B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyHover3d

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enable or disable pointer interaction. |
| `Visible` | Boolean | `True` | Controls visibility. |
| `MaxTilt` | Float | `10` | Maximum tilt angle in degrees for the hover surface. |
| `ScaleOnHover` | Float | `1.05` | Surface scale applied while hovering. |
| `ShineEffect` | Boolean | `True` | Shows a highlight sheen on hover. |
| `Perspective` | Float | `1200` | 3D camera distance for the surface. |
| `ResetDuration` | Int | `500` | Reset animation duration in milliseconds. |
| `Variant` | String | `none` | Theme variant for the surface background. |
| `Rounded` | String | `rounded-2xl` | Border radius token for the hover surface. |
| `Shadow` | String | `none` | Hover shadow intensity. |
| `Padding` | String | `p-0` | Tailwind-style padding utilities for hosted content. |
| `Margin` | String | `` | Tailwind-style margin utilities for the outer host. |
| `Width` | String | `w-full` | Tailwind size token or CSS size for the host width. |
| `Height` | String | `h-content` | Tailwind size token or CSS size for the host height. Use h-content or h-auto to fit hosted content. |
| `ContentType` | String | `custom` | Mutually exclusive content mode. Use image for internal image rendering or custom for hosted child content. |
| `Image` | String | `` | Asset name or path used when Content Type is image. |
| `ContentBackgroundColor` | Color | `0x00000000` | Optional background override for the custom content shell. |
| `ContentRounded` | String | `none` | Border radius token for the custom content shell. |
| `ContentPadding` | String | `` | Tailwind-style padding utilities for the custom content shell. |
| `ContentShadow` | String | `none` | Shadow intensity for the custom content shell. |
| `BackgroundColor` | Color | `0x00000000` | Optional explicit surface background override. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setMaxTilt(Value As Float)`
- `getMaxTiltAs Float`
- `setScaleOnHover(Value As Float)`
- `getScaleOnHoverAs Float`
- `setShineEffect(Value As Boolean)`
- `getShineEffectAs Boolean`
- `setPerspective(Value As Float)`
- `getPerspectiveAs Float`
- `setResetDuration(Value As Int)`
- `getResetDurationAs Int`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setContentType(Value As String)`
- `getContentTypeAs String`
- `setImage(Value As String)`
- `getImageAs String`
- `setContentBackgroundColor(Value As Int)`
- `getContentBackgroundColorAs Int`
- `setContentRounded(Value As String)`
- `getContentRoundedAs String`
- `setContentPadding(Value As String)`
- `getContentPaddingAs String`
- `setContentShadow(Value As String)`
- `getContentShadowAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddView(View As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `RemoveAllViews`
- `getContentPanelAs B4XView`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyIconButton

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `IconAsset` | String | `` | SVG icon asset file name. |
| `IconColor` | Color | `0x00FFFFFF` | Override icon color (0 = auto from variant). |
| `Variant` | String | `default` | Semantic color variant. |
| `Style` | String | `solid` | Daisy button style. |
| `Size` | String | `md` | Daisy button size token. |
| `CustomSize` | Int | `0` | Custom width/height in dip. When > 0, overrides the Size token. |
| `Shape` | String | `square` | Icon button shape (square uses Rounded for corners; circle is always rounded-full). |
| `Rounded` | String | `theme` | Border radius for square shape. Ignored when Shape=circle. |
| `Padding` | String | `` | Tailwind padding utility tokens. |
| `Margin` | String | `` | Tailwind margin utility tokens. |
| `Loading` | Boolean | `False` | Shows loading spinner and disables button. |
| `Disabled` | Boolean | `False` | Applies disabled behavior. |
| `Active` | Boolean | `False` | Applies btn-active behavior. |
| `BackgroundColor` | Color | `0x00FFFFFF` | Override background color. |
| `BorderColor` | Color | `0x00FFFFFF` | Override border color. |
| `Visible` | Boolean | `True` | Show or hide component. |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(SizeDip As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setIconAsset(Value As String)`
- `getIconAssetAs String`
- `setIconColor(Value As Int)`
- `getIconColorAs Int`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setCustomSize(Value As Int)`
- `getCustomSizeAs Int`
- `setShape(Value As String)`
- `getShapeAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setLoading(Value As Boolean)`
- `getLoadingAs Boolean`
- `setDisabled(Value As Boolean)`
- `getDisabledAs Boolean`
- `setActive(Value As Boolean)`
- `getActiveAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getViewAs B4XView`
- `GetComputedHeightAs Int`
- `GetComputedWidthAs Int`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisyImage

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `ResizeMode` | String | `FIT` |  |
| `Round` | Boolean | `False` |  |
| `CornersRadius` | Int | `0` |  |
| `BackgroundColor` | Color | `0xFFAAAAAA` |  |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getRoundedImageAs Boolean`
- `setRoundedImage(b As Boolean)`
- `getCornersRadiusAs Int`
- `setCornersRadius(i As Int)`
- `getResizeModeAs String`
- `setResizeMode(s As String)`
- `Update`
- `Load(Dir As String, FileName As String)`
- `Clear`
- `setBitmap(Bmp As B4XBitmap)`
- `getBitmapAs B4XBitmap`


---

## B4XDaisyIndicator

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `HorizontalPlacement` | String | `end` | Horizontal indicator placement. |
| `VerticalPlacement` | String | `top` | Vertical indicator placement. |
| `OffsetX` | String | `0` | Horizontal offset (Tailwind/CSS size token). |
| `OffsetY` | String | `0` | Vertical offset (Tailwind/CSS size token). |
| `Text` | String | `` | Indicator text content. |
| `Counter` | Boolean | `False` | Counter mode: 0 hides, 1..99 shows number, >99 shows 99+. |
| `CapValue` | Int | `99` | Numeric cap - values above this display as cap+ (0 disables capping). |
| `Variant` | String | `none` | Badge variant for indicator content. |
| `Size` | String | `sm` | Badge size token for indicator content. |
| `IconAsset` | String | `` | Optional SVG icon asset. |
| `Rounded` | String | `rounded` | Rounded mode for indicator content. |
| `TextColor` | Color | `0x00000000` | Optional text color override (0 = auto). |
| `BackgroundColor` | Color | `0x00000000` | Optional background color override (0 = auto). |
| `Visible` | Boolean | `True` | Show or hide indicator. |
| `Clickable` | Boolean | `False` | When True, the indicator handles clicks via the Click event. When False (default), touches pass through to the parent. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `AttachToTarget(Target As B4XView)`
- `DetachTarget`
- `RefreshPlacement`
- `setHorizontalPlacement(Value As String)`
- `getHorizontalPlacementAs String`
- `setVerticalPlacement(Value As String)`
- `getVerticalPlacementAs String`
- `setOffsetX(Value As Object)`
- `getOffsetXAs Float`
- `setOffsetY(Value As Object)`
- `getOffsetYAs Float`
- `setText(Value As String)`
- `getTextAs String`
- `setCounter(Value As Boolean)`
- `getCounterAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setIconAsset(Value As String)`
- `getIconAssetAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setBackgroundColorVariant(VariantName As String)`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setCapValue(Value As Int)`
- `getCapValueAs Int`
- `setValue(Value As Int)`
- `getValueAs Int`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`
- `IncrementAs Int`
- `IncrementBy(Amount As Int) As Int`
- `DecrementAs Int`
- `DecrementBy(Amount As Int) As Int`


---

## B4XDaisyInput

### Events
- `TextChanged (Old As String, New As String)`
- `EnterPressed (Text As String)`
- `FocusChanged (HasFocus As Boolean)`
- `Click (Tag As Object)`
- `PrependClick`
- `AppendClick`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Variant` | String | `none` | DaisyUI color variant applied To border color. |
| `Size` | String | `md` | DaisyUI size variant controlling height And font-size. |
| `Text` | String | `` | Current input text value. |
| `Placeholder` | String | `Type here` | Placeholder text shown inside the input when empty. |
| `HintText` | String | `` | Helper text displayed below the input (maps To CSS .label standalone pattern). |
| `ErrorText` | String | `` | Text displayed below the input when in the error validation state. |
| `RequiredErrorText` | String | `` | Error text shown when the required field is empty. |
| `MinLengthErrorText` | String | `` | Error text shown when the input is shorter than Min Length. |
| `MaxLengthErrorText` | String | `` | Error text shown when the input exceeds Max Length. |
| `PatternErrorText` | String | `` | Error text shown when the Validation Pattern does not match. |
| `LabelAbove` | String | `` | Label text. When FloatingLabel=True this becomes the floating label; otherwise shown above the input. |
| `InputType` | String | `text` | Keyboard input Type For the native EditText. |
| `MinValue` | String | `0` | Minimum value for stepper input type. |
| `MaxValue` | String | `100` | Maximum value for stepper input type. |
| `StepValue` | String | `1` | Increment/decrement amount for stepper input type. |
| `PasswordChar` | String | `*` | Character to use for password masking. Defaults to * (asterisk). |
| `IconLeft` | String | `` | Left-side SVG icon asset filename. |
| `IconRight` | String | `` | Right-side SVG icon asset filename. |
| `LabelLeft` | String | `` | Left-side text label inside input. |
| `LabelRight` | String | `` | Right-side text label inside input. |
| `FloatingLabel` | Boolean | `False` | If True the label floats between placeholder and above-input positions. |
| `Required` | Boolean | `False` | Whether this field is required. |
| `ValidationPattern` | String | `` | Regular expression pattern For validation. |
| `MinLength` | Int | `0` | Minimum character count. |
| `MaxLength` | Int | `0` | Maximum character count. |
| `Radius` | String | `theme` | Corner radius token. |
| `Enabled` | Boolean | `True` | Whether the input is enabled. |
| `SingleLine` | Boolean | `True` | Restrict input To a single line. |
| `Visible` | Boolean | `True` | Controls view visibility. |
| `BackgroundColor` | Color | `0x00000000` | Override background color. |
| `TextColor` | Color | `0x00000000` | Override text color. |
| `PlaceholderColor` | Color | `0x00000000` | Override placeholder color. |
| `Padding` | String | `` | Tailwind spacing utility. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `ImeOptions` | String | `normal` | Keyboard action button (IME options) For the native EditText. |
| `Gravity` | String | `LEFT` | Horizontal text alignment within the input. |
| `Typeface` | String | `DEFAULT` | Font family For the input text. |
| `MaxLines` | Int | `1` | Maximum visible lines (set > 1 For multiline input). |
| `MinLines` | Int | `1` | Minimum visible lines For multiline input. |
| `AllCaps` | Boolean | `False` | Force all input text To uppercase. |
| `ReadOnly` | Boolean | `False` | Makes the input read-only (selectable but Not editable). |
| `CursorVisible` | Boolean | `True` | Whether the text cursor is visible. |
| `LetterSpacing` | Float | `0` | Extra spacing between characters (em units, 0 = normal). |
| `Alpha` | Float | `1.0` | View opacity from 0 (invisible) To 1 (fully opaque). |
| `AutoHeight` | Boolean | `False` | Auto-grow height based on text lines (multiline only). |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `RemoveViewFromParent`
- `RequestFocus`
- `Release`
- `UpdateTheme`
- `setText(Value As String)`
- `getTextAs String`
- `setPlaceholder(Value As String)`
- `getPlaceholderAs String`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `GetValidationErrorAs String`
- `setLabelAbove(Value As String)`
- `getLabelAboveAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setInputType(Value As String)`
- `setPasswordChar(Value As String)`
- `getPasswordCharAs String`
- `getInputTypeAs String`
- `setMinValue(Value As String)`
- `getMinValueAs String`
- `setMaxValue(Value As String)`
- `getMaxValueAs String`
- `setStepValue(Value As String)`
- `getStepValueAs String`
- `setIconLeft(Value As String)`
- `getIconLeftAs String`
- `setIconRight(Value As String)`
- `getIconRightAs String`
- `setRadius(Value As String)`
- `getRadiusAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setSingleLine(Value As Boolean)`
- `getSingleLineAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setPlaceholderColor(Value As Int)`
- `getPlaceholderColorAs Int`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setLabelLeft(Value As String)`
- `getLabelLeftAs String`
- `setLabelRight(Value As String)`
- `getLabelRightAs String`
- `setFloatingLabel(Value As Boolean)`
- `getFloatingLabelAs Boolean`
- `setImeOptions(Value As String)`
- `getImeOptionsAs String`
- `setGravity(Value As String)`
- `getGravityAs String`
- `setTypeface(Value As String)`
- `getTypefaceAs String`
- `setMaxLines(Value As Int)`
- `getMaxLinesAs Int`
- `setMinLines(Value As Int)`
- `getMinLinesAs Int`
- `setAllCaps(Value As Boolean)`
- `getAllCapsAs Boolean`
- `setReadOnly(Value As Boolean)`
- `getReadOnlyAs Boolean`
- `setCursorVisible(Value As Boolean)`
- `getCursorVisibleAs Boolean`
- `setLetterSpacing(Value As Float)`
- `getLetterSpacingAs Float`
- `setAlpha(Value As Float)`
- `getAlphaAs Float`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setValidationPattern(Value As String)`
- `getValidationPatternAs String`
- `setMinLength(Value As Int)`
- `getMinLengthAs Int`
- `setMaxLength(Value As Int)`
- `getMaxLengthAs Int`
- `setValidationState(Value As String)`
- `getValidationStateAs String`
- `getIsValidAs Boolean`
- `Revalidate`
- `setErrorText(Value As String)`
- `setRequiredErrorText(Value As String)`
- `getRequiredErrorTextAs String`
- `setMinLengthErrorText(Value As String)`
- `getMinLengthErrorTextAs String`
- `setMaxLengthErrorText(Value As String)`
- `getMaxLengthErrorTextAs String`
- `setPatternErrorText(Value As String)`
- `getPatternErrorTextAs String`
- `getErrorTextAs String`
- `getIsBlankAs Boolean`
- `ValidateAs Boolean`
- `CheckValidationAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ReceiveFocus`
- `Blur`
- `setFocus(Value As Boolean)`
- `getIsFocusedAs Boolean`
- `getEditTextAs B4XView`
- `SelectAll`
- `SetSelection(StartPos As Int, Length As Int)`


---

## B4XDaisyKbd

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Text` | String | `K` | Kbd label text. |
| `Size` | String | `md` | Daisy kbd size token. |
| `Rounded` | String | `theme` | Border radius token. |
| `Padding` | String | `` | Tailwind padding utility tokens (for example: px-2 py-1). |
| `Margin` | String | `` | Tailwind margin utility tokens. |
| `BackgroundColor` | Color | `0x00FFFFFF` | Override background color. |
| `TextColor` | Color | `0x00FFFFFF` | Override text color. |
| `Visible` | Boolean | `True` | Show or hide component. |
| `AutoResize` | Boolean | `True` | Automatically resize width to fit text content. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setText(Value As String)`
- `getTextAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyLabel

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Text` | String | `` | The label text. |
| `TextColor` | Color | `0xFF000000` | Text color (overrides theme token when set). |
| `TextColorVariant` | String | `text-current/60` | DaisyUI text color token. |
| `TextSize` | String | `text-sm` | Tailwind text size token (e.g., text-xs, text-sm, text-md, text-lg, text-xl). |
| `FontBold` | Boolean | `False` | Whether the text is bold. |
| `SingleLine` | Boolean | `True` | Whether the text is single-line (whitespace-nowrap). |
| `HAlign` | String | `LEFT` | Horizontal text alignment. |
| `VAlign` | String | `CENTER` | Vertical text alignment. |
| `Padding` | String | `` | Tailwind padding token(s) (e.g., px-3 For input/Select context). |
| `Gap` | Int | `0` | Gap In dip when used In flex/grid context (maps To gap-1.5 = 6dip default). |
| `IsInsideInput` | Boolean | `False` | When True, applies input/Select child styling (px-3, border separator). |
| `Position` | String | `NONE` | Position inside input/Select (FIRST = -ms-3 me-3, LAST = ms-3 -me-3). |
| `Enabled` | Boolean | `True` | Whether the label is enabled. |
| `Visible` | Boolean | `True` | Whether the label is visible. |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setText(Value As String)`
- `getTextAs String`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(Variant As String)`
- `getTextColorVariantAs String`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setFontBold(Value As Boolean)`
- `getFontBoldAs Boolean`
- `setSingleLine(Value As Boolean)`
- `getSingleLineAs Boolean`
- `setHAlign(Value As String)`
- `getHAlignAs String`
- `setVAlign(Value As String)`
- `getVAlignAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setIsInsideInput(Value As Boolean)`
- `getIsInsideInputAs Boolean`
- `setPosition(Value As String)`
- `getPositionAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `getViewAs B4XView`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`


---

## B4XDaisyList

### Events
- `ItemClick (Index As Int, Tag As Object)`
- `ItemLongClick (Index As Int, Tag As Object)`
- `CreateRowContent (Index As Int)`
- `ReachEnd`
- `ScrollChanged (Offset As Int)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `full` | Tailwind size token or CSS size (eg full, 72, 320px, 80%) |
| `Height` | String | `300` | Viewport height in dip |
| `BackgroundColor` | String | `base-100` | DaisyUI background color token |
| `TextColor` | String | `` | DaisyUI text color token |
| `Rounded` | String | `rounded-box` | Border radius mode |
| `Shadow` | String | `shadow-md` | DaisyUI shadow class |
| `Padding` | String | `0` | Container padding in dip |
| `RowPadding` | String | `4` | Gap around row content in dip |
| `RowGap` | String | `4` | Gap between row items in dip |
| `DividerColor` | String | `base-content/5` | Divider border color token |
| `Divider` | Boolean | `True` | Show divider line between rows |
| `RowHeight` | Int | `72` | Default row height in dip for recycling |
| `AutoHeight` | Boolean | `False` | Automatically resize list height to fit all rows |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `ResizeToFitContent`
- `RegisterTemplate(TemplateName As String, Callback As Object, EventName As String)`
- `AddRowDataWithTemplate(Data As Map, TemplateName As String) As Int`
- `AddRowData(Data As Map) As Int`
- `AddHeader(Title As String) As Int`
- `AddRowDataBatch(Items As List)`
- `SetRowCount(Count As Int)`
- `AddRow(Data As Map) As Int`
- `Clear`
- `getRowCountAs Int`
- `GetRowData(Index As Int) As Map`
- `GetRow(Index As Int) As Map`
- `RemoveRow(Index As Int)`
- `InsertRowAt(Index As Int, Data As Map)`
- `RefreshRow(Index As Int)`
- `RefreshAllRows`
- `ScrollToIndex(Index As Int)`
- `SmoothScrollToIndex(Index As Int)`
- `getScrollPositionAs Int`
- `getFirstVisibleIndexAs Int`
- `getLastVisibleIndexAs Int`
- `GetItemFromView(v As B4XView) As Int`
- `setBackgroundColor(Value As String)`
- `getBackgroundColorAs String`
- `setTextColor(Value As String)`
- `getTextColorAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setRowPadding(Value As Int)`
- `getRowPaddingAs Int`
- `setRowGap(Value As Int)`
- `getRowGapAs Int`
- `setDivider(Value As Boolean)`
- `getDividerAs Boolean`
- `setDividerColor(Value As String)`
- `getDividerColorAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setRowHeight(Value As Int)`
- `getRowHeightAs Int`
- `setWidth(Value As Object)`
- `getWidthAs Int`
- `setHeight(Value As Object)`
- `getHeightAs Int`
- `getContentHeightAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `GetComputedHeightAs Int`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `RemoveViewFromParent`
- `getViewAs B4XView`
- `Release`
- `GetCurrentRowPanelAs B4XView`
- `GetCurrentRowDataAs Map`
- `AddTextRow(Title As String, OptionalSubtitle As String) As Int`
- `CreateTextItemView(Text As String, Width As Int, Height As Int, TextSize As Object, TextColor As Int, Bold As Boolean, SingleLine As Boolean) As B4XView`
- `CreateStackedTextView(Title As String, Subtitle As String, Width As Int, TitleSize As Object, SubtitleSize As Object, TitleColor As Int, SubtitleColor As Int) As B4XView`
- `GetCLVAs CustomListView`


---

## B4XDaisyLoading

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `LoadingStyle` | String | `spinner` | The loading animation style. |
| `Size` | String | `md` | Size of the loading indicator (xs, sm, md, lg, xl). |
| `Speed` | Int | `100` | Animation speed percentage (100 = normal). |
| `Visible` | Boolean | `True` | Visibility of the component. |
| `Variant` | String | `none` | DaisyUI semantic color variant (sets spinner color). |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `StopAnimation`
- `getStyleAs String`
- `setStyle(Value As String)`
- `getSizeAs String`
- `setSize(Value As String)`
- `getSpeedAs Int`
- `setSpeed(Value As Int)`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setTag(Tag As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `setColor(Value As Int)`
- `getColorAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyMenu

### Events
- `Click (Tag As Object)`
- `ItemClick (Tag As Object, Text As String)`
- `SubmenuToggle (Tag As Object, Open As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables menu interactions. |
| `Visible` | Boolean | `True` | Shows or hides the menu. |
| `Width` | String | `w-full` | Tailwind size token or CSS size used as preferred width. |
| `Height` | String | `h-auto` | Tailwind size token, CSS size, or h-auto. |
| `Padding` | String | `p-2` | Tailwind padding utilities for the menu surface. |
| `Margin` | String | `` | Tailwind margin utilities for the menu container. |
| `Dividers` | Boolean | `True` | Adds automatic dividers between clickable menu items. |
| `DividerGap` | String | `1` | Gap around automatic dividers using Tailwind spacing token or CSS size. |
| `Size` | String | `md` | Daisy menu size token. |
| `Orientation` | String | `vertical` | Top-level menu layout direction. |
| `Rounded` | String | `theme` | Corner radius mode. |
| `RoundedBox` | Boolean | `True` | Uses theme rounded-box radius when Rounded=theme. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `BringToFront` | Boolean | `True` | Brings the full menu view above siblings after layout. |
| `BackgroundColor` | Color | `0x00000000` | Optional surface background override. |
| `TextColor` | Color | `0x00000000` | Optional menu text color override. |
| `ActiveColor` | Color | `0x00000000` | Active menu color. Used as border or background based on ActiveBorder. |
| `ActiveTextColor` | Color | `0x00000000` | Active item text color when ActiveBorder is False. |
| `ActiveBorder` | Boolean | `False` | Shows a left border for the active item instead of filling the item background. |
| `AutoResize` | Boolean | `True` | Automatically resize height to fit menu items. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `Clear`
- `AddTitle(Text As String) As Int`
- `AddDividerAs Int`
- `AddItem(TagValue As Object, Text As String) As Int`
- `AddIconItem(TagValue As Object, Text As String, IconName As String) As Int`
- `AddBadgeItem(TagValue As Object, Text As String, BadgeText As String, BadgeVariant As String) As Int`
- `AddIconBadgeItem(TagValue As Object, Text As String, IconName As String, BadgeText As String, BadgeVariant As String) As Int`
- `AddSubmenu(TagValue As Object, Text As String, InitiallyOpen As Boolean) As B4XDaisyMenu`
- `SetItemDisabled(TagValue As Object, Value As Boolean)`
- `ClearActive`
- `ScrollToItem(TagValue As Object)`
- `SetItemActive(TagValue As Object, Value As Boolean)`
- `SetSubmenuOpen(Index As Int, Value As Boolean)`
- `SetItemBadgeText(TagValue As Object, Value As String)`
- `SetItemBadgeBackgroundColor(TagValue As Object, Color As Int)`
- `SetItemBadgeTextColor(TagValue As Object, Color As Int)`
- `SetItemText(TagValue As Object, Value As String)`
- `SetItemIcon(TagValue As Object, IconName As String)`
- `SetItemVisible(TagValue As Object, Value As Boolean)`
- `GetItemView(Index As Int) As B4XView`
- `GetPreferredHeightAs Int`
- `GetPreferredWidthAs Int`
- `SetLevelInternal(Level As Int)`
- `SetParentMenuInternal(ParentMenu As B4XDaisyMenu)`
- `SetPopupMode(Value As Boolean)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `ViewAs B4XView`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setDividers(Value As Boolean)`
- `getDividersAs Boolean`
- `setDividerGap(Value As String)`
- `getDividerGapAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setOrientation(Value As String)`
- `getOrientationAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setBringToFront(Value As Boolean)`
- `getBringToFrontAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setActiveColor(Value As Int)`
- `getActiveColorAs Int`
- `setActiveTextColor(Value As Int)`
- `getActiveTextColorAs Int`
- `setActiveBorder(Value As Boolean)`
- `getActiveBorderAs Boolean`
- `setDebugDividerBorders(Value As Boolean)`
- `getDebugDividerBordersAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`


---

## B4XDaisyModal

### Events
- `Click (Tag As Object)`
- `CloseClick (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Auto-generated property for Enabled. |
| `Visible` | Boolean | `True` | Auto-generated property for Visible. |
| `ClickOutsideToClose` | Boolean | `True` |  |
| `FullScreen` | Boolean | `False` |  |
| `GlassSize` | String | `none` |  |
| `Placement` | String | `middle` |  |
| `Width` | String | `w-[91.6%]` |  |
| `Height` | String | `h-auto` |  |
| `Rounded` | String | `rounded-box` |  |
| `BackgroundColor` | String | `base-100` |  |
| `BackdropColor` | String | `black` |  |
| `BackdropOpacity` | Int | `40` |  |
| `Title` | String | `Modal Title` |  |
| `Padding` | String | `p-6` |  |
| `ActionsJustify` | String | `end` | Horizontal alignment of action buttons in the footer. |
| `ActionsVariant` | String | `primary` | Visual variant/style of action buttons. |
| `ShowCloseButton` | Boolean | `False` |  |
| `Sidebar` | Boolean | `False` | When True the modal slides in as a side panel, ignoring Placement. |
| `SidebarSide` | String | `left` |  |
| `SidebarDuration` | Int | `300` |  |
| `Shadow` | String | `lg` | Elevation shadow on the modal content box. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `GetComputedHeightAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `CreateView(Parent As B4XView, Tag As Object)`
- `getViewAs B4XView`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getActionsContainerAs B4XView`
- `AddAction(btn As B4XDaisyButton)`
- `getActionsCountAs Int`
- `ClearActions`
- `Show`
- `ShowModal`
- `Close`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setFullScreen(Value As Boolean)`
- `getFullScreenAs Boolean`
- `setGlassSize(Value As String)`
- `getGlassSizeAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setClickOutsideToClose(Value As Boolean)`
- `getClickOutsideToCloseAs Boolean`
- `setPlacement(Value As String)`
- `getPlacementAs String`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setBackgroundColor(Value As String)`
- `getBackgroundColorAs String`
- `setBackdropColor(Value As String)`
- `getBackdropColorAs String`
- `setBackdropOpacity(Value As Int)`
- `getBackdropOpacityAs Int`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setShowCloseButton(Value As Boolean)`
- `getShowCloseButtonAs Boolean`
- `setSidebar(Value As Boolean)`
- `getSidebarAs Boolean`
- `setSidebarSide(Value As String)`
- `getSidebarSideAs String`
- `setSidebarDuration(Value As Int)`
- `getSidebarDurationAs Int`
- `setActionsJustify(Value As String)`
- `getActionsJustifyAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `AddToContent(View As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `getBodyContainerAs B4XView`


---

## B4XDaisyNavbar

### Events
- `Click (Payload As Object)`
- `Opened`
- `Closed`
- `Back (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Variant` | String | `none` | Daisy variant for coloring |
| `BackgroundColor` | Color | `0x00000000` | Navbar background color (0 = theme base-100/variant) |
| `TextColor` | Color | `0x00000000` | Navbar text color (0 = theme base-content/variant) |
| `Shadow` | String | `sm` | Shadow level |
| `Rounded` | String | `none` | Corner radius style |
| `Glass` | Boolean | `False` | Enable glass effect |
| `GlassSize` | String | `none` | Glass opacity scale; overrides Glass when not none |
| `Padding` | Int | `8` | Internal padding in dip |
| `Width` | String | `full` | Tailwind size token or CSS size (eg full, 72, 320px, 20rem) |
| `Height` | String | `h-64` | Tailwind size token or CSS size (eg h-64, 4rem, 80px) |
| `Title` | String | `` | Navbar title text |
| `TitlePosition` | String | `start` | Title position in navbar |
| `TitleVisible` | Boolean | `True` | Show/hide the title |
| `HamburgerVisible` | Boolean | `False` | Show hamburger menu button |
| `HamburgerSize` | Int | `48` | Size of the hamburger menu button in dip |
| `BackVisible` | Boolean | `False` | Show a back button in the start slot |
| `BackSize` | Int | `48` | Size of the back button in dip |
| `BackLabel` | String | `` | Label text on the back button; leave empty for icon-only |
| `BackNudge` | Int | `10` | Left-offset in dip for the back button (nudges start slot left) |
| `LogoImage` | String | `` | Path to the logo image |
| `LogoWidth` | Int | `32` | Logo width in dip |
| `LogoHeight` | Int | `32` | Logo height in dip |
| `LogoMask` | String | `none` | Logo mask shape |
| `LogoVisible` | Boolean | `True` | Show/hide the logo |
| `LogoPosition` | String | `start` | Logo slot position |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `GetStartPanelAs B4XView`
- `GetCenterPanelAs B4XView`
- `GetEndPanelAs B4XView`
- `AddViewToStart(v As B4XView, Width As Int, Height As Int)`
- `AddViewToCenter(v As B4XView, Width As Int, Height As Int)`
- `AddViewToEnd(v As B4XView, Width As Int, Height As Int)`
- `ClearStartSlot`
- `ClearCenterSlot`
- `ClearEndSlot`
- `AddTitleToCenter(Title As String)`
- `AddTitleToStart(Title As String)`
- `AddTitleToEnd(Title As String)`
- `AddBackButton(SizeDip As Int, Label As String)`
- `BackBtn_Click(Tag As Object)`
- `AddHamburger(SizeDip As Int)`
- `Hamburger_Changed(State As String, Checked As Boolean)`
- `LogoAvatar_Click`
- `AddLogo(ImagePath As String, WidthDip As Int, HeightDip As Int, Mask As String) As B4XDaisyAvatar`
- `AddAvatarToEnd(ID As String, ImagePath As String, SizeDip As Int, Mask As String) As B4XDaisyAvatar`
- `AddAvatarToStart(ID As String, ImagePath As String, SizeDip As Int, Mask As String) As B4XDaisyAvatar`
- `AddSVGIconToEnd(ID As String, AssetPath As String, SizeDip As Int, Color As Int) As B4XDaisySvgIcon`
- `AddSVGIconToStart(ID As String, AssetPath As String, SizeDip As Int, Color As Int) As B4XDaisySvgIcon`
- `AddButtonIconToStart(ID As String, SizeDip As Int, Icon As String, Color As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddButtonIconToEnd(ID As String, SizeDip As Int, Icon As String, Color As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddButtonIconToCenter(ID As String, SizeDip As Int, Icon As String, Color As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddButtonToStart(ID As String, ButtonText As String, Variant As String, WidthDip As Int, HeightDip As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddButtonToCenter(ID As String, ButtonText As String, Variant As String, WidthDip As Int, HeightDip As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddButtonToEnd(ID As String, ButtonText As String, Variant As String, WidthDip As Int, HeightDip As Int, Ghost As Boolean) As B4XDaisyButton`
- `AddFabToEnd(ID As String, OverlayHost As B4XView, SizeDip As Int) As B4XDaisyFab`
- `AddFabToStart(ID As String, OverlayHost As B4XView, SizeDip As Int) As B4XDaisyFab`
- `AddFabToCenter(ID As String, OverlayHost As B4XView, SizeDip As Int) As B4XDaisyFab`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setGlass(Value As Boolean)`
- `getGlassAs Boolean`
- `setGlassSize(Value As String)`
- `getGlassSizeAs String`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setLogoImage(Value As String)`
- `getLogoImageAs String`
- `setLogoWidth(Value As Int)`
- `getLogoWidthAs Int`
- `setLogoHeight(Value As Int)`
- `getLogoHeightAs Int`
- `setLogoMask(Value As String)`
- `getLogoMaskAs String`
- `setLogoVisible(Value As Boolean)`
- `getLogoVisibleAs Boolean`
- `setLogoPosition(Value As String)`
- `getLogoPositionAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setTitlePosition(Value As String)`
- `getTitlePositionAs String`
- `setTitleVisible(Value As Boolean)`
- `getTitleVisibleAs Boolean`
- `setHamburgerVisible(Value As Boolean)`
- `getHamburgerVisibleAs Boolean`
- `setHamburgerSize(Value As Int)`
- `getHamburgerSizeAs Int`
- `setBackVisible(Value As Boolean)`
- `getBackVisibleAs Boolean`
- `setBackSize(Value As Int)`
- `getBackSizeAs Int`
- `setBackLabel(Value As String)`
- `getBackLabelAs String`
- `setBackNudge(Value As Int)`
- `getBackNudgeAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColorVariant(VariantName As String)`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `getViewAs B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `SendToBack`
- `BringToFront`


---

## B4XDaisyOverlay

### Events
- `Click (Tag As Object)`
- `Opened (Tag As Object)`
- `Closed (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `OverlayColor` | Color | `0xFF000000` | Base RGB color of the overlay surface. Alpha channel is overridden by Opacity. |
| `Opacity` | Float | `0.4` | Surface opacity from 0.0 (fully transparent) to 1.0 (fully opaque). |
| `Rounded` | String | `none` | Corner radius token applied to the overlay surface. |
| `PassThrough` | Boolean | `False` | When True the overlay does not intercept touch events (Enabled = False). |
| `Visible` | Boolean | `False` | Show or hide the overlay. |
| `CloseOnClick` | Boolean | `False` | When True, clicking the overlay automatically closes it and fires the Closed event. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `GetComputedHeightAs Int`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AttachTo(Target As B4XView) As B4XView`
- `Resize(Width As Int, Height As Int)`
- `AddChild(View As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `GetHostViewAs B4XView`
- `getOverlayColorAs Int`
- `setOverlayColor(Value As Int)`
- `getOpacityAs Float`
- `setOpacity(Value As Float)`
- `getRoundedAs String`
- `setRounded(Value As String)`
- `getPassThroughAs Boolean`
- `setPassThrough(Value As Boolean)`
- `getVisibleAs Boolean`
- `setVisible(Value As Boolean)`
- `getTagAs Object`
- `setTag(Value As Object)`
- `getCloseOnClickAs Boolean`
- `setCloseOnClick(Value As Boolean)`
- `getIsAttachedAs Boolean`
- `getIsOpenAs Boolean`
- `Open`
- `Close`


---

## B4XDaisyPageScroll

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `PagePadding` | Int | `12` | Content padding in dip from page edges. |
| `BackgroundColor` | Color | `0x00000000` | Background color for the page (0 uses default light gray). |
| `RootColor` | Color | `0x00000000` | Background color for the page parent panel (0 uses default light gray). |
| `Transparent` | Boolean | `False` | Set to True to make the page background transparent. |
| `AutoFitHeight` | Boolean | `True` | Automatically resize scroll view panel to fit content. |
| `YGap` | Int | `12` | Vertical spacing between added elements in dip. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `SendToBack`
- `BringToFront`
- `getPanelAs B4XView`
- `getScrollViewAs ScrollView`
- `getUsableWidthAs Int`
- `AutoFit`
- `Clear`
- `AddSectionTitle(Text As String, Y As Int, Center As Boolean) As Int`
- `AddDivider(Y As Int) As Int`
- `getPagePaddingAs Int`
- `setPagePadding(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColor(Value As Int)`
- `getRootColorAs Int`
- `setRootColor(Value As Int)`
- `getTransparentAs Boolean`
- `setTransparent(Value As Boolean)`
- `getYGapAs Int`
- `setYGap(Value As Int)`
- `getAutoFitHeightAs Boolean`
- `setAutoFitHeight(Value As Boolean)`
- `getTagAs Object`
- `setTag(Value As Object)`


---

## B4XDaisyPagination

### Events
- `PageChanged (PageIndex As Int, ItemId As String)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Size` | String | `md` | Button size token |
| `Style` | String | `solid` | Button style variant |
| `ActiveColor` | String | `primary` | Variant color applied to the active button |
| `ActiveIndex` | Int | `0` | Zero-based index of the active page |
| `Disabled` | Boolean | `False` | Disable all pagination buttons |
| `ShowPrevNext` | Boolean | `True` | Show previous/next navigation buttons |
| `PrevText` | String | `chevron-left-solid.svg` | Text or SVG icon for the previous button |
| `NextText` | String | `chevron-right-solid.svg` | Text or SVG icon for the next button |
| `ShowFirstLast` | Boolean | `False` | Show first/last navigation buttons |
| `FirstText` | String | `angles-left-solid.svg` | Text or SVG icon for the first button |
| `LastText` | String | `angles-right-solid.svg` | Text or SVG icon for the last button |
| `ShowPageNumbers` | Boolean | `True` | Show numbered page buttons |
| `PageCount` | Int | `5` | Number of page buttons to display |
| `EqualWidth` | Boolean | `False` | Make prev/next buttons equal width (grid-cols-2 mode) |
| `Shadow` | String | `none` | Shadow applied to each button |
| `Circle` | Boolean | `True` | Each button is square — combine with Rounded=full for circle shape |
| `GapX` | Int | `1` | Horizontal gap between pagination buttons in dip |
| `Visible` | Boolean | `True` | Show or hide component |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getVisibleAs Boolean`
- `setVisible(Value As Boolean)`
- `getSizeAs String`
- `setSize(Value As String)`
- `getStyleAs String`
- `setStyle(Value As String)`
- `getActiveColorAs String`
- `setActiveColor(Value As String)`
- `getActiveIndexAs Int`
- `setActiveIndex(Value As Int)`
- `getDisabledAs Boolean`
- `setDisabled(Value As Boolean)`
- `getShowPrevNextAs Boolean`
- `setShowPrevNext(Value As Boolean)`
- `getShowFirstLastAs Boolean`
- `setShowFirstLast(Value As Boolean)`
- `getFirstTextAs String`
- `setFirstText(Value As String)`
- `getLastTextAs String`
- `setLastText(Value As String)`
- `getShadowAs String`
- `setShadow(Value As String)`
- `getPrevTextAs String`
- `setPrevText(Value As String)`
- `getNextTextAs String`
- `setNextText(Value As String)`
- `getShowPageNumbersAs Boolean`
- `setShowPageNumbers(Value As Boolean)`
- `getPageCountAs Int`
- `setPageCount(Value As Int)`
- `getEqualWidthAs Boolean`
- `setEqualWidth(Value As Boolean)`
- `getCircleAs Boolean`
- `setCircle(Value As Boolean)`
- `getGapXAs Int`
- `setGapX(Value As Int)`
- `getTagAs Object`
- `setTag(Value As Object)`
- `getViewAs B4XView`
- `GetActualPageCountAs Int`
- `PrevPage`
- `NextPage`
- `GoToPage(Index As Int)`
- `GetItemCountAs Int`
- `GetItemIdAt(Index As Int) As String`
- `SetItemDisabled(Id As String, Disabled As Boolean)`


---

## B4XDaisyProgress

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Value` | Int | `0` | Current progress value. |
| `MaxValue` | Int | `100` | Maximum progress bound. |
| `Variant` | String | `none` |  |
| `Size` | String | `none` |  |
| `Visible` | Boolean | `True` |  |
| `Width` | String | `w-full` |  |
| `Height` | String | `h-2` |  |
| `ShowTooltip` | Boolean | `False` |  |
| `TooltipPosition` | String | `top` |  |
| `Indeterminate` | Boolean | `False` | Shows animated repeating-gradient progress (no value needed). |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `SetValueColor(Color As Int)`
- `SetTrackColor(Color As Int)`
- `setVariant(VariantName As String)`
- `getVariantAs String`
- `setValue(Value As Float)`
- `getValueAs Float`
- `SetValueAnimated(Value As Float, Duration As Int)`
- `StartTimer(DurationMs As Int)`
- `setMaxValue(MaxValue As Float)`
- `getMaxValueAs Float`
- `setSize(Size As String)`
- `getSizeAs String`
- `setShowTooltip(b As Boolean)`
- `getShowTooltipAs Boolean`
- `setTooltipPosition(s As String)`
- `getTooltipPositionAs String`
- `setIndeterminate(b As Boolean)`
- `getIndeterminateAs Boolean`
- `setTag(Tag As Object)`
- `getTagAs Object`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `SetLayoutAnimated(Duration As Int, LeftPos As Int, TopPos As Int, Width As Int, Height As Int)`
- `StopAnimation`
- `GetComputedHeightAs Int`
- `getViewAs B4XView`
- `RemoveViewFromParent`


---

## B4XDaisyRadialProgress

### Events
- `None`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Value` | Int | `0` | Current progress value |
| `MinValue` | Int | `0` | Minimum possible value |
| `MaxValue` | Int | `100` | Maximum possible value |
| `StepValue` | Int | `1` | Step size for increments |
| `Size` | String | `80px` | Tailwind size token or CSS size (eg 20, 80dip, 5rem, 80px) |
| `Thickness` | String | `10%` | Stroke thickness (e.g. 10%, 4dip, 8px) |
| `Variant` | String | `none` | Semantic color variant |
| `DisplayType` | String | `text` | Content shown in the center |
| `Text` | String | `0` | Base text to show when DisplayType is text |
| `Prefix` | String | `` | Text shown before the value |
| `Suffix` | String | `%` | Text shown after the value |
| `TextCountUp` | Boolean | `False` | Animate text value incrementally |
| `CountUpSpeed` | Int | `300` | Duration for Text CountUp in ms |
| `SvgAsset` | String | `` | SVG file used when DisplayType is svg |
| `TrackColor` | Color | `0x00000000` | Color of the background ring (0 uses default base-200) |
| `BackgroundColor` | Color | `0x00000000` | 0/transparent |
| `TextColor` | Color | `0xFF000000` | Default text/arc color |
| `BorderColor` | Color | `0x00000000` | 0/variant fallback |
| `BorderWidth` | String | `0` | Outer border width (e.g. 4dip) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `DrawComponent`
- `SetValueAnimated(NewValue As Float, Duration As Int)`
- `StopAnimation`
- `StartTimer(DurationMs As Int)`
- `getMaxValueAs Int`
- `setMaxValue(MaxVal As Int)`
- `getMinValueAs Int`
- `setMinValue(MinVal As Int)`
- `getValueAs Int`
- `setValue(Val As Int)`
- `setStepValue(StepVal As Int)`
- `getStepValueAs Int`
- `setDisplayType(DType As String)`
- `getDisplayTypeAs String`
- `setText(NewText As String)`
- `getTextAs String`
- `setVariant(NewVariant As String)`
- `getVariantAs String`
- `setSize(Value As Object)`
- `getSizeAs Float`
- `getWidthAs Float`
- `getHeightAs Float`
- `setThickness(NewThickness As String)`
- `getThicknessAs String`
- `setSvgAsset(NewSvgAsset As String)`
- `getSvgAssetAs String`
- `setPrefix(NewPrefix As String)`
- `getPrefixAs String`
- `setSuffix(NewSuffix As String)`
- `getSuffixAs String`
- `setTextCountUp(NewTextCountUp As Boolean)`
- `getTextCountUpAs Boolean`
- `setCountUpSpeed(NewCountUpSpeed As Int)`
- `getCountUpSpeedAs Int`
- `setTrackColor(NewTrackColor As Int)`
- `getTrackColorAs Int`
- `setBackgroundColor(NewBackgroundColor As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(NewTextColor As Int)`
- `getTextColorAs Int`
- `setBorderColor(NewBorderColor As Int)`
- `getBorderColorAs Int`
- `setBorderWidth(NewBorderWidth As String)`
- `getBorderWidthAs String`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyRadio

### Events
- `Checked (Checked As Boolean)`
- `Click (Tag As Object)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `GroupName` | String | `` | Radio group name. |
| `Checked` | Boolean | `False` | Checked state. |
| `Value` | String | `` | Value assigned to the radio. |
| `Text` | String | `` | Label text. |
| `Variant` | String | `none` | Color variant. |
| `Size` | String | `md` | Size variant. |
| `Position` | String | `start` | Position alignment. |
| `Enabled` | Boolean | `True` | Enabled state. |
| `Visible` | Boolean | `True` | Visible state. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `CheckedBackgroundColor` | Color | `0x00FFFFFF` | Override checked background color. |
| `CheckedBorderColor` | Color | `0x00FFFFFF` | Override checked border color. |
| `CheckedTextColor` | Color | `0x00FFFFFF` | Override checked center dot color. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setChecked(Value As Boolean)`
- `getRoleAs String`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `getCheckedAs Boolean`
- `setValue(Value As String)`
- `getValueAs String`
- `setText(Value As String)`
- `getTextAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setPosition(Value As String)`
- `getPositionAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `getIsValidAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ValidateAs Boolean`
- `setBackgroundColor(Color As Int)`
- `getBackgroundColorAs Int`
- `setBorderColor(Color As Int)`
- `getBorderColorAs Int`
- `setTextColor(Color As Int)`
- `getTextColorAs Int`
- `setCheckedBackgroundColor(Color As Int)`
- `getCheckedBackgroundColorAs Int`
- `setCheckedBorderColor(Color As Int)`
- `getCheckedBorderColorAs Int`
- `setCheckedTextColor(Color As Int)`
- `getCheckedTextColorAs Int`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getComputedHeightAs Int`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisyRadioGroup

### Events
- `ItemChanged (id As String, text As String, checked As Boolean)`
- `Changed (SelectedIds As List)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Legend` | String | `Select an option` | Fieldset legend text |
| `LegendSize` | String | `theme` | Legend text size token |
| `LegendBold` | Boolean | `False` | Render the fieldset legend caption in bold |
| `Variant` | String | `none` | Optional accent variant for border tint |
| `BorderStyle` | String | `outlined` | Border visual style |
| `Padding` | Int | `16` | Inner content padding in dip |
| `AutoHeight` | Boolean | `True` | Automatically grow to fit added content |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use box radius for container |
| `Shadow` | String | `none` | Elevation shadow level |
| `BackgroundColor` | Color | `0x00000000` | Background color (0 = default bg-base-200) |
| `TextColor` | Color | `0x00000000` | Legend text color (0 = use theme token) |
| `BorderColor` | Color | `0x00000000` | Border color override (0 = default border-base-300) |
| `BorderSize` | Int | `1` | Border width in dip |
| `InputBorder` | Boolean | `False` | When True, apply B4XDaisyInput border color and width to the fieldset |
| `Direction` | String | `vertical` | Items layout direction |
| `Alignment` | String | `start` | Radio element dot position |
| `RadioColor` | String | `neutral` | Default radio color variant |
| `RadioSize` | String | `md` | Radio size token |
| `Gap` | Int | `8` | Gap between elements in dip |
| `RowGap` | Int | `8` | Row gap for wrapped flow mode in dip |
| `GroupName` | String | `` | Radio group name for mutual exclusivity |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `AddItem(Id As String, Text As String)`
- `RemoveItem(Id As String)`
- `ClearItems`
- `setItems(Items As Map)`
- `getItemsAs Map`
- `setSelectedIndex(Index As Int)`
- `getSelectedIndexAs Int`
- `setChecked(CheckedIds As String)`
- `getCheckedAs String`
- `setLegend(Value As String)`
- `getLegendAs String`
- `setLegendSize(Value As String)`
- `getLegendSizeAs String`
- `setLegendBold(Value As Boolean)`
- `getLegendBoldAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setAlignment(Value As String)`
- `getAlignmentAs String`
- `setRadioColor(Value As String)`
- `getRadioColorAs String`
- `setRadioSize(Value As String)`
- `getRadioSizeAs String`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setRowGap(Value As Int)`
- `getRowGapAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `ReceiveFocus`
- `Blur`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `isRoundedAs Boolean`
- `setRoundedBox(Value As Boolean)`
- `isRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setInputBorder(Value As Boolean)`
- `getInputBorderAs Boolean`
- `GetComputedHeightAs Int`
- `Release`
- `RemoveViewFromParent`


---

## B4XDaisyRange

### Events
- `Changed (Value As Int)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `MinValue` | Int | `0` | Minimum slider value. |
| `MaxValue` | Int | `100` | Maximum slider value. |
| `Value` | Int | `40` | Current slider value. |
| `StepValue` | Int | `1` | Step increment (0 for continuous). |
| `Size` | String | `md` | Size variant. |
| `Variant` | String | `none` | Color variant. |
| `TrackColor` | Color | `0` | Custom track background color. |
| `ProgressColor` | Color | `0` | Custom progress fill color. |
| `ThumbColor` | Color | `0` | Custom thumb knob color. |
| `Enabled` | Boolean | `True` | Enabled state. |
| `ShowFill` | Boolean | `True` | Show progress fill from min to thumb position. |
| `RTL` | Boolean | `False` | Right-to-left progress direction (--range-dir:-1 parity). |
| `Visible` | Boolean | `True` | Visible state. |
| `Required` | Boolean | `False` | Whether the value must be greater than the minimum value. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setMinValue(Value As Int)`
- `getMinValueAs Int`
- `setMaxValue(Value As Int)`
- `getMaxValueAs Int`
- `setValue(Value As Int)`
- `getValueAs Int`
- `setStepValue(Value As Int)`
- `getStepValueAs Int`
- `setSize(Value As String)`
- `getSizeAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setShowFill(Value As Boolean)`
- `getShowFillAs Boolean`
- `setRTL(Value As Boolean)`
- `getRTLAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTrackColor(Value As Int)`
- `getTrackColorAs Int`
- `setProgressColor(Value As Int)`
- `getProgressColorAs Int`
- `setThumbColor(Value As Int)`
- `getThumbColorAs Int`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getRoleAs String`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getComputedHeightAs Int`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `getIsValidAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ValidateAs Boolean`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisyRating

### Events
- `Changed(Value As Float)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Value` | Float | `0` | Current rating value (0 = no selection, use 0.5 for half stars). |
| `MaxValue` | Int | `5` | Maximum number of rating items. |
| `Size` | String | `md` | Size variant (rating-xs through rating-xl). |
| `Variant` | String | `none` | Color variant. |
| `IconStyle` | String | `star-2` | Icon mask shape. |
| `Half` | Boolean | `False` | Enable half-star increments (rating-half parity). |
| `AllowClear` | Boolean | `False` | Allow clearing the rating by selecting same value (rating-hidden parity). |
| `ReadOnly` | Boolean | `False` | Read-only mode â€” no interaction, display only. |
| `Required` | Boolean | `False` | Whether a rating value greater than 0 is required. |
| `ActiveColor` | Color | `0` | Custom color for active/filled items (0 = theme default bg-base-content). |
| `InactiveColor` | Color | `0` | Custom color for inactive/empty items (0 = theme default opacity-20). |
| `Gap` | Int | `4` | Gap between items in dip (maps to gap-1 â‰ˆ 4dip). |
| `Enabled` | Boolean | `True` | Enabled state. |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setValue(Value As Float)`
- `getValueAs Float`
- `setMaxValue(Value As Int)`
- `getMaxValueAs Int`
- `setSize(Value As String)`
- `getSizeAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setIconStyle(Value As String)`
- `getIconStyleAs String`
- `setHalf(Value As Boolean)`
- `getHalfAs Boolean`
- `setAllowClear(Value As Boolean)`
- `getAllowClearAs Boolean`
- `setReadOnly(Value As Boolean)`
- `getReadOnlyAs Boolean`
- `setActiveColor(Value As Int)`
- `getActiveColorAs Int`
- `setInactiveColor(Value As Int)`
- `getInactiveColorAs Int`
- `SetItemColors(ItemColorList As List)`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setLeft(Value As Int)`
- `setTop(Value As Int)`
- `getRoleAs String`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getComputedHeightAs Int`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setBackgroundColorVariant(Variant As String)`
- `setTextColorVariant(Variant As String)`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `getIsValidAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ValidateAs Boolean`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisySelect

### Events
- `Changed(Index As Int, Key As String, Value As String)`
- `Click(Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Variant` | String | `none` | DaisyUI color variant applied to border and focus outline. |
| `Size` | String | `md` | DaisyUI size variant controlling height and font-size. |
| `Placeholder` | String | `Pick an option` | Placeholder text shown when no item is selected. |
| `LabelAbove` | String | `` | Optional label text displayed above the select trigger. |
| `HintText` | String | `` | Helper text displayed below the select trigger. |
| `Required` | Boolean | `False` | Whether an option must be selected. |
| `ErrorText` | String | `` | Error text displayed below the select when validation fails. |
| `Radius` | String | `theme` | Corner radius token for the select trigger. |
| `Enabled` | Boolean | `True` | Whether the select is enabled. |
| `Visible` | Boolean | `True` | Controls view visibility. |
| `BackgroundColor` | Color | `0x00000000` | Override background color for the trigger. |
| `TextColor` | Color | `0x00000000` | Override text color for the selected value. |
| `Shadow` | String | `none` | Elevation shadow level for the trigger. |
| `Alpha` | Float | `1.0` | View opacity from 0 (invisible) to 1 (fully opaque). |
| `MaxDropdownRows` | Int | `5` | Maximum number of visible rows in the dropdown before scrolling. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `setTag(Value As Object)`
- `getTagAs Object`
- `IsReadyAs Boolean`
- `UpdateTheme`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setPlaceholder(Value As String)`
- `getPlaceholderAs String`
- `setLabelAbove(Value As String)`
- `getLabelAboveAs String`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setRadius(Value As String)`
- `getRadiusAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setAlpha(Value As Float)`
- `getAlphaAs Float`
- `setMaxDropdownRows(Value As Int)`
- `getMaxDropdownRowsAs Int`
- `setItems(KeyValues As Map)`
- `getItemsAs List`
- `setSelectedIndex(Value As Int)`
- `getSelectedIndexAs Int`
- `getSelectedValueAs String`
- `AddItem(Value As String, Text As String)`
- `LoadMonths`
- `LoadCountries`
- `getItemValuesAs List`
- `getSelectedKeyAs String`
- `getValueAs String`
- `setValue(Value As String)`
- `Clear`
- `Open`
- `Close`
- `Toggle`
- `getIsOpenAs Boolean`
- `RemoveViewFromParent`
- `Release`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `GetComputedHeightAs Int`


---

## B4XDaisyStack

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-10` | Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem) |
| `Height` | String | `h-10` | Tailwind size token or CSS size (eg h-12, 80px, 4em, 5rem) |
| `Padding` | String | `` | Tailwind/spacing padding utilities (eg p-2, px-3, 2) |
| `Margin` | String | `` | Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1) |
| `Direction` | String | `bottom` | Daisy stack direction. |
| `StepPrimary` | Int | `7` | Primary offset in dip used for the deepest layer. |
| `StepSecondary` | Int | `3` | Secondary offset in dip used for the middle layer. |
| `AutoFillLayers` | Boolean | `True` | Resize each child to fill its layer frame. |
| `LayoutAnimationMs` | Int | `0` | Animation duration in milliseconds when relayout runs. |
| `RoundedBox` | Boolean | `False` | Apply 16px rounded corners to the base view. |
| `StrictDaisyParity` | Boolean | `True` | Use DaisyUI stack geometry and per-layer opacity (1.0, 0.9, 0.7). |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `AddLayer(ChildView As B4XView) As Int`
- `AddLayerWithTag(ChildView As B4XView, Tag As Object) As Int`
- `SetLayers(Views As List)`
- `RemoveLayerAt(Index As Int) As Boolean`
- `Clear`
- `getLayer(Index As Int) As B4XView`
- `getLayerCountAs Int`
- `setLayerTag(Index As Int, Tag As Object)`
- `getLayerTag(Index As Int) As Object`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setSize(Width As Int, Height As Int)`
- `setStepPrimary(Value As Object)`
- `getStepPrimaryAs Float`
- `setStepSecondary(Value As Object)`
- `getStepSecondaryAs Float`
- `setAutoFillLayers(Value As Boolean)`
- `getAutoFillLayersAs Boolean`
- `setLayoutAnimationMs(Value As Int)`
- `getLayoutAnimationMsAs Int`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setStrictDaisyParity(Value As Boolean)`
- `getStrictDaisyParityAs Boolean`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `AddColorLayer(BackColor As Int, Text As String, TextColor As Int, CornerRadius As Float) As B4XView`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyStat

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Orientation` | String | `horizontal` | Layout orientation. |
| `Shadow` | String | `none` | Elevation level. |
| `Rounded` | String | `box` | Border radius token (none=0, selector=--radius-selector, field=--radius-field, box=--radius-box, full=9999dip). |
| `BorderWidth` | String | `token` | Border width in dip, or "token" to use --border theme value, or "0" for none. |
| `BorderColor` | String | `base-300` | Border color token. |
| `Width` | String | `w-content` | Card width: empty = use AddToParent width, "w-content" = shrink-wrap to content, or a number (dip). |
| `Height` | String | `` | Card height: empty or "h-content" = driven by tallest item, or a number (dip) to force a fixed height. |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `getContentWidthAs Int`
- `getContentHeightAs Int`
- `UpdateTheme`
- `AddItem(Item As B4XDaisyStatItem)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setOrientation(Value As String)`
- `getOrientationAs String`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setBorderWidth(Value As String)`
- `getBorderWidthAs String`
- `setBorderColor(Value As String)`
- `getBorderColorAs String`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)`
- `setLeft(Value As Int)`
- `setTop(Value As Int)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyStatItem

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Title` | String | `` | The stat title. |
| `Value` | String | `` | The stat value. |
| `Description` | String | `` | The stat description. |
| `ValueColor` | String | `none` | Text color variant for the value label. |
| `DescriptionColor` | String | `none` | Text color variant for the description label. |
| `Variant` | String | `none` | Background color variant. |
| `FigureType` | String | `none` | Type of figure to display in the figure slot. |
| `FigureSource` | String | `` | SVG asset filename, image path, or initial radial value. |
| `FigureSize` | Int | `48` | Size of the figure in dip. |
| `FigureColor` | String | `none` | Color variant for the figure. |
| `Padding` | String | `px-6 py-4` | Tailwind padding utilities (e.g. px-6 py-4). |
| `GapX` | Int | `16` | Gap between text column and figure (in dip). |
| `CenterItems` | Boolean | `False` | Center align all items. |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `getContentWidthAs Int`
- `getContentHeightAs Int`
- `UpdateTheme`
- `EstimatePreferredWidthAs Float`
- `EstimatePreferredHeightAs Float`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setOrientation(Value As String)`
- `setShowSeparator(Value As Boolean)`
- `setCenterItems(Value As Boolean)`
- `getCenterItemsAs Boolean`
- `getFigureAs B4XView`
- `setFigure(v As B4XView)`
- `getActionsAs B4XView`
- `AddAction(btn As B4XDaisyButton)`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setValue(Value As String)`
- `getValueAs String`
- `setDescription(Value As String)`
- `getDescriptionAs String`
- `setValueColor(Value As String)`
- `getValueColorAs String`
- `setDescriptionColor(Value As String)`
- `getDescriptionColorAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setFigureType(Value As String)`
- `getFigureTypeAs String`
- `setFigureSource(Value As String)`
- `getFigureSourceAs String`
- `setFigureSize(Value As Int)`
- `getFigureSizeAs Int`
- `setFigureColor(Value As String)`
- `getFigureColorAs String`
- `setFigureValue(v As Int)`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setGapX(Value As Int)`
- `getGapXAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `LogLabelWidths(Tag As String)`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyStatus

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `` | Optional width token (Tailwind/CSS). Leave empty to use Size token. |
| `Height` | String | `` | Optional height token (Tailwind/CSS). Leave empty to use Size token. |
| `Size` | String | `md` | Daisy status size token. |
| `Variant` | String | `none` | Daisy semantic status color. |
| `Animation` | String | `none` | Built-in status animation. |
| `Padding` | String | `` | Optional padding utility token(s). |
| `Margin` | String | `1` | Optional margin utility token(s). |
| `Visible` | Boolean | `True` | Show or hide status view. |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `CenterInParent(Parent As B4XView)`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setSize(Value As String)`
- `getSizeAs String`
- `setAnimation(Value As String)`
- `getAnimationAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setTextColorVariant(VariantName As String)`
- `setDepth(Value As Float)`
- `getDepthAs Float`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`


---

## B4XDaisySteps

### Events
- `StepClick (Index As Int, Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables or disables the component. |
| `Visible` | Boolean | `True` | Shows or hides the component. |
| `Orientation` | String | `horizontal` | Layout direction: horizontal (default) or vertical. |
| `ActiveColor` | String | `primary` | Color variant for active/completed steps. |
| `ActiveStep` | Int | `-1` | Index of the active step (0-based). Steps up to and including this index use ActiveColor. -1 means no active highlighting. |
| `Padding` | String | `` | Tailwind padding tokens (e.g., p-4, px-2 py-1). |
| `Margin` | String | `` | Tailwind margin tokens (e.g., m-4, mx-auto, mb-2). |
| `CircleSize` | Int | `32` | Diameter of the step circle in dip. |
| `Scrollable` | Boolean | `False` | Enables scrolling when steps overflow the container. Horizontal for horizontal orientation, vertical for vertical orientation. |
| `Width` | String | `w-full` | Tailwind size token or CSS size used as preferred width. |
| `Height` | String | `h-auto` | Tailwind size token, CSS size, or h-auto. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `getViewAs B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddStep(Text As String, Variant As String)`
- `AddStepWithContent(Text As String, Variant As String, Content As String)`
- `AddStepWithIcon(Text As String, Variant As String, Icon As String)`
- `AddStepWithSvgIcon(Text As String, Variant As String, SvgFileName As String)`
- `SetSteps(Steps As List)`
- `ClearSteps`
- `getStepCountAs Int`
- `setOrientation(Value As String)`
- `getOrientationAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setActiveColor(Value As String)`
- `getActiveColorAs String`
- `setActiveStep(Value As Int)`
- `getActiveStepAs Int`
- `setPadding(Value As String)`
- `getPaddingAs String`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setScrollable(Value As Boolean)`
- `getScrollableAs Boolean`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `UpdateTheme`
- `GetComputedHeightAs Int`


---

## B4XDaisySvgIcon

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `SvgAsset` | String | `` | SVG file name from assets or full local path |
| `Width` | String | `w-6` | Tailwind size token or CSS size (eg w-6, 24px, 2rem) |
| `Height` | String | `h-6` | Tailwind size token or CSS size (eg h-6, 24px, 2rem) |
| `Color` | Color | `0xFF3B82F6` | Icon color used when Preserve Colors is False |
| `PreserveColors` | Boolean | `False` | Keep original SVG colors instead of applying tint color |
| `Padding` | Int | `0` | Inner padding in dip around the icon |
| `BorderWidth` | Int | `0` | Border width in dip |
| `BorderColor` | Color | `0x00000000` | Border color (transparent by default) |
| `BackgroundColor` | Color | `0x00000000` | Background fill color (transparent by default) |
| `RoundedBox` | Boolean | `False` | Applies rounded-box corner radius |
| `Variant` | String | `none` | DaisyUI semantic color variant (sets icon color). |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `ResizeToParent(ParentView As B4XView)`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `getViewAs B4XView`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `GetContentViewAs B4XView`
- `setSvgAsset(Path As String)`
- `setSvgFile(Dir As String, FileName As String)`
- `getSvgAssetAs String`
- `setSvgContent(Content As String)`
- `getSvgContentAs String`
- `setColor(Value As Int)`
- `getColorAs Int`
- `setColorVariant(VariantName As String)`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setPreserveOriginalColors(Value As Boolean)`
- `getPreserveOriginalColorsAs Boolean`
- `setPreserveColors(Value As Boolean)`
- `getPreserveColorsAs Boolean`
- `getLastRendererAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setPadding(Value As Float)`
- `getPaddingAs Float`
- `setBorderWidth(Value As Float)`
- `getBorderWidthAs Float`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderColorVariant(VariantName As String)`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setBackgroundColorVariant(VariantName As String)`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setSize(Value As Object)`
- `GetComputedHeightAs Int`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `RemoveViewFromParent`


---

## B4XDaisySwap

### Events
- `Click (State As String, Checked As Boolean)`
- `Changed (State As String, Checked As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `SwapType` | String | `text` | Slot content type. For svg/avatar, On/Off/Indeterminate text values are file paths. |
| `SwapStyle` | String | `none` | Visual effect style |
| `State` | String | `off` | Swap state |
| `OnText` | String | `ON` | Text shown in swap-on slot |
| `OffText` | String | `OFF` | Text shown in swap-off slot |
| `IndeterminateText` | String | `` | Text shown in indeterminate slot |
| `OnColor` | Color | `0x00000000` | On slot text/icon color (0 = theme base-content) |
| `OffColor` | Color | `0x00000000` | Off slot text/icon color (0 = theme base-content) |
| `IndeterminateColor` | Color | `0x00000000` | Indeterminate slot text/icon color (0 = theme base-content) |
| `TextSize` | String | `text-sm` | Tailwind text size token (eg text-xs, text-sm, text-lg, text-9xl, text-sm/6) |
| `Width` | String | `w-12` | Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem) |
| `Height` | String | `h-12` | Tailwind size token or CSS size (eg h-12, 80px, 4em, 5rem) |
| `AnimationMs` | Int | `200` | Visibility animation in milliseconds |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `Toggle`
- `SetOnView(v As B4XView)`
- `SetOffView(v As B4XView)`
- `SetIndeterminateView(v As B4XView)`
- `getOnPanelAs B4XView`
- `getOffPanelAs B4XView`
- `getIndeterminatePanelAs B4XView`
- `setOnText(Value As String)`
- `getOnTextAs String`
- `setOffText(Value As String)`
- `getOffTextAs String`
- `setIndeterminateText(Value As String)`
- `getIndeterminateTextAs String`
- `setState(Value As String)`
- `getStateAs String`
- `setChecked(Value As Boolean)`
- `getCheckedAs Boolean`
- `setSwapStyle(Value As String)`
- `getSwapStyleAs String`
- `setSwapType(Value As String)`
- `getSwapTypeAs String`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `getTextLineHeightDipAs Float`
- `setAnimationMs(Value As Int)`
- `getAnimationMsAs Int`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setOnColor(Value As Object)`
- `getOnColorAs Int`
- `setOnColorVariant(VariantName As String)`
- `setOnTextColorVariant(VariantName As String)`
- `setOffColor(Value As Object)`
- `getOffColorAs Int`
- `setOffColorVariant(VariantName As String)`
- `setOffTextColorVariant(VariantName As String)`
- `setIndeterminateColor(Value As Object)`
- `getIndeterminateColorAs Int`
- `setIndeterminateColorVariant(VariantName As String)`
- `setIndeterminateTextColorVariant(VariantName As String)`
- `setTag(Value As Object)`
- `getTagAs Object`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisySweetAlert

### Events
- `Result (Result As B4XDaisySweetAlertResult)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Title` | String | `Are you sure?` | The popup title. |
| `Text` | String | `You will not be able to revert this!` | The popup body text. |
| `Icon` | String | `none` | Built-in icon type (loading shows a spinner). |
| `IconColor` | Color | `0xFF545454` | Tint color for the SVG icon. |
| `IconSize` | Int | `80` | Icon width/height in dip. |
| `ShowConfirmButton` | Boolean | `True` | Show the confirm button. |
| `ConfirmButtonText` | String | `OK` | Text for the confirm button. |
| `ShowDenyButton` | Boolean | `False` | Show the deny button. |
| `DenyButtonText` | String | `No` | Text for the deny button. |
| `ShowCancelButton` | Boolean | `False` | Show the cancel button. |
| `CancelButtonText` | String | `Cancel` | Text for the cancel button. |
| `ShowCloseButton` | Boolean | `False` | Show a close button top-right. |
| `AllowOutsideClick` | Boolean | `True` | Close when clicking the backdrop. |
| `ReverseButtons` | Boolean | `False` | Swap confirm/deny/cancel order. |
| `Footer` | String | `` | Optional footer text shown below actions. |
| `BackgroundColor` | Color | `0xFFFFFFFF` | Modal background color. |
| `TextColor` | Color | `0xFF545454` | Title and body text color. |
| `Width` | Int | `360` | Maximum modal width in dip. |
| `TimerMs` | Int | `0` | Auto close timer in milliseconds. 0 disables. |

### Public Methods
- `Initialize(Callback As Object, Parent As B4XView, EventName As String)`
- `Show`
- `ShowAsyncAs ResumableSub`
- `showLoading`
- `hideLoading`
- `Update(Config As Map)`
- `Close`
- `CloseWithReason(Reason As String)`
- `GetDismissReasonAs Map`
- `btnConfirm_Click(Tag As Object)`
- `btnDeny_Click(Tag As Object)`
- `btnCancel_Click(Tag As Object)`
- `btnClose_Click(Tag As Object)`
- `setParent(Parent As B4XView)`
- `getParentAs B4XView`
- `setTitle(Value As String)`
- `getTitleAs String`
- `setText(Value As String)`
- `getTextAs String`
- `setIcon(Value As String)`
- `getIconAs String`
- `setIconColor(Value As Int)`
- `getIconColorAs Int`
- `setIconSize(Value As Int)`
- `getIconSizeAs Int`
- `setShowConfirmButton(Value As Boolean)`
- `getShowConfirmButtonAs Boolean`
- `setConfirmButtonText(Value As String)`
- `getConfirmButtonTextAs String`
- `setShowDenyButton(Value As Boolean)`
- `getShowDenyButtonAs Boolean`
- `setDenyButtonText(Value As String)`
- `getDenyButtonTextAs String`
- `setShowCancelButton(Value As Boolean)`
- `getShowCancelButtonAs Boolean`
- `setCancelButtonText(Value As String)`
- `getCancelButtonTextAs String`
- `setShowCloseButton(Value As Boolean)`
- `getShowCloseButtonAs Boolean`
- `setAllowOutsideClick(Value As Boolean)`
- `getAllowOutsideClickAs Boolean`
- `setReverseButtons(Value As Boolean)`
- `getReverseButtonsAs Boolean`
- `setFooter(Value As String)`
- `getFooterAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setWidth(Value As Int)`
- `getWidthAs Int`
- `setTimerMs(Value As Int)`
- `getTimerMsAs Int`


---

## B4XDaisySweetAlertIcon

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `IconType` | String | `success` | The icon to animate. |
| `AnimationDuration` | Int | `500` | Duration of the drawing animation in milliseconds. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `Play`
- `Stop`
- `setAnimationDuration(Value As Int)`
- `getAnimationDurationAs Int`
- `setIconType(Icon As String)`
- `getIconTypeAs String`


---

## B4XDaisyTab

### Events
- `TabClick (Index As Int)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Enabled` | Boolean | `True` | Enables or disables the component. |
| `Visible` | Boolean | `True` | Shows or hides the component. |
| `Style` | String | `default` | Tab style variant: default, border (bottom line), lift (raised with corners), box (enclosed container). |
| `Size` | String | `md` | Tab size: xs, sm, md (default), lg, xl. |
| `Placement` | String | `top` | Tab bar placement relative to content: top (default) or bottom. |
| `ActiveIndex` | Int | `0` | Index of the active tab (0-based). |
| `Scrollable` | Boolean | `False` | Enables horizontal scrolling when tabs overflow the container width. |
| `Alignment` | String | `center` | Horizontal alignment of tabs within the tab bar. |
| `ActiveColor` | String | `primary` | Accent color applied to the active tab (background + text/border). |
| `Width` | String | `w-full` | Tailwind width token or CSS size. |
| `Height` | String | `h-auto` | Tailwind height token or CSS size. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddTab(Text As String)`
- `AddTabWithIcon(Text As String, IconText As String)`
- `SetTabDisabled(Index As Int, Disabled As Boolean)`
- `SetTabVariant(Index As Int, Variant As String)`
- `SetTabTitle(Index As Int, Text As String)`
- `SetTabTitleTextColor(Index As Int, Color As Int)`
- `SetTabTitleColor(Index As Int, Color As Int)`
- `SetTabContent(Index As Int, Content As B4XView)`
- `GetTabContent(Index As Int) As B4XView`
- `SetTabContentText(Index As Int, Text As String)`
- `SetTabs(TabsList As List)`
- `ClearTabs`
- `getTabCountAs Int`
- `GetComputedHeightAs Int`
- `setActiveIndex(Value As Int)`
- `getActiveIndexAs Int`
- `setStyle(Value As String)`
- `getStyleAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setPlacement(Value As String)`
- `getPlacementAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setScrollable(Value As Boolean)`
- `getScrollableAs Boolean`
- `setAlignment(Value As String)`
- `getAlignmentAs String`
- `setActiveColor(Value As String)`
- `getActiveColorAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `UpdateTheme`
- `RemoveViewFromParent`
- `ResizeTab`


---

## B4XDaisyText

### Events
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-full` | Tailwind size token or CSS size (eg w-12, 80px, 4em, 5rem) |
| `Height` | String | `h-auto` | Tailwind size token or CSS size (eg h-6, 24px, 1.5rem). |
| `Text` | String | `` | Label text. |
| `TextColor` | Color | `0xFF000000` | Text color. |
| `BackgroundColor` | Color | `0x00000000` | Background color. |
| `TextSize` | String | `text-sm` | Number in dip or Tailwind token (eg 12, text-sm, text-lg). |
| `FontBold` | Boolean | `False` | Use bold font. |
| `SingleLine` | Boolean | `False` | Single line text. |
| `Ellipsize` | String | `none` | Truncate with ellipsis when text overflows. Requires Single Line for start/middle/end. |
| `HAlign` | String | `LEFT` | Text horizontal alignment. |
| `VAlign` | String | `CENTER` | Text vertical alignment. |
| `Padding` | Int | `0` | Inner padding in dip. |
| `Margin` | String | `` | Tailwind/spacing margin utilities (eg m-2, mx-1.5, 1) |
| `RoundedBox` | Boolean | `False` | Use rounded-box radius. |
| `BorderWidth` | Int | `0` | Border width in dip. |
| `BorderColor` | Color | `0x00000000` | Border color. |
| `Visible` | Boolean | `True` | Visible state. |
| `Enabled` | Boolean | `True` | Enabled state. |
| `IsSkeleton` | Boolean | `False` | Show skeleton loading state. |
| `Variant` | String | `none` | DaisyUI semantic color variant. |
| `AutoResize` | Boolean | `True` | Automatically resize height to fit text content using CSS line-height calculation (line_height_px * num_lines). |
| `Link` | Boolean | `False` | Render as a clickable link (applies underline styling). |
| `Underline` | Boolean | `False` | Show underline when Link is enabled. |
| `Url` | String | `` | URL to open when the link is clicked (requires Link = true). |
| `Clickable` | Boolean | `True` | When False, touch events pass through to parent (useful inside clickable list rows) |
| `UpperCase` | Boolean | `False` | Transform text to uppercase. |
| `Italic` | Boolean | `False` | Render text in italic style. |
| `Strikethrough` | Boolean | `False` | Draw a horizontal line through the text. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `UpdateTheme`
- `RefreshText`
- `MeasureTextWidthAs Float`
- `MeasureTextHeightAs Float`
- `GetPreferredHeight(MaxContentWidth As Int) As Int`
- `GetComputedHeightAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `setText(Value As String)`
- `getTextAs String`
- `setWidth(Value As Object)`
- `getWidthAs Float`
- `setHeight(Value As Object)`
- `getHeightAs Float`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColorVariant(VariantName As String)`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setTextSize(Value As Object)`
- `getTextSizeAs Object`
- `setFontBold(Value As Boolean)`
- `getFontBoldAs Boolean`
- `setSingleLine(Value As Boolean)`
- `getSingleLineAs Boolean`
- `setEllipsize(Value As String)`
- `getEllipsizeAs String`
- `setHAlign(Value As String)`
- `getHAlignAs String`
- `setVAlign(Value As String)`
- `getVAlignAs String`
- `setPadding(Value As Float)`
- `getPaddingAs Float`
- `setMargin(Value As String)`
- `getMarginAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setBorderWidth(Value As Float)`
- `getBorderWidthAs Float`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setLink(Value As Boolean)`
- `getLinkAs Boolean`
- `setUnderline(Value As Boolean)`
- `getUnderlineAs Boolean`
- `setUrl(Value As String)`
- `getUrlAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `StartAnimation`
- `StopAnimation`
- `setIsSkeleton(Value As Boolean)`
- `getIsSkeletonAs Boolean`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `setLeft(Value As Int)`
- `getLeftAs Int`
- `setTop(Value As Int)`
- `getTopAs Int`
- `setColor(BackgroundColor As Int)`
- `getColorAs Int`
- `SetTextAlignment(Vertical As String, Horizontal As String)`
- `SetLayoutAnimated(Duration As Int, LeftPos As Int, TopPos As Int, Width As Int, Height As Int)`
- `SetColorAndBorder(CBackgroundColor As Int, CBorderW As Float, CBorderC As Int, CornerRadius As Float)`
- `setClickable(Value As Boolean)`
- `getClickableAs Boolean`
- `setUpperCase(Value As Boolean)`
- `getUpperCaseAs Boolean`
- `setItalic(Value As Boolean)`
- `getItalicAs Boolean`
- `setStrikethrough(Value As Boolean)`
- `getStrikethroughAs Boolean`
- `RemoveViewFromParent`


---

## B4XDaisyTextRotate

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Items` | Int | `1` | The number of items to rotate. |
| `Duration` | String | `3s` | The duration of the rotation (e.g., 3s). |
| `Variant` | String | `none` | DaisyUI semantic color variant. |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `UpdateTheme`
- `SetItems(ItemList As List)`
- `AddItem(dt As B4XDaisyText)`
- `ClearItems`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setDuration(Value As String)`
- `getDurationAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getViewAs B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyTimeline

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Orientation` | String | `vertical` | Timeline orientation. |
| `Compact` | Boolean | `False` | If True, all items are pushed to one side. |
| `SnapIcon` | Boolean | `False` | If True, snaps the icon to start instead of middle. |
| `LineColor` | String | `base-300` | Color of the connecting lines. |
| `MarkerSize` | Int | `20` | Size of the middle marker. |
| `MarkerColor` | String | `neutral` | Color of the middle marker. |
| `TextSize` | String | `text-xs` | Text size token applied to both start and end content (matches Daisy default for boxes). |
| `BoxShadow` | String | `sm` | Elevation token for boxed items (shadow-sm by default). |
| `Visible` | Boolean | `True` | Visible state. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `AddItem(Id As String, StartText As String, EndText As String) As String`
- `AddItemBox(Id As String, StartText As String, EndText As String, BoxOnStart As Boolean, BoxOnEnd As Boolean) As String`
- `UpdateItem(id As String, StartText As String, MiddleIcon As Object, IconColor As Int, EndText As String, IsBox As Boolean, BoxOnStart As Boolean, BoxOnEnd As Boolean, Variant As String, DashedBorder As Boolean)`
- `SetItemStartText(id As String, StartText As String)`
- `SetItemMiddleIcon(id As String, MiddleIcon As Object)`
- `SetItemIconColor(id As String, IconColor As Int)`
- `SetItemEndText(id As String, EndText As String)`
- `SetItemVariant(id As String, Variant As String)`
- `SetItemDashedBorder(id As String, Dashed As Boolean)`
- `SetItemDone(id As String, bDone As Boolean)`
- `Clear`
- `getSizeAs Int`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `setOrientation(Value As String)`
- `getOrientationAs String`
- `setCompact(Value As Boolean)`
- `getCompactAs Boolean`
- `setSnapIcon(Value As Boolean)`
- `getSnapIconAs Boolean`
- `setLineColor(Value As String)`
- `getLineColorAs String`
- `setMarkerSize(Value As Int)`
- `getMarkerSizeAs Int`
- `setMarkerColor(Value As String)`
- `getMarkerColorAs String`
- `setVisible(Value As Boolean)`
- `setTextSize(Value As String)`
- `getTextSizeAs String`
- `setBoxShadow(Value As String)`
- `getBoxShadowAs String`
- `getVisibleAs Boolean`
- `setTag(Value As Object)`
- `getTagAs Object`
- `getViewAs B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyToast

### Events
- `NotificationClosed (View As B4XView)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `HorizontalAlignment` | String | `end` |  |
| `VerticalAlignment` | String | `bottom` |  |
| `ShowProgress` | Boolean | `True` | Show a progress bar for timed notifications. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateViewAs B4XView`
- `ApplyDesignerProps(Props As Map)`
- `SetPosition(Horizontal As String, Vertical As String)`
- `Show`
- `Hide`
- `SetRoot(Root1 As B4XView)`
- `Attach(View As B4XView)`
- `Detach(View As B4XView)`
- `Clear`
- `Success(Message As String)`
- `SuccessWithDuration(Message As String, DurationMs As Int)`
- `Info(Message As String)`
- `InfoWithDuration(Message As String, DurationMs As Int)`
- `Warning(Message As String)`
- `WarningWithDuration(Message As String, DurationMs As Int)`
- `Error(Message As String)`
- `ErrorWithDuration(Message As String, DurationMs As Int)`
- `AttachWithDuration(View As B4XView, DurationMs As Int)`
- `setShowProgress(Value As Boolean)`
- `getShowProgressAs Boolean`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyToggle

### Events
- `Checked (Checked As Boolean)`
- `Click (Tag As Object)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `GroupName` | String | `` | Toggle group name. |
| `Checked` | Boolean | `False` | Checked state. |
| `Indeterminate` | Boolean | `False` | Indeterminate state. |
| `Value` | String | `` | Value assigned to the toggle. |
| `Text` | String | `` | Label text. |
| `Variant` | String | `none` | Color variant. |
| `Size` | String | `md` | Size variant. |
| `Position` | String | `start` | Position alignment. |
| `Enabled` | Boolean | `True` | Enabled state. |
| `Visible` | Boolean | `True` | Visible state. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `CheckedBackgroundColor` | Color | `0x00FFFFFF` | Override checked background color. |
| `CheckedBorderColor` | Color | `0x00FFFFFF` | Override checked border color. |
| `CheckedTextColor` | Color | `0x00FFFFFF` | Override checked text color. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `setChecked(Value As Boolean)`
- `getCheckedAs Boolean`
- `setIndeterminate(Value As Boolean)`
- `getIndeterminateAs Boolean`
- `setText(Value As String)`
- `getTextAs String`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setSize(Value As String)`
- `getSizeAs String`
- `setPosition(Value As String)`
- `getPositionAs String`
- `setEnabled(Value As Boolean)`
- `getEnabledAs Boolean`
- `setVisible(Value As Boolean)`
- `getVisibleAs Boolean`
- `setValue(Value As String)`
- `getValueAs String`
- `setGroupName(Value As String)`
- `getGroupNameAs String`
- `getRoleAs String`
- `setRequired(Value As Boolean)`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `getIsValidAs Boolean`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `ValidateAs Boolean`
- `setBackgroundColor(Color As Int)`
- `getBackgroundColorAs Int`
- `setBorderColor(Color As Int)`
- `getBorderColorAs Int`
- `setTextColor(Color As Int)`
- `getTextColorAs Int`
- `setCheckedBackgroundColor(Color As Int)`
- `getCheckedBackgroundColorAs Int`
- `setCheckedBorderColor(Color As Int)`
- `getCheckedBorderColorAs Int`
- `setCheckedTextColor(Color As Int)`
- `getCheckedTextColorAs Int`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setTag(Value As Object)`
- `getTagAs Object`
- `UpdateTheme`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `getComputedHeightAs Int`
- `RequestFocus`
- `setFocus(Value As Boolean)`
- `ReceiveFocus`
- `Blur`
- `RemoveViewFromParent`
- `Release`


---

## B4XDaisyToggleGroup

### Events
- `ItemChanged (id As String, text As String, checked As Boolean)`
- `Changed (SelectedIds As List)`
- `FocusChanged (HasFocus As Boolean)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Legend` | String | `Select options` | Fieldset legend text |
| `LegendSize` | String | `theme` | Legend text size token |
| `LegendBold` | Boolean | `False` | Render the fieldset legend caption in bold |
| `Variant` | String | `none` | Optional accent variant for border tint |
| `BorderStyle` | String | `outlined` | Border visual style |
| `Padding` | Int | `16` | Inner content padding in dip |
| `AutoHeight` | Boolean | `True` | Automatically grow to fit added content |
| `Rounded` | String | `theme` | Corner radius mode |
| `RoundedBox` | Boolean | `True` | Use box radius for container |
| `Shadow` | String | `none` | Elevation shadow level |
| `BackgroundColor` | Color | `0x00000000` | Background color (0 = default bg-base-200) |
| `TextColor` | Color | `0x00000000` | Legend text color (0 = use theme token) |
| `BorderColor` | Color | `0x00000000` | Border color override (0 = default border-base-300) |
| `BorderSize` | Int | `1` | Border width in dip |
| `InputBorder` | Boolean | `False` | When True, apply B4XDaisyInput border color and width to the fieldset |
| `ItemsSpec` | String | `` | Pipe-and-colon separated items list |
| `Direction` | String | `vertical` | Items layout direction |
| `Alignment` | String | `start` | Toggle element position alignment |
| `ToggleColor` | String | `neutral` | Default toggle color variant |
| `ToggleSize` | String | `md` | Toggle size token |
| `Gap` | Int | `8` | Gap between elements in dip |
| `RowGap` | Int | `8` | Row gap for wrapped flow mode in dip |
| `Required` | Boolean | `False` | Whether at least one option must be selected. |
| `HintText` | String | `` | Helper text displayed below the group. |
| `ErrorText` | String | `` | Error text displayed below the group when validation fails. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `AddToParentAt(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `getViewAs B4XView`
- `ViewAs B4XView`
- `IsReadyAs Boolean`
- `AddItem(Id As String, Text As String)`
- `RemoveItem(Id As String)`
- `ClearItems`
- `setItems(Items As Map)`
- `getItemsAs Map`
- `setItemsSpec(Value As String)`
- `getItemsSpecAs String`
- `setChecked(CheckedIds As String)`
- `getCheckedAs String`
- `SetItemChecked(Id As String, Checked As Boolean)`
- `CheckItem(Id As String)`
- `UncheckItem(Id As String)`
- `IsItemChecked(Id As String) As Boolean`
- `setLegend(Value As String)`
- `getLegendAs String`
- `setLegendSize(Value As String)`
- `getLegendSizeAs String`
- `setLegendBold(Value As Boolean)`
- `getLegendBoldAs Boolean`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setDirection(Value As String)`
- `getDirectionAs String`
- `setAlignment(Value As String)`
- `getAlignmentAs String`
- `setToggleColor(Value As String)`
- `getToggleColorAs String`
- `setToggleSize(Value As String)`
- `getToggleSizeAs String`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`
- `setPadding(Value As Int)`
- `getPaddingAs Int`
- `setGap(Value As Int)`
- `getGapAs Int`
- `setRowGap(Value As Int)`
- `getRowGapAs Int`
- `setTag(Value As Object)`
- `getTagAs Object`
- `setRequired(Value As Boolean)`
- `setHintText(Value As String)`
- `getHintTextAs String`
- `getRequiredAs Boolean`
- `setErrorText(Value As String)`
- `getErrorTextAs String`
- `ShowError(ErrorMessage As String)`
- `ClearError`
- `getIsValidAs Boolean`
- `ValidateAs Boolean`
- `ReceiveFocus`
- `Blur`
- `setBorderStyle(Value As String)`
- `getBorderStyleAs String`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `isRoundedAs Boolean`
- `setRoundedBox(Value As Boolean)`
- `isRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setBackgroundColor(Value As Int)`
- `getBackgroundColorAs Int`
- `setTextColor(Value As Int)`
- `getTextColorAs Int`
- `setBorderColor(Value As Int)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setInputBorder(Value As Boolean)`
- `getInputBorderAs Boolean`
- `GetComputedHeightAs Int`
- `Release`
- `RemoveViewFromParent`


---

## B4XDaisyTooltip

### Events
- `Shown`
- `Hidden`
- `Click (Tag As Object)`

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Message` | String | `Tooltip message` | Tooltip text content. |
| `Position` | String | `top` | Anchor position relative to target. |
| `Variant` | String | `neutral` | Daisy variant for tooltip background. |
| `ShowArrow` | Boolean | `True` | Show the small tail/arrow pointing to target. |
| `ClickToClose` | Boolean | `True` | Hide tooltip when clicked. |
| `TextWrapped` | Boolean | `True` | Enable multi-line text wrapping. |
| `Visible` | Boolean | `True` | Initial visibility. |
| `AutoResize` | Boolean | `True` | Automatically resize tooltip to fit message content. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Props As Map) As B4XView`
- `AttachToTarget(Target As B4XView)`
- `DetachTarget`
- `Show`
- `ShowAnimated(Duration As Int)`
- `Hide`
- `getVisibleAs Boolean`
- `setAutoResize(Value As Boolean)`
- `getAutoResizeAs Boolean`
- `setVisible(b As Boolean)`
- `setVariant(Value As String)`
- `getVariantAs String`
- `setMessage(Value As String)`
- `getMessageAs String`
- `setPosition(Value As String)`
- `getPositionAs String`
- `SetCustomContent(View As B4XView)`
- `GetComputedHeightAs Int`
- `getViewAs B4XView`
- `GetActualHeightAs Int`
- `GetActualWidthAs Int`
- `RemoveViewFromParent`


---

## B4XDaisyVariants

### Events
*(None)*

### Designer Properties
*(None)*

### Public Methods
- `GetCheckedRadio(Parent As B4XView, GroupName As String) As B4XDaisyRadio`
- `GetCheckedValue(Parent As B4XView, GroupName As String) As String`
- `SetCheckedByValue(Parent As B4XView, GroupName As String, Value As String) As Boolean`
- `SetTextOrCSBuilderToLabel(xlbl As B4XView, Text As Object)`
- `SetActiveTheme(ThemeName As String)`
- `GetActiveThemeAs String`
- `HasTheme(ThemeName As String) As Boolean`
- `RegisterTheme(ThemeName As String, Tokens As Map)`
- `ResolveAssetImage(FileName As String, DefaultImage As String) As String`
- `ResolveAssetSVG(FileName As String, DefaultText As String) As String`
- `GetThemeTokens(ThemeName As String) As Map`
- `GetActiveTokensAs Map`
- `SetOverflowHidden(v As B4XView)`
- `SetStyleVariable(v As B4XView, Name As String, Value As Object)`
- `IsClass(Obj As Object, ClassName As String) As Boolean`
- `GetTokenColor(Token As String, DefaultColor As Int) As Int`
- `ResolveThemeColorTokenName(Name As String) As String`
- `NormalizeOrientation(Value As String) As String`
- `NormalizeHorizontalPlacement(Value As String) As String`
- `NormalizeVerticalPlacement(Value As String) As String`
- `NormalizeAlertVariant(Value As String) As String`
- `NormalizeSize(Value As String) As String`
- `NormalizeBadgeStyle(Value As String) As String`
- `NormalizeStyle(Value As String) As String`
- `NormalizeSelectionMode(Value As String) As String`
- `NormalizeFieldsetBorderStyle(Value As String) As String`
- `NormalizeLegendSize(Value As String) As String`
- `NormalizeSizeSpec(Value As Object, DefaultValue As String) As String`
- `GetPropObject(Props As Map, Key As String, DefaultValue As Object) As Object`
- `ResolveSizeSpec(Value As String, ParentSize As Int, Fallback As Int) As Int`
- `NormalizeAvatarPosition(Value As String) As String`
- `NormalizeDirection(Value As String) As String`
- `NormalizeAnimation(Value As String) As String`
- `NormalizeSingleLineText(Value As String) As String`
- `GetTokenString(Token As String, DefaultValue As String) As String`
- `GetTokenNumber(Token As String, DefaultValue As Float) As Float`
- `GetThemeColor(Token As String, DefaultColor As Int) As Int`
- `GetTokenDip(Token As String, DefaultDipValue As Float) As Float`
- `GetBorderDip(DefaultDip As Float) As Float`
- `GetRadiusBoxDip(DefaultDip As Float) As Float`
- `GetRadiusFieldDip(DefaultDip As Float) As Float`
- `GetRadiusSelectorDip(DefaultDip As Float) As Float`
- `GetVariantPaletteAs Map`
- `BuildVariantPalette(ThemeName As String) As Map`
- `VariantListAs String`
- `NormalizeVariant(Name As String) As String`
- `BuildVariantMap(BackColor As Int, TextColor As Int) As Map`
- `DefaultPaletteAs Map`
- `ResolveVariantMap(Palette As Map, VariantName As String) As Map`
- `ResolveVariantColor(Palette As Map, VariantName As String, Key As String, DefaultColor As Int) As Int`
- `ResolveBackgroundColorVariantFromPalette(Palette As Map, VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveTextColorVariantFromPalette(Palette As Map, VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveBorderColorVariantFromPalette(Palette As Map, VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveBackgroundColorVariant(VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveTextColorVariant(VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveTextColor(VariantName As String, DefaultColor As Int) As Int`
- `ResolveBorderColorVariant(VariantOrToken As String, DefaultColor As Int) As Int`
- `ResolveColorVariantFromPalette(Palette As Map, VariantOrToken As String, PaletteKey As String, DefaultColor As Int) As Int`
- `ResolveOnlineColor(VariantName As String, DefaultColor As Int) As Int`
- `ResolveOfflineColor(VariantName As String, DefaultColor As Int) As Int`
- `Blend(c1 As Int, c2 As Int, t As Double) As Int`
- `ShadowListAs String`
- `NormalizeShadow(Name As String) As String`
- `ResolveShadowElevation(Level As String) As Float`
- `ResolveShadowSpec(Level As String) As Map`
- `MaskListAs String`
- `MaskListSimpleAs String`
- `NormalizeMask(MaskName As String) As String`
- `CreateMaskPath(Size As Float, MaskName As String) As B4XPath`
- `CreateMaskPathRect(Width As Float, Height As Float, MaskName As String) As B4XPath`
- `CreateMaskPathInRect(TargetRect As B4XRect, MaskName As String) As B4XPath`
- `ResolveRoundedRadiusDip(MaskName As String, Size As Float) As Float`
- `ClipCanvasToShape(cvs As B4XCanvas, TargetRect As B4XRect, MaskName As String) As Boolean`
- `RestoreCanvasClip(cvs As B4XCanvas)`
- `DisableViewClipping(v As B4XView)`
- `EnableShapedClipping(v As B4XView, MaskName As String)`
- `DisableShapedClipping(v As B4XView)`
- `SetLineSpacing(v As B4XView, Multiple As Float, Add As Float)`
- `NormalizeDateTimeFormat(Value As String, DefaultFlatpickrFormat As String) As String`
- `FormatDateTime(FormatText As String, ValueMillis As Long) As String`
- `LooksLikeJavaDateFormat(FormatText As String) As Boolean`
- `ConvertFlatpickrToDateFormat(FormatText As String) As String`
- `GetPropString(Props As Map, Key As String, DefaultValue As String) As String`
- `GetPropFloat(Props As Map, Key As String, DefaultValue As Float) As Float`
- `GetPropInt(Props As Map, Key As String, DefaultValue As Int) As Int`
- `GetPropBool(Props As Map, Key As String, DefaultValue As Boolean) As Boolean`
- `GetPropColor(Props As Map, Key As String, DefaultValue As Int) As Int`
- `ResolveColorValue(Value As Object, DefaultColor As Int) As Int`
- `GetPropDip(Props As Map, Key As String, DefaultDip As Float) As Float`
- `GetPropSizeDip(Props As Map, Key As String, DefaultDip As Object) As Float`
- `TailwindSizeToPx(Value As Object, DefaultPx As Float) As Float`
- `TailwindSizeToDip(Value As Object, DefaultDip As Float) As Float`
- `TailwindSpacingToPx(Value As Object, DefaultPx As Float) As Float`
- `TailwindSpacingToDip(Value As Object, DefaultDip As Float) As Float`
- `TailwindGapToDip(Value As Object, DefaultDip As Float) As Float`
- `ParseGapUtilities(Utilities As String, DefaultGapDip As Float) As Map`
- `BorderStyleListAs String`
- `TailwindBorderWidthToDip(Value As Object, DefaultDip As Float) As Float`
- `TailwindBorderRadiusToDip(Value As Object, DefaultDip As Float) As Float`
- `TailwindBorderColorToColor(Value As String, DefaultColor As Int) As Int`
- `ParseBorderUtilities(Utilities As String, DefaultBorderDip As Float, DefaultBorderColor As Int, DefaultRadiusDip As Float, RtlEnabled As Boolean) As Map`
- `ApplyBorderSpecToBoxModel(Model As Map, BorderSpec As Map)`
- `TailwindTextMetrics(Value As Object, DefaultFontSize As Float, DefaultLineHeightPx As Float) As Map`
- `ResolvePxSizeSpec(SizeDip As Float) As String`
- `ResolveWidthBase(Base As B4XView, DefaultValue As Float) As Float`
- `ResolveHeightBase(Base As B4XView, DefaultValue As Float) As Float`
- `ResolveTextSizeDip(Token As String) As Float`
- `ResolveLabelSizeDip(SizeToken As String) As Float`
- `MeasureTextWidthSafe(Text As String, TextSize As Float, tf As Object, BufferDip As Float) As Int`
- `MeasureTextHeightSafe(Text As String, TextSize As Float, tf As Object, Width As Int, BufferDip As Float) As Int`
- `GetGlassSpecAs Map`
- `GetGlassSpecForSize(Size As String) As Map`
- `ApplyGlassStyle(Target As B4XView, RadiusDip As Float, Size As String)`
- `SetColorPerCornerRadius(v As B4XView, BgColor As Int, TL As Float, TR As Float, BR As Float, BL As Float)`
- `ApplyGlassTextStyle(TextTarget As B4XView)`
- `AlphaColor(ColorValue As Int, Alpha01 As Float) As Int`
- `GetJoinSpec(Orientation As String) As Map`
- `ApplyJoinToContainer(Container As B4XView, Spec As Map)`
- `ApplyJoinItemToChild(Item As B4XView, Spec As Map, Index As Int, Total As Int)`
- `ApplyJoinToAllChildren(Container As B4XView, Spec As Map)`
- `TailwindTextFontSize(Value As Object, DefaultFontSize As Float) As Float`
- `TailwindTextLineHeightDip(Value As Object, DefaultLineHeightDip As Float) As Float`
- `ExtractSpacingValue(Value As String) As String`
- `ResolveIconTypeface(icon As String) As Typeface`
- `ContainsAny(Text As String, Needles() As String) As Boolean`
- `IsRtlAs Boolean`
- `NormalizeRounded(Value As String) As String`
- `ResolveRoundedDip(Rounded As String, DefaultDip As Float) As Float`
- `SetAlpha(Color As Int, Alpha As Int) As Int`
- `ShiftColor(Color As Int, Factor As Float) As Int`
- `CloneProps(Props As Map) As Map`
- `DisableClipping(v As B4XView)`
- `DisableClippingChain(StartView As B4XView, MaxLevels As Int)`
- `DisableClippingRecursive(v As B4XView)`
- `ApplyElevation(v As B4XView, ShadowLevel As String)`
- `ParseFlexContainerTokens(TokenString As String) As Map`
- `ApplyFlexContainerTokens(fp As B4XDaisyFlexPanel, TokenString As String, DoRelayout As Boolean)`
- `ApplyParsedFlexContainerTokens(fp As B4XDaisyFlexPanel, Parsed As Map, DoRelayout As Boolean)`
- `ParseFlexItemTokens(TokenString As String) As Map`
- `ApplyFlexItemTokens(fp As B4XDaisyFlexPanel, v As B4XView, TokenString As String, DoRelayout As Boolean)`
- `ApplyParsedFlexItemTokens(fp As B4XDaisyFlexPanel, v As B4XView, Parsed As Map, DoRelayout As Boolean)`
- `ApplyThemeToPage(ThemeName As String, RootView As B4XView)`
- `ApplyDashedBorder(Target As B4XView, FillColor As Int, BorderWidth As Float, BorderColor As Int, Radius As Float, Style As String)`
- `ShiftSiblingsBelow(View As B4XView, Delta As Int, AnimDuration As Int)`
- `CreateEditTextBorder(BackgroundColor As Int, BorderWidthDip As Int, BorderColor As Int, CornerDip As Int) As ColorDrawable`
- `ApplyEditTextBorder(Target As B4XView, BackgroundColor As Int, BorderWidthDip As Int, BorderColor As Int, CornerDip As Int)`
- `ValidateRequiredControls(Parent As B4XView) As Boolean`
- `ValidateControls(Controls As List) As Boolean`


---

## B4XDaisyWindow

### Events
*(None)*

### Designer Properties
| Property Key | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `Width` | String | `w-full` | Window width (for example w-full, 320dip, 320). |
| `Height` | String | `h-220` | Window height (for example h-220, 220dip). |
| `BackgroundColor` | Color | `0x00000000` | Window background (0 uses bg-base-100). |
| `BorderColor` | Color | `0x00000000` | Window border color (0 uses border-base-300). |
| `BorderSize` | Int | `1` | Border width in dip. |
| `Rounded` | String | `theme` | Corner radius mode. |
| `RoundedBox` | Boolean | `True` | Use rounded-box radius when Rounded is theme. |
| `Shadow` | String | `none` | Elevation shadow level. |
| `ShowHeader` | Boolean | `True` | Show top header area. |
| `HeaderHeight` | Int | `24` | Header height in dip. |
| `ShowControls` | Boolean | `True` | Show top-left three control dots. |
| `ToolBarTitle` | String | `` | Text shown centred in the header toolbar pill. Leave blank to hide the toolbar. |
| `ContentPadding` | String | `p-4` | Tailwind padding token(s) for the content area (e.g. p-4, px-6 py-3, pl-4). |
| `AutoHeight` | Boolean | `True` | Automatically grow/shrink height to fit content panel children. |

### Public Methods
- `Initialize(Callback As Object, EventName As String)`
- `CreateView(Width As Int, Height As Int) As B4XView`
- `AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView`
- `ContentAs B4XView`
- `ContentWidthAs Int`
- `ContentHeightAs Int`
- `GetHeaderPanelAs B4XView`
- `AddContentView(v As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `AddHeaderView(v As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)`
- `ClearContent`
- `RefreshContent`
- `ClearHeader`
- `getTagAs Object`
- `setTag(Value As Object)`
- `ViewAs B4XView`
- `GetComputedHeightAs Int`
- `RemoveViewFromParent`
- `setWidth(Value As String)`
- `getWidthAs String`
- `setHeight(Value As String)`
- `getHeightAs String`
- `setBackgroundColor(Value As Object)`
- `getBackgroundColorAs Int`
- `setBorderColor(Value As Object)`
- `getBorderColorAs Int`
- `setBorderSize(Value As Int)`
- `getBorderSizeAs Int`
- `setRounded(Value As String)`
- `getRoundedAs String`
- `setRoundedBox(Value As Boolean)`
- `getRoundedBoxAs Boolean`
- `setShadow(Value As String)`
- `getShadowAs String`
- `setShowHeader(Value As Boolean)`
- `getShowHeaderAs Boolean`
- `setHeaderHeight(Value As Int)`
- `getHeaderHeightAs Int`
- `setShowControls(Value As Boolean)`
- `getShowControlsAs Boolean`
- `setContentPadding(Value As String)`
- `getContentPaddingAs String`
- `setToolBarTitle(Value As String)`
- `getToolBarTitleAs String`
- `setAutoHeight(Value As Boolean)`
- `getAutoHeightAs Boolean`


---

---

## B4XDaisyAlert.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#Event: Click (Tag As Object)
#Event: ActionClick (Tag As Object)

#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: full, Description: Tailwind size token or CSS size (eg full, 72, 320px, 20rem)
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: h-12, Description: Tailwind size token or CSS size (eg h-12, 80px, 5rem)
#DesignerProperty: Key: Variant, DisplayName: Variant, FieldType: String, DefaultValue: none, List: none|info|success|warning|error|primary|secondary|accent|neutral, Description: Daisy variant used for alert colors
#DesignerProperty: Key: AlertStyle, DisplayName: Style, FieldType: String, DefaultValue: solid, List: solid|soft|outline|dash, Description: Alert visual style
#DesignerProperty: Key: Direction, DisplayName: Direction, FieldType: String, DefaultValue: horizontal, List: horizontal|vertical, Description: Horizontal or vertical layout
#DesignerProperty: Key: Title, DisplayName: Title, FieldType: String, DefaultValue:, Description: Optional title text
#DesignerProperty: Key: Text, DisplayName: Text, FieldType: String, DefaultValue: 12 unread messages. Tap to see., Description: Main alert message
#DesignerProperty: Key: Description, DisplayName: Description, FieldType: String, DefaultValue:, Description: Optional secondary description
#DesignerProperty: Key: IconAsset, DisplayName: Icon Asset, FieldType: String, DefaultValue:, Description: SVG file name from assets (empty uses variant default icon)
#DesignerProperty: Key: IconSize, DisplayName: Icon Size, FieldType: String, DefaultValue: 6, Description: Tailwind size token or CSS size for icon
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: True, Description: Use rounded corners similar to Daisy rounded-box
#DesignerProperty: Key: BorderWidth, DisplayName: Border Width, FieldType: Int, DefaultValue: 1, Description: Border width in dip
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation shadow level
#DesignerProperty: Key: ActionSpacing, DisplayName: Action Spacing, FieldType: Int, DefaultValue: 6, Description: Spacing in dip between action views
#DesignerProperty: Key: AutoResize, DisplayName: Auto Resize, FieldType: Boolean, DefaultValue: True, Description: Automatically resize height to fit content.

#IgnoreWarnings:12,9
Sub Class_Globals
	' Core cross-platform helper and base references.
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
	Private mTag As Object

	' Public-facing runtime properties mirrored by getters/setters.
	Private mWidth As Float = 100%x
	Private mHeight As Float = 48dip
	Private mWidthExplicit As Boolean = False
	Private mHeightExplicit As Boolean = False
	Private mVariant As String = "none"
	Private mStyle As String = "solid"
	Private mDirection As String = "horizontal"
	Private mTitle As String = ""
	Private mText As String = "12 unread messages. Tap to see."
	Private mDescription As String = ""
	Private mRoundedBox As Boolean = True
	Private mIconVisible As Boolean = True
	Private mIconAsset As String = ""
	Private mIconSize As Float = 24dip
	Private mBorderWidth As Float = 1dip
	Private mActionSpacing As Float = 6dip
	Private mShadow As String = "none"
	Private mIconColor As Int = 0
	Private mBackgroundColor As Int = 0
	Private mTextColor As Int = 0
	Private mBorderColor As Int = 0
	Private mbAutoResize As Boolean = True

	' Active and default variant palettes used to resolve visual colors.
	Private VariantPalette As Map
	Private DefaultVariantPalette As Map
	Private UsingCustomVariantPalette As Boolean = False
	Private BorderWidthFromTheme As Boolean = True

	' Internal view tree references.
	Private Surface As B4XView
	Private TextHost As B4XView
	Private ActionsHost As B4XView
	Private lblTitle As B4XView
	Private lblText As B4XView
	Private lblDescription As B4XView
	Private IconComp As B4XDaisySvgIcon
	Private IconView As B4XView

	' Internal spacing/layout tokens in dip.
	Private ContentPaddingH As Float = 16dip
	Private ContentPaddingV As Float = 12dip
	Private ContentGap As Float = 16dip
	Private TextGap As Float = 2dip
	Private tu As StringUtils
	' Detached copy of designer/runtime properties.
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	' Store callback/event contract and load default palette/property state.
	mCallBack = Callback
	mEventName = EventName
	InitializePalette
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	' Programmatic creation path that mirrors DesignerCreateView usage.
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map = BuildRuntimeProps(Width, Height)
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Return mBase
End Sub



Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	' Build visual hierarchy once, then apply properties and first layout pass.
	mBase = Base
	If mTag = Null Then mTag = mBase.Tag
	mBase.Tag = Me
	mBase.Color = xui.Color_Transparent

	Dim pSurface As Panel
	pSurface.Initialize("surface")
	Surface = pSurface
	Surface.Color = xui.Color_Transparent
	mBase.AddView(Surface, 0, 0, mBase.Width, mBase.Height)

	Dim ic As B4XDaisySvgIcon
	ic.Initialize(Me, "icon")
	IconComp = ic
	IconView = IconComp.AddToParent(Surface, 0, 0, 24dip, 24dip)
	IconComp.SetClickable(False)

	Dim pText As Panel
	pText.Initialize("surface")
	TextHost = pText
	TextHost.Color = xui.Color_Transparent
	Surface.AddView(TextHost, 0, 0, 10dip, 10dip)

	Dim tmTitle As Map = B4XDaisyVariants.TailwindTextMetrics("text-sm", 14, 20)
	Dim tmBody As Map = B4XDaisyVariants.TailwindTextMetrics("text-sm", 14, 20)
	Dim tmDesc As Map = B4XDaisyVariants.TailwindTextMetrics("text-xs", 12, 16)
	lblTitle = CreateLabel("surface", tmTitle.GetDefault("font_size", 14), True)
	lblText = CreateLabel("surface", tmBody.GetDefault("font_size", 14), False)
	lblDescription = CreateLabel("surface", tmDesc.GetDefault("font_size", 12), False)
	TextHost.AddView(lblTitle, 0, 0, 1dip, 1dip)
	TextHost.AddView(lblText, 0, 0, 1dip, 1dip)
	TextHost.AddView(lblDescription, 0, 0, 1dip, 1dip)

	Dim pActions As Panel
	pActions.Initialize("surface")
	ActionsHost = pActions
	ActionsHost.Color = xui.Color_Transparent
	Surface.AddView(ActionsHost, 0, 0, 1dip, 1dip)

	ApplyDesignerProps(Props)
	Base_Resize(mBase.Width, mBase.Height)
End Sub

Private Sub CreateLabel(EventName As String, Size As Float, Bold As Boolean) As B4XView
	' Shared label factory used for title/body/description labels.
	Dim l As Label
	l.Initialize(EventName)
	l.SingleLine = False
	Dim v As B4XView = l
	v.Color = xui.Color_Transparent
	If Bold Then
		v.Font = xui.CreateDefaultBoldFont(Size)
	Else
		v.Font = xui.CreateDefaultFont(Size)
	End If
	Return v
End Sub

Private Sub ApplyDesignerProps(Props As Map)
	mWidthExplicit = Props.ContainsKey("Width")
	mHeightExplicit = Props.ContainsKey("Height")
	mbAutoResize = B4XDaisyVariants.GetPropBool(Props, "AutoResize", mbAutoResize)
	If mbAutoResize Then mHeightExplicit = False
	' use TailwindSizeToDip so "full" is interpreted relative to parent dimensions
	mWidth = Max(1dip, B4XDaisyVariants.TailwindSizeToDip( _
		B4XDaisyVariants.GetPropString(Props, "Width", "full"), _
		B4XDaisyVariants.ResolveWidthBase(mBase, mWidth) _
	))
	mHeight = Max(1dip, B4XDaisyVariants.TailwindSizeToDip( _
		B4XDaisyVariants.GetPropString(Props, "Height", "h-12"), _
		B4XDaisyVariants.ResolveHeightBase(mBase, mHeight) _
	))
	mVariant = B4XDaisyVariants.NormalizeAlertVariant(B4XDaisyVariants.GetPropString(Props, "Variant", mVariant))
	mStyle = B4XDaisyVariants.NormalizeStyle(B4XDaisyVariants.GetPropString(Props, "AlertStyle", mStyle))
	mDirection = B4XDaisyVariants.NormalizeDirection(B4XDaisyVariants.GetPropString(Props, "Direction", mDirection))
	mTitle = B4XDaisyVariants.GetPropString(Props, "Title", mTitle)
	mText = B4XDaisyVariants.GetPropString(Props, "Text", mText)
	mDescription = B4XDaisyVariants.GetPropString(Props, "Description", mDescription)
	mRoundedBox = B4XDaisyVariants.GetPropBool(Props, "RoundedBox", mRoundedBox)
	mIconAsset = B4XDaisyVariants.GetPropString(Props, "IconAsset", mIconAsset).Trim
	If mIconAsset.Length > 0 Then mIconVisible = True
	mIconSize = Max(12dip, B4XDaisyVariants.GetPropSizeDip(Props, "IconSize", "6"))
	If Props.ContainsKey("BorderWidth") Then
		mBorderWidth = Max(0, B4XDaisyVariants.GetPropDip(Props, "BorderWidth", 1))
		BorderWidthFromTheme = False
	End If
	mActionSpacing = Max(0, B4XDaisyVariants.GetPropDip(Props, "ActionSpacing", 6))
	mShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", mShadow))
	mBackgroundColor = B4XDaisyVariants.GetPropColor(Props, "BackgroundColor", mBackgroundColor)
	mTextColor = B4XDaisyVariants.GetPropColor(Props, "TextColor", mTextColor)
	mBorderColor = B4XDaisyVariants.GetPropColor(Props, "BorderColor", mBorderColor)
End Sub

Public Sub Base_Resize(Width As Double, Height As Double)
	' Main layout entry point used by page resize and property refresh.
	If mBase.IsInitialized = False Then Return
	If Surface.IsInitialized = False Then Return
	Dim w As Int = Max(1dip, Width)
	Dim h As Int = Max(1dip, Height)
	Dim box As Map = BuildBoxModel
	Dim host As B4XRect
	host.Initialize(0, 0, w, h)
	Dim outerRect As B4XRect = B4XDaisyBoxModel.ResolveOuterRect(host, box)
	Surface.SetLayoutAnimated(0, outerRect.Left, outerRect.Top, outerRect.Width, outerRect.Height)
	ApplyVisualStyle(box)
	Dim contentLocalRect As B4XRect = ResolveContentLocalRect(outerRect, box)
	LayoutContent(contentLocalRect)
	DoAutoResize
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	' Snapshot current properties, create view, and attach to parent bounds.
	Dim empty As B4XView
	If Parent.IsInitialized = False Then Return empty
	Dim targetW As Int = Width
	Dim targetH As Int = Height
	If targetW <= 0 Then
		If Parent.Width > 0 Then
			targetW = Parent.Width
		Else
			targetW = mWidth
		End If
	End If
	If targetH <= 0 Then
		targetH = mHeight
	End If
	
	Dim w As Int = Max(1dip, targetW)
	Dim h As Int = Max(1dip, targetH)
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, w, h)
	Dim props As Map = BuildRuntimeProps(w, h)
	Dim dummy As Label
	DesignerCreateView(b, dummy, props)
	Parent.AddView(mBase, Left, Top, w, h)
	mBase.BringToFront
	
	' If requested width/height was 0 or negative, we automatically size to fit or use defaults
	If Height <= 0 Then
		SizeToFit(w)
	End If
	
	Return mBase
End Sub

Private Sub BuildRuntimeProps(Width As Int, Height As Int) As Map
	' Preserve runtime property state when recreating through DesignerCreateView.
	Dim props As Map
	props.Initialize
	props.Put("Width", B4XDaisyVariants.ResolvePxSizeSpec(Width))
	props.Put("Height", B4XDaisyVariants.ResolvePxSizeSpec(Height))
	props.Put("Variant", mVariant)
	props.Put("AlertStyle", mStyle)
	props.Put("Direction", mDirection)
	props.Put("Title", mTitle)
	props.Put("Text", mText)
	props.Put("Description", mDescription)
	props.Put("IconAsset", mIconAsset)
	props.Put("IconSize", B4XDaisyVariants.ResolvePxSizeSpec(mIconSize))
	props.Put("RoundedBox", mRoundedBox)
	props.Put("BorderWidth", Max(0, Round(mBorderWidth / 1dip)))
	props.Put("Shadow", mShadow)
	props.Put("ActionSpacing", Max(0, Round(mActionSpacing / 1dip)))
	props.Put("BackgroundColor", mBackgroundColor)
	props.Put("TextColor", mTextColor)
	props.Put("BorderColor", mBorderColor)
	props.Put("AutoResize", mbAutoResize)
	Return props
End Sub

Public Sub View As B4XView
	' Safe accessor for root component view.
	Dim empty As B4XView
	If mBase.IsInitialized = False Then Return empty
	Return mBase
End Sub

Public Sub AddViewToContent(ChildView As B4XView, Left As Int, Top As Int, Width As Int, Height As Int)
	' Allow callers to append arbitrary action/content views at runtime.
	If ActionsHost.IsInitialized = False Then Return
	If ChildView.IsInitialized = False Then Return
	ChildView.RemoveViewFromParent
	ActionsHost.AddView(ChildView, Left, Top, Max(1dip, Width), Max(1dip, Height))
	Refresh
End Sub

Public Sub ClearActions
	' Remove all dynamic action children from actions container.
	If ActionsHost.IsInitialized = False Then Return
	ActionsHost.RemoveAllViews
	Refresh
End Sub

Public Sub GetContentPanel As B4XView
	Dim empty As B4XView
	If ActionsHost.IsInitialized = False Then Return empty
	Return ActionsHost
End Sub

Public Sub AddActionButton(Text As String, Tag As Object) As B4XView
	Dim b As Button
	b.Initialize("actionbtn")
	Dim xb As B4XView = b
	xb.Text = Text
	xb.Tag = Tag
	xb.TextSize = 12
	xb.SetColorAndBorder(xui.Color_Transparent, 1dip, xui.Color_Gray, 6dip)
	AddViewToContent(xb, 0, 0, 78dip, 32dip)
	Return xb
End Sub

Public Sub IsReady As Boolean
	Return mBase.IsInitialized And Surface.IsInitialized And mBase.Width > 0 And mBase.Height > 0
End Sub

Private Sub BuildBoxModel As Map
	Dim box As Map = B4XDaisyBoxModel.CreateDefaultModel
	box.Put("padding_left", ContentPaddingH)
	box.Put("padding_right", ContentPaddingH)
	box.Put("padding_top", ContentPaddingV)
	box.Put("padding_bottom", ContentPaddingV)
	Dim bw As Float = Max(0, IIf(BorderWidthFromTheme, B4XDaisyVariants.GetBorderDip(1dip), mBorderWidth))
	If mStyle = "outline" Or mStyle = "dash" Then bw = Max(1dip, bw)
	box.Put("border_width", bw)
	box.Put("radius", IIf(mRoundedBox, B4XDaisyVariants.GetRadiusBoxDip(8dip), 0))
	Return box
End Sub

Private Sub ResolveContentLocalRect(OuterRect As B4XRect, Box As Map) As B4XRect
	Dim borderRect As B4XRect = B4XDaisyBoxModel.ResolveBorderRect(OuterRect, Box)
	Dim contentAbs As B4XRect = B4XDaisyBoxModel.ResolveContentRect(borderRect, Box)
	Return B4XDaisyBoxModel.ToLocalRect(contentAbs, OuterRect)
End Sub

Private Sub LayoutContent(ContentRect As B4XRect)
	Dim colorState As Map
	colorState = ResolveVisualColors
	Dim iconWanted As Boolean = mIconVisible
	Dim iconAsset As String = ResolveIconAsset
	If iconAsset.Trim.Length = 0 Then iconWanted = False
	RefreshIcon(colorState, iconAsset, iconWanted)

	Dim actionSize As Map = MeasureActions
	Dim actionW As Int = actionSize.Get("w")
	Dim actionH As Int = actionSize.Get("h")
	LayoutActionChildren

	Dim isVertical As Boolean = (mDirection = "vertical")
	Dim iconSpace As Int = IIf(iconWanted, Max(16dip, mIconSize), 0)
	Dim actionSpace As Int = IIf(actionW > 0, actionW, 0)
	Dim baseLeft As Int = ContentRect.Left
	Dim baseTop As Int = ContentRect.Top
	Dim innerW As Int = Max(1dip, ContentRect.Width)
	Dim innerH As Int = Max(1dip, ContentRect.Height)
	Dim y As Int

	If isVertical Then
		Dim contentW As Int = innerW
		y = baseTop
		If iconWanted Then
			Dim ix As Int = baseLeft + (contentW - iconSpace) / 2
			IconView.Visible = True
			IconView.SetLayoutAnimated(0, ix, y, iconSpace, iconSpace)
			y = y + iconSpace + ContentGap
		Else
			IconView.Visible = False
		End If

		Dim textH As Int = LayoutTextLabels(contentW, True)
		TextHost.SetLayoutAnimated(0, baseLeft, y, contentW, textH)
		y = y + textH

		If actionW > 0 And actionH > 0 Then
			y = y + ContentGap
			Dim ax As Int = baseLeft + (contentW - actionW) / 2
			ActionsHost.SetLayoutAnimated(0, ax, y, actionW, actionH)
		Else
			ActionsHost.SetLayoutAnimated(0, 0, 0, 0, 0)
		End If
	Else
		Dim actionSlot As Int = IIf(actionSpace > 0, actionSpace + ContentGap, 0)
		Dim iconSlot As Int = IIf(iconWanted, iconSpace + ContentGap, 0)
		Dim textW As Int = Max(1dip, innerW - actionSlot - iconSlot)
		Dim textH As Int = LayoutTextLabels(textW, False)
		Dim blockH As Int = Max(textH, Max(iconSpace, actionH))
		y = baseTop + Max(0, (innerH - blockH) / 2)

		Dim cursorX As Int = baseLeft
		If iconWanted Then
			IconView.Visible = True
			IconView.SetLayoutAnimated(0, cursorX, y + (blockH - iconSpace) / 2, iconSpace, iconSpace)
			cursorX = cursorX + iconSpace + ContentGap
		Else
			IconView.Visible = False
		End If

		TextHost.SetLayoutAnimated(0, cursorX, y + (blockH - textH) / 2, textW, textH)

		If actionW > 0 And actionH > 0 Then
			Dim ax As Int = baseLeft + innerW - actionW
			ActionsHost.SetLayoutAnimated(0, ax, y + (blockH - actionH) / 2, actionW, actionH)
		Else
			ActionsHost.SetLayoutAnimated(0, 0, 0, 0, 0)
		End If
	End If
End Sub

Private Sub LayoutTextLabels(AvailableWidth As Int, CenterAligned As Boolean) As Int
	Dim w As Int = Max(1dip, AvailableWidth)
	Dim y As Int = 0
	Dim align As String = IIf(CenterAligned, "CENTER", "LEFT")

	Dim titleText As String = mTitle.Trim
	Dim bodyText As String = mText.Trim
	Dim descText As String = mDescription.Trim

	lblTitle.Visible = titleText.Length > 0
	lblText.Visible = bodyText.Length > 0
	lblDescription.Visible = descText.Length > 0

	If lblTitle.Visible Then
		lblTitle.Text = titleText
		lblTitle.SetTextAlignment("TOP", align)
		Dim hTitle As Int = MeasureLabelHeight(lblTitle, titleText, w)
		lblTitle.SetLayoutAnimated(0, 0, y, w, hTitle)
		y = y + hTitle
	End If

	If lblText.Visible Then
		If y > 0 Then y = y + TextGap
		lblText.Text = bodyText
		lblText.SetTextAlignment("TOP", align)
		Dim hText As Int = MeasureLabelHeight(lblText, bodyText, w)
		lblText.SetLayoutAnimated(0, 0, y, w, hText)
		y = y + hText
	End If

	If lblDescription.Visible Then
		If y > 0 Then y = y + TextGap
		lblDescription.Text = descText
		lblDescription.SetTextAlignment("TOP", align)
		Dim hDesc As Int = MeasureLabelHeight(lblDescription, descText, w)
		lblDescription.SetLayoutAnimated(0, 0, y, w, hDesc)
		y = y + hDesc
	End If

	If y <= 0 Then
		lblTitle.SetLayoutAnimated(0, 0, 0, 0, 0)
		lblText.SetLayoutAnimated(0, 0, 0, 0, 0)
		lblDescription.SetLayoutAnimated(0, 0, 0, 0, 0)
	End If
	Return y
End Sub

Private Sub MeasureLabelHeight(v As B4XView, Text As String, Width As Int) As Int
	Dim l As Label = v
	l.Width = Max(1dip, Width)
	Dim h As Int = tu.MeasureMultilineTextHeight(l, Text)
	Return Max(1dip, h)
End Sub

Private Sub MeasureActions As Map
	Dim totalW As Int = 0
	Dim maxH As Int = 0
	If ActionsHost.IsInitialized = False Then Return CreateMap("w": 0, "h": 0)
	For i = 0 To ActionsHost.NumberOfViews - 1
		Dim v As B4XView = ActionsHost.GetView(i)
		Dim w As Int = Max(1dip, v.Width)
		Dim h As Int = Max(1dip, v.Height)
		If totalW > 0 Then totalW = totalW + mActionSpacing
		totalW = totalW + w
		If h > maxH Then maxH = h
	Next
	Return CreateMap("w": totalW, "h": maxH)
End Sub

Private Sub LayoutActionChildren
	If ActionsHost.IsInitialized = False Then Return
	If ActionsHost.NumberOfViews = 0 Then Return
	Dim cursorX As Int = 0
	For i = 0 To ActionsHost.NumberOfViews - 1
		Dim v As B4XView = ActionsHost.GetView(i)
		Dim w As Int = Max(1dip, v.Width)
		Dim h As Int = Max(1dip, v.Height)
		Dim y As Int = Max(0, (ActionsHost.Height - h) / 2)
		v.SetLayoutAnimated(0, cursorX, y, w, h)
		cursorX = cursorX + w + mActionSpacing
	Next
End Sub

Private Sub ApplyVisualStyle(Box As Map)
	If Surface.IsInitialized = False Then Return
	Dim colorState As Map
	colorState = ResolveVisualColors
	Dim bg As Int = colorState.Get("back")
	Dim fg As Int = colorState.Get("text")
	Dim muted As Int = colorState.Get("muted")
	Dim border As Int = colorState.Get("border")
	Dim bw As Int = Max(0, Box.GetDefault("border_width", 0))
	Dim radius As Float = Max(0, Box.GetDefault("radius", 0))

	lblTitle.TextColor = fg
	lblText.TextColor = fg
	lblDescription.TextColor = muted

	' border and width were accidentally swapped, fixed to match ApplyDashedBorder signature
	B4XDaisyVariants.ApplyDashedBorder(Surface, bg, bw, border, radius, mStyle)

	ApplyShadow
End Sub

' Returns the resolved colors for the current variant and style.
Public Sub GetVisualColors As Map
	Return ResolveVisualColors
End Sub

Private Sub ResolveVisualColors As Map
	InitializePalette
	Dim theme As Map = B4XDaisyVariants.GetActiveTokens

	Dim neutralBack As Int = theme.GetDefault("--color-neutral", xui.Color_RGB(63, 64, 77))
	Dim neutralText As Int = theme.GetDefault("--color-neutral-content", xui.Color_RGB(248, 248, 249))
	Dim neutralBorder As Int = neutralBack
	Dim base100 As Int = theme.GetDefault("--color-base-100", xui.Color_White)

	Dim variant As String = B4XDaisyVariants.NormalizeAlertVariant(mVariant)
	Dim effectiveVariant As String = IIf(variant = "none", "neutral", variant)
	Dim accent As Int = B4XDaisyVariants.ResolveVariantColor(VariantPalette, effectiveVariant, "back", neutralBack)

	Dim back As Int = neutralBack
	Dim text As Int = neutralText
	Dim border As Int = neutralBorder

	If variant <> "none" Then
		back = B4XDaisyVariants.ResolveVariantColor(VariantPalette, variant, "back", neutralBack)
		text = B4XDaisyVariants.ResolveVariantColor(VariantPalette, variant, "text", neutralText)
		Dim fallbackText As Int = IIf(IsColorLight(back), xui.Color_RGB(15, 23, 42), xui.Color_White)
		text = ResolveReadableTextColor(back, text, fallbackText)
		border = B4XDaisyVariants.Blend(back, xui.Color_Black, 0.14)
	End If

	Select Case mStyle
		Case "soft"
			back = B4XDaisyVariants.Blend(accent, base100, 0.88)
			text = accent
			border = B4XDaisyVariants.Blend(accent, base100, 0.8)
		Case "outline", "dash"
			back = xui.Color_Transparent
			text = accent
			border = accent
	End Select

	If mBackgroundColor <> 0 Then back = mBackgroundColor
	If mTextColor <> 0 Then text = mTextColor
	If mBorderColor <> 0 Then border = mBorderColor
	Dim muted As Int = B4XDaisyVariants.Blend(text, xui.Color_Gray, 0.45)
	Return CreateMap("back": back, "text": text, "muted": muted, "border": border, "accent": accent)
End Sub

Private Sub ResolveReadableTextColor(BackColor As Int, PreferredColor As Int, FallbackColor As Int) As Int
	Dim prefScore As Float = ContrastScore(BackColor, PreferredColor)
	Dim fallbackScore As Float = ContrastScore(BackColor, FallbackColor)
	If prefScore >= 125 Or prefScore >= fallbackScore Then Return PreferredColor
	Return FallbackColor
End Sub

Private Sub IsColorLight(Color As Int) As Boolean
	Return RelativeBrightness(Color) >= 150
End Sub

Private Sub ContrastScore(c1 As Int, c2 As Int) As Float
	Return Abs(RelativeBrightness(c1) - RelativeBrightness(c2))
End Sub

Private Sub RelativeBrightness(Color As Int) As Float
	Dim r As Int = Bit.And(Bit.ShiftRight(Color, 16), 0xFF)
	Dim g As Int = Bit.And(Bit.ShiftRight(Color, 8), 0xFF)
	Dim b As Int = Bit.And(Color, 0xFF)
	Return (r * 299 + g * 587 + b * 114) / 1000
End Sub

Private Sub RefreshIcon(ColorMap As Map, AssetPath As String, ShowIcon As Boolean)
	If IconComp.IsInitialized = False Or IconView.IsInitialized = False Then Return
	If ShowIcon = False Then
		IconView.Visible = False
		Return
	End If
	IconView.Visible = True
	IconComp.SetSvgAsset(AssetPath)
	IconComp.SetPreserveOriginalColors(False)
	Dim iconColor As Int = ColorMap.Get("text")
	If mIconColor <> 0 Then iconColor = mIconColor
	IconComp.SetColor(iconColor)
	IconComp.SetSize(mIconSize)
	IconComp.ResizeToParent(IconView)
End Sub

Private Sub ResolveIconAsset As String
	If mIconAsset.Trim.Length > 0 Then Return mIconAsset.Trim
	Select Case B4XDaisyVariants.NormalizeAlertVariant(mVariant)
		Case "success"
			Return "check-solid.svg"
		Case "info"
			Return "circle-info-solid.svg"
		Case "error"
			Return "xmark-solid.svg"
		Case "warning"
			Return "triangle-exclamation-solid.svg"
		Case Else
			Return "circle-question-regular.svg"
	End Select
End Sub

Private Sub InitializePalette
	DefaultVariantPalette = B4XDaisyVariants.GetVariantPalette
	If VariantPalette = Null Then
		VariantPalette = DefaultVariantPalette
	Else If VariantPalette.IsInitialized = False Or UsingCustomVariantPalette = False Then
		VariantPalette = DefaultVariantPalette
	End If
End Sub

Private Sub ApplyShadow
	Dim level As String = mShadow
	If mStyle <> "solid" Then level = "none"
	B4XDaisyVariants.ApplyElevation(Surface, level)
End Sub








Private Sub Refresh

	If mBase.IsInitialized = False Then Return
	Base_Resize(mBase.Width, mBase.Height)
End Sub

' Resizes the alert to the preferred height based on its current content and provided width.
Public Sub SizeToFit(AvailableWidth As Int)
	If mBase.IsInitialized = False Then Return
	' Safety check for width
	Dim aw As Int = Max(4dip, AvailableWidth)
	Dim h As Int = GetPreferredHeight(aw)
	mBase.Height = h
	Base_Resize(mBase.Width, h)
End Sub

Private Sub GetPreferredHeight(AvailableWidth As Int) As Int
	If lblText.IsInitialized = False Then Return Max(40dip, mHeight)
	Dim w As Int = Max(120dip, AvailableWidth)
	Dim box As Map = BuildBoxModel
	Dim actionSize As Map = MeasureActions
	Dim actionW As Int = actionSize.Get("w")
	Dim actionH As Int = actionSize.Get("h")
	Dim iconPath As String = ResolveIconAsset
	Dim iconVisible As Boolean = mIconVisible And iconPath.Trim.Length > 0
	Dim iconSize As Int = IIf(iconVisible, Max(16dip, mIconSize), 0)
	Dim textW As Int
	Dim textH As Int
	Dim padL As Float = box.GetDefault("padding_left", 0)
	Dim padR As Float = box.GetDefault("padding_right", 0)
	Dim borderW As Float = box.GetDefault("border_width", 0)
	Dim contentW As Int = Max(1dip, w - (padL + padR) - (borderW * 2))
	If mDirection = "vertical" Then
		textW = contentW
		textH = LayoutTextLabels(textW, True)
		Dim hActual As Int = textH
		If iconVisible Then hActual = hActual + iconSize + ContentGap
		If actionW > 0 And actionH > 0 Then hActual = hActual + ContentGap + actionH
		hActual = Max(40dip, B4XDaisyBoxModel.ExpandContentHeight(hActual, box))
		' Log values for debugging
		' Log("Alert.GetPreferredHeight: w=" & AvailableWidth & " -> h=" & hActual)
		Return hActual
	Else
		Dim actionSlot As Int = IIf(actionW > 0, actionW + ContentGap, 0)
		Dim iconSlot As Int = IIf(iconVisible, iconSize + ContentGap, 0)
		textW = Max(1dip, contentW - actionSlot - iconSlot)
		textH = LayoutTextLabels(textW, False)
		Dim blockH As Int = Max(textH, Max(iconSize, actionH))
		Return Max(40dip, B4XDaisyBoxModel.ExpandContentHeight(blockH, box))
	End If
End Sub

Private Sub DoAutoResize
	If mbAutoResize = False Then Return
	If mHeightExplicit Then Return
	If mBase.IsInitialized = False Then Return
	Dim h As Int = GetPreferredHeight(mBase.Width)
	If h <> mBase.Height Then
		mBase.Height = h
	End If
End Sub

Private Sub surface_Click
	RaiseClick
End Sub

Private Sub actionbtn_Click
	Dim v As B4XView = Sender
	RaiseActionClick(v.Tag)
End Sub

Private Sub RaiseClick
	Dim payload As Object = mTag
	If payload = Null Then payload = mBase
	If xui.SubExists(mCallBack, mEventName & "_Click", 1) Then
		CallSub2(mCallBack, mEventName & "_Click", payload)
	Else If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
		CallSub(mCallBack, mEventName & "_Click")
	End If
End Sub

Public Sub RaiseActionClick(Tag As Object)
	If xui.SubExists(mCallBack, mEventName & "_ActionClick", 1) Then
		CallSub2(mCallBack, mEventName & "_ActionClick", Tag)
	End If
End Sub

'========================
' Public API
'========================

Public Sub setWidth(Value As Object)
	' Width setter: convert Tailwind/CSS-like size to dip and relayout when ready.
	mWidth = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveWidthBase(mBase, mWidth)))
	mWidthExplicit = True
	If mBase.IsInitialized = False Then Return
	Dim targetH As Int = Max(1dip, mBase.Height)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, mWidth, targetH)
	Base_Resize(mWidth, targetH)
End Sub

Public Sub getWidth As Float
	' Return cached width in dip.
	Return mWidth
End Sub

Public Sub setHeight(Value As Object)
	' Height setter: convert Tailwind/CSS-like size to dip and relayout when ready.
	mbAutoResize = False
	mHeight = Max(1dip, B4XDaisyVariants.TailwindSizeToDip(Value, B4XDaisyVariants.ResolveHeightBase(mBase, mHeight)))
	mHeightExplicit = True
	If mBase.IsInitialized = False Then Return
	Dim targetW As Int = Max(1dip, mBase.Width)
	mBase.SetLayoutAnimated(0, mBase.Left, mBase.Top, targetW, mHeight)
	Base_Resize(targetW, mHeight)
End Sub

Public Sub getHeight As Float
	' Return cached height in dip.
	Return mHeight
End Sub

Public Sub setAutoResize(Value As Boolean)
	mbAutoResize = Value
	If mbAutoResize Then mHeightExplicit = False
	If mBase.IsInitialized Then Base_Resize(mBase.Width, mBase.Height)
End Sub

Public Sub getAutoResize As Boolean
	Return mbAutoResize
End Sub

Public Sub setVariant(Value As String)
	' Update alert variant token and refresh styles.
	mVariant = B4XDaisyVariants.NormalizeAlertVariant(Value)
	' Reset manual colors to ensure variant colors take full effect.
	mBackgroundColor = 0
	mTextColor = 0
	mBorderColor = 0
	mIconColor = 0
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getVariant As String
	' Return normalized variant token.
	Return mVariant
End Sub

Public Sub setStyle(Value As String)
	' Sets alert style (solid, soft, outline, dash, ghost, link).
	mStyle = B4XDaisyVariants.NormalizeStyle(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getStyle As String
	' Return normalized style token.
	Return mStyle
End Sub

Public Sub setAlertStyle(Value As String)
	' Alias for setStyle.
	mStyle = B4XDaisyVariants.NormalizeStyle(Value)
End Sub

Public Sub getAlertStyle As String
	Return mStyle
End Sub

Public Sub setDirection(Value As String)
	' Update content layout direction (horizontal/vertical).
	mDirection = B4XDaisyVariants.NormalizeDirection(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getDirection As String
	' Return normalized direction token.
	Return mDirection
End Sub

Public Sub setTitle(Value As String)
	' Update optional title text.
	If Value = Null Then Value = ""
	mTitle = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getTitle As String
	' Return title text.
	Return mTitle
End Sub

Public Sub setText(Value As String)
	' Update main message text.
	If Value = Null Then Value = ""
	mText = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getText As String
	' Return main message text.
	Return mText
End Sub

Public Sub setMessage(Value As String)
	' Alias for setText.
	setText(Value)
End Sub

Public Sub getMessage As String
	' Alias for getText.
	Return mText
End Sub

Public Sub setDescription(Value As String)
	' Update optional secondary description text.
	If Value = Null Then Value = ""
	mDescription = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getDescription As String
	' Return secondary description text.
	Return mDescription
End Sub

Public Sub setIconVisible(Value As Boolean)
	' Toggle icon host visibility.
	mIconVisible = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getIconVisible As Boolean
	' Return current icon visibility flag.
	Return mIconVisible
End Sub

Public Sub setIconAsset(Path As String)
	' Update SVG asset path; non-empty value auto-enables icon visibility.
	If Path = Null Then Path = ""
	mIconAsset = Path.Trim
	If mIconAsset.Length > 0 Then mIconVisible = True
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getIconAsset As String
	' Return current icon asset path.
	Return mIconAsset
End Sub

Public Sub setIconSize(Value As Object)
	' Update icon size from Tailwind/CSS-like value with a minimum of 12dip.
	mIconSize = Max(12dip, B4XDaisyVariants.TailwindSizeToDip(Value, mIconSize))
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getIconSize As Float
	' Return icon size in dip.
	Return mIconSize
End Sub

Public Sub setIconColor(Value As Object)
	' Accept explicit color ints or theme color token names for icon tint.
	Dim shouldRefresh As Boolean = False
	If Value = Null Then
		mIconColor = 0
		shouldRefresh = True
	Else If IsNumber(Value) Then
		mIconColor = Value
		shouldRefresh = True
	Else
		Dim s As String = Value
		s = s.Trim
		If s.Length = 0 Then
			mIconColor = 0
			shouldRefresh = True
		Else
			Dim k As String = s.ToLowerCase
			If k = "auto" Or k = "default" Or k = "none" Then
				mIconColor = 0
				shouldRefresh = True
			Else
				Dim token As String = B4XDaisyVariants.ResolveThemeColorTokenName(k)
				If token.Length > 0 Then
					Dim fallback As Int = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_Black)
					mIconColor = B4XDaisyVariants.GetTokenColor(token, fallback)
					shouldRefresh = True
				End If
			End If
		End If
	End If
	If mBase.IsInitialized = False Then Return
	If shouldRefresh Then Refresh
End Sub

Public Sub getIconColor As Int
	' Return current icon color (0 means auto/theme).
	Return mIconColor
End Sub

Public Sub setRoundedBox(Value As Boolean)
	' Toggle rounded-corner style on alert surface.
	mRoundedBox = Value
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getRoundedBox As Boolean
	' Return rounded-box flag.
	Return mRoundedBox
End Sub

Public Sub setBorderWidth(Value As Float)
	' Set explicit border width and disable theme-driven border width mode.
	mBorderWidth = Max(0, Value)
	BorderWidthFromTheme = False
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderWidth As Float
	' Return active border width (theme-derived or explicit override).
	If BorderWidthFromTheme Then Return B4XDaisyVariants.GetBorderDip(1dip)
	Return mBorderWidth
End Sub

Public Sub resetBorderWidthToTheme
	' Restore border width resolution back to active theme value.
	BorderWidthFromTheme = True
	mBorderWidth = B4XDaisyVariants.GetBorderDip(1dip)
	Refresh
End Sub

Public Sub setShadow(Value As String)
	' Update shadow token and refresh component.
	mShadow = B4XDaisyVariants.NormalizeShadow(Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getShadow As String
	' Return normalized shadow token.
	Return mShadow
End Sub

Public Sub setActionSpacing(Value As Float)
	' Update spacing between action views/buttons in dip.
	mActionSpacing = Max(0, Value)
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getActionSpacing As Float
	' Return action spacing in dip.
	Return mActionSpacing
End Sub

Public Sub setVariantPalette(Palette As Map)
	' Replace palette map for variant resolution; Null restores defaults.
	InitializePalette
	If Palette <> Null Then
		If Palette.IsInitialized Then
			VariantPalette = Palette
			UsingCustomVariantPalette = True
		Else
			VariantPalette = DefaultVariantPalette
			UsingCustomVariantPalette = False
		End If
	Else
		VariantPalette = DefaultVariantPalette
		UsingCustomVariantPalette = False
	End If
	If mBase.IsInitialized Then Refresh
End Sub

Public Sub getVariantPalette As Map
	' Return currently active variant palette map.
	InitializePalette
	Return VariantPalette
End Sub

Public Sub applyActiveTheme
	' Re-resolve theme-sensitive values after global theme/color mode change.
	InitializePalette
	If BorderWidthFromTheme Then mBorderWidth = B4XDaisyVariants.GetBorderDip(1dip)
	Refresh
End Sub

Public Sub setBackgroundColor(Color As Int)
	' Set explicit background color.
	mBackgroundColor = Color
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBackgroundColor As Int
	' Return background color.
	Return mBackgroundColor
End Sub

Public Sub setBackgroundColorVariant(VariantName As String)
	' Resolve and apply background color from a palette variant key.
	InitializePalette
	Dim c As Int = B4XDaisyVariants.ResolveBackgroundColorVariantFromPalette(VariantPalette, VariantName, mBackgroundColor)
	setBackgroundColor(c)
End Sub

Public Sub setTextColor(Color As Int)
	' Set explicit text color.
	mTextColor = Color
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getTextColor As Int
	' Return text color.
	Return mTextColor
End Sub

Public Sub setTextColorVariant(VariantName As String)
	' Resolve and apply text color from a palette variant key.
	InitializePalette
	Dim c As Int = B4XDaisyVariants.ResolveTextColorVariantFromPalette(VariantPalette, VariantName, mTextColor)
	setTextColor(c)
End Sub

Public Sub setBorderColor(Color As Int)
	' Set explicit border color.
	mBorderColor = Color
	If mBase.IsInitialized = False Then Return
	Refresh
End Sub

Public Sub getBorderColor As Int
	' Return border color.
	Return mBorderColor
End Sub

Public Sub setBorderColorVariant(VariantName As String)
	' Resolve and apply border color from a palette variant key.
	InitializePalette
	Dim c As Int = B4XDaisyVariants.ResolveBorderColorVariantFromPalette(VariantPalette, VariantName, mBorderColor)
	setBorderColor(c)
End Sub

Public Sub setTag(Value As Object)
	' Store caller tag object for event correlation.
	mTag = Value
End Sub

Public Sub getTag As Object
	' Return caller tag object.
	Return mTag
End Sub



Public Sub GetComputedHeight As Int
	If mBase.IsInitialized = False Then Return 0
	Return mBase.Height
End Sub

Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub

#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XDaisyActionSheet.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.5
@EndOfDesignText@
' B4XDaisyActionSheet
' 100% Parity with Ionic v8 ion-action-sheet (2026 Specifications)
#IgnoreWarnings:12,9

#Region Events
' Standard Events
#Event: DidPresent
#Event: WillPresent
#Event: DidDismiss (Data As Object, Role As String)
#Event: WillDismiss (Data As Object, Role As String)
#Event: ButtonClick (ButtonId As String, Data As Object)
#End Region

#Region Designer Properties
#DesignerProperty: Key: Header, DisplayName: Header, FieldType: String, DefaultValue: , Description: Title for the action sheet.
#DesignerProperty: Key: SubHeader, DisplayName: SubHeader, FieldType: String, DefaultValue: , Description: Subtitle for the action sheet.
#DesignerProperty: Key: Animated, DisplayName: Animated, FieldType: Boolean, DefaultValue: True, Description: If true, the action sheet will animate.
#DesignerProperty: Key: BackdropDismiss, DisplayName: Backdrop Dismiss, FieldType: Boolean, DefaultValue: True, Description: Dismiss when the backdrop is clicked.
#DesignerProperty: Key: KeyboardClose, DisplayName: Keyboard Close, FieldType: Boolean, DefaultValue: True, Description: Automatically dismiss keyboard when presented.
#DesignerProperty: Key: Translucent, DisplayName: Translucent, FieldType: Boolean, DefaultValue: False, Description: Enable translucent glass effects (iOS mode).
#DesignerProperty: Key: Mode, DisplayName: Mode, FieldType: String, DefaultValue: md, List: ios|md, Description: Platform rendering mode.
#DesignerProperty: Key: ButtonSize, DisplayName: Button Size, FieldType: String, DefaultValue: md, List: xs\|sm\|md\|lg\|xl, Description: Tailwind size token applied to every action button.
#DesignerProperty: Key: Outline, DisplayName: Outline, FieldType: Boolean, DefaultValue: False, Description: If true, all action buttons are rendered with an outline style.
#DesignerProperty: Key: TextAlignment, DisplayName: Text Alignment, FieldType: String, DefaultValue: left, List: left\|center\|right, Description: Horizontal text alignment applied to each action button (left for md, center for ios).
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: String, DefaultValue: base-100, List: base-100|base-200|base-300|primary|secondary|accent|neutral|info|success|warning|error, Description: Surface color of the action sheet group (accepts DaisyUI variant colors).
#DesignerProperty: Key: BackdropOpacity, DisplayName: Backdrop Opacity, FieldType: String, DefaultValue: 0.4, Description: Backdrop dimming as a float string (e.g. 0.4 or 40%).
#DesignerProperty: Key: ButtonsColor, DisplayName: Buttons Color, FieldType: String, DefaultValue: default, List: default|neutral|primary|secondary|accent|info|success|warning|error|none, Description: DaisyUI variant color applied to all action buttons.
#DesignerProperty: Key: TextColor, DisplayName: Text Color, FieldType: String, DefaultValue: base-content, List: base-content|base-100|primary|secondary|accent|neutral|info|success|warning|error, Description: Header / SubHeader text color (accepts DaisyUI variant colors).
#DesignerProperty: Key: HeaderBold, DisplayName: Header Bold, FieldType: Boolean, DefaultValue: False, Description: If true, the header is rendered in bold.
#DesignerProperty: Key: ButtonGhosted, DisplayName: Button Ghosted, FieldType: Boolean, DefaultValue: True, Description: If true, action buttons use the ghost style; if false they render solid.
#End Region

Sub Class_Globals
	Private xui As XUI
	Public mBase As B4XView
	Private mEventName As String
	Private mCallBack As Object
    
	' Animation helper (B4XAnimation easing functions)
	Private Anim As B4XAnimation
    
	' Overlay & Layout Views
	Private pnlBackdrop As B4XView
	Private pnlWrapper As B4XView
	Private pnlGroup As B4XView
	Private pnlCancelGroup As B4XView
    
	' Ionic Properties
	Private msHeader As String
	Private msSubHeader As String
	Private mbAnimated As Boolean = True
	Private mbBackdropDismiss As Boolean = True
	Private mbKeyboardClose As Boolean = True
	Private mbTranslucent As Boolean = False
	Private msMode As String = "md"
	Private mbOutline As Boolean = False
	Private msButtonSize As String = "md"
	Private msTextAlignment As String = "left"
	Private miBottomGap As Int = 0
	Private mbIsOpen As Boolean = False
	Private mTrigger As String

	' Styling Properties
	Private msBackgroundColor As String = "base-100"
	Private msBackdropOpacity As String = "0.4"
	Private msButtonColor As String = "default"
	Private msTextColor As String = "base-content"
	Private mbHeaderBold As Boolean = False
	Private mbButtonGhosted As Boolean = True

	' Internal Data
	Type ActionSheetButton(Id As String, Text As String, Role As String, Icon As String, Data As Object, Disabled As Boolean, Variant As String, IconVariant As String)
	Private mButtons As List
    
	' CSS Custom Properties Parity
	Private mcBackdropOpacity As Float = 0.4
	Private mcBackground As Int
	Private mcButtonBackground As Int
	Private mcButtonBackgroundHover As Int
	Private mcButtonColor As Int
	Private mcButtonColorDestructive As Int
	Private mcColor As Int
End Sub

Public Sub Initialize(Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	mButtons.Initialize
	Anim.Initialize
    
	' Default theme variables
	mcBackground = B4XDaisyVariants.GetTokenColor("--color-base-100", xui.Color_White)
	mcButtonBackground = xui.Color_Transparent
	mcButtonBackgroundHover = B4XDaisyVariants.GetTokenColor("--color-base-200", 0xFFE5E7EB)
	mcButtonColor = B4XDaisyVariants.GetTokenColor("--color-base-content", xui.Color_Black)
	mcButtonColorDestructive = B4XDaisyVariants.GetTokenColor("--color-error", 0xFFEF4444)
	mcColor = B4XDaisyVariants.GetTokenColor("--color-base-content", 0xFF666666)
End Sub

Private Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	mBase.Color = xui.Color_Transparent
    
	msHeader = Props.GetDefault("Header", "")
	msSubHeader = Props.GetDefault("SubHeader", "")
	mbAnimated = Props.GetDefault("Animated", True)
	mbBackdropDismiss = Props.GetDefault("BackdropDismiss", True)
	mbKeyboardClose = Props.GetDefault("KeyboardClose", True)
	mbTranslucent = Props.GetDefault("Translucent", False)
	msMode = Props.GetDefault("Mode", "md")
	mbOutline = Props.GetDefault("Outline", False)
	msButtonSize = B4XDaisyVariants.NormalizeSize(Props.GetDefault("ButtonSize", "md"))
	msTextAlignment = Props.GetDefault("TextAlignment", "")
	If msTextAlignment.Trim = "" Then
		msTextAlignment = IIf(msMode = "ios", "CENTER", "LEFT")
	Else
		msTextAlignment = msTextAlignment.ToUpperCase
	End If

	' Styling properties
	msBackgroundColor = Props.GetDefault("BackgroundColor", "base-100")
	msBackdropOpacity = Props.GetDefault("BackdropOpacity", "0.4")
	msButtonColor = Props.GetDefault("ButtonsColor", "default")
	msTextColor = Props.GetDefault("TextColor", "base-content")
	mbHeaderBold = Props.GetDefault("HeaderBold", False)
	mbButtonGhosted = Props.GetDefault("ButtonGhosted", True)

	' Resolve theme colors from the variant strings
	mcBackground = B4XDaisyVariants.ResolveBackgroundColorVariant(msBackgroundColor, mcBackground)
	mcColor = B4XDaisyVariants.ResolveTextColorVariant(msTextColor, mcColor)
	mcBackdropOpacity = ParseOpacityFloat(msBackdropOpacity, mcBackdropOpacity)

	miBottomGap = 0
End Sub

#Region API Methods
' Adds an ActionSheetButton matching the exact interface specification.
Public Sub AddButton(Id As String, Text As String, Role As String, Icon As String)
	Dim btn As ActionSheetButton
	btn.Initialize
	btn.Id = IIf(Id = "", "btn_" & mButtons.Size, Id)
	btn.Text = Text
	btn.Role = Role.ToLowerCase ' supports "cancel", "destructive", "selected"
	btn.Icon = Icon
	btn.Data = Null ' attach a payload later via SetButtonData
	btn.Disabled = False
	btn.Variant = "" ' empty = use ButtonColor property (or role default)
	btn.IconVariant = "" ' empty = icon follows the button color; set via SetButtonIconColor
	mButtons.Add(btn)
End Sub

' Attaches a data payload to a single action button by id. The payload is returned
' in the ButtonClick(Id, Data) and DidDismiss(Data, Role) events when the button is tapped.
Public Sub SetButtonData(ButtonId As String, Data As Object)
	For Each btn As ActionSheetButton In mButtons
		If btn.Id = ButtonId Then
			btn.Data = Data
			Return
		End If
	Next
End Sub

' Attaches a data payload to the action button at the given 0-based index.
Public Sub SetButtonDataByIndex(Index As Int, Data As Object)
	If Index >= 0 And Index < mButtons.Size Then
		Dim btn As ActionSheetButton = mButtons.Get(Index)
		btn.Data = Data
	End If
End Sub

' Sets the icon (svg asset name) of a single action button by id. Useful to change
' or swap an icon after the button has been added. Call before Present.
Public Sub SetButtonIcon(ButtonId As String, IconName As String)
	For Each btn As ActionSheetButton In mButtons
		If btn.Id = ButtonId Then
			btn.Icon = IconName
			Return
		End If
	Next
End Sub

' Sets the icon (svg asset name) of the action button at the given 0-based index.
Public Sub SetButtonIconByIndex(Index As Int, IconName As String)
	If Index >= 0 And Index < mButtons.Size Then
		Dim btn As ActionSheetButton = mButtons.Get(Index)
		btn.Icon = IconName
	End If
End Sub

' Sets the icon color of a single action button by id (as a DaisyUI variant, e.g.
' "primary", "success", "error"). When set, the icon is recolored independently of
' the button's text color. Call before Present. Empty/"" clears it (icon follows the
' button color).
Public Sub SetButtonIconColor(ButtonId As String, ColorVariant As String)
	For Each btn As ActionSheetButton In mButtons
		If btn.Id = ButtonId Then
			btn.IconVariant = ColorVariant
			Return
		End If
	Next
End Sub

' Sets the icon color (as a DaisyUI variant) of the action button at the given index.
Public Sub SetButtonIconColorByIndex(Index As Int, ColorVariant As String)
	If Index >= 0 And Index < mButtons.Size Then
		Dim btn As ActionSheetButton = mButtons.Get(Index)
		btn.IconVariant = ColorVariant
	End If
End Sub

' Sets the DaisyUI color variant of a single action button (e.g. "primary", "error").
' Matches the "Button Colors" examples in the buttons demo. An explicit per-button
' color overrides the global ButtonColor property and the destructive role default.
' The variant drives both the button text and its icon color.
Public Sub SetButtonColor(ButtonId As String, ColorVariant As String)
	For Each btn As ActionSheetButton In mButtons
		If btn.Id = ButtonId Then
			btn.Variant = ColorVariant
			Return
		End If
	Next
End Sub

' Sets the DaisyUI color variant of the action button at the given 0-based index.
Public Sub SetButtonColorByIndex(Index As Int, ColorVariant As String)
	If Index >= 0 And Index < mButtons.Size Then
		Dim btn As ActionSheetButton = mButtons.Get(Index)
		btn.Variant = ColorVariant
	End If
End Sub

' Controls the declarative presentation state of the Action Sheet
Public Sub setIsOpen(Value As Boolean)
	If mbIsOpen = Value Then Return
	mbIsOpen = Value
	If mbIsOpen Then
		Present
	Else
		Dismiss(Null, "")
	End If
End Sub

Public Sub getIsOpen As Boolean
	Return mbIsOpen
End Sub

' Programmatic method to present the action sheet, returns a ResumableSub mapping to Promise<void>
Public Sub Present As ResumableSub
	If mbKeyboardClose Then HideKeyboard
    
	If xui.SubExists(mCallBack, mEventName & "_WillPresent", 0) Then
		CallSub(mCallBack, mEventName & "_WillPresent")
	End If
    
	BuildOverlay
    
	If mbAnimated Then
		' 1. Fade the backdrop in natively
		pnlBackdrop.SetColorAnimated(300, xui.Color_Transparent, xui.Color_ARGB(Round(mcBackdropOpacity * 255), 0, 0, 0))
        
		' 2. Beautiful "Elastic" Slide-Up using easeOutExpo
		Dim duration As Int = 300
		Dim startTop As Float = mBase.Height
		Dim targetTop As Float = mBase.Height - pnlWrapper.Height - miBottomGap
		Dim change As Float = targetTop - startTop
		Dim startTime As Long = DateTime.Now
        
		pnlWrapper.Top = startTop
		Do While DateTime.Now - startTime < duration
			Dim elapsed As Float = DateTime.Now - startTime
			' Easing driven by the B4XAnimation instance (Anim.easeOutExpo).
			pnlWrapper.Top = Anim.easeOutExpo(elapsed, startTop, change, duration)
			Sleep(0) ' Yield to UI thread to paint the frame
		Loop
		pnlWrapper.Top = targetTop
	Else
		pnlBackdrop.Color = xui.Color_ARGB(Round(mcBackdropOpacity * 255), 0, 0, 0)
		pnlWrapper.Top = mBase.Height - pnlWrapper.Height - miBottomGap
	End If
    
	mbIsOpen = True
    
	If xui.SubExists(mCallBack, mEventName & "_DidPresent", 0) Then
		CallSub(mCallBack, mEventName & "_DidPresent")
	End If
	Return True
End Sub

' Programmatic method to dismiss, passing payload and role
Public Sub Dismiss(Data As Object, Role As String) As ResumableSub
	If Not(mbIsOpen) Then Return False
    
	If xui.SubExists(mCallBack, mEventName & "_WillDismiss", 2) Then
		CallSub3(mCallBack, mEventName & "_WillDismiss", Data, Role)
	End If
    
	If mbAnimated Then
		' 1. Fade the backdrop out natively
		pnlBackdrop.SetColorAnimated(250, pnlBackdrop.Color, xui.Color_Transparent)
        
		' 2. Smooth slide-down using easeOutCubic for exit
		Dim duration As Int = 250
		Dim startTop As Float = pnlWrapper.Top
		Dim targetTop As Float = mBase.Height
		Dim change As Float = targetTop - startTop
		Dim startTime As Long = DateTime.Now
        
		Do While DateTime.Now - startTime < duration
			Dim elapsed As Float = DateTime.Now - startTime
			pnlWrapper.Top = Anim.easeOutCubic(elapsed, startTop, change, duration)
			Sleep(0)
		Loop
		pnlWrapper.Top = targetTop
	End If
    
	pnlBackdrop.RemoveViewFromParent
	mbIsOpen = False
    
	If xui.SubExists(mCallBack, mEventName & "_DidDismiss", 2) Then
		CallSub3(mCallBack, mEventName & "_DidDismiss", Data, Role)
	End If
	Return True
End Sub
#End Region

#Region Rendering & Mechanics
Private Sub BuildOverlay
	' Create Full-Screen Backdrop
	pnlBackdrop = xui.CreatePanel("Backdrop")
	mBase.AddView(pnlBackdrop, 0, 0, mBase.Width, mBase.Height)
    
	' Create Wrapper sliding pane
	pnlWrapper = xui.CreatePanel("")
	pnlWrapper.Color = xui.Color_Transparent
	pnlBackdrop.AddView(pnlWrapper, 0, mBase.Height, mBase.Width, 0) ' Height calculated later

	' Layout dimensions (ios insets the group; md is full width)
	Dim groupInset As Int = IIf(msMode = "ios", 10dip, 0)
	Dim groupWidth As Int = mBase.Width - IIf(msMode = "ios", 20dip, 0)
	' MD mode rounds only the TOP corners using the same radius as the modal
	' (rounded-box), leaving the bottom corners square so the sheet visually
	' anchors to the bottom edge of the screen. iOS keeps all four corners
	' rounded for its inset, detached-card look.
	Dim modalRadius As Float = B4XDaisyVariants.ResolveRoundedDip("rounded-box", 16dip)
	Dim groupRadius As Int = IIf(msMode = "ios", modalRadius, 0)
	Dim btnRowGap As Int = 4dip
	Dim groupPadding As Int = IIf(msMode = "ios", 12dip, 8dip)
	Dim btnHeight As Int = 0 ' Let DaisyButton size naturally from ButtonSize

	' Setup Layout based on OS Mode (ios detaches the cancel button)
	pnlGroup = xui.CreatePanel("")
	If msMode = "ios" Then
		pnlGroup.SetColorAndBorder(mcBackground, 0, 0, groupRadius)
	Else
		' MD: round top-left/top-right only (matches modal), keep bottom square
		B4XDaisyVariants.SetColorPerCornerRadius(pnlGroup, mcBackground, modalRadius, modalRadius, 0, 0)
	End If

	' Optional translucent (frosted glass) surface. Applies to BOTH ios and md
	' when Translucent=True. Default (False) keeps a solid mcBackground surface.
	If mbTranslucent Then
		If msMode = "ios" Then
			B4XDaisyVariants.ApplyGlassStyle(pnlGroup, groupRadius, "glass-md")
		Else
			' MD glass keeps the top-only rounding so the bottom stays flush to the screen.
			B4XDaisyVariants.ApplyGlassStylePerCorner(pnlGroup, modalRadius, modalRadius, 0, 0, "glass-md")
		End If
	End If

	Dim currentY As Int = groupPadding

	' Render Header (only when a non-blank header is provided)
	If msHeader <> "" Then
		Dim lblHead As Label
		LblHead.Initialize("")
		Dim xlblHead As B4XView = lblHead
		xlblHead.Text = msHeader
		xlblHead.TextColor = mcColor
		If mbHeaderBold Then
			xlblHead.Font = xui.CreateDefaultBoldFont(14)
		Else
			xlblHead.Font = xui.CreateDefaultFont(14)
		End If
		xlblHead.SetTextAlignment("CENTER", IIf(msMode = "ios", "CENTER", "LEFT"))
		pnlGroup.AddView(xlblHead, groupPadding, currentY, groupWidth - (groupPadding * 2), 30dip)
		currentY = currentY + 30dip
	End If

	' Render SubHeader (only when a non-blank sub-header is provided)
	If msSubHeader <> "" Then
		Dim lblSub As Label
		LblSub.Initialize("")
		Dim xlblSub As B4XView = lblSub
		xlblSub.Text = msSubHeader
		xlblSub.TextColor = mcColor
		xlblSub.Font = xui.CreateDefaultFont(12)
		xlblSub.SetTextAlignment("CENTER", IIf(msMode = "ios", "CENTER", "LEFT"))
		pnlGroup.AddView(xlblSub, groupPadding, currentY, groupWidth - (groupPadding * 2), 22dip)
		currentY = currentY + 22dip
	End If

	' Split buttons into Normal and Cancel
	Dim normalBtns As List : normalBtns.Initialize
	Dim cancelBtn As ActionSheetButton

	For Each btn As ActionSheetButton In mButtons
		If btn.Role = "cancel" Then
			cancelBtn = btn
		Else
			normalBtns.Add(btn)
		End If
	Next

	' Render Normal Buttons
	For i = 0 To normalBtns.Size - 1
		Dim btn As ActionSheetButton = normalBtns.Get(i)
		If i > 0 Then currentY = currentY + btnRowGap
		Dim h As Int = AddActionButton(pnlGroup, btn, groupPadding, currentY, groupWidth - (groupPadding * 2), btnHeight)
		currentY = currentY + h
	Next

	pnlGroup.Height = currentY + groupPadding
	pnlWrapper.AddView(pnlGroup, groupInset, 0, groupWidth, pnlGroup.Height)

	Dim totalWrapperHeight As Int = pnlGroup.Height + IIf(msMode = "ios", 20dip, 0)

	' Render Cancel Button (Detached in iOS, inline in MD)
	If cancelBtn.IsInitialized Then
		If msMode = "ios" Then
			pnlCancelGroup = xui.CreatePanel("")
			pnlCancelGroup.SetColorAndBorder(mcBackground, 0, 0, groupRadius)
			If mbTranslucent Then B4XDaisyVariants.ApplyGlassStyle(pnlCancelGroup, groupRadius, "glass-md")

			Dim ch As Int = AddActionButton(pnlCancelGroup, cancelBtn, groupPadding, groupPadding, groupWidth - (groupPadding * 2), btnHeight)
			pnlCancelGroup.Height = ch + (groupPadding * 2)

			pnlWrapper.AddView(pnlCancelGroup, groupInset, pnlGroup.Height + 8dip, groupWidth, pnlCancelGroup.Height)
			totalWrapperHeight = pnlGroup.Height + pnlCancelGroup.Height + 24dip
		Else
			If normalBtns.Size > 0 Then currentY = currentY + btnRowGap
			Dim ch As Int = AddActionButton(pnlGroup, cancelBtn, groupPadding, currentY, groupWidth - (groupPadding * 2), btnHeight)
			pnlGroup.Height = currentY + ch + groupPadding
			totalWrapperHeight = pnlGroup.Height
		End If
	End If

	pnlWrapper.Height = totalWrapperHeight
End Sub

' Builds a ghost-styled B4XDaisyButton (icon + text) for the action sheet row.
' Reuses the existing Daisy button + SvgIcon rendering so icons display correctly.
Private Sub AddActionButton(Parent As B4XView, BtnData As ActionSheetButton, Left As Int, Top As Int, Width As Int, Height As Int) As Int
	Dim btn As B4XDaisyButton
	btn.Initialize(Me, "ActionBtn")
	btn.setTag(BtnData)

	' Configure before AddToParent so DesignerCreateView picks up the values.
	' Style: Outline takes precedence, otherwise Ghosted (default) vs Solid.
	btn.Style = IIf(mbOutline, "outline", IIf(mbButtonGhosted, "ghost", "solid"))
	' Both iOS and MD buttons keep the DaisyButton default radius ("theme").
	' (TODO: a ripple/touch-feedback effect will be wired up here later.)
	btn.Size = msButtonSize
	btn.TextAlignment = msTextAlignment
	btn.Text = BtnData.Text
	If BtnData.Icon.Trim.Length > 0 Then btn.IconName = BtnData.Icon

	' Resolve the button color variant: an explicit per-button color wins,
	' otherwise destructive defaults to "error", otherwise the global ButtonColor.
	Dim btnVariant As String = BtnData.Variant
	If btnVariant.Trim.Length > 0 Then
		' explicit per-button color overrides everything
	Else If BtnData.Role = "destructive" Then
		btnVariant = "error"
	Else
		btnVariant = msButtonColor
	End If
	btn.Variant = btnVariant
	' By default the icon follows the button's variant fg color. An explicit
	' per-button icon color variant (SetButtonIconColor) recolors the icon only.
	If BtnData.IconVariant.Trim.Length > 0 Then
		btn.IconColor = B4XDaisyVariants.ResolveTextColor(BtnData.IconVariant, mcButtonColor)
	End If

	If BtnData.Disabled Then btn.Disabled = True

	btn.AddToParent(Parent, Left, Top, Width, Height)

	Return btn.GetComputedHeight
End Sub
#End Region

#Region Internal Event Handlers
Private Sub Backdrop_Click
	If mbBackdropDismiss Then
		Dismiss(Null, "backdrop")
	End If
End Sub

Private Sub ActionBtn_Click(Tag As Object)
	Dim btn As ActionSheetButton = Tag
	If btn = Null Then Return
	If btn.Disabled Then Return

	If xui.SubExists(mCallBack, mEventName & "_ButtonClick", 3) Then
		CallSub3(mCallBack, mEventName & "_ButtonClick", btn.Id, btn.Data)
	End If

	' Cancel automatically triggers dismiss, others rely on user code or standard close
	Dismiss(btn.Data, btn.Role)
End Sub

Private Sub HideKeyboard
	' Helper to abstract away keyboard closure (as per keyboardClose=true)
	' Usually implemented via the IME library:
	' Dim ime As IME
	' ime.HideKeyboard
End Sub

' Parses a backdrop opacity string into a 0..1 float.
' Accepts plain floats ("0.5") and percentages ("50%"). Falls back to DefaultVal.
Private Sub ParseOpacityFloat(Value As String, DefaultVal As Float) As Float
	Try
		Dim s As String = Value.Trim
		If s.Length = 0 Then Return DefaultVal
		If s.EndsWith("%") Then
			Dim pct As Float = s.SubString2(0, s.Length - 1)
			Return Max(0, Min(1, pct / 100))
		End If
		Dim f As Float = s
		Return Max(0, Min(1, f))
	Catch
		Return DefaultVal
	End Try
End Sub

Public Sub CreateView(Width As Int, Height As Int) As B4XView
	Dim p As Panel
	p.Initialize("")
	Dim b As B4XView = p
	b.Color = xui.Color_Transparent
	b.SetLayoutAnimated(0, 0, 0, Width, Height)
	Dim props As Map
	props.Initialize
	DesignerCreateView(b, Null, props)
	Return mBase
End Sub

Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
	If Parent.IsInitialized = False Then Return mBase
	If mBase.IsInitialized = False Then
		CreateView(Width, Height)
	End If
	Parent.AddView(mBase, Left, Top, Width, Height)
	Return mBase
End Sub

Public Sub setHeader(Value As String)
	msHeader = Value
End Sub

Public Sub getHeader As String
	Return msHeader
End Sub

Public Sub setSubHeader(Value As String)
	msSubHeader = Value
End Sub

Public Sub getSubHeader As String
	Return msSubHeader
End Sub

Public Sub setAnimated(Value As Boolean)
	mbAnimated = Value
End Sub

Public Sub getAnimated As Boolean
	Return mbAnimated
End Sub

Public Sub setBackdropDismiss(Value As Boolean)
	mbBackdropDismiss = Value
End Sub

Public Sub getBackdropDismiss As Boolean
	Return mbBackdropDismiss
End Sub

Public Sub setKeyboardClose(Value As Boolean)
	mbKeyboardClose = Value
End Sub

Public Sub getKeyboardClose As Boolean
	Return mbKeyboardClose
End Sub

Public Sub setTranslucent(Value As Boolean)
	mbTranslucent = Value
End Sub

Public Sub getTranslucent As Boolean
	Return mbTranslucent
End Sub

Public Sub setMode(Value As String)
	msMode = Value
End Sub

Public Sub getMode As String
	Return msMode
End Sub

Public Sub setOutline(Value As Boolean)
	mbOutline = Value
End Sub

Public Sub getOutline As Boolean
	Return mbOutline
End Sub

Public Sub setButtonSize(Value As String)
	msButtonSize = B4XDaisyVariants.NormalizeSize(Value)
End Sub

Public Sub getButtonSize As String
	Return msButtonSize
End Sub

Public Sub setTextAlignment(Value As String)
	msTextAlignment = Value
End Sub

Public Sub getTextAlignment As String
	Return msTextAlignment
End Sub

Public Sub setBackgroundColor(Value As String)
	msBackgroundColor = Value
	mcBackground = B4XDaisyVariants.ResolveBackgroundColorVariant(msBackgroundColor, mcBackground)
End Sub

Public Sub getBackgroundColor As String
	Return msBackgroundColor
End Sub

Public Sub setBackdropOpacity(Value As String)
	msBackdropOpacity = Value
	mcBackdropOpacity = ParseOpacityFloat(msBackdropOpacity, mcBackdropOpacity)
End Sub

Public Sub getBackdropOpacity As String
	Return msBackdropOpacity
End Sub

Public Sub setButtonsColor(Value As String)
	msButtonColor = Value
End Sub

Public Sub getButtonsColor As String
	Return msButtonColor
End Sub

Public Sub setTextColor(Value As String)
	msTextColor = Value
	mcColor = B4XDaisyVariants.ResolveTextColorVariant(msTextColor, mcColor)
End Sub

Public Sub getTextColor As String
	Return msTextColor
End Sub

Public Sub setHeaderBold(Value As Boolean)
	mbHeaderBold = Value
End Sub

Public Sub getHeaderBold As Boolean
	Return mbHeaderBold
End Sub

Public Sub setButtonGhosted(Value As Boolean)
	mbButtonGhosted = Value
End Sub

Public Sub getButtonGhosted As Boolean
	Return mbButtonGhosted
End Sub
#End Region

---

## B4XDaisyAccordion.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
#IgnoreWarnings:12,9

#Region Events
#Event: Change (ActiveTag As Object, Status As Boolean)
#End Region

#Region Designer Properties
#DesignerProperty: Key: OpenOnlyOne, DisplayName: Open Only One, FieldType: Boolean, DefaultValue: True, Description: If True, only one collapse can be open at a time.
#DesignerProperty: Key: IconPosition, DisplayName: Icon Position, FieldType: String, DefaultValue: right, List: left|right, Description: Default icon position for all children.
#DesignerProperty: Key: Icon, DisplayName: Icon, FieldType: String, DefaultValue: arrow, List: none|arrow|plus, Description: Expansion indicator icon for all children.
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: Visible state.
#DesignerProperty: Key: SpaceY, DisplayName: Space Y, FieldType: Int, DefaultValue: 2, MinRange: 0, MaxRange: 32, Description: Vertical gap (in dip) between collapse items.
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, List: none|xs|sm|md|lg|xl|2xl, Description: Elevation level applied to all children.
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: theme, List: theme|rounded-none|rounded-sm|rounded|rounded-md|rounded-lg|rounded-xl|rounded-2xl|rounded-3xl|rounded-full, Description: Radius mode applied to all children.
#DesignerProperty: Key: GroupName, DisplayName: Group Name, FieldType: String, DefaultValue: , Description: Explicit group name shared by all child collapses (used for single-open enforcement). Leave empty to auto-generate from component tag.
#End Region

#Region Variables
Sub Class_Globals
    Private xui As XUI
    Public mBase As B4XView
    Private mEventName As String
    Private mCallBack As Object
    Private mTag As Object
    Private s As String
    
    ' Local properties
    Private mbOpenOnlyOne As Boolean = True
    Private msIconPosition As String = "right"
    Private msIcon As String = "arrow"
    Private mbVisible As Boolean = True
    Private miSpaceY As Int = 2dip
    Private msShadow As String = "none"
    Private msRounded As String = "theme"
    
    ' Internal management
    Private mItems As List
    Private mIsRefreshing As Boolean = False
    Private msGroupName As String = ""
End Sub
#End Region

#Region Initialization
''' <summary>
''' Initializes the component.
''' </summary>
Public Sub Initialize(Callback As Object, EventName As String)
    mCallBack = Callback
    mEventName = EventName
    mItems.Initialize
End Sub

''' <summary>
''' Designer entry point.
''' </summary>
Public Sub DesignerCreateView(Base As Object, Lbl As Label, Props As Map)
    mBase = Base
    If mTag = Null Then mTag = mBase.Tag
    mBase.Tag = Me
    mBase.Color = xui.Color_Transparent
    
    ' Load properties
    mbOpenOnlyOne = B4XDaisyVariants.GetPropBool(Props, "OpenOnlyOne", mbOpenOnlyOne)
    msIconPosition = B4XDaisyVariants.GetPropString(Props, "IconPosition", msIconPosition).ToLowerCase
    msIcon = B4XDaisyVariants.GetPropString(Props, "Icon", msIcon).ToLowerCase
    If Props.ContainsKey("Visible") Then
        mbVisible = B4XDaisyVariants.GetPropBool(Props, "Visible", mbVisible)
    Else
        mbVisible = B4XDaisyVariants.GetPropBool(Props, "Visibility", mbVisible)
    End If
    miSpaceY = DipToCurrent(B4XDaisyVariants.GetPropInt(Props, "SpaceY", 2))
    msShadow = B4XDaisyVariants.NormalizeShadow(B4XDaisyVariants.GetPropString(Props, "Shadow", msShadow))
    msRounded = B4XDaisyVariants.NormalizeRounded(B4XDaisyVariants.GetPropString(Props, "Rounded", msRounded))
    msGroupName = B4XDaisyVariants.GetPropString(Props, "GroupName", msGroupName)
    
    Refresh
End Sub
#End Region


#Region Public API
''' <summary>
''' Forces the component to re-evaluate its styling against the currently active global Theme.
''' </summary>
Public Sub UpdateTheme
    If mBase.IsInitialized = False Then Return
    For Each item As B4XDaisyCollapse In mItems
        item.UpdateTheme
    Next
    Refresh
End Sub

''' <summary>
''' Renders/Refreshes the component state.
''' </summary>
Public Sub Refresh
    If mBase.IsInitialized = False Then Return
    ' Guard against re-entry: child setters trigger Collapse.Refresh which
    ' calls acc.Refresh via UpdateOpenState parent detection
    If mIsRefreshing Then Return
    mIsRefreshing = True
    mBase.Visible = mbVisible
    
    ' Apply shared properties to existing children
    For Each item As B4XDaisyCollapse In mItems
        item.setIconPosition(msIconPosition)
        item.setIcon(msIcon)
        item.setShadow(msShadow)
        item.setRounded(msRounded)
    Next
    
    Base_Resize(mBase.Width, mBase.Height)
    mIsRefreshing = False
End Sub

''' <summary>
''' Adds a collapse item to the accordion.
''' </summary>
Public Sub AddItem(Item As B4XDaisyCollapse)
    If mItems.IndexOf(Item) = -1 Then
        mItems.Add(Item)
        Dim gn As String = msGroupName
        If gn.Length = 0 Then gn = "group_" & mBase.Tag
        Item.setGroupName(gn)
        Item.setIconPosition(msIconPosition)
        Item.setIcon(msIcon)
        Item.setShadow(msShadow)
        Item.setRounded(msRounded)
        ' ensure the collapse view is a child of our base panel
        ' Note: the item's internal mBase may not yet be initialized (it happens only when the
        ' component is first added to a parent).  The previous implementation accessed
        ' Item.mBase.Parent.IsInitialized directly which throws when mBase itself is not
        ' initialized.  Guard against that and create the view if needed.
        If Item.mBase.IsInitialized = False Then
            ' first add the item to our base using its public helper which will initialize the
            ' view and respect any width/height specs set on the item.
            Item.AddToParent(mBase, 0, 0, 1dip, 1dip)
        Else
            Dim parent As B4XView = Item.mBase.Parent
            If parent.IsInitialized = False Or parent <> mBase Then
                ' preserve item's width/height if already set
                Dim w As Int = Item.mBase.Width
                Dim h As Int = Item.mBase.Height
                mBase.AddView(Item.mBase, 0, 0, Max(1dip, w), Max(1dip, h))
            End If
        End If
        ' reposition all items
        Refresh
    End If
End Sub

''' <summary>
''' Handles child request to open. If OpenOnlyOne is true, others are closed.
''' </summary>
Public Sub HandleChildRequestOpen(RequestedChild As B4XDaisyCollapse)
    ' ignore requests from children that are closing
    If RequestedChild.getOpen = False Then Return
    If mbOpenOnlyOne Then
        For Each item As B4XDaisyCollapse In mItems
            If item <> RequestedChild Then
                item.setOpen(False)
            End If
        Next
    End If
    ' layout may need updating (opened child height changed)
    Refresh
    If xui.SubExists(mCallBack, mEventName & "_Change", 2) Then
        CallSub3(mCallBack, mEventName & "_Change", RequestedChild.getTag, RequestedChild.getOpen)
    End If
End Sub

''' <summary>
''' Adds the component to a parent B4XView.
''' </summary>
Public Sub AddToParent(Parent As B4XView, Left As Int, Top As Int, Width As Int, Height As Int) As B4XView
    If Parent.IsInitialized = False Then Return mBase
    If mBase.IsInitialized = False Then
        Dim p As Panel
        p.Initialize("mBase")
        DesignerCreateView(p, Null, CreateMap( _
            "OpenOnlyOne": mbOpenOnlyOne, _
            "IconPosition": msIconPosition, _
            "Icon": msIcon, _
            "Visible": mbVisible, _
            "SpaceY": miSpaceY / 1dip, _
            "Shadow": msShadow, _
            "Rounded": msRounded, _
            "GroupName": msGroupName _
        ))
    End If
    Parent.AddView(mBase, Left, Top, Width, Height)
    Return mBase
End Sub

Public Sub setOpenOnlyOne(Value As Boolean)
    mbOpenOnlyOne = Value
End Sub

Public Sub getOpenOnlyOne As Boolean
    Return mbOpenOnlyOne
End Sub

Public Sub setIconPosition(Value As String)
    msIconPosition = Value.ToLowerCase
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIconPosition As String
	Return msIconPosition
End Sub

Public Sub setIcon(Value As String)
    msIcon = Value.ToLowerCase
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getIcon As String
    Return msIcon
End Sub

Public Sub setSpaceY(Value As Int)
    miSpaceY = DipToCurrent(Value)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getSpaceY As Int
    Return miSpaceY
End Sub

Public Sub setShadow(Value As String)
    msShadow = B4XDaisyVariants.NormalizeShadow(Value)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getShadow As String
    Return msShadow
End Sub

Public Sub setRounded(Value As String)
    msRounded = B4XDaisyVariants.NormalizeRounded(Value)
    If mBase.IsInitialized Then Refresh
End Sub

Public Sub getRounded As String
    Return msRounded
End Sub

''' <summary>
''' Sets the component tag.
''' </summary>
Public Sub setTag(Value As Object)
    mTag = Value
End Sub

''' <summary>
''' Gets the component tag.
''' </summary>
Public Sub getTag As Object
    Return mTag
End Sub

''' <summary>
''' Sets the explicit group name shared by all child collapses.
''' Leave empty to auto-generate from the component tag.
''' </summary>
Public Sub setGroupName(Value As String)
    msGroupName = Value
    ' Re-apply group name to all existing items
    If mBase.IsInitialized = False Then Return
    Dim gn As String = msGroupName
    If gn.Length = 0 Then gn = "group_" & mBase.Tag
    For Each item As B4XDaisyCollapse In mItems
        item.setGroupName(gn)
    Next
End Sub

Public Sub getGroupName As String
    Return msGroupName
End Sub

''' <summary>
''' Creates a fully initialised B4XDaisyCollapse, adds it to the accordion and returns it.
''' The returned item can be used to add content via getContentView / RefreshContent.
''' </summary>
Public Sub AddItemBasic(ItemTag As Object, Icon As String, Title As String) As B4XDaisyCollapse
    Dim c As B4XDaisyCollapse
    c.Initialize(mCallBack, mEventName)
    c.setTag(ItemTag)
    c.TitleText = Title
    If Icon.Length > 0 Then c.Icon = Icon
    AddItem(c)
    Return c
End Sub

''' <summary>
''' Finds a child collapse whose tag equals the given value. Returns an uninitialised collapse if not found.
''' </summary>
Private Sub FindItemByTag(ItemTag As Object) As B4XDaisyCollapse
    Dim empty As B4XDaisyCollapse
    For Each item As B4XDaisyCollapse In mItems
        If item.getTag = ItemTag Then Return item
    Next
    Return empty
End Sub

''' <summary>
''' Opens or closes the item identified by ItemTag.
''' </summary>
Public Sub SetItemActive(ItemTag As Object, Value As Boolean)
    Dim item As B4XDaisyCollapse = FindItemByTag(ItemTag)
    If item.mBase.IsInitialized = False Then Return
    item.setOpen(Value)
End Sub

''' <summary>
''' Sets the title text of the item identified by ItemTag.
''' </summary>
Public Sub SetItemTitle(ItemTag As Object, Title As String)
    Dim item As B4XDaisyCollapse = FindItemByTag(ItemTag)
    If item.mBase.IsInitialized = False Then Return
    item.setTitleText(Title)
End Sub

''' <summary>
''' Sets the variant of the item identified by ItemTag.
''' </summary>
Public Sub SetItemVariant(ItemTag As Object, Variant As String)
    Dim item As B4XDaisyCollapse = FindItemByTag(ItemTag)
    If item.mBase.IsInitialized = False Then Return
    item.setVariant(Variant)
End Sub

''' <summary>
''' Sets the title icon (SVG asset name) of the item identified by ItemTag.
''' </summary>
Public Sub SetItemTitleIcon(ItemTag As Object, IconName As String)
    Dim item As B4XDaisyCollapse = FindItemByTag(ItemTag)
    If item.mBase.IsInitialized = False Then Return
    item.setTitleIconName(IconName)
End Sub

''' <summary>
''' Shows or hides the item identified by ItemTag.
''' </summary>
Public Sub SetItemVisible(ItemTag As Object, Value As Boolean)
    Dim item As B4XDaisyCollapse = FindItemByTag(ItemTag)
    If item.mBase.IsInitialized = False Then Return
    item.setVisible(Value)
End Sub

''' <summary>
''' Returns the current rendered height of the accordion (sum of all items + spacing).
''' </summary>
Public Sub GetComputedHeight As Int
    If mBase.IsInitialized = False Then Return 0
    Return mBase.Height
End Sub
#End Region

#Region Base Events
Public Sub Base_Resize(Width As Double, Height As Double)
    If mBase.IsInitialized = False Then Return
    ' Accordion is a vertical stack of its children.
    ' Ensure they fit the width and auto-size the accordion height.
    Dim currentY As Int = 0
    For i = 0 To mBase.NumberOfViews - 1
        Dim v As B4XView = mBase.GetView(i)
        If v.Tag Is B4XDaisyCollapse Then
            v.SetLayoutAnimated(0, 0, currentY, Width, v.Height)
            ' Programmatic custom views don't auto-fire Base_Resize on SetLayoutAnimated,
            ' so explicitly tell the Collapse to re-render at the new width (draws icons etc.)
            Dim col As B4XDaisyCollapse = v.Tag
            col.Base_Resize(Width, v.Height)
            currentY = currentY + v.Height + miSpaceY
        End If
    Next
    ' Auto-shrink accordion to fit contents and shift page siblings by the delta
    If currentY > 0 Then
        Dim prevH As Int = mBase.Height
        mBase.Height = currentY
        B4XDaisyVariants.ShiftSiblingsBelow(mBase, currentY - prevH, 200)
    End If
End Sub
#End Region

#Region Cleanup
Public Sub RemoveViewFromParent
	If mBase.IsInitialized Then mBase.RemoveViewFromParent
End Sub
#End Region


#Region B4XView Layout Wrapper Methods
Public Sub SetLayoutAnimated(Duration As Int, Left As Int, Top As Int, Width As Int, Height As Int)
	If mBase.IsInitialized Then mBase.SetLayoutAnimated(Duration, Left, Top, Width, Height)
End Sub

Public Sub setLeft(Value As Int)
	If mBase.IsInitialized Then mBase.Left = Value
End Sub

Public Sub getLeft As Int
	If mBase.IsInitialized Then Return mBase.Left
	Return 0
End Sub

Public Sub setTop(Value As Int)
	If mBase.IsInitialized Then mBase.Top = Value
End Sub

Public Sub getTop As Int
	If mBase.IsInitialized Then Return mBase.Top
	Return 0
End Sub

Public Sub setWidth(Value As Int)
	If mBase.IsInitialized Then mBase.Width = Value
End Sub

Public Sub getWidth As Int
	If mBase.IsInitialized Then Return mBase.Width
	Return 0
End Sub

Public Sub setHeight(Value As Int)
	If mBase.IsInitialized Then mBase.Height = Value
End Sub

Public Sub getHeight As Int
	If mBase.IsInitialized Then Return mBase.Height
	Return 0
End Sub

#End Region

#Region B4XView Z-Order and Visibility Methods
Public Sub BringToFront
	If mBase.IsInitialized Then mBase.BringToFront
End Sub

Public Sub SendToBack
	If mBase.IsInitialized Then mBase.SendToBack
End Sub

Public Sub setVisible(Value As Boolean)
	If mBase.IsInitialized Then mBase.Visible = Value
End Sub

Public Sub getVisible As Boolean
	If mBase.IsInitialized Then Return mBase.Visible
	Return False
End Sub

#End Region

---

## B4XAnimation.bas

﻿B4A=true
Group=Default Group\DaisyUIKit
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@

#IgnoreWarnings:12,9
Sub Class_Globals
End Sub

Public Sub Initialize
End Sub

Public Sub SetNativeAlpha(v As B4XView, AlphaValue As Float)
	#If B4A
	Dim jo As JavaObject = v
	jo.RunMethod("setAlpha", Array As Object(AlphaValue))
	#Else
	Dim ignore As Object = v
	Dim ignore2 As Float = AlphaValue
	#End If
End Sub

Public Sub SetNativeRotation(v As B4XView, Degrees As Float)
	#If B4A
	Dim jo As JavaObject = v
	jo.RunMethod("setRotation", Array As Object(Degrees))
	#Else
	Dim ignore As Object = v
	Dim ignore2 As Float = Degrees
	#End If
End Sub

Public Sub SetNativeRotationY(v As B4XView, Degrees As Float)
	#If B4A
	Dim jo As JavaObject = v
	jo.RunMethod("setRotationY", Array As Object(Degrees))
	#Else
	Dim ignore As Object = v
	Dim ignore2 As Float = Degrees
	#End If
End Sub

Public Sub AnimateLayerNative(v As B4XView, AlphaValue As Float, Degrees As Float, DegreesY As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	Dim safeDuration As Long = Max(0, DurationMs)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(safeDuration))
	Catch
	End Try 'ignore
	anim.RunMethod("alpha", Array As Object(AlphaValue))
	anim.RunMethod("rotation", Array As Object(Degrees))
	anim.RunMethod("rotationY", Array As Object(DegreesY))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	Dim ignore1 As Float = AlphaValue
	Dim ignore2 As Float = Degrees
	Dim ignore3 As Float = DegreesY
	Dim ignore4 As Int = DurationMs
	#End If
End Sub

Public Sub SetNativeCameraDistance(v As B4XView, DistancePx As Float)
	#If B4A
	Dim jo As JavaObject = v
	Dim safeDistance As Float = Max(0, DistancePx)
	Try
		jo.RunMethod("setCameraDistance", Array As Object(safeDistance))
	Catch
	End Try 'ignore
	#Else
	Dim ignore As Object = v
	Dim ignore2 As Float = DistancePx
	#End If
End Sub

'Sets the translationX property of a view without animation.
Public Sub SetTranslationX(v As B4XView, TranslationXPx As Float)
	#If B4A
	Dim jo As JavaObject = v
	jo.RunMethod("setTranslationX", Array As Object(TranslationXPx))
	#Else
	Dim ignore As Object = v
	Dim ignore2 As Float = TranslationXPx
	#End If
End Sub

'Animates the translationX property using ViewPropertyAnimator (hardware-accelerated).
Public Sub AnimateTranslationX(v As B4XView, TranslationXPx As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try 'ignore
	anim.RunMethod("translationX", Array As Object(TranslationXPx))
	anim.RunMethod("setInterpolator", Array(CreateLinearInterpolator))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	Dim ignore1 As Float = TranslationXPx
	Dim ignore2 As Int = DurationMs
	#End If
End Sub

Private Sub CreateLinearInterpolator As JavaObject
	Dim li As JavaObject
	li.InitializeNewInstance("android.view.animation.LinearInterpolator", Null)
	Return li
End Sub


Public Sub linearTween (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return ChangeInValue  * Time/ Duration + Start
End Sub
		
'// quadratic easing in - accelerating from zero velocity


Public Sub easeInQuad (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return ChangeInValue  * Time*Time + Start
End Sub
		

'// quadratic easing out - decelerating To zero velocity


Public Sub easeOutQuad (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return - ChangeInValue * Time*(Time-2) + Start
End Sub

		

'// quadratic easing in/out - acceleration Until halfway, Then deceleration


Public Sub easeInOutQuad (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / (Duration / 2)
	If (Time < 1) Then Return ChangeInValue  /2*Time*Time + Start
	Time = Time - 1
	Return - ChangeInValue/2 * (Time*(Time-2) - 1) + Start
End Sub


'// cubic easing in - accelerating from zero velocity


Public Sub easeInCubic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return ChangeInValue  * Time*Time*Time + Start
End Sub

		

'// cubic easing out - decelerating To zero velocity


Public Sub easeOutCubic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Time = Time -1
	Return ChangeInValue  * (Time*Time*Time + 1) + Start
End Sub

		

'// cubic easing in/out - acceleration Until halfway, Then deceleration


Public Sub easeInOutCubic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / (Duration / 2)
	If (Time < 1) Then Return ChangeInValue  /2*Time*Time*Time + Start
	Time = Time + 2
	Return ChangeInValue  /2*(Time*Time*Time + 2) + Start
End Sub
	

'// quartic easing in - accelerating from zero velocity


Public Sub easeInQuart (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return ChangeInValue  * Time*Time*Time*Time + Start
End Sub

		

'// quartic easing out - decelerating To zero velocity


Public Sub easeOutQuart (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Time = Time -1
	Return - ChangeInValue * (Time*Time*Time*Time - 1) + Start
End Sub

		

'// quartic easing in/out - acceleration Until halfway, Then deceleration


Public Sub easeInOutQuart (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time /(Duration / 2)
	If (Time < 1) Then Return ChangeInValue  /2 * Time * Time * Time * Time + Start
	Time = Time -2
	Return - ChangeInValue/2 * (Time*Time*Time*Time - 2) + Start
End Sub


'// quintic easing in - accelerating from zero velocity


Public Sub easeInQuint (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return ChangeInValue  * Time*Time*Time*Time*Time + Start
End Sub

		

'// quintic easing out - decelerating To zero velocity


Public Sub easeOutQuint (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Time = Time -1
	Return ChangeInValue  * (Time * Time*Time * Time * Time + 1) + Start
End Sub

		

'// quintic easing in/out - acceleration Until halfway, Then deceleration


Public Sub easeInOutQuint (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / (Duration / 2)
	If (Time < 1) Then Return ChangeInValue /2 * Time * Time * Time * Time * Time + Start
	Time = Time -2
	Return ChangeInValue  /2 * (Time * Time * Time * Time * Time + 2) + Start
End Sub
		

'// sinusoidal easing in - accelerating from zero velocity


Public Sub easeInSine (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return - ChangeInValue * Cos(Time/ Duration * (cPI/2)) + ChangeInValue + Start
End Sub

		

'// sinusoidal easing out - decelerating To zero velocity


Public Sub easeOutSine (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return ChangeInValue  * Sin(Time/ Duration * (cPI/2)) + Start
End Sub

		

'// sinusoidal easing in/out - accelerating Until halfway, Then decelerating


Public Sub easeInOutSine (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return - ChangeInValue/2 * (Cos(cPI*Time/ Duration) - 1) + Start
End Sub

		

'// exponential easing in - accelerating from zero velocity


Public Sub easeInExpo (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return ChangeInValue  * Power( 2, 10 * (Time/ Duration - 1) ) + Start
End Sub

		

'// exponential easing out - decelerating To zero velocity


Public Sub easeOutExpo (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return ChangeInValue  * ( -Power( 2, -10 * Time/ Duration ) + 1 ) + Start
End Sub

		

'// exponential easing in/out - accelerating Until halfway, Then decelerating


Public Sub easeInOutExpo (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time /(Duration / 2)
	If (Time < 1) Then Return ChangeInValue  /2 * Power( 2, 10 * (Time - 1) ) + Start
	Time = Time -1
	Return ChangeInValue  /2 * ( -Power( 2, -10 * Time) + 2 ) + Start
End Sub
		

'// circular easing in - accelerating from zero velocity


Public Sub easeInCirc (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Return - ChangeInValue * (Sqrt(1 - Time * Time) - 1) + Start
End Sub

		

'// circular easing out - decelerating To zero velocity


Public Sub easeOutCirc (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / Duration
	Time = Time -1
	Return ChangeInValue  * Sqrt(1 - Time * Time) + Start
End Sub

'// circular easing in/out - acceleration Until halfway, Then deceleration

Public Sub easeInOutCirc (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time =  Time / (Duration / 2)
	If (Time < 1) Then Return - ChangeInValue/2 * (Sqrt(1 - Time * Time) - 1) + Start
	Time = Time -2
	Return ChangeInValue  /2 * (Sqrt(1 - Time * Time) + 1) + Start
End Sub

'// back easing in - overshoots starting coordinate
Public Sub easeInBack (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Dim s As Float = 1.70158
	Time = Time / Duration
	Return ChangeInValue * Time * Time * ((s + 1) * Time - s) + Start
End Sub

'// back easing out - overshoots target coordinate
Public Sub easeOutBack (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Dim s As Float = 1.70158
	Time = (Time / Duration) - 1
	Return ChangeInValue * (Time * Time * ((s + 1) * Time + s) + 1) + Start
End Sub

'// back easing in/out - overshoots on both ends
Public Sub easeInOutBack (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Dim s As Float = 1.70158 * 1.525
	Time = Time / (Duration / 2)
	If Time < 1 Then Return ChangeInValue / 2 * (Time * Time * ((s + 1) * Time - s)) + Start
	Time = Time - 2
	Return ChangeInValue / 2 * (Time * Time * ((s + 1) * Time + s) + 2) + Start
End Sub

'// bounce easing out - bounces settling at the target
Public Sub easeOutBounce (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Time = Time / Duration
	If Time < (1 / 2.75) Then
		Return ChangeInValue * (7.5625 * Time * Time) + Start
	Else If Time < (2 / 2.75) Then
		Time = Time - (1.5 / 2.75)
		Return ChangeInValue * (7.5625 * Time * Time + 0.75) + Start
	Else If Time < (2.5 / 2.75) Then
		Time = Time - (2.25 / 2.75)
		Return ChangeInValue * (7.5625 * Time * Time + 0.9375) + Start
	Else
		Time = Time - (2.625 / 2.75)
		Return ChangeInValue * (7.5625 * Time * Time + 0.984375) + Start
	End If
End Sub

'// bounce easing in - bounces before starting
Public Sub easeInBounce (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	Return ChangeInValue - easeOutBounce(Duration - Time, 0, ChangeInValue, Duration) + Start
End Sub

'// bounce easing in/out - bounces on both ends
Public Sub easeInOutBounce (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	If Time < Duration / 2 Then
		Return easeInBounce(Time * 2, 0, ChangeInValue, Duration) * 0.5 + Start
	Else
		Return easeOutBounce(Time * 2 - Duration, 0, ChangeInValue, Duration) * 0.5 + ChangeInValue * 0.5 + Start
	End If
End Sub

'// elastic easing in - snaps at start
Public Sub easeInElastic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	If Time = 0 Then Return Start
	Time = Time / Duration
	If Time = 1 Then Return Start + ChangeInValue
    
	Dim p As Float = Duration * 0.3
	Dim a As Float = ChangeInValue
	Dim s As Float = p / 4
    
	Time = Time - 1
	Return -(a * Power(2, 10 * Time) * Sin((Time * Duration - s) * (2 * cPI) / p)) + Start
End Sub

'// elastic easing out - snaps at target (Crucial for the Floating Drawer and BoomMenu effects)
Public Sub easeOutElastic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	If Time = 0 Then Return Start
	Time = Time / Duration
	If Time = 1 Then Return Start + ChangeInValue
    
	Dim p As Float = Duration * 0.3
	Dim a As Float = ChangeInValue
	Dim s As Float = p / 4
    
	Return a * Power(2, -10 * Time) * Sin((Time * Duration - s) * (2 * cPI) / p) + ChangeInValue + Start
End Sub

'// elastic easing in/out - snaps on both ends
Public Sub easeInOutElastic (Time As Float, Start As Float, ChangeInValue As Float, Duration As Int) As Float
	If Time = 0 Then Return Start
	Time = Time / (Duration / 2)
	If Time = 2 Then Return Start + ChangeInValue
    
	Dim p As Float = Duration * (0.3 * 1.5)
	Dim a As Float = ChangeInValue
	Dim s As Float = p / 4
    
	Time = Time - 1
	If Time < 0 Then
		Return -0.5 * (a * Power(2, 10 * Time) * Sin((Time * Duration - s) * (2 * cPI) / p)) + Start
	End If
	Return a * Power(2, -10 * Time) * Sin((Time * Duration - s) * (2 * cPI) / p) * 0.5 + ChangeInValue + Start
End Sub

'Animates the translationY property using ViewPropertyAnimator (hardware-accelerated).
Public Sub AnimateTranslationY(v As B4XView, TranslationYPx As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try 'ignore
	anim.RunMethod("translationY", Array As Object(TranslationYPx))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	Dim ignore1 As Float = TranslationYPx
	Dim ignore2 As Int = DurationMs
	#End If
End Sub

'Animates both X and Y translation simultaneously.
Public Sub AnimateTranslationXY(v As B4XView, TransX As Float, TransY As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try
	anim.RunMethod("translationX", Array As Object(TransX))
	anim.RunMethod("translationY", Array As Object(TransY))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Sets the pivot point for Rotation and Scale effects. (0,0) is top-left corner.
Public Sub SetNativePivot(v As B4XView, PivotX As Float, PivotY As Float)
	#If B4A
	Dim jo As JavaObject = v
	jo.RunMethod("setPivotX", Array As Object(PivotX))
	jo.RunMethod("setPivotY", Array As Object(PivotY))
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Animates the hardware ScaleX and ScaleY properties. Normal scale is 1.0.
Public Sub AnimateScale(v As B4XView, ScaleX As Float, ScaleY As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try
	anim.RunMethod("scaleX", Array As Object(ScaleX))
	anim.RunMethod("scaleY", Array As Object(ScaleY))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Animates hardware RotationX (Vertical Flip).
Public Sub AnimateRotationX(v As B4XView, DegreesX As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try
	anim.RunMethod("rotationX", Array As Object(DegreesX))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Animates hardware RotationY (Horizontal Flip).
Public Sub AnimateRotationY(v As B4XView, DegreesY As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try
	anim.RunMethod("rotationY", Array As Object(DegreesY))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Animates multiple hardware properties simultaneously to mimic complex ViewTransforms.
Public Sub AnimateExtended(v As B4XView, Alpha As Float, TransX As Float, TransY As Float, ScaleX As Float, ScaleY As Float, RotX As Float, RotY As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject = jo.RunMethodJO("animate", Null)
	anim.RunMethod("cancel", Null)
	Try
		anim.RunMethod("setDuration", Array As Object(Max(0, DurationMs)))
	Catch
	End Try
	anim.RunMethod("alpha", Array As Object(Alpha))
	anim.RunMethod("translationX", Array As Object(TransX))
	anim.RunMethod("translationY", Array As Object(TransY))
	anim.RunMethod("scaleX", Array As Object(ScaleX))
	anim.RunMethod("scaleY", Array As Object(ScaleY))
	anim.RunMethod("rotationX", Array As Object(RotX))
	anim.RunMethod("rotationY", Array As Object(RotY))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "Tada"
Public Sub AnimateTada(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 10
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Step 1: Shrink and rotate slightly left
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(0.9))
	anim.RunMethod("scaleY", Array As Object(0.9))
	anim.RunMethod("rotation", Array As Object(-3.0))
	anim.RunMethod("setDuration", Array As Object(stepTime * 2))
	anim.RunMethod("start", Null)
	Sleep(stepTime * 2)
	
	' Step 2: Pop out and wobble
	For i = 1 To 6
		Dim rot As Float = 3.0
		If i Mod 2 = 0 Then rot = -3.0
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("scaleX", Array As Object(1.1))
		anim.RunMethod("scaleY", Array As Object(1.1))
		anim.RunMethod("rotation", Array As Object(rot))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	
	' Step 3: Return to normal
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.0))
	anim.RunMethod("scaleY", Array As Object(1.0))
	anim.RunMethod("rotation", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(stepTime * 2))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "RubberBand"
Public Sub AnimateRubberBand(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 6
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.25))
	anim.RunMethod("scaleY", Array As Object(0.75))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(0.75))
	anim.RunMethod("scaleY", Array As Object(1.25))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.15))
	anim.RunMethod("scaleY", Array As Object(0.85))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(0.95))
	anim.RunMethod("scaleY", Array As Object(1.05))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.05))
	anim.RunMethod("scaleY", Array As Object(0.95))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.0))
	anim.RunMethod("scaleY", Array As Object(1.0))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "Shake"
Public Sub AnimateShake(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 10
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	For i = 1 To 10
		Dim offset As Float = 10dip
		If i Mod 2 = 0 Then offset = -10dip
		If i = 10 Then offset = 0 ' Settle back to center on last frame
		
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("translationX", Array As Object(offset))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "Wobble"
Public Sub AnimateWobble(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 6
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	Dim w As Float = v.Width
	
	' Define keyframes for TranslationX and Rotation
	Dim tx() As Float = Array As Float(-0.25 * w, 0.2 * w, -0.15 * w, 0.1 * w, -0.05 * w, 0)
	Dim rot() As Float = Array As Float(-5, 3, -3, 2, -1, 0)
	
	For i = 0 To 5
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("translationX", Array As Object(tx(i)))
		anim.RunMethod("rotation", Array As Object(rot(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "Pulse"
Public Sub AnimatePulse(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 2
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.05))
	anim.RunMethod("scaleY", Array As Object(1.05))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	Sleep(stepTime)
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.0))
	anim.RunMethod("scaleY", Array As Object(1.0))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Pushes the current view off the screen to the left
Public Sub AnimateSlidePushLeftOut(v As B4XView, ScreenWidth As Int, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(1.0 * -ScreenWidth))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Slides the new view in from the right edge
Public Sub AnimateSlidePushLeftIn(v As B4XView, ScreenWidth As Int, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Snap to starting position off-screen right
	jo.RunMethod("setTranslationX", Array As Object(1.0 * ScreenWidth))
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Shrinks and slides the current view out
Public Sub AnimateZoomSlideOut(v As B4XView, ScreenWidth As Int, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(-0.5 * ScreenWidth))
	anim.RunMethod("scaleX", Array As Object(0.8))
	anim.RunMethod("scaleY", Array As Object(0.8))
	anim.RunMethod("alpha", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Expands and slides the new view in
Public Sub AnimateZoomSlideIn(v As B4XView, ScreenWidth As Int, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Snap to starting state
	jo.RunMethod("setTranslationX", Array As Object(0.5 * ScreenWidth))
	jo.RunMethod("setScaleX", Array As Object(0.8))
	jo.RunMethod("setScaleY", Array As Object(0.8))
	jo.RunMethod("setAlpha", Array As Object(0.0))
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(0.0))
	anim.RunMethod("scaleX", Array As Object(1.0))
	anim.RunMethod("scaleY", Array As Object(1.0))
	anim.RunMethod("alpha", Array As Object(1.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Drops the current view into the background
Public Sub AnimateStackOut(v As B4XView, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(0.85))
	anim.RunMethod("scaleY", Array As Object(0.85))
	anim.RunMethod("alpha", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub


' Swings the current view out like a door turning left
Public Sub AnimateCubeLeftOut(v As B4XView, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Pivot on the right edge
	jo.RunMethod("setPivotX", Array As Object(1.0 * v.Width))
	jo.RunMethod("setPivotY", Array As Object(v.Height / 2.0))
	Try
		jo.RunMethod("setCameraDistance", Array As Object(12000.0))
	Catch
	End Try
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("rotationY", Array As Object(-90.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Swings the new view in like a door closing from the right
Public Sub AnimateCubeLeftIn(v As B4XView, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Pivot on the left edge and start rotated 90 degrees away
	jo.RunMethod("setPivotX", Array As Object(0.0))
	jo.RunMethod("setPivotY", Array As Object(v.Height / 2.0))
	jo.RunMethod("setRotationY", Array As Object(90.0))
	Try
		jo.RunMethod("setCameraDistance", Array As Object(12000.0))
	Catch
	End Try
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("rotationY", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Squeezes the current view flat to the left
Public Sub AnimateAccordionOut(v As B4XView, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	jo.RunMethod("setPivotX", Array As Object(0.0))
	jo.RunMethod("setPivotY", Array As Object(v.Height / 2.0))
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Stretches the new view open from the right
Public Sub AnimateAccordionIn(v As B4XView, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	' Snap to 0 width anchored to the right side
	jo.RunMethod("setPivotX", Array As Object(1.0 * v.Width))
	jo.RunMethod("setPivotY", Array As Object(v.Height / 2.0))
	jo.RunMethod("setScaleX", Array As Object(0.0))
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(1.0))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#Else
	Dim ignore As Object = v
	#End If
End Sub

' Mimics AndroidViewAnimations "Flash"
Public Sub AnimateFlash(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 4
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	Dim alphas() As Float = Array As Float(0.0, 1.0, 0.0, 1.0)
	
	For i = 0 To 3
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("alpha", Array As Object(alphas(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#End If
End Sub

' Mimics AndroidViewAnimations "Swing"
Public Sub AnimateSwing(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 5
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	jo.RunMethod("setPivotX", Array As Object(v.Width / 2.0))
	jo.RunMethod("setPivotY", Array As Object(0.0)) ' Pivot top center
	
	Dim angles() As Float = Array As Float(15, -10, 5, -5, 0)
	For i = 0 To 4
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("rotation", Array As Object(angles(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#End If
End Sub

' Mimics AndroidViewAnimations "Bounce" (Attention style)
Public Sub AnimateAttentionBounce(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 4
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim offsets() As Float = Array As Float(-30dip, 0, -15dip, 0)
	For i = 0 To 3
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("translationY", Array As Object(offsets(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#End If
End Sub

' Mimics AndroidViewAnimations "StandUp"
Public Sub AnimateStandUp(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 5
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	jo.RunMethod("setPivotX", Array As Object(v.Width / 2.0))
	jo.RunMethod("setPivotY", Array As Object(v.Height * 1.0)) ' Pivot bottom center
	
	Dim angles() As Float = Array As Float(55, -30, 15, -15, 0)
	For i = 0 To 4
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("rotationX", Array As Object(angles(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#End If
End Sub

' Mimics AndroidViewAnimations "Wave"
Public Sub AnimateWave(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 5
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	jo.RunMethod("setPivotX", Array As Object(v.Width / 2.0))
	jo.RunMethod("setPivotY", Array As Object(v.Height * 1.0))
	
	Dim angles() As Float = Array As Float(12, -12, 3, -3, 0)
	For i = 0 To 4
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("rotation", Array As Object(angles(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	#End If
End Sub

' Mimics AndroidViewAnimations "Hinge" (Swings from top left, then drops out)
Public Sub AnimateHinge(v As B4XView, DurationMs As Int)
	#If B4A
	Dim stepTime As Int = DurationMs / 5
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	jo.RunMethod("setPivotX", Array As Object(0.0))
	jo.RunMethod("setPivotY", Array As Object(0.0)) ' Pivot top left
	
	Dim angles() As Float = Array As Float(80, 60, 80, 60)
	For i = 0 To 3
		anim = jo.RunMethodJO("animate", Null)
		anim.RunMethod("rotation", Array As Object(angles(i)))
		anim.RunMethod("setDuration", Array As Object(stepTime))
		anim.RunMethod("start", Null)
		Sleep(stepTime)
	Next
	
	' Drop down and fade out
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationY", Array As Object(v.Height * 2.0))
	anim.RunMethod("alpha", Array As Object(0.0))
	anim.RunMethod("setDuration", Array As Object(stepTime))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Mimics AndroidViewAnimations "Landing" & "TakingOff"
Public Sub AnimateFlight(v As B4XView, Mode As String, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim startAlpha As Float = IIf(Mode = "in", 0.0, 1.0)
	Dim targetAlpha As Float = IIf(Mode = "in", 1.0, 0.0)
	Dim startScale As Float = IIf(Mode = "in", 1.5, 1.0)
	Dim targetScale As Float = IIf(Mode = "in", 1.0, 1.5)
	
	If Mode = "in" Then
		jo.RunMethod("setScaleX", Array As Object(startScale))
		jo.RunMethod("setScaleY", Array As Object(startScale))
		jo.RunMethod("setAlpha", Array As Object(startAlpha))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("scaleX", Array As Object(targetScale))
	anim.RunMethod("scaleY", Array As Object(targetScale))
	anim.RunMethod("alpha", Array As Object(targetAlpha))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Mimics AndroidViewAnimations "RollIn" & "RollOut"
Public Sub AnimateRoll(v As B4XView, Mode As String, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim startAlpha As Float = IIf(Mode = "in", 0.0, 1.0)
	Dim targetAlpha As Float = IIf(Mode = "in", 1.0, 0.0)
	Dim startRot As Float = IIf(Mode = "in", -120.0, 0.0)
	Dim targetRot As Float = IIf(Mode = "in", 0.0, 120.0)
	Dim startX As Float = IIf(Mode = "in", -v.Width * 1.0, 0.0)
	Dim targetX As Float = IIf(Mode = "in", 0.0, v.Width * 1.0)
	
	If Mode = "in" Then
		jo.RunMethod("setTranslationX", Array As Object(startX))
		jo.RunMethod("setRotation", Array As Object(startRot))
		jo.RunMethod("setAlpha", Array As Object(startAlpha))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(targetX))
	anim.RunMethod("rotation", Array As Object(targetRot))
	anim.RunMethod("alpha", Array As Object(targetAlpha))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Handles ALL Fade animations (FadeIn, FadeOut, FadeInUp, FadeOutLeft, etc.)
' Mode: "in" or "out"
' Direction: "up", "down", "left", "right", or "none"
Public Sub AnimateFadeDirectional(v As B4XView, Mode As String, Direction As String, OffsetPx As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim startAlpha As Float = IIf(Mode = "in", 0.0, 1.0)
	Dim targetAlpha As Float = IIf(Mode = "in", 1.0, 0.0)
	Dim startX As Float = 0, startY As Float = 0
	Dim targetX As Float = 0, targetY As Float = 0
	
	If Mode = "in" Then
		If Direction = "up" Then startY = OffsetPx
		If Direction = "down" Then startY = -OffsetPx
		If Direction = "left" Then startX = OffsetPx
		If Direction = "right" Then startX = -OffsetPx
	Else
		If Direction = "up" Then targetY = -OffsetPx
		If Direction = "down" Then targetY = OffsetPx
		If Direction = "left" Then targetX = -OffsetPx
		If Direction = "right" Then targetX = OffsetPx
	End If
	
	If Mode = "in" Then
		jo.RunMethod("setAlpha", Array As Object(startAlpha))
		jo.RunMethod("setTranslationX", Array As Object(startX))
		jo.RunMethod("setTranslationY", Array As Object(startY))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("alpha", Array As Object(targetAlpha))
	anim.RunMethod("translationX", Array As Object(targetX))
	anim.RunMethod("translationY", Array As Object(targetY))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Handles ALL Zoom animations (ZoomIn, ZoomOut, ZoomInDown, ZoomOutLeft, etc.)
Public Sub AnimateZoomDirectional(v As B4XView, Mode As String, Direction As String, OffsetPx As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim startAlpha As Float = IIf(Mode = "in", 0.0, 1.0)
	Dim targetAlpha As Float = IIf(Mode = "in", 1.0, 0.0)
	Dim startScale As Float = IIf(Mode = "in", 0.3, 1.0)
	Dim targetScale As Float = IIf(Mode = "in", 1.0, 0.3)
	
	Dim startX As Float = 0, startY As Float = 0
	Dim targetX As Float = 0, targetY As Float = 0
	
	If Mode = "in" Then
		If Direction = "up" Then startY = OffsetPx
		If Direction = "down" Then startY = -OffsetPx
		If Direction = "left" Then startX = OffsetPx
		If Direction = "right" Then startX = -OffsetPx
	Else
		If Direction = "up" Then targetY = -OffsetPx
		If Direction = "down" Then targetY = OffsetPx
		If Direction = "left" Then targetX = -OffsetPx
		If Direction = "right" Then targetX = OffsetPx
	End If
	
	If Mode = "in" Then
		jo.RunMethod("setAlpha", Array As Object(startAlpha))
		jo.RunMethod("setScaleX", Array As Object(startScale))
		jo.RunMethod("setScaleY", Array As Object(startScale))
		jo.RunMethod("setTranslationX", Array As Object(startX))
		jo.RunMethod("setTranslationY", Array As Object(startY))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("alpha", Array As Object(targetAlpha))
	anim.RunMethod("scaleX", Array As Object(targetScale))
	anim.RunMethod("scaleY", Array As Object(targetScale))
	anim.RunMethod("translationX", Array As Object(targetX))
	anim.RunMethod("translationY", Array As Object(targetY))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Handles ALL Slide animations (SlideInLeft, SlideOutUp, etc.)
' Exact same behavior as FadeDirectional but without altering the Alpha.
Public Sub AnimateSlideDirectional(v As B4XView, Mode As String, Direction As String, OffsetPx As Float, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	
	Dim startX As Float = 0, startY As Float = 0
	Dim targetX As Float = 0, targetY As Float = 0
	
	If Mode = "in" Then
		If Direction = "up" Then startY = OffsetPx
		If Direction = "down" Then startY = -OffsetPx
		If Direction = "left" Then startX = OffsetPx
		If Direction = "right" Then startX = -OffsetPx
	Else
		If Direction = "up" Then targetY = -OffsetPx
		If Direction = "down" Then targetY = OffsetPx
		If Direction = "left" Then targetX = -OffsetPx
		If Direction = "right" Then targetX = OffsetPx
	End If
	
	If Mode = "in" Then
		jo.RunMethod("setTranslationX", Array As Object(startX))
		jo.RunMethod("setTranslationY", Array As Object(startY))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("translationX", Array As Object(targetX))
	anim.RunMethod("translationY", Array As Object(targetY))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub

' Handles ALL Flip Animations (FlipInX, FlipOutY)
Public Sub AnimateFlipDirectional(v As B4XView, Mode As String, Axis As String, DurationMs As Int)
	#If B4A
	Dim jo As JavaObject = v
	Dim anim As JavaObject
	Try
		jo.RunMethod("setCameraDistance", Array As Object(12000.0)) ' Prevents 3D clipping
	Catch
	End Try
	
	Dim startAlpha As Float = IIf(Mode = "in", 0.0, 1.0)
	Dim targetAlpha As Float = IIf(Mode = "in", 1.0, 0.0)
	Dim startRot As Float = IIf(Mode = "in", 90.0, 0.0)
	Dim targetRot As Float = IIf(Mode = "in", 0.0, 90.0)
	
	If Mode = "in" Then
		jo.RunMethod("setAlpha", Array As Object(startAlpha))
		If Axis.ToUpperCase = "X" Then jo.RunMethod("setRotationX", Array As Object(startRot))
		If Axis.ToUpperCase = "Y" Then jo.RunMethod("setRotationY", Array As Object(startRot))
	End If
	
	anim = jo.RunMethodJO("animate", Null)
	anim.RunMethod("alpha", Array As Object(targetAlpha))
	If Axis.ToUpperCase = "X" Then anim.RunMethod("rotationX", Array As Object(targetRot))
	If Axis.ToUpperCase = "Y" Then anim.RunMethod("rotationY", Array As Object(targetRot))
	anim.RunMethod("setDuration", Array As Object(DurationMs))
	anim.RunMethod("start", Null)
	#End If
End Sub
