diff --git a/flumi/Scenes/Tags/li.tscn b/flumi/Scenes/Tags/li.tscn
deleted file mode 100644
index 5929bb0..0000000
--- a/flumi/Scenes/Tags/li.tscn
+++ /dev/null
@@ -1,9 +0,0 @@
-[gd_scene load_steps=2 format=3 uid="uid://bli1234568aa"]
-
-[ext_resource type="Script" uid="uid://ps8duq0aw3tu" path="res://Scripts/Tags/li.gd" id="1_li"]
-
-[node name="li" type="VBoxContainer"]
-anchors_preset = 10
-anchor_right = 1.0
-grow_horizontal = 2
-script = ExtResource("1_li")
diff --git a/flumi/Scenes/Tags/option.tscn b/flumi/Scenes/Tags/option.tscn
index 767c64a..ab0b65b 100644
--- a/flumi/Scenes/Tags/option.tscn
+++ b/flumi/Scenes/Tags/option.tscn
@@ -1,6 +1,5 @@
-[gd_scene load_steps=3 format=3 uid="uid://bopt1234568aa"]
+[gd_scene load_steps=2 format=3 uid="uid://bopt1234568aa"]
-[ext_resource type="Script" uid="uid://ps8duq0aw3tu" path="res://Scripts/Tags/li.gd" id="1_option"]
[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"]
[node name="option" type="Control"]
@@ -8,7 +7,6 @@ layout_mode = 3
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
-script = ExtResource("1_option")
[node name="RichTextLabel" type="RichTextLabel" parent="."]
layout_mode = 2
diff --git a/flumi/Scripts/B9/HTMLParser.gd b/flumi/Scripts/B9/HTMLParser.gd
index edd3535..bdb1068 100644
--- a/flumi/Scripts/B9/HTMLParser.gd
+++ b/flumi/Scripts/B9/HTMLParser.gd
@@ -18,8 +18,8 @@ class HTMLElement:
func has_attribute(name_: String) -> bool:
return attributes.has(name_)
- func get_class_name() -> String:
- return get_attribute("class")
+ func set_attribute(name_: String, value: String) -> void:
+ attributes.set(name_, value)
func get_id() -> String:
return get_attribute("id")
diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd
index b660250..f4194bc 100644
--- a/flumi/Scripts/B9/Lua.gd
+++ b/flumi/Scripts/B9/Lua.gd
@@ -16,6 +16,30 @@ var event_subscriptions: Dictionary = {}
var next_subscription_id: int = 1
var next_callback_ref: int = 1
+var timeout_manager: LuaTimeoutManager
+var element_id_counter: int = 1
+var element_id_registry: Dictionary = {}
+
+func _init():
+ timeout_manager = LuaTimeoutManager.new()
+
+func get_or_assign_element_id(element: HTMLParser.HTMLElement) -> String:
+ var existing_id = element.get_attribute("id")
+ if not existing_id.is_empty():
+ element_id_registry[element] = existing_id
+ return existing_id
+
+ if element_id_registry.has(element):
+ return element_id_registry[element]
+
+ var new_id = "auto_" + str(element_id_counter)
+ element_id_counter += 1
+
+ element.set_attribute("id", new_id)
+ element_id_registry[element] = new_id
+
+ return new_id
+
func _gurt_select_handler(vm: LuauVM) -> int:
var selector: String = vm.luaL_checkstring(1)
@@ -38,6 +62,87 @@ func _gurt_select_handler(vm: LuauVM) -> int:
add_element_methods(vm)
return 1
+# selectAll() function to find multiple elements
+func _gurt_select_all_handler(vm: LuauVM) -> int:
+ var selector: String = vm.luaL_checkstring(1)
+
+ var elements: Array[HTMLParser.HTMLElement] = []
+
+ # Handle different selector types
+ if selector.begins_with("#"):
+ # ID selector - find single element
+ var element_id = selector.substr(1)
+ var element = dom_parser.find_by_id(element_id)
+ if element:
+ elements.append(element)
+ LuaPrintUtils.lua_print_direct("WARNING: Using ID selector in select_all is not recommended, use select instead.")
+ elif selector.begins_with("."):
+ # Class selector - find all elements with class
+ var cls = selector.substr(1)
+ for element in dom_parser.parse_result.all_elements:
+ var element_classes = CSSParser.smart_split_utility_classes(element.get_attribute("style"))
+ if cls in element_classes:
+ elements.append(element)
+ else:
+ # Tag selector - find all elements with tag name
+ elements = dom_parser.find_all(selector)
+
+ vm.lua_newtable()
+ var index = 1
+
+ for element in elements:
+ var element_id = get_or_assign_element_id(element)
+
+ # Create element wrapper
+ vm.lua_newtable()
+ vm.lua_pushstring(element_id)
+ vm.lua_setfield(-2, "_element_id")
+ vm.lua_pushstring(element.tag_name)
+ vm.lua_setfield(-2, "_tag_name")
+
+ add_element_methods(vm)
+
+ # Add to array at index
+ vm.lua_rawseti(-2, index)
+ index += 1
+
+ return 1
+
+# create() function to create HTML element
+func _gurt_create_handler(vm: LuauVM) -> int:
+ var tag_name: String = vm.luaL_checkstring(1)
+ var options: Dictionary = {}
+
+ if vm.lua_gettop() >= 2 and vm.lua_istable(2):
+ options = vm.lua_todictionary(2)
+
+ var element = HTMLParser.HTMLElement.new(tag_name)
+
+ # Apply options as attributes and content
+ for key in options:
+ if key == "text":
+ element.text_content = str(options[key])
+ else:
+ element.attributes[str(key)] = str(options[key])
+
+ # Add to parser's element collection first
+ dom_parser.parse_result.all_elements.append(element)
+
+ # Get or assign stable ID
+ var unique_id = get_or_assign_element_id(element)
+
+ # Create Lua element wrapper with methods
+ vm.lua_newtable()
+ vm.lua_pushstring(unique_id)
+ vm.lua_setfield(-2, "_element_id")
+ vm.lua_pushstring(tag_name)
+ vm.lua_setfield(-2, "_tag_name")
+ vm.lua_pushboolean(true)
+ vm.lua_setfield(-2, "_is_dynamic")
+
+ add_element_methods(vm)
+ return 1
+
func add_element_methods(vm: LuauVM) -> void:
vm.lua_pushcallable(_element_set_text_handler, "element.set_text")
vm.lua_setfield(-2, "set_text")
@@ -47,6 +152,45 @@ func add_element_methods(vm: LuauVM) -> void:
vm.lua_pushcallable(_element_on_event_handler, "element.on")
vm.lua_setfield(-2, "on")
+
+ vm.lua_pushcallable(_element_append_handler, "element.append")
+ vm.lua_setfield(-2, "append")
+
+ vm.lua_pushcallable(_element_remove_handler, "element.remove")
+ vm.lua_setfield(-2, "remove")
+
+ vm.lua_pushcallable(_element_get_children_handler, "element.get_children")
+ vm.lua_setfield(-2, "get_children")
+
+func _element_get_children_handler(vm: LuauVM) -> int:
+ vm.luaL_checktype(1, vm.LUA_TTABLE)
+
+ vm.lua_getfield(1, "_element_id")
+ var element_id: String = vm.lua_tostring(-1)
+ vm.lua_pop(1)
+
+ # Find the element
+ var element: HTMLParser.HTMLElement = null
+ if element_id == "body":
+ element = dom_parser.find_first("body")
+ else:
+ element = dom_parser.find_by_id(element_id)
+
+ vm.lua_newtable()
+ var index = 1
+
+ if element:
+ for child in element.children:
+ vm.lua_newtable()
+ vm.lua_pushstring(child.tag_name)
+ vm.lua_setfield(-2, "tag_name")
+ vm.lua_pushstring(child.get_text_content())
+ vm.lua_setfield(-2, "text")
+
+ vm.lua_rawseti(-2, index)
+ index += 1
+
+ return 1
# Element manipulation handlers
func _element_set_text_handler(vm: LuauVM) -> int:
@@ -83,6 +227,129 @@ func _element_get_text_handler(vm: LuauVM) -> int:
vm.lua_pushstring(text)
return 1
+# append() function to add a child element
+func _element_append_handler(vm: LuauVM) -> int:
+ vm.luaL_checktype(1, vm.LUA_TTABLE)
+ vm.luaL_checktype(2, vm.LUA_TTABLE)
+
+ # Get parent element info
+ vm.lua_getfield(1, "_element_id")
+ var parent_element_id: String = vm.lua_tostring(-1)
+ vm.lua_pop(1)
+
+ # Get child element info
+ vm.lua_getfield(2, "_element_id")
+ var child_element_id: String = vm.lua_tostring(-1)
+ vm.lua_pop(1)
+
+ vm.lua_getfield(2, "_is_dynamic")
+ vm.lua_pop(1)
+
+ # Find parent element
+ var parent_element: HTMLParser.HTMLElement = null
+ if parent_element_id == "body":
+ parent_element = dom_parser.find_first("body")
+ else:
+ parent_element = dom_parser.find_by_id(parent_element_id)
+
+ if not parent_element:
+ return 0
+
+ # Find child element
+ var child_element = dom_parser.find_by_id(child_element_id)
+ if not child_element:
+ return 0
+
+ # Add child to parent in DOM tree
+ child_element.parent = parent_element
+ parent_element.children.append(child_element)
+
+ # If the parent is already rendered, we need to create and add the visual node
+ var parent_dom_node: Node = null
+ if parent_element_id == "body":
+ var main_scene = get_node("/root/Main")
+ if main_scene:
+ parent_dom_node = main_scene.website_container
+ else:
+ parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_element_id, null)
+
+ if parent_dom_node:
+ _render_new_element.call_deferred(child_element, parent_dom_node)
+
+ return 0
+
+# remove() function to remove an element
+func _element_remove_handler(vm: LuauVM) -> int:
+ vm.luaL_checktype(1, vm.LUA_TTABLE)
+
+ vm.lua_getfield(1, "_element_id")
+ var element_id: String = vm.lua_tostring(-1)
+ vm.lua_pop(1)
+
+ # Find the element in DOM
+ var element = dom_parser.find_by_id(element_id)
+ if not element:
+ return 0
+
+ # Remove from parent's children array
+ if element.parent:
+ var parent_children = element.parent.children
+ var idx = parent_children.find(element)
+ if idx >= 0:
+ parent_children.remove_at(idx)
+
+ # Remove the visual node
+ var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
+ if dom_node:
+ dom_node.queue_free()
+ dom_parser.parse_result.dom_nodes.erase(element_id)
+
+ # Remove from all_elements array
+ var all_elements = dom_parser.parse_result.all_elements
+ var index = all_elements.find(element)
+ if index >= 0:
+ all_elements.remove_at(index)
+
+ # Remove from element_id_registry to avoid memory leaks
+ if element_id_registry.has(element):
+ element_id_registry.erase(element)
+
+ return 0
+
+func _render_new_element(element: HTMLParser.HTMLElement, parent_node: Node) -> void:
+ # Get reference to main scene for rendering
+ var main_scene = get_node("/root/Main")
+ if not main_scene:
+ return
+
+ # Create the visual node for the element
+ var element_node = await main_scene.create_element_node(element, dom_parser)
+ if not element_node:
+ LuaPrintUtils.lua_print_direct("Failed to create visual node for element: " + str(element))
+ return
+
+ # Set metadata so ul/ol can detect dynamically added li elements
+ element_node.set_meta("html_element", element)
+
+ # Register the DOM node
+ dom_parser.register_dom_node(element, element_node)
+
+ # Add to parent - handle body special case
+ var container_node = parent_node
+ if parent_node is MarginContainer and parent_node.get_child_count() > 0:
+ container_node = parent_node.get_child(0)
+ elif parent_node == main_scene.website_container:
+ container_node = parent_node
+
+ main_scene.safe_add_child(container_node, element_node)
+
+# Timeout management handlers
+func _gurt_set_timeout_handler(vm: LuauVM) -> int:
+ return timeout_manager.set_timeout_handler(vm, self)
+
+func _gurt_clear_timeout_handler(vm: LuauVM) -> int:
+ return timeout_manager.clear_timeout_handler(vm)
+
# Event system handlers
func _element_on_event_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd
index 9d20b85..c1477c0 100644
--- a/flumi/Scripts/Constants.gd
+++ b/flumi/Scripts/Constants.gd
@@ -590,7 +590,8 @@ var HTML_CONTENT3 = """
""".to_utf8_buffer()
-var HTML_CONTENT = """
+var HTML_CONTENT = """
+
Lua API Demo
@@ -663,6 +664,38 @@ var HTML_CONTENT = """
else
gurt.log('Could not find button or event log element')
end
+
+ -- DOM Manipulation Demo
+ gurt.log('Testing DOM manipulation...')
+
+ -- Create a new div with styling
+ local new_div = gurt.create('div', { style = 'bg-red-500 p-4 rounded-lg mb-4' })
+
+ -- Create a paragraph with text
+ local new_p = gurt.create('p', {
+ style = 'text-white font-bold text-lg',
+ text = 'This element was created dynamically with Lua!'
+ })
+
+ -- Append paragraph to div
+ new_div:append(new_p)
+
+ -- Append div to body
+ gurt.body:append(new_div)
+
+ -- Create another element to test removal
+ local temp_element = gurt.create('div', {
+ style = 'bg-yellow-400 p-2 rounded text-black',
+ text = 'This will be removed in 3 seconds...'
+ })
+ gurt.body:append(temp_element)
+
+ local test = gurt.set_timeout(function()
+ print('removed')
+ temp_element:remove()
+ end, 3000)
+
+ -- gurt.clear_timeout(test)
@@ -681,5 +714,61 @@ var HTML_CONTENT = """