diff --git a/README.md b/README.md index 5b01306..2826b27 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ TODO: 11. **< table >** component. [🔗 Related Godot proposal](https://github.com/godotengine/godot-proposals/issues/97) 12. **< canvas >** component should be theoretically impossible by exposing Godot `_draw()` APIs to Lua. 13. `grid` display property for CSS, using `GridContainer` in Godot. +14. Position `absolute` can be achieved by wrapping inside a Control node with a 0,0 size (so it's ignored by the layout), and moving the inner child according to CSS. Issues: 1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing diff --git a/dns/frontend/dashboard.lua b/dns/frontend/dashboard.lua index b491fd2..41f0931 100644 --- a/dns/frontend/dashboard.lua +++ b/dns/frontend/dashboard.lua @@ -8,6 +8,7 @@ local domainsList = gurt.select('#domains-list') local tldSelector = gurt.select('#tld-selector') local loadingElement = gurt.select('#tld-loading') local displayElement = gurt.select('#invite-code-display') +local options displayElement:hide() @@ -49,18 +50,23 @@ local function renderTLDSelector() ['data-tld'] = tld }) + tldSelector:append(option) + option:on('click', function() -- Clear previous selection - local options = gurt.selectAll('.tld-option') + if not options then + options = gurt.selectAll('.tld-option') + end + for j = 1, #options do - options[j].classList:remove('tld-selected') + if options[j].classList:contains('tld-selected') then + options[j].classList:remove('tld-selected') + end end -- Select this option option.classList:add('tld-selected') end) - - tldSelector:append(option) i = i + 1 end, 16) end diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd index 46b3ee5..32859b9 100644 --- a/flumi/Scripts/B9/Lua.gd +++ b/flumi/Scripts/B9/Lua.gd @@ -23,6 +23,7 @@ var next_callback_ref: int = 1 var element_id_counter: int = 1 var element_id_registry: Dictionary = {} +var pending_event_registrations: Array = [] func _init(): timeout_manager = LuaTimeoutManager.new() @@ -729,6 +730,19 @@ func _register_event_on_main_thread(element_id: String, event_name: String, call # This runs on the main thread - safe to access DOM nodes var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) if not dom_node: + var pending_registration = { + "element_id": element_id, + "event_name": event_name, + "callback_ref": callback_ref, + "subscription_id": subscription_id if subscription_id != -1 else next_subscription_id + } + + if subscription_id == -1: + next_subscription_id += 1 + + pending_event_registrations.append(pending_registration) + + call_deferred("_process_pending_event_registrations") return # Use provided subscription_id or generate a new one @@ -750,6 +764,29 @@ func _register_event_on_main_thread(element_id: String, event_name: String, call var signal_node = get_dom_node(dom_node, "signal") LuaEventUtils.connect_element_event(signal_node, event_name, subscription) +func _process_pending_event_registrations(): + + var i = 0 + while i < pending_event_registrations.size(): + var registration = pending_event_registrations[i] + var element_id = registration.element_id + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + + if dom_node: + pending_event_registrations.remove_at(i) + + _register_event_on_main_thread( + registration.element_id, + registration.event_name, + registration.callback_ref, + registration.subscription_id + ) + else: + i += 1 + + if pending_event_registrations.size() > 0: + call_deferred("_process_pending_event_registrations") + func _unsubscribe_event_on_main_thread(subscription_id: int): # This runs on the main thread - safe to cleanup event subscriptions var subscription = event_subscriptions.get(subscription_id, null) diff --git a/flumi/Scripts/Utils/Lua/DOM.gd b/flumi/Scripts/Utils/Lua/DOM.gd index 66293c0..9fb6f83 100644 --- a/flumi/Scripts/Utils/Lua/DOM.gd +++ b/flumi/Scripts/Utils/Lua/DOM.gd @@ -268,6 +268,8 @@ static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Nod if not main_scene: return + var element_id = element.get_attribute("id") + # Create the visual node for the element var element_node = await main_scene.create_element_node(element, dom_parser) if not element_node: @@ -279,7 +281,6 @@ static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Nod # 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: @@ -945,6 +946,17 @@ static func _add_classlist_support(vm: LuauVM, lua_api: LuaAPI) -> void: vm.lua_pushcallable(LuaDOMUtils._classlist_toggle_wrapper, "classList.toggle") vm.lua_setfield(-2, "toggle") + 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) + # Set classList on the element vm.lua_setfield(-2, "classList") @@ -1017,6 +1029,88 @@ static func _classlist_toggle_wrapper(vm: LuauVM) -> int: emit_dom_operation(lua_api, operation) return 0 +static func _classlist_contains_wrapper(vm: LuauVM) -> int: + var start_time = Time.get_ticks_msec() + + 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 + static func _element_newindex_wrapper(vm: LuauVM) -> int: # Get lua_api from VM metadata var lua_api = vm.get_meta("lua_api") as LuaAPI