|
|
@@ -28,7 +28,10 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public string text_content {
|
|
|
owned get { return xml_node->get_content() ?? ""; }
|
|
|
- set { xml_node->set_content(value); }
|
|
|
+ set {
|
|
|
+ xml_node->set_content(value);
|
|
|
+ document.update(this);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -43,6 +46,7 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public void set_attribute(string name, string value) {
|
|
|
xml_node->set_prop(name, value);
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -50,6 +54,7 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public void remove_attribute(string name) {
|
|
|
xml_node->unset_prop(name);
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -119,6 +124,7 @@ namespace Astralis {
|
|
|
}
|
|
|
new_classes[current_classes.length] = class_name;
|
|
|
set_attribute("class", string.joinv(" ", new_classes));
|
|
|
+ // Note: set_attribute already emits update signal
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -148,6 +154,7 @@ namespace Astralis {
|
|
|
} else {
|
|
|
remove_attribute("class");
|
|
|
}
|
|
|
+ // Note: set_attribute/remove_attribute already emit update signal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -160,6 +167,7 @@ namespace Astralis {
|
|
|
} else {
|
|
|
add_class(class_name);
|
|
|
}
|
|
|
+ // Note: remove_class/add_class already emit update signal
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -167,6 +175,144 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public void set_classes(string[] new_classes) {
|
|
|
set_attribute("class", string.joinv(" ", new_classes));
|
|
|
+ // Note: set_attribute already emits update signal
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the style attribute as a string
|
|
|
+ /// </summary>
|
|
|
+ public string? style_string {
|
|
|
+ owned get { return get_attribute("style"); }
|
|
|
+ set {
|
|
|
+ if (value != null) {
|
|
|
+ set_attribute("style", value);
|
|
|
+ } else {
|
|
|
+ remove_attribute("style");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Parses the style attribute and returns a dictionary of CSS property names to values
|
|
|
+ /// </summary>
|
|
|
+ public ReadOnlyAssociative<string, string> styles {
|
|
|
+ owned get {
|
|
|
+ var result = new Dictionary<string, string>();
|
|
|
+ var style_attr = get_attribute("style");
|
|
|
+ if (style_attr == null || style_attr.strip() == "") {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Split by semicolons to get individual property: value pairs
|
|
|
+ var declarations = style_attr.split(";");
|
|
|
+ foreach (unowned var decl in declarations) {
|
|
|
+ var trimmed = decl.strip();
|
|
|
+ if (trimmed == "") {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Split by colon to separate property and value
|
|
|
+ var parts = trimmed.split(":", 2);
|
|
|
+ if (parts.length == 2) {
|
|
|
+ var prop = parts[0].strip();
|
|
|
+ var val = parts[1].strip();
|
|
|
+ if (prop != "" && val != "") {
|
|
|
+ result.set(prop, val);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets a specific CSS style property value
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="property">The CSS property name (e.g., "color", "background-color")</param>
|
|
|
+ /// <returns>The property value, or null if not set</returns>
|
|
|
+ public string? get_style(string property) {
|
|
|
+ var style_dict = styles;
|
|
|
+ string? result = null;
|
|
|
+ style_dict.try_get(property, out result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Checks if a specific CSS style property is set
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="property">The CSS property name to check</param>
|
|
|
+ /// <returns>True if the property is set, false otherwise</returns>
|
|
|
+ public bool has_style(string property) {
|
|
|
+ var style_dict = styles;
|
|
|
+ string? unused = null;
|
|
|
+ return style_dict.try_get(property, out unused);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Sets a CSS style property value
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="property">The CSS property name (e.g., "color", "font-size")</param>
|
|
|
+ /// <param name="value">The property value (e.g., "red", "14px")</param>
|
|
|
+ public void set_style(string property, string value) {
|
|
|
+ var style_dict = styles.select_to_dictionary<string, string>(s => s.key, s => s.value);
|
|
|
+ style_dict.set(property, value);
|
|
|
+ rebuild_style_attribute(style_dict);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Removes a CSS style property
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="property">The CSS property name to remove</param>
|
|
|
+ public void remove_style(string property) {
|
|
|
+ var style_dict = styles;
|
|
|
+ string? existing = null;
|
|
|
+ if (!style_dict.try_get(property, out existing)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // Rebuild without the property
|
|
|
+ var new_dict = new Dictionary<string, string>();
|
|
|
+ style_dict.to_immutable_buffer().iterate((kv) => {
|
|
|
+ if (kv.key != property) {
|
|
|
+ new_dict.set(kv.key, kv.value);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ rebuild_style_attribute(new_dict);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Replaces all styles with a new set of styles
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="new_styles">A dictionary of CSS property names to values</param>
|
|
|
+ public void set_styles(Enumerable<KeyValuePair<string, string>> new_styles) {
|
|
|
+ rebuild_style_attribute(new_styles);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Helper method to rebuild the style attribute from a dictionary of properties
|
|
|
+ /// </summary>
|
|
|
+ private void rebuild_style_attribute(Enumerable<KeyValuePair<string, string>> style_dict) {
|
|
|
+ // Check if empty by trying to count via iteration
|
|
|
+ var builder = new StringBuilder();
|
|
|
+ bool first = true;
|
|
|
+ bool has_any = false;
|
|
|
+
|
|
|
+ style_dict.to_immutable_buffer().iterate((kv) => {
|
|
|
+ has_any = true;
|
|
|
+ if (!first) {
|
|
|
+ builder.append("; ");
|
|
|
+ }
|
|
|
+ builder.append(kv.key);
|
|
|
+ builder.append(": ");
|
|
|
+ builder.append(kv.value);
|
|
|
+ first = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!has_any) {
|
|
|
+ remove_attribute("style");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ set_attribute("style", builder.str);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -242,7 +388,9 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public MarkupNode append_child_element(string tag_name) {
|
|
|
var new_node = xml_node->new_child(null, tag_name);
|
|
|
- return new MarkupNode(document, new_node);
|
|
|
+ var result = new MarkupNode(document, new_node);
|
|
|
+ document.update(this);
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -250,6 +398,7 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public void append_text(string text) {
|
|
|
xml_node->add_content(text);
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -257,15 +406,25 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public MarkupNode append_child_with_text(string tag_name, string text) {
|
|
|
var new_node = xml_node->new_text_child(null, tag_name, text);
|
|
|
- return new MarkupNode(document, new_node);
|
|
|
+ var result = new MarkupNode(document, new_node);
|
|
|
+ document.update(this);
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Removes this element from the DOM
|
|
|
/// </summary>
|
|
|
public void remove() {
|
|
|
+ var parent = xml_node->parent;
|
|
|
+ MarkupNode? parent_wrapper = null;
|
|
|
+ if (parent != null && parent->type != ElementType.DOCUMENT_NODE) {
|
|
|
+ parent_wrapper = new MarkupNode(document, parent);
|
|
|
+ }
|
|
|
xml_node->unlink();
|
|
|
delete xml_node;
|
|
|
+ if (parent_wrapper != null) {
|
|
|
+ document.update(parent_wrapper);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -277,6 +436,7 @@ namespace Astralis {
|
|
|
child->unlink();
|
|
|
delete child;
|
|
|
}
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -284,28 +444,50 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
/// <param name="node">The node to replace this element with</param>
|
|
|
public void replace_with_node(MarkupNode node) {
|
|
|
+ var parent = xml_node->parent;
|
|
|
+ MarkupNode? parent_wrapper = null;
|
|
|
+ if (parent != null && parent->type != ElementType.DOCUMENT_NODE) {
|
|
|
+ parent_wrapper = new MarkupNode(document, parent);
|
|
|
+ }
|
|
|
// Insert the new node before this node
|
|
|
xml_node->add_prev_sibling(node.native->copy(1));
|
|
|
|
|
|
// Remove this node
|
|
|
- remove();
|
|
|
+ xml_node->unlink();
|
|
|
+ delete xml_node;
|
|
|
+
|
|
|
+ if (parent_wrapper != null) {
|
|
|
+ document.update(parent_wrapper);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- public void replace_with_nodes(Enumerable<MarkupNode> nodes) {
|
|
|
+ public void replace_with_nodes(Enumerable<MarkupNode> nodes) {
|
|
|
+ var parent = xml_node->parent;
|
|
|
+ MarkupNode? parent_wrapper = null;
|
|
|
+ if (parent != null && parent->type != ElementType.DOCUMENT_NODE) {
|
|
|
+ parent_wrapper = new MarkupNode(document, parent);
|
|
|
+ }
|
|
|
foreach(var node in nodes) {
|
|
|
xml_node->add_prev_sibling(node.native->copy(1));
|
|
|
}
|
|
|
- remove();
|
|
|
+ xml_node->unlink();
|
|
|
+ delete xml_node;
|
|
|
+
|
|
|
+ if (parent_wrapper != null) {
|
|
|
+ document.update(parent_wrapper);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public void append_node(MarkupNode node) {
|
|
|
xml_node->add_child(node.native->copy(1));
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
public void append_nodes(Enumerable<MarkupNode> nodes) {
|
|
|
foreach(var child in nodes) {
|
|
|
xml_node->add_child(child.native->copy(1));
|
|
|
}
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -313,7 +495,7 @@ namespace Astralis {
|
|
|
/// </summary>
|
|
|
public string inner_html {
|
|
|
owned set {
|
|
|
- clear_children();
|
|
|
+ clear_children(); // Note: clear_children already emits update signal
|
|
|
if (value == null || value == "") {
|
|
|
return;
|
|
|
}
|
|
|
@@ -345,6 +527,7 @@ namespace Astralis {
|
|
|
}
|
|
|
|
|
|
delete temp_doc;
|
|
|
+ document.update(this);
|
|
|
}
|
|
|
owned get {
|
|
|
// Use Html.Doc to serialize the children properly
|
|
|
@@ -404,8 +587,14 @@ namespace Astralis {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Import and insert children before this node
|
|
|
+ // Get parent before modification
|
|
|
var parent = xml_node->parent;
|
|
|
+ MarkupNode? parent_wrapper = null;
|
|
|
+ if (parent != null && parent->type != ElementType.DOCUMENT_NODE) {
|
|
|
+ parent_wrapper = new MarkupNode(document, parent);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Import and insert children before this node
|
|
|
if (parent != null) {
|
|
|
for (var child = temp_root->children; child != null; child = child->next) {
|
|
|
var imported = doc.import_node(child, true);
|
|
|
@@ -415,8 +604,13 @@ namespace Astralis {
|
|
|
}
|
|
|
|
|
|
// Remove this node
|
|
|
- remove();
|
|
|
+ xml_node->unlink();
|
|
|
+ delete xml_node;
|
|
|
delete temp_doc;
|
|
|
+
|
|
|
+ if (parent_wrapper != null) {
|
|
|
+ document.update(parent_wrapper);
|
|
|
+ }
|
|
|
}
|
|
|
owned get {
|
|
|
// Create a temporary HTML document to serialize this node
|
|
|
@@ -516,6 +710,7 @@ namespace Astralis {
|
|
|
public void set_attribute_all(string name, string value) {
|
|
|
foreach (var node in nodes) {
|
|
|
node->set_prop(name, value);
|
|
|
+ document.update(new MarkupNode(document, node));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -526,6 +721,7 @@ namespace Astralis {
|
|
|
foreach (var node in nodes) {
|
|
|
var wrapper = new MarkupNode(document, node);
|
|
|
wrapper.add_class(class_name);
|
|
|
+ // Note: add_class already emits update signal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -536,6 +732,7 @@ namespace Astralis {
|
|
|
foreach (var node in nodes) {
|
|
|
var wrapper = new MarkupNode(document, node);
|
|
|
wrapper.remove_class(class_name);
|
|
|
+ // Note: remove_class already emits update signal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -545,6 +742,7 @@ namespace Astralis {
|
|
|
public void set_text_all(string text) {
|
|
|
foreach (var node in nodes) {
|
|
|
node->set_content(text);
|
|
|
+ document.update(new MarkupNode(document, node));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -552,11 +750,22 @@ namespace Astralis {
|
|
|
/// Removes all nodes in the list from the DOM
|
|
|
/// </summary>
|
|
|
public void remove_all() {
|
|
|
+ // Collect parent nodes before removal for update signals
|
|
|
+ var parents = new List<Xml.Node*>();
|
|
|
foreach (var node in nodes) {
|
|
|
+ var parent = node->parent;
|
|
|
+ if (parent != null && parent->type != ElementType.DOCUMENT_NODE) {
|
|
|
+ parents.append(parent);
|
|
|
+ }
|
|
|
node->unlink();
|
|
|
delete node;
|
|
|
}
|
|
|
nodes = new List<Xml.Node*>();
|
|
|
+
|
|
|
+ // Emit update for all affected parents
|
|
|
+ foreach (var parent in parents) {
|
|
|
+ document.update(new MarkupNode(document, parent));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|