CSS selectors in select() Lua API // fix class attr functions

This commit is contained in:
Face
2025-08-06 13:59:43 +03:00
parent f68f31d526
commit 05a959647d
7 changed files with 79 additions and 66 deletions

View File

@@ -137,6 +137,7 @@ const config: Config = {
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['lua'],
},
} satisfies Preset.ThemeConfig,
};

View File

@@ -112,7 +112,7 @@ class CSSStylesheet:
match rule.selector_type:
"simple":
return matches_simple_selector(rule.selector_parts[0], tag_name, cls_names)
return matches_simple_selector_with_element(rule.selector_parts[0], element)
"descendant":
return matches_descendant_selector(rule.selector_parts, element)
"child":
@@ -130,16 +130,33 @@ class CSSStylesheet:
if selector.begins_with("."):
var cls = selector.substr(1)
return cls in cls_names
elif selector.begins_with("#"):
# need access to the element to check its ID
return false # will be handled by matches_simple_selector_with_element
else:
return selector == tag_name
func matches_simple_selector_with_element(selector: String, element: HTMLParser.HTMLElement) -> bool:
if not element:
return false
if selector.begins_with("."):
var cls = selector.substr(1)
var cls_names = HTMLParser.extract_class_names(element)
return cls in cls_names
elif selector.begins_with("#"):
var element_id = selector.substr(1)
return element.get_attribute("id") == element_id
else:
return selector == element.tag_name
func matches_descendant_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
if not element or parts.size() < 2:
return false
# Last part should match current element
var last_part = parts[-1].strip_edges()
if not matches_simple_selector(last_part, element.tag_name, get_element_class_names(element)):
if not matches_simple_selector_with_element(last_part, element):
return false
# Check ancestors for remaining parts
@@ -148,7 +165,7 @@ class CSSStylesheet:
while current_element and part_index >= 0:
var part = parts[part_index].strip_edges()
if matches_simple_selector(part, current_element.tag_name, get_element_class_names(current_element)):
if matches_simple_selector_with_element(part, current_element):
part_index -= 1
if part_index < 0:
return true
@@ -164,11 +181,11 @@ class CSSStylesheet:
var parent_part = parts[0].strip_edges()
# Element must match the child part
if not matches_simple_selector(child_part, element.tag_name, get_element_class_names(element)):
if not matches_simple_selector_with_element(child_part, element):
return false
# Parent must match the parent part
return matches_simple_selector(parent_part, element.parent.tag_name, get_element_class_names(element.parent))
return matches_simple_selector_with_element(parent_part, element.parent)
func matches_adjacent_sibling_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
if not element or not element.parent or parts.size() != 2:
@@ -177,7 +194,7 @@ class CSSStylesheet:
var second_part = parts[1].strip_edges()
var first_part = parts[0].strip_edges()
if not matches_simple_selector(second_part, element.tag_name, get_element_class_names(element)):
if not matches_simple_selector_with_element(second_part, element):
return false
# Find previous sibling
@@ -187,7 +204,7 @@ class CSSStylesheet:
return false
var prev_sibling = siblings[element_index - 1]
return matches_simple_selector(first_part, prev_sibling.tag_name, get_element_class_names(prev_sibling))
return matches_simple_selector_with_element(first_part, prev_sibling)
func matches_general_sibling_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
if not element or not element.parent or parts.size() != 2:
@@ -196,7 +213,7 @@ class CSSStylesheet:
var second_part = parts[1].strip_edges()
var first_part = parts[0].strip_edges()
if not matches_simple_selector(second_part, element.tag_name, get_element_class_names(element)):
if not matches_simple_selector_with_element(second_part, element):
return false
# Check all previous siblings
@@ -205,7 +222,7 @@ class CSSStylesheet:
for i in range(element_index):
var sibling = siblings[i]
if matches_simple_selector(first_part, sibling.tag_name, get_element_class_names(sibling)):
if matches_simple_selector_with_element(first_part, sibling):
return true
return false
@@ -218,7 +235,7 @@ class CSSStylesheet:
var attribute_part = parts[1].strip_edges()
# Check if element matches
if element_part != "" and not matches_simple_selector(element_part, tag_name, cls_names):
if element_part != "" and not matches_simple_selector_with_element(element_part, element):
return false
# Parse attribute condition
@@ -261,17 +278,6 @@ class CSSStylesheet:
attr_value = attr_value.substr(1, attr_value.length() - 2)
return {"name": attr_name, "value": attr_value}
func get_element_class_names(element: HTMLParser.HTMLElement) -> Array[String]:
var class_names: Array[String] = []
var class_attr = element.get_attribute("class")
if class_attr.length() > 0:
var classes = class_attr.split(" ")
for cls in classes:
cls = cls.strip_edges()
if cls.length() > 0:
class_names.append(cls)
return class_names
var stylesheet: CSSStylesheet
var css_text: String

View File

@@ -142,7 +142,7 @@ func get_element_styles_with_inheritance(element: HTMLElement, event: String = "
var styles = {}
var class_names = get_css_class_names(element)
var class_names = extract_class_names(element)
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names, element))
# Apply inline styles (higher priority) - force override CSS rules
var inline_style = element.get_attribute("style")
@@ -169,7 +169,7 @@ func get_element_styles_internal(element: HTMLElement, event: String = "") -> Di
# Apply CSS rules
if parse_result.css_parser:
var class_names = get_css_class_names(element)
var class_names = extract_class_names(element)
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names, element))
# Apply inline styles (higher priority) - force override CSS rules
@@ -227,18 +227,7 @@ func parse_inline_style_with_event(style_string: String, event: String = "") ->
return properties
func get_css_class_names(element: HTMLElement) -> Array[String]:
var class_names: Array[String] = []
var class_attr = element.get_attribute("class")
if class_attr.length() > 0:
var classes = class_attr.split(" ")
for cls in classes:
cls = cls.strip_edges()
if cls.length() > 0:
class_names.append(cls)
return class_names
func extract_class_names_from_style(element: HTMLElement) -> Array[String]:
static func extract_class_names(element: HTMLElement) -> Array[String]:
var class_names: Array[String] = []
var style_attr = element.get_attribute("style")
if style_attr.length() > 0:
@@ -281,8 +270,10 @@ func find_all_by_class(tag: String, the_class_name: String) -> Array[HTMLElement
var results: Array[HTMLElement] = []
for element in parse_result.all_elements:
if element.tag_name == tag and element.get_class_name() == the_class_name:
results.append(element)
if element.tag_name == tag:
var class_names = extract_class_names(element)
if the_class_name in class_names:
results.append(element)
return results

View File

@@ -43,21 +43,18 @@ func get_or_assign_element_id(element: HTMLParser.HTMLElement) -> String:
func _gurt_select_handler(vm: LuauVM) -> int:
var selector: String = vm.luaL_checkstring(1)
var element_id = ""
if selector.begins_with("#"):
element_id = selector.substr(1)
else:
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
if not element:
vm.lua_pushnil()
return 1
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
if not dom_node:
vm.lua_pushnil()
return 1
var element_id = get_or_assign_element_id(element)
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)
return 1
@@ -66,26 +63,7 @@ func _gurt_select_handler(vm: LuauVM) -> int:
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)
var elements = SelectorUtils.find_all_matching(selector, dom_parser.parse_result.all_elements)
vm.lua_newtable()
var index = 1

View File

@@ -823,7 +823,11 @@ var HTML_CONTENT = """<head>
local mySignal = Signal.new()
local dataSignal = Signal.new()
local userActionSignal = Signal.new()
print(".container > div: ", gurt.selectAll('.container > div'))
print(".container div: ", gurt.selectAll('.container div'))
print("button[disabled]: ", gurt.selectAll('button[disabled]'))
print(".container: ", gurt.selectAll('.container'))
print("#log-area: ", gurt.selectAll('#log-area'))
-- Get UI elements
local logArea = gurt.select('#log-area')
local statusDisplay = gurt.select('#status-display')
@@ -984,6 +988,8 @@ var HTML_CONTENT = """<head>
<li><strong>Argument Passing:</strong> Signals can pass multiple arguments to connected callbacks</li>
</ul>
</div>
<button disabled="true">Test</button>
<button disabled="true">Test2</button>
</div>
</body>
""".to_utf8_buffer()

View File

@@ -0,0 +1,30 @@
class_name SelectorUtils
extends RefCounted
static func match_element(selector: String, element: HTMLParser.HTMLElement) -> bool:
if not element:
return false
var rule = CSSParser.CSSRule.new()
rule.init(selector)
var class_names = HTMLParser.extract_class_names(element)
var stylesheet = CSSParser.CSSStylesheet.new()
return stylesheet.selector_matches(rule, element.tag_name, "", class_names, element)
static func find_all_matching(selector: String, elements: Array[HTMLParser.HTMLElement]) -> Array[HTMLParser.HTMLElement]:
var matches: Array[HTMLParser.HTMLElement] = []
for element in elements:
if match_element(selector, element):
matches.append(element)
return matches
static func find_first_matching(selector: String, elements: Array[HTMLParser.HTMLElement]) -> HTMLParser.HTMLElement:
for element in elements:
if match_element(selector, element):
return element
return null

View File

@@ -0,0 +1 @@
uid://ce151ks0vlr2v