2025-08-06 16:03:09 +03:00
|
|
|
class_name LuaDOMUtils
|
|
|
|
|
extends RefCounted
|
|
|
|
|
|
|
|
|
|
# DOM traversal properties (read-only)
|
|
|
|
|
static func get_element_parent_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api: LuaAPI) -> 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)
|
|
|
|
|
|
|
|
|
|
var element = find_element_by_id(element_id, dom_parser)
|
|
|
|
|
if not element or not element.parent:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
create_element_wrapper(vm, element.parent, lua_api)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func get_element_next_sibling_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api: LuaAPI) -> 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)
|
|
|
|
|
|
|
|
|
|
var element = find_element_by_id(element_id, dom_parser)
|
|
|
|
|
if not element or not element.parent:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
var siblings = element.parent.children
|
|
|
|
|
var current_index = siblings.find(element)
|
|
|
|
|
|
|
|
|
|
if current_index >= 0 and current_index < siblings.size() - 1:
|
|
|
|
|
var next_sibling = siblings[current_index + 1]
|
|
|
|
|
create_element_wrapper(vm, next_sibling, lua_api)
|
|
|
|
|
else:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func get_element_previous_sibling_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api: LuaAPI) -> 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)
|
|
|
|
|
|
|
|
|
|
var element = find_element_by_id(element_id, dom_parser)
|
|
|
|
|
if not element or not element.parent:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
var siblings = element.parent.children
|
|
|
|
|
var current_index = siblings.find(element)
|
|
|
|
|
|
|
|
|
|
if current_index > 0:
|
|
|
|
|
var prev_sibling = siblings[current_index - 1]
|
|
|
|
|
create_element_wrapper(vm, prev_sibling, lua_api)
|
|
|
|
|
else:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func get_element_first_child_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api: LuaAPI) -> 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)
|
|
|
|
|
|
|
|
|
|
var element = find_element_by_id(element_id, dom_parser)
|
|
|
|
|
if not element or element.children.is_empty():
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
create_element_wrapper(vm, element.children[0], lua_api)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func get_element_last_child_handler(vm: LuauVM, dom_parser: HTMLParser, lua_api: LuaAPI) -> 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)
|
|
|
|
|
|
|
|
|
|
var element = find_element_by_id(element_id, dom_parser)
|
|
|
|
|
if not element or element.children.is_empty():
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
create_element_wrapper(vm, element.children[-1], lua_api)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# DOM Manipulation Methods
|
2025-08-08 16:51:21 +03:00
|
|
|
|
2025-08-30 14:19:03 +03:00
|
|
|
static func handle_element_append(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
2025-08-08 16:51:21 +03:00
|
|
|
var parent_id: String = operation.parent_id
|
|
|
|
|
var child_id: String = operation.child_id
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find the parent and child elements
|
|
|
|
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var child_element = dom_parser.find_by_id(child_id) if child_id != "body" else dom_parser.find_first("body")
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
if not parent_element or not child_element:
|
|
|
|
|
return
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Remove child from its current parent if it has one
|
|
|
|
|
if child_element.parent:
|
|
|
|
|
var current_parent = child_element.parent
|
|
|
|
|
var current_index = current_parent.children.find(child_element)
|
|
|
|
|
if current_index >= 0:
|
|
|
|
|
current_parent.children.remove_at(current_index)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Append child to new parent
|
|
|
|
|
child_element.parent = parent_element
|
|
|
|
|
parent_element.children.append(child_element)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Handle visual rendering if parent is already rendered
|
|
|
|
|
var parent_dom_node: Node = null
|
|
|
|
|
if parent_id == "body":
|
2025-08-16 14:36:47 +03:00
|
|
|
var main_scene = Engine.get_main_loop().current_scene
|
|
|
|
|
parent_dom_node = main_scene.website_container
|
2025-08-08 16:51:21 +03:00
|
|
|
else:
|
|
|
|
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
if parent_dom_node:
|
|
|
|
|
# Render the appended element
|
2025-08-27 20:56:29 +03:00
|
|
|
render_new_element.call_deferred(child_element, parent_dom_node, dom_parser)
|
2025-08-08 16:51:21 +03:00
|
|
|
|
|
|
|
|
static func handle_element_remove(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
|
|
|
|
var element_id: String = operation.element_id
|
|
|
|
|
|
|
|
|
|
var element = dom_parser.find_by_id(element_id) if element_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
if element and element.parent:
|
|
|
|
|
# Remove element from HTML parser tree
|
|
|
|
|
var parent_element = element.parent
|
|
|
|
|
var element_index = parent_element.children.find(element)
|
|
|
|
|
if element_index >= 0:
|
|
|
|
|
parent_element.children.remove_at(element_index)
|
|
|
|
|
element.parent = null
|
|
|
|
|
|
|
|
|
|
# Remove element from DOM tree
|
|
|
|
|
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
|
|
|
if dom_node and dom_node.get_parent():
|
|
|
|
|
dom_node.get_parent().remove_child(dom_node)
|
|
|
|
|
dom_node.queue_free()
|
|
|
|
|
dom_parser.parse_result.dom_nodes.erase(element_id)
|
|
|
|
|
|
|
|
|
|
# Remove from parser's all_elements list
|
|
|
|
|
var all_elements_index = dom_parser.parse_result.all_elements.find(element)
|
|
|
|
|
if all_elements_index >= 0:
|
|
|
|
|
dom_parser.parse_result.all_elements.remove_at(all_elements_index)
|
|
|
|
|
|
2025-08-30 14:19:03 +03:00
|
|
|
static func handle_insert_before(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
2025-08-08 16:51:21 +03:00
|
|
|
var parent_id: String = operation.parent_id
|
|
|
|
|
var new_child_id: String = operation.new_child_id
|
|
|
|
|
var reference_child_id: String = operation.reference_child_id
|
|
|
|
|
|
|
|
|
|
# Find the elements
|
|
|
|
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var reference_child_element = dom_parser.find_by_id(reference_child_id) if reference_child_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
|
|
|
|
|
if not parent_element or not new_child_element or not reference_child_element:
|
|
|
|
|
return
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
# Remove new child from its current parent if it has one
|
|
|
|
|
if new_child_element.parent:
|
|
|
|
|
var current_parent = new_child_element.parent
|
|
|
|
|
var current_index = current_parent.children.find(new_child_element)
|
|
|
|
|
if current_index >= 0:
|
|
|
|
|
current_parent.children.remove_at(current_index)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find reference child position in parent
|
|
|
|
|
var reference_index = parent_element.children.find(reference_child_element)
|
|
|
|
|
if reference_index >= 0:
|
|
|
|
|
# Insert new child before reference child
|
|
|
|
|
new_child_element.parent = parent_element
|
|
|
|
|
parent_element.children.insert(reference_index, new_child_element)
|
|
|
|
|
|
|
|
|
|
# Handle visual rendering
|
|
|
|
|
var parent_dom_node: Node = null
|
|
|
|
|
if parent_id == "body":
|
2025-08-16 14:36:47 +03:00
|
|
|
var main_scene = Engine.get_main_loop().current_scene
|
2025-08-08 16:51:21 +03:00
|
|
|
if main_scene:
|
|
|
|
|
parent_dom_node = main_scene.website_container
|
|
|
|
|
else:
|
|
|
|
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
|
|
|
|
|
|
|
|
|
if parent_dom_node:
|
2025-08-27 20:56:29 +03:00
|
|
|
handle_visual_insertion_by_reference(parent_id, new_child_element, reference_child_id, true, dom_parser)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func handle_insert_after(operation: Dictionary, dom_parser: HTMLParser) -> void:
|
2025-08-08 16:51:21 +03:00
|
|
|
var parent_id: String = operation.parent_id
|
|
|
|
|
var new_child_id: String = operation.new_child_id
|
|
|
|
|
var reference_child_id: String = operation.reference_child_id
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find the elements
|
|
|
|
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var reference_child_element = dom_parser.find_by_id(reference_child_id) if reference_child_id != "body" else dom_parser.find_first("body")
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
if not parent_element or not new_child_element or not reference_child_element:
|
2025-08-08 16:51:21 +03:00
|
|
|
return
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
# Remove new child from its current parent if it has one
|
|
|
|
|
if new_child_element.parent:
|
|
|
|
|
var current_parent = new_child_element.parent
|
|
|
|
|
var current_index = current_parent.children.find(new_child_element)
|
|
|
|
|
if current_index >= 0:
|
|
|
|
|
current_parent.children.remove_at(current_index)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find reference child position in parent
|
|
|
|
|
var reference_index = parent_element.children.find(reference_child_element)
|
|
|
|
|
if reference_index >= 0:
|
|
|
|
|
# Insert new child after reference child
|
|
|
|
|
new_child_element.parent = parent_element
|
|
|
|
|
parent_element.children.insert(reference_index + 1, new_child_element)
|
|
|
|
|
|
|
|
|
|
# Handle visual rendering
|
|
|
|
|
var parent_dom_node: Node = null
|
|
|
|
|
if parent_id == "body":
|
2025-08-16 14:36:47 +03:00
|
|
|
var main_scene = Engine.get_main_loop().current_scene
|
2025-08-08 16:51:21 +03:00
|
|
|
if main_scene:
|
|
|
|
|
parent_dom_node = main_scene.website_container
|
|
|
|
|
else:
|
|
|
|
|
parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_id, null)
|
|
|
|
|
|
|
|
|
|
if parent_dom_node:
|
2025-08-27 20:56:29 +03:00
|
|
|
handle_visual_insertion_by_reference(parent_id, new_child_element, reference_child_id, false, dom_parser)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
static func handle_replace_child(operation: Dictionary, dom_parser: HTMLParser, lua_api) -> void:
|
|
|
|
|
var parent_id: String = operation.parent_id
|
|
|
|
|
var new_child_id: String = operation.new_child_id
|
|
|
|
|
var old_child_id: String = operation.old_child_id
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find the elements
|
|
|
|
|
var parent_element = dom_parser.find_by_id(parent_id) if parent_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var new_child_element = dom_parser.find_by_id(new_child_id) if new_child_id != "body" else dom_parser.find_first("body")
|
|
|
|
|
var old_child_element = dom_parser.find_by_id(old_child_id) if old_child_id != "body" else dom_parser.find_first("body")
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
if not parent_element or not new_child_element or not old_child_element:
|
2025-08-08 16:51:21 +03:00
|
|
|
return
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
# Remove new child from its current parent if it has one
|
|
|
|
|
if new_child_element.parent:
|
|
|
|
|
var current_parent = new_child_element.parent
|
|
|
|
|
var current_index = current_parent.children.find(new_child_element)
|
|
|
|
|
if current_index >= 0:
|
|
|
|
|
current_parent.children.remove_at(current_index)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Find old child position in parent
|
|
|
|
|
var old_index = parent_element.children.find(old_child_element)
|
|
|
|
|
if old_index >= 0:
|
|
|
|
|
# Replace old child with new child
|
|
|
|
|
new_child_element.parent = parent_element
|
|
|
|
|
parent_element.children[old_index] = new_child_element
|
|
|
|
|
old_child_element.parent = null
|
|
|
|
|
|
|
|
|
|
# Handle visual rendering
|
|
|
|
|
handle_visual_replacement(old_child_id, new_child_element, parent_id, dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Node, dom_parser: HTMLParser) -> void:
|
2025-08-08 16:51:21 +03:00
|
|
|
# Get reference to main scene for rendering
|
2025-08-16 14:36:47 +03:00
|
|
|
var main_scene = Engine.get_main_loop().current_scene
|
2025-08-08 16:51:21 +03:00
|
|
|
if not main_scene:
|
|
|
|
|
return
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-19 09:47:58 +03:00
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# 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)
|
2025-08-06 16:03:09 +03:00
|
|
|
|
2025-08-19 18:27:25 +03:00
|
|
|
static func _get_input_value(element: HTMLParser.HTMLElement, dom_node: Node) -> Variant:
|
|
|
|
|
var input_type = element.get_attribute("type").to_lower()
|
|
|
|
|
|
|
|
|
|
match input_type:
|
|
|
|
|
"checkbox", "radio":
|
|
|
|
|
if dom_node is CheckBox:
|
|
|
|
|
return dom_node.button_pressed
|
|
|
|
|
return false
|
|
|
|
|
"color":
|
|
|
|
|
if dom_node is ColorPickerButton:
|
|
|
|
|
return "#" + dom_node.color.to_html(false)
|
|
|
|
|
return "#ffffff"
|
|
|
|
|
"range":
|
|
|
|
|
if dom_node is HSlider:
|
|
|
|
|
return dom_node.value
|
|
|
|
|
return 0.0
|
|
|
|
|
"number":
|
|
|
|
|
if dom_node is SpinBox:
|
|
|
|
|
return dom_node.value
|
|
|
|
|
return 0.0
|
|
|
|
|
"file":
|
|
|
|
|
# For file inputs, need to find the input control that has get_file_info method
|
|
|
|
|
var input_control = _find_input_control_with_file_info(dom_node)
|
|
|
|
|
if input_control:
|
|
|
|
|
var file_info = input_control.get_file_info()
|
|
|
|
|
return file_info.get("fileName", "")
|
|
|
|
|
return ""
|
|
|
|
|
"date":
|
|
|
|
|
if dom_node is DateButton:
|
|
|
|
|
if dom_node.has_method("get_date_text"):
|
|
|
|
|
return dom_node.get_date_text()
|
|
|
|
|
return ""
|
|
|
|
|
_: # text, password, email, etc.
|
|
|
|
|
if dom_node is LineEdit:
|
|
|
|
|
return dom_node.text
|
|
|
|
|
elif dom_node is TextEdit:
|
|
|
|
|
return dom_node.text
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
static func _find_input_control_with_file_info(node: Node) -> Node:
|
|
|
|
|
if node and node.has_method("get_file_info"):
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
var current = node
|
|
|
|
|
while current:
|
|
|
|
|
current = current.get_parent()
|
|
|
|
|
if current and current.has_method("get_file_info"):
|
|
|
|
|
return current
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func _get_select_value(_element: HTMLParser.HTMLElement, dom_node: Node) -> String:
|
2025-08-20 14:37:57 +03:00
|
|
|
if dom_node is OptionButton:
|
|
|
|
|
var option_button = dom_node as OptionButton
|
|
|
|
|
var selected_index = option_button.selected
|
|
|
|
|
if selected_index >= 0 and selected_index < option_button.get_item_count():
|
|
|
|
|
var metadata = option_button.get_item_metadata(selected_index)
|
|
|
|
|
if metadata:
|
|
|
|
|
return str(metadata)
|
|
|
|
|
else:
|
|
|
|
|
return option_button.get_item_text(selected_index)
|
|
|
|
|
return ""
|
|
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func _set_select_value(_element: HTMLParser.HTMLElement, dom_node: Node, value: Variant) -> void:
|
2025-08-20 14:37:57 +03:00
|
|
|
if dom_node is OptionButton:
|
|
|
|
|
var option_button = dom_node as OptionButton
|
|
|
|
|
var target_value = str(value)
|
|
|
|
|
|
|
|
|
|
# Find the correct index to select
|
|
|
|
|
var selected_index = -1
|
|
|
|
|
|
|
|
|
|
# Find option with matching value
|
|
|
|
|
for i in range(option_button.get_item_count()):
|
|
|
|
|
var metadata = option_button.get_item_metadata(i)
|
|
|
|
|
var option_value = str(metadata) if metadata else option_button.get_item_text(i)
|
|
|
|
|
if option_value == target_value:
|
|
|
|
|
selected_index = i
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# If no matching value found, try to find by text
|
|
|
|
|
if selected_index == -1:
|
|
|
|
|
for i in range(option_button.get_item_count()):
|
|
|
|
|
if option_button.get_item_text(i) == target_value:
|
|
|
|
|
selected_index = i
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# Use call_deferred to set the property on main thread
|
|
|
|
|
if selected_index != -1:
|
|
|
|
|
option_button.call_deferred("set", "selected", selected_index)
|
|
|
|
|
|
2025-08-19 18:27:25 +03:00
|
|
|
static func _set_input_value(element: HTMLParser.HTMLElement, dom_node: Node, value: Variant) -> void:
|
|
|
|
|
var input_type = element.get_attribute("type").to_lower()
|
|
|
|
|
|
|
|
|
|
match input_type:
|
|
|
|
|
"checkbox", "radio":
|
|
|
|
|
if dom_node is CheckBox:
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set", "button_pressed", bool(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
"color":
|
|
|
|
|
if dom_node is ColorPickerButton:
|
|
|
|
|
var color_value = str(value)
|
|
|
|
|
if color_value.begins_with("#"):
|
2025-08-20 14:37:57 +03:00
|
|
|
var color = Color.from_string(color_value, Color.WHITE)
|
|
|
|
|
dom_node.call_deferred("set", "color", color)
|
2025-08-19 18:27:25 +03:00
|
|
|
"range":
|
|
|
|
|
if dom_node is HSlider:
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set", "value", float(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
"number":
|
|
|
|
|
if dom_node is SpinBox:
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set", "value", float(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
"date":
|
|
|
|
|
if dom_node is DateButton and dom_node.has_method("set_date_from_string"):
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set_date_from_string", str(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
_: # text, password, email, etc.
|
|
|
|
|
if dom_node is LineEdit:
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set", "text", str(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
elif dom_node is TextEdit:
|
2025-08-20 14:37:57 +03:00
|
|
|
dom_node.call_deferred("set", "text", str(value))
|
2025-08-19 18:27:25 +03:00
|
|
|
|
2025-08-06 16:03:09 +03:00
|
|
|
# Helper functions
|
|
|
|
|
static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement:
|
|
|
|
|
if element_id == "body":
|
|
|
|
|
return dom_parser.find_first("body")
|
|
|
|
|
else:
|
|
|
|
|
return dom_parser.find_by_id(element_id)
|
|
|
|
|
|
|
|
|
|
static func clone_element(element: HTMLParser.HTMLElement, deep: bool) -> HTMLParser.HTMLElement:
|
|
|
|
|
var cloned = HTMLParser.HTMLElement.new(element.tag_name)
|
|
|
|
|
|
|
|
|
|
for attr_name in element.attributes:
|
|
|
|
|
if attr_name != "id":
|
|
|
|
|
cloned.attributes[attr_name] = element.attributes[attr_name]
|
|
|
|
|
|
|
|
|
|
cloned.text_content = element.text_content
|
|
|
|
|
|
|
|
|
|
if deep:
|
|
|
|
|
for child in element.children:
|
|
|
|
|
var cloned_child = clone_element(child, true)
|
|
|
|
|
cloned_child.parent = cloned
|
|
|
|
|
cloned.children.append(cloned_child)
|
|
|
|
|
|
|
|
|
|
return cloned
|
|
|
|
|
|
|
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func handle_visual_insertion_by_reference(parent_element_id: String, new_child_element: HTMLParser.HTMLElement, reference_element_id: String, insert_before: bool, dom_parser: HTMLParser) -> void:
|
2025-08-06 16:03:09 +03:00
|
|
|
var parent_dom_node: Node = null
|
|
|
|
|
if parent_element_id == "body":
|
2025-08-16 14:36:47 +03:00
|
|
|
var main_scene = Engine.get_main_loop().current_scene
|
2025-08-06 16:03:09 +03:00
|
|
|
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_by_reference.call_deferred(new_child_element, parent_dom_node, reference_element_id, insert_before, dom_parser)
|
|
|
|
|
|
|
|
|
|
static func handle_visual_replacement(old_child_element_id: String, new_child_element: HTMLParser.HTMLElement, parent_element_id: String, dom_parser: HTMLParser, lua_api) -> void:
|
|
|
|
|
var old_dom_node = dom_parser.parse_result.dom_nodes.get(old_child_element_id, null)
|
|
|
|
|
if not old_dom_node:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var parent_container = old_dom_node.get_parent()
|
|
|
|
|
if not parent_container:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var old_position = -1
|
|
|
|
|
for i in parent_container.get_child_count():
|
|
|
|
|
if parent_container.get_child(i) == old_dom_node:
|
|
|
|
|
old_position = i
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
old_dom_node.queue_free()
|
|
|
|
|
dom_parser.parse_result.dom_nodes.erase(old_child_element_id)
|
|
|
|
|
|
|
|
|
|
if old_position >= 0:
|
|
|
|
|
var parent_dom_node: Node = null
|
|
|
|
|
if parent_element_id == "body":
|
2025-08-07 14:05:41 +03:00
|
|
|
var main_scene = lua_api.get_main_scene()
|
2025-08-06 16:03:09 +03:00
|
|
|
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_at_position.call_deferred(new_child_element, parent_dom_node, old_position, dom_parser)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
|
2025-08-06 16:03:09 +03:00
|
|
|
|
|
|
|
|
static func is_same_element_visual_node(node1: Node, node2: Node) -> bool:
|
|
|
|
|
if node1 == node2:
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
var current = node1
|
|
|
|
|
var node1_grandparent = node1.get_parent()
|
|
|
|
|
if node1_grandparent:
|
|
|
|
|
node1_grandparent = node1_grandparent.get_parent()
|
|
|
|
|
while current:
|
|
|
|
|
if current == node2:
|
|
|
|
|
return true
|
|
|
|
|
var parent = current.get_parent()
|
|
|
|
|
if not parent or (node1_grandparent and current == node1_grandparent):
|
|
|
|
|
break
|
|
|
|
|
current = parent
|
|
|
|
|
|
|
|
|
|
current = node2
|
|
|
|
|
var node2_grandparent = node2.get_parent()
|
|
|
|
|
if node2_grandparent:
|
|
|
|
|
node2_grandparent = node2_grandparent.get_parent()
|
|
|
|
|
while current:
|
|
|
|
|
if current == node1:
|
|
|
|
|
return true
|
|
|
|
|
var parent = current.get_parent()
|
|
|
|
|
if not parent or (node2_grandparent and current == node2_grandparent):
|
|
|
|
|
break
|
|
|
|
|
current = parent
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
static func render_new_element_at_position(element: HTMLParser.HTMLElement, parent_node: Node, position: int, dom_parser: HTMLParser) -> void:
|
|
|
|
|
var main_scene = Engine.get_main_loop().current_scene.get_node("/root/Main")
|
|
|
|
|
if not main_scene:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var element_node = await main_scene.create_element_node(element, dom_parser)
|
|
|
|
|
if not element_node:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
element_node.set_meta("html_element", element)
|
|
|
|
|
dom_parser.register_dom_node(element, element_node)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
container_node.add_child(element_node)
|
|
|
|
|
if position >= 0 and position < container_node.get_child_count():
|
|
|
|
|
container_node.move_child(element_node, position)
|
|
|
|
|
|
|
|
|
|
static func render_new_element_by_reference(element: HTMLParser.HTMLElement, parent_node: Node, reference_element_id: String, insert_before: bool, dom_parser: HTMLParser) -> void:
|
|
|
|
|
var main_scene = Engine.get_main_loop().current_scene.get_node("/root/Main")
|
|
|
|
|
if not main_scene:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var reference_dom_node = dom_parser.parse_result.dom_nodes.get(reference_element_id, null)
|
|
|
|
|
if not reference_dom_node:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
var reference_position = -1
|
|
|
|
|
for i in container_node.get_child_count():
|
|
|
|
|
var child = container_node.get_child(i)
|
|
|
|
|
if child == reference_dom_node or is_same_element_visual_node(child, reference_dom_node):
|
|
|
|
|
reference_position = i
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if reference_position < 0:
|
|
|
|
|
reference_position = container_node.get_child_count()
|
|
|
|
|
|
|
|
|
|
var insert_position = reference_position
|
|
|
|
|
if not insert_before:
|
|
|
|
|
insert_position = reference_position + 1
|
|
|
|
|
|
|
|
|
|
var element_node = await main_scene.create_element_node(element, dom_parser)
|
|
|
|
|
if not element_node:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
element_node.set_meta("html_element", element)
|
|
|
|
|
dom_parser.register_dom_node(element, element_node)
|
|
|
|
|
|
|
|
|
|
container_node.add_child(element_node)
|
|
|
|
|
if insert_position >= 0 and insert_position < container_node.get_child_count() - 1:
|
|
|
|
|
container_node.move_child(element_node, insert_position)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Threaded-safe wrapper functions
|
|
|
|
|
static func emit_dom_operation(lua_api: LuaAPI, operation: Dictionary) -> void:
|
|
|
|
|
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
|
|
|
|
|
|
|
|
|
|
static func create_element_wrapper(vm: LuauVM, element: HTMLParser.HTMLElement, lua_api: LuaAPI) -> void:
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
|
|
|
|
|
var element_id: String
|
|
|
|
|
if element.tag_name == "body":
|
|
|
|
|
element_id = "body"
|
|
|
|
|
else:
|
|
|
|
|
element_id = element.get_attribute("id")
|
|
|
|
|
if element_id.is_empty():
|
|
|
|
|
element_id = lua_api.get_or_assign_element_id(element)
|
|
|
|
|
element.set_attribute("id", element_id)
|
|
|
|
|
|
|
|
|
|
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, lua_api)
|
|
|
|
|
|
|
|
|
|
static func add_element_methods(vm: LuauVM, lua_api: LuaAPI) -> void:
|
|
|
|
|
vm.set_meta("lua_api", lua_api)
|
|
|
|
|
|
2025-08-11 17:08:39 +03:00
|
|
|
vm.lua_pushcallable(LuaAudioUtils._dom_audio_play_handler, "_dom_audio_play")
|
|
|
|
|
vm.lua_setglobal("_dom_audio_play")
|
|
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaAudioUtils._dom_audio_pause_handler, "_dom_audio_pause")
|
|
|
|
|
vm.lua_setglobal("_dom_audio_pause")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_on_wrapper, "element.on")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "on")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_append_wrapper, "element.append")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "append")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_set_text_wrapper, "element.setText")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "setText")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_remove_wrapper, "element.remove")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "remove")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_insert_before_wrapper, "element.insertBefore")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "insertBefore")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_insert_after_wrapper, "element.insertAfter")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "insertAfter")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_replace_wrapper, "element.replace")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "replace")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_clone_wrapper, "element.clone")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "clone")
|
2025-08-09 13:28:24 +03:00
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_get_attribute_wrapper, "element.getAttribute")
|
|
|
|
|
vm.lua_setfield(-2, "getAttribute")
|
|
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_set_attribute_wrapper, "element.setAttribute")
|
|
|
|
|
vm.lua_setfield(-2, "setAttribute")
|
2025-08-08 16:51:21 +03:00
|
|
|
|
2025-08-10 13:52:27 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_create_tween_wrapper, "element.createTween")
|
|
|
|
|
vm.lua_setfield(-2, "createTween")
|
|
|
|
|
|
2025-08-18 17:45:46 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_show_wrapper, "element.show")
|
|
|
|
|
vm.lua_setfield(-2, "show")
|
|
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_hide_wrapper, "element.hide")
|
|
|
|
|
vm.lua_setfield(-2, "hide")
|
|
|
|
|
|
2025-08-27 20:23:05 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_focus_wrapper, "element.focus")
|
|
|
|
|
vm.lua_setfield(-2, "focus")
|
|
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_unfocus_wrapper, "element.unfocus")
|
|
|
|
|
vm.lua_setfield(-2, "unfocus")
|
|
|
|
|
|
2025-08-31 14:17:34 +03:00
|
|
|
vm.lua_pushcallable(LuaCanvasUtils._element_withContext_wrapper, "element.withContext")
|
|
|
|
|
vm.lua_setfield(-2, "withContext")
|
|
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
add_classlist_support(vm)
|
2025-08-08 16:51:21 +03:00
|
|
|
|
|
|
|
|
vm.lua_newtable()
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_index_wrapper, "element.__index")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "__index")
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._element_newindex_wrapper, "element.__newindex")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "__newindex")
|
|
|
|
|
vm.lua_setmetatable(-2)
|
|
|
|
|
|
|
|
|
|
static func _element_on_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var event_name: String = vm.luaL_checkstring(2)
|
|
|
|
|
vm.luaL_checktype(3, vm.LUA_TFUNCTION)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var callback_ref = lua_api.next_callback_ref
|
|
|
|
|
lua_api.next_callback_ref += 1
|
|
|
|
|
|
|
|
|
|
var subscription_id = lua_api.next_subscription_id
|
|
|
|
|
lua_api.next_subscription_id += 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushvalue(3)
|
|
|
|
|
vm.lua_rawseti(vm.LUA_REGISTRYINDEX, callback_ref)
|
|
|
|
|
|
|
|
|
|
lua_api.call_deferred("_register_event_on_main_thread", element_id, event_name, callback_ref, subscription_id)
|
|
|
|
|
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
vm.lua_pushinteger(subscription_id)
|
|
|
|
|
vm.lua_setfield(-2, "_subscription_id")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._unsubscribe_wrapper, "subscription.unsubscribe")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "unsubscribe")
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func _element_append_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Queue append operation for main thread
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
|
|
|
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # child
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var parent_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(2, "_element_id")
|
|
|
|
|
var child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "append_element",
|
|
|
|
|
"parent_id": parent_id,
|
|
|
|
|
"child_id": child_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_set_text_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var text: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
element.text_content = text
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "set_text",
|
|
|
|
|
"selector": "#" + element_id,
|
|
|
|
|
"text": text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_remove_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get element ID from self table
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "remove_element",
|
|
|
|
|
"element_id": element_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Queue operation for main thread
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_insert_before_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get parent element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
|
|
|
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
|
|
|
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var parent_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(2, "_element_id")
|
|
|
|
|
var new_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(3, "_element_id")
|
|
|
|
|
var reference_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "insert_before",
|
|
|
|
|
"parent_id": parent_id,
|
|
|
|
|
"new_child_id": new_child_id,
|
|
|
|
|
"reference_child_id": reference_child_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_insert_after_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get parent element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
|
|
|
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
|
|
|
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # reference_child
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var parent_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(2, "_element_id")
|
|
|
|
|
var new_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(3, "_element_id")
|
|
|
|
|
var reference_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "insert_after",
|
|
|
|
|
"parent_id": parent_id,
|
|
|
|
|
"new_child_id": new_child_id,
|
|
|
|
|
"reference_child_id": reference_child_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_replace_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get parent element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # parent
|
|
|
|
|
vm.luaL_checktype(2, vm.LUA_TTABLE) # new_child
|
|
|
|
|
vm.luaL_checktype(3, vm.LUA_TTABLE) # old_child
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var parent_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(2, "_element_id")
|
|
|
|
|
var new_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(3, "_element_id")
|
|
|
|
|
var old_child_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "replace_child",
|
|
|
|
|
"parent_id": parent_id,
|
|
|
|
|
"new_child_id": new_child_id,
|
|
|
|
|
"old_child_id": old_child_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_clone_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # element
|
|
|
|
|
var deep: bool = true # Default to deep clone
|
|
|
|
|
|
|
|
|
|
if vm.lua_gettop() >= 2:
|
|
|
|
|
deep = vm.lua_toboolean(2)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
# Find the element to clone
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var cloned_element = clone_element(element, deep)
|
|
|
|
|
|
|
|
|
|
# Assign new ID to cloned element
|
2025-08-27 20:56:29 +03:00
|
|
|
lua_api.get_or_assign_element_id(cloned_element)
|
2025-08-08 16:51:21 +03:00
|
|
|
|
|
|
|
|
# Add to parser's element collection
|
|
|
|
|
lua_api.dom_parser.parse_result.all_elements.append(cloned_element)
|
|
|
|
|
|
|
|
|
|
# Create element wrapper for the cloned element
|
|
|
|
|
create_element_wrapper(vm, cloned_element, lua_api)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
static func _element_get_attribute_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Get element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # element
|
|
|
|
|
var attribute_name: String = vm.luaL_checkstring(2) # attribute name
|
|
|
|
|
|
|
|
|
|
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 = lua_api.dom_parser.find_first("body")
|
|
|
|
|
else:
|
|
|
|
|
element = lua_api.dom_parser.find_by_id(element_id)
|
|
|
|
|
|
|
|
|
|
if not element:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Get the attribute value
|
|
|
|
|
var attribute_value = element.get_attribute(attribute_name)
|
|
|
|
|
if attribute_value.is_empty():
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
else:
|
|
|
|
|
vm.lua_pushstring(attribute_value)
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func _element_set_attribute_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Get element ID from self table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # element
|
|
|
|
|
var attribute_name: String = vm.luaL_checkstring(2) # attribute name
|
|
|
|
|
var attribute_value: String = vm.luaL_checkstring(3) # attribute value
|
|
|
|
|
|
|
|
|
|
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 = lua_api.dom_parser.find_first("body")
|
|
|
|
|
else:
|
|
|
|
|
element = lua_api.dom_parser.find_by_id(element_id)
|
|
|
|
|
|
|
|
|
|
if not element:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# Handle removing attribute when value is empty
|
|
|
|
|
if attribute_value == "":
|
|
|
|
|
element.attributes.erase(attribute_name)
|
|
|
|
|
else:
|
|
|
|
|
element.set_attribute(attribute_name, attribute_value)
|
|
|
|
|
|
|
|
|
|
# Trigger visual update by calling init() again for DOM nodes (must be on main thread)
|
|
|
|
|
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
|
|
|
if dom_node and dom_node.has_method("init"):
|
|
|
|
|
dom_node.call_deferred("init", element, lua_api.dom_parser)
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
static func _element_index_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var key: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
2025-08-11 17:08:39 +03:00
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_tag_name")
|
|
|
|
|
var tag_name: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
if tag_name == "audio":
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
return LuaAudioUtils.handle_dom_audio_index(vm, element_id, key)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
match key:
|
2025-08-19 18:27:25 +03:00
|
|
|
"value":
|
2025-08-20 14:37:57 +03:00
|
|
|
if lua_api and (tag_name == "input" or tag_name == "select"):
|
2025-08-19 18:27:25 +03:00
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
|
|
|
|
|
|
|
|
if element and dom_node:
|
2025-08-20 14:37:57 +03:00
|
|
|
var value_result: String
|
|
|
|
|
if tag_name == "input":
|
|
|
|
|
value_result = str(_get_input_value(element, dom_node))
|
|
|
|
|
elif tag_name == "select":
|
|
|
|
|
value_result = _get_select_value(element, dom_node)
|
|
|
|
|
|
|
|
|
|
vm.lua_pushstring(value_result)
|
2025-08-19 18:27:25 +03:00
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Fallback to empty string
|
|
|
|
|
vm.lua_pushstring("")
|
|
|
|
|
return 1
|
2025-08-08 16:51:21 +03:00
|
|
|
"text":
|
|
|
|
|
if lua_api:
|
|
|
|
|
# Get element ID and find the element
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
vm.lua_pushstring(element.text_content)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Fallback to empty string
|
|
|
|
|
vm.lua_pushstring("")
|
|
|
|
|
return 1
|
|
|
|
|
"children":
|
|
|
|
|
if lua_api:
|
|
|
|
|
# Get element ID and find the element
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
# Create array of child elements
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
var index = 1
|
|
|
|
|
for child in element.children:
|
|
|
|
|
create_element_wrapper(vm, child, lua_api)
|
|
|
|
|
vm.lua_rawseti(-2, index)
|
|
|
|
|
index += 1
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Fallback to empty array
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
return 1
|
2025-08-18 17:45:46 +03:00
|
|
|
"visible":
|
|
|
|
|
if lua_api:
|
|
|
|
|
# Get element ID and find the element
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
# Check if element has display: none (hidden class)
|
|
|
|
|
var class_attr = element.get_attribute("class")
|
|
|
|
|
var is_hidden = "hidden" in class_attr or element.get_attribute("style").contains("display:none") or element.get_attribute("style").contains("display: none")
|
|
|
|
|
vm.lua_pushboolean(not is_hidden)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Fallback to true (visible by default)
|
|
|
|
|
vm.lua_pushboolean(true)
|
|
|
|
|
return 1
|
2025-08-08 16:51:21 +03:00
|
|
|
_:
|
|
|
|
|
# Check for DOM traversal properties first
|
|
|
|
|
if lua_api:
|
|
|
|
|
match key:
|
|
|
|
|
"parent":
|
|
|
|
|
return get_element_parent_handler(vm, lua_api.dom_parser, lua_api)
|
|
|
|
|
"nextSibling":
|
|
|
|
|
return get_element_next_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
|
|
|
|
"previousSibling":
|
|
|
|
|
return get_element_previous_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
|
|
|
|
"firstChild":
|
|
|
|
|
return get_element_first_child_handler(vm, lua_api.dom_parser, lua_api)
|
|
|
|
|
"lastChild":
|
|
|
|
|
return get_element_last_child_handler(vm, lua_api.dom_parser, lua_api)
|
|
|
|
|
|
|
|
|
|
# Check if it's a method in the original table
|
|
|
|
|
vm.lua_pushvalue(1)
|
|
|
|
|
vm.lua_pushstring(key)
|
|
|
|
|
vm.lua_rawget(-2)
|
|
|
|
|
vm.lua_remove(-2)
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-27 20:56:29 +03:00
|
|
|
static func add_classlist_support(vm: LuauVM) -> void:
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_newtable()
|
|
|
|
|
|
2025-08-19 18:27:25 +03:00
|
|
|
vm.lua_getfield(-2, "_element_id")
|
|
|
|
|
vm.lua_setfield(-2, "_element_id")
|
2025-08-08 16:51:21 +03:00
|
|
|
|
|
|
|
|
# Add classList methods
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_add_wrapper, "classList.add")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "add")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_remove_wrapper, "classList.remove")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "remove")
|
|
|
|
|
|
2025-08-09 13:28:24 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_toggle_wrapper, "classList.toggle")
|
2025-08-08 16:51:21 +03:00
|
|
|
vm.lua_setfield(-2, "toggle")
|
|
|
|
|
|
2025-08-19 09:47:58 +03:00
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_contains_wrapper, "classList.contains")
|
|
|
|
|
vm.lua_setfield(-2, "contains")
|
|
|
|
|
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_item_wrapper, "classList.item")
|
|
|
|
|
vm.lua_setfield(-2, "item")
|
|
|
|
|
|
|
|
|
|
vm.lua_newtable()
|
|
|
|
|
vm.lua_pushcallable(LuaDOMUtils._classlist_index_wrapper, "classList.__index")
|
|
|
|
|
vm.lua_setfield(-2, "__index")
|
|
|
|
|
vm.lua_setmetatable(-2)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
# Set classList on the element
|
|
|
|
|
vm.lua_setfield(-2, "classList")
|
|
|
|
|
|
|
|
|
|
static func _classlist_add_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
2025-08-19 18:27:25 +03:00
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
2025-08-08 16:51:21 +03:00
|
|
|
var cls: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
# Get element_id from classList table
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "add_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": cls
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _classlist_remove_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
# Get lua_api from VM metadata
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table
|
|
|
|
|
var cls: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
# Get element_id from classList table
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "remove_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": cls
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _classlist_toggle_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
# Get lua_api from VM metadata
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table
|
|
|
|
|
var cls: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
# Get element_id from classList table
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "toggle_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": cls
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
|
|
|
|
|
2025-08-19 09:47:58 +03:00
|
|
|
static func _classlist_contains_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var cls: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var current_style = element.get_attribute("style", "")
|
|
|
|
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
|
|
|
|
|
|
|
|
|
var has_class = cls in style_classes
|
|
|
|
|
|
|
|
|
|
vm.lua_pushboolean(has_class)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func _classlist_item_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var index: int = vm.luaL_checkint(2) - 1 # Convert from 1-based to 0-based indexing
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var current_style = element.get_attribute("style", "")
|
|
|
|
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
|
|
|
|
if index >= 0 and index < style_classes.size():
|
|
|
|
|
vm.lua_pushstring(style_classes[index])
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func _classlist_index_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var key: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
if key == "length":
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushinteger(0)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var current_style = element.get_attribute("style", "")
|
|
|
|
|
var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else []
|
|
|
|
|
vm.lua_pushinteger(style_classes.size())
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushinteger(0)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
vm.lua_pushvalue(1)
|
|
|
|
|
vm.lua_pushstring(key)
|
|
|
|
|
vm.lua_rawget(-2)
|
|
|
|
|
vm.lua_remove(-2)
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
static func _element_newindex_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
# Get lua_api from VM metadata
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var key: String = vm.luaL_checkstring(2)
|
|
|
|
|
var value = vm.lua_tovariant(3)
|
|
|
|
|
|
2025-08-11 17:08:39 +03:00
|
|
|
vm.lua_getfield(1, "_tag_name")
|
|
|
|
|
var tag_name: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
if tag_name == "audio":
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
return LuaAudioUtils.handle_dom_audio_newindex(vm, element_id, key, value)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
match key:
|
2025-08-19 18:27:25 +03:00
|
|
|
"value":
|
2025-08-20 14:37:57 +03:00
|
|
|
if tag_name == "input" or tag_name == "select":
|
2025-08-19 18:27:25 +03:00
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
|
|
|
|
|
|
|
|
|
|
if element and dom_node:
|
2025-08-20 14:37:57 +03:00
|
|
|
if tag_name == "input":
|
|
|
|
|
element.set_attribute("value", str(value))
|
|
|
|
|
_set_input_value(element, dom_node, value)
|
|
|
|
|
elif tag_name == "select":
|
|
|
|
|
element.set_attribute("value", str(value))
|
|
|
|
|
_set_select_value(element, dom_node, value)
|
2025-08-19 18:27:25 +03:00
|
|
|
return 0
|
2025-08-08 16:51:21 +03:00
|
|
|
"text":
|
|
|
|
|
var text: String = str(value) # Convert value to string
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
element.text_content = text
|
|
|
|
|
|
|
|
|
|
# Also queue the DOM operation for visual updates if the element is already rendered
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "set_text",
|
|
|
|
|
"selector": "#" + element_id,
|
|
|
|
|
"text": text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
2025-08-18 17:45:46 +03:00
|
|
|
"visible":
|
|
|
|
|
var is_visible: bool = vm.lua_toboolean(3)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_element_id")
|
|
|
|
|
var element_id: String = vm.lua_tostring(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var class_attr = element.get_attribute("class")
|
|
|
|
|
var classes = class_attr.split(" ") if not class_attr.is_empty() else []
|
|
|
|
|
|
|
|
|
|
if is_visible:
|
|
|
|
|
# Remove hidden class if present
|
|
|
|
|
var hidden_index = classes.find("hidden")
|
|
|
|
|
if hidden_index >= 0:
|
|
|
|
|
classes.remove_at(hidden_index)
|
|
|
|
|
var new_class_attr = " ".join(classes).strip_edges()
|
|
|
|
|
element.set_attribute("class", new_class_attr)
|
|
|
|
|
|
|
|
|
|
# Update visual element
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "remove_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": "hidden"
|
|
|
|
|
}
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
else:
|
|
|
|
|
# Add hidden class if not present
|
|
|
|
|
if not "hidden" in classes:
|
|
|
|
|
classes.append("hidden")
|
|
|
|
|
var new_class_attr = " ".join(classes).strip_edges()
|
|
|
|
|
element.set_attribute("class", new_class_attr)
|
|
|
|
|
|
|
|
|
|
# Update visual element
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "add_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": "hidden"
|
|
|
|
|
}
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
return 0
|
2025-08-08 16:51:21 +03:00
|
|
|
_:
|
|
|
|
|
# Store in table normally
|
|
|
|
|
vm.lua_pushvalue(2)
|
|
|
|
|
vm.lua_pushvalue(3)
|
|
|
|
|
vm.lua_rawset(1)
|
|
|
|
|
return 0
|
|
|
|
|
|
2025-08-18 17:45:46 +03:00
|
|
|
static func _element_show_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var class_attr = element.get_attribute("class")
|
|
|
|
|
var classes = class_attr.split(" ") if not class_attr.is_empty() else []
|
|
|
|
|
|
|
|
|
|
# Remove hidden class if present
|
|
|
|
|
var hidden_index = classes.find("hidden")
|
|
|
|
|
if hidden_index >= 0:
|
|
|
|
|
classes.remove_at(hidden_index)
|
|
|
|
|
var new_class_attr = " ".join(classes).strip_edges()
|
|
|
|
|
element.set_attribute("class", new_class_attr)
|
|
|
|
|
|
|
|
|
|
# Update visual element
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "remove_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": "hidden"
|
|
|
|
|
}
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
static func _element_hide_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
|
|
|
|
|
if element:
|
|
|
|
|
var class_attr = element.get_attribute("class")
|
|
|
|
|
var classes = class_attr.split(" ") if not class_attr.is_empty() else []
|
|
|
|
|
|
|
|
|
|
# Add hidden class if not present
|
|
|
|
|
if not "hidden" in classes:
|
|
|
|
|
classes.append("hidden")
|
|
|
|
|
var new_class_attr = " ".join(classes).strip_edges()
|
|
|
|
|
element.set_attribute("class", new_class_attr)
|
|
|
|
|
|
|
|
|
|
# Update visual element
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "add_class",
|
|
|
|
|
"element_id": element_id,
|
|
|
|
|
"class_name": "hidden"
|
|
|
|
|
}
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
2025-08-27 20:23:05 +03:00
|
|
|
static func _element_focus_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "focus_element",
|
|
|
|
|
"element_id": element_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
vm.lua_pushboolean(true)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
static func _element_unfocus_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
var operation = {
|
|
|
|
|
"type": "unfocus_element",
|
|
|
|
|
"element_id": element_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit_dom_operation(lua_api, operation)
|
|
|
|
|
vm.lua_pushboolean(true)
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-10 13:52:27 +03:00
|
|
|
static func _element_create_tween_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushnil()
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
return LuaTweenUtils.create_element_tween(vm, lua_api)
|
|
|
|
|
|
2025-08-08 16:51:21 +03:00
|
|
|
static func _unsubscribe_wrapper(vm: LuauVM) -> int:
|
|
|
|
|
# Get subscription ID from the subscription table
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
|
|
|
|
|
vm.lua_getfield(1, "_subscription_id")
|
|
|
|
|
var subscription_id: int = vm.lua_tointeger(-1)
|
|
|
|
|
vm.lua_pop(1)
|
|
|
|
|
|
|
|
|
|
# Get lua_api from VM metadata
|
|
|
|
|
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
|
|
|
|
if not lua_api:
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# Handle unsubscribe on main thread
|
|
|
|
|
if subscription_id > 0:
|
|
|
|
|
lua_api.call_deferred("_unsubscribe_event_on_main_thread", subscription_id)
|
|
|
|
|
vm.lua_pushboolean(true)
|
|
|
|
|
else:
|
|
|
|
|
vm.lua_pushboolean(false)
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
2025-08-06 16:03:09 +03:00
|
|
|
static func _index_handler(vm: LuauVM, lua_api: LuaAPI) -> int:
|
|
|
|
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
|
|
|
|
var key: String = vm.luaL_checkstring(2)
|
|
|
|
|
|
|
|
|
|
match key:
|
|
|
|
|
"parent":
|
2025-08-08 16:51:21 +03:00
|
|
|
return get_element_parent_handler(vm, lua_api.dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
"nextSibling":
|
2025-08-08 16:51:21 +03:00
|
|
|
return get_element_next_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
"previousSibling":
|
2025-08-08 16:51:21 +03:00
|
|
|
return get_element_previous_sibling_handler(vm, lua_api.dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
"firstChild":
|
2025-08-08 16:51:21 +03:00
|
|
|
return get_element_first_child_handler(vm, lua_api.dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
"lastChild":
|
2025-08-08 16:51:21 +03:00
|
|
|
return get_element_last_child_handler(vm, lua_api.dom_parser, lua_api)
|
2025-08-06 16:03:09 +03:00
|
|
|
_:
|
2025-08-08 16:51:21 +03:00
|
|
|
return _element_index_wrapper(vm)
|