xbintsc 0.3.18 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/src/driver/bundler/graph.d.ts +2 -1
  2. package/dist/src/driver/bundler/graph.js +18 -3
  3. package/dist/src/driver/bundler/graph.js.map +1 -1
  4. package/dist/src/driver/bundler/merge.d.ts +2 -1
  5. package/dist/src/driver/bundler/merge.js +2 -2
  6. package/dist/src/driver/bundler/merge.js.map +1 -1
  7. package/dist/src/driver/compiler.js +2 -2
  8. package/dist/src/driver/compiler.js.map +1 -1
  9. package/dist/src/extensions/gui/html.d.ts +25 -0
  10. package/dist/src/extensions/gui/html.js +203 -0
  11. package/dist/src/extensions/gui/html.js.map +1 -0
  12. package/dist/src/extensions/gui/index.js +11 -0
  13. package/dist/src/extensions/gui/index.js.map +1 -1
  14. package/dist/src/extensions/registry.d.ts +29 -0
  15. package/dist/src/extensions/registry.js +14 -0
  16. package/dist/src/extensions/registry.js.map +1 -1
  17. package/dist/src/index.d.ts +1 -1
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/lexer/scanner/identifiers.js +4 -12
  20. package/dist/src/lexer/scanner/identifiers.js.map +1 -1
  21. package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
  22. package/dist/tests/driver/bundler-resolve.test.js +230 -0
  23. package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
  24. package/dist/tests/driver/modules.test.js +68 -0
  25. package/dist/tests/driver/modules.test.js.map +1 -1
  26. package/dist/tests/e2e/gui.test.js +210 -1
  27. package/dist/tests/e2e/gui.test.js.map +1 -1
  28. package/dist/tests/extensions/gui-index.test.d.ts +1 -0
  29. package/dist/tests/extensions/gui-index.test.js +49 -0
  30. package/dist/tests/extensions/gui-index.test.js.map +1 -0
  31. package/dist/tests/extensions/gui.test.d.ts +1 -0
  32. package/dist/tests/extensions/gui.test.js +177 -0
  33. package/dist/tests/extensions/gui.test.js.map +1 -0
  34. package/dist/tests/extensions/registry.test.js +36 -0
  35. package/dist/tests/extensions/registry.test.js.map +1 -1
  36. package/dist/tests/public-api.test.d.ts +1 -0
  37. package/dist/tests/public-api.test.js +31 -0
  38. package/dist/tests/public-api.test.js.map +1 -0
  39. package/package.json +1 -1
  40. package/runtime/ext_gui/document.cpp +90 -0
  41. package/runtime/ext_gui/document.h +23 -0
  42. package/runtime/ext_gui/dom.cpp +36 -0
  43. package/runtime/ext_gui/dom.h +7 -0
  44. package/runtime/ext_gui/dom_api.cpp +1247 -0
  45. package/runtime/ext_gui/dom_api.h +12 -0
  46. package/runtime/ext_gui/gui.cpp +49 -3
  47. package/runtime/ext_gui/gui_engine.h +54 -0
  48. package/runtime/ext_gui/script.cpp +63 -0
  49. package/runtime/ext_gui/window.cpp +60 -0
  50. package/src/driver/bundler/graph.ts +21 -2
  51. package/src/driver/bundler/merge.ts +3 -1
  52. package/src/driver/compiler.ts +2 -2
  53. package/src/extensions/gui/html.ts +236 -0
  54. package/src/extensions/gui/index.ts +11 -0
  55. package/src/extensions/registry.ts +39 -0
  56. package/src/index.ts +1 -1
  57. package/src/lexer/scanner/identifiers.ts +4 -11
@@ -0,0 +1,1247 @@
1
+ /*
2
+ * xbintsc GUI engine — the DOM object model exposed to TypeScript.
3
+ *
4
+ * Node/document handles are plain runtime objects with a shared prototype,
5
+ * exactly like the window handles in `window.cpp`. Each handle carries hidden
6
+ * properties:
7
+ *
8
+ * __xt_gui_win the owning window object (resolved with xt_gui_window_from_this)
9
+ * __xt_gui_gen the document generation it was created for
10
+ * __xt_gui_node an index into the window's node table (element handles)
11
+ * __xt_gui_doc true for the singleton `document` handle
12
+ *
13
+ * A DOM mutation marks the document dirty; the frame loop restyles/relayouts
14
+ * before the next paint.
15
+ */
16
+
17
+ #include "dom_api.h"
18
+
19
+ #include <algorithm>
20
+ #include <cctype>
21
+ #include <cmath>
22
+ #include <string>
23
+ #include <vector>
24
+
25
+ static xt_value g_node_proto = XT_UNDEFINED;
26
+ static xt_value g_document_proto = XT_UNDEFINED;
27
+ static xt_value g_event_proto = XT_UNDEFINED;
28
+ static xt_value g_class_list_proto = XT_UNDEFINED;
29
+ static xt_value g_style_proto = XT_UNDEFINED;
30
+
31
+ static xt_value node_proto();
32
+ static xt_value document_proto();
33
+ static xt_value event_proto();
34
+ static xt_value class_list_proto();
35
+ static xt_value style_proto();
36
+
37
+ /* -- small helpers -------------------------------------------------------- */
38
+
39
+ static const char *to_cstr(xt_value value, const char *fallback = "") {
40
+ const char *data = xt_string_data(xt_to_string(value));
41
+ return data != nullptr ? data : fallback;
42
+ }
43
+
44
+ static std::string to_string_value(xt_value value) { return std::string(to_cstr(value)); }
45
+
46
+ static std::string to_upper(std::string text) {
47
+ for (char &c : text) c = (char)std::toupper((unsigned char)c);
48
+ return text;
49
+ }
50
+
51
+ static bool value_truthy(xt_value value) { return xt_truthy(value) != 0; }
52
+
53
+ static void define_method(xt_value proto, const char *name, void *fn) {
54
+ xt_object_set(proto, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
55
+ }
56
+
57
+ static void define_getter(xt_value proto, const char *name, void *fn) {
58
+ xt_object_define_getter(proto, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
59
+ }
60
+
61
+ static void define_setter(xt_value proto, const char *name, void *fn) {
62
+ xt_object_define_setter(proto, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
63
+ }
64
+
65
+ /* -- handle plumbing ------------------------------------------------------ */
66
+
67
+ static XtGuiWindow *resolve_window(xt_value self) {
68
+ return xt_gui_window_from_this(xt_object_get_cstr(self, "__xt_gui_win"));
69
+ }
70
+
71
+ /** Resolve a node handle to its node, or NULL when it is stale/foreign. */
72
+ static xtgui::Node *resolve_node(xt_value self, XtGuiWindow **winOut) {
73
+ XtGuiWindow *win = resolve_window(self);
74
+ if (winOut != nullptr) *winOut = win;
75
+ if (win == nullptr || win->document == nullptr) return nullptr;
76
+ xt_value gen = xt_object_get_cstr(self, "__xt_gui_gen");
77
+ if (!XT_IS_NUMBER(gen) || xt_to_number(gen) != win->doc_generation) return nullptr;
78
+ if (value_truthy(xt_object_get_cstr(self, "__xt_gui_doc"))) {
79
+ return const_cast<xtgui::Node *>(win->document->root());
80
+ }
81
+ xt_value index = xt_object_get_cstr(self, "__xt_gui_node");
82
+ if (!XT_IS_NUMBER(index)) return nullptr;
83
+ int slot = (int)xt_to_number(index);
84
+ if (slot < 0 || slot >= (int)win->node_order.size()) return nullptr;
85
+ return win->node_order[(size_t)slot];
86
+ }
87
+
88
+ xt_value xt_gui_node_handle(XtGuiWindow *win, const xtgui::Node *node) {
89
+ if (win == nullptr || node == nullptr) return XT_UNDEFINED;
90
+ auto cached = win->node_handles.find(node);
91
+ if (cached != win->node_handles.end()) return cached->second;
92
+ int slot;
93
+ auto found = win->node_index.find(node);
94
+ if (found != win->node_index.end()) {
95
+ slot = found->second;
96
+ } else {
97
+ slot = (int)win->node_order.size();
98
+ win->node_order.push_back(const_cast<xtgui::Node *>(node));
99
+ win->node_index[node] = slot;
100
+ }
101
+ xt_value handle = xt_object_new_with_proto(node_proto());
102
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_win"), win->object);
103
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_gen"), xt_number(win->doc_generation));
104
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_node"), xt_number((double)slot));
105
+ win->node_handles[node] = handle;
106
+ return handle;
107
+ }
108
+
109
+ xt_value xt_gui_document_handle(XtGuiWindow *win) {
110
+ if (win == nullptr) return XT_UNDEFINED;
111
+ if (!XT_IS_OBJECT(win->document_object)) {
112
+ xt_value handle = xt_object_new_with_proto(document_proto());
113
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_win"), win->object);
114
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_doc"), XT_TRUE);
115
+ win->document_object = handle;
116
+ }
117
+ xt_object_set(win->document_object, xt_string_from_cstr("__xt_gui_gen"),
118
+ xt_number(win->doc_generation));
119
+ return win->document_object;
120
+ }
121
+
122
+ void xt_gui_handles_reset(XtGuiWindow *win) {
123
+ if (win == nullptr) return;
124
+ win->doc_generation += 1.0;
125
+ win->node_order.clear();
126
+ win->node_index.clear();
127
+ win->node_handles.clear();
128
+ win->node_listeners.clear();
129
+ win->struct_dirty = 0;
130
+ if (XT_IS_OBJECT(win->document_object)) {
131
+ xt_object_set(win->document_object, xt_string_from_cstr("__xt_gui_gen"),
132
+ xt_number(win->doc_generation));
133
+ }
134
+ }
135
+
136
+ static void mark_dirty(XtGuiWindow *win) {
137
+ if (win != nullptr && win->document != nullptr) win->document->invalidate();
138
+ if (win != nullptr) win->struct_dirty = 1;
139
+ }
140
+
141
+ /* -- attribute / class / text helpers ------------------------------------- */
142
+
143
+ static void set_attribute_raw(xtgui::Node *node, const std::string &name, const std::string &value) {
144
+ for (xtgui::Attr &attr : node->attrs) {
145
+ if (attr.name == name) {
146
+ attr.value = value;
147
+ return;
148
+ }
149
+ }
150
+ node->attrs.push_back({name, value});
151
+ }
152
+
153
+ static void remove_attribute_raw(xtgui::Node *node, const std::string &name) {
154
+ for (auto it = node->attrs.begin(); it != node->attrs.end(); ++it) {
155
+ if (it->name == name) {
156
+ node->attrs.erase(it);
157
+ return;
158
+ }
159
+ }
160
+ }
161
+
162
+ static void set_attribute(XtGuiWindow *win, xtgui::Node *node, const std::string &name,
163
+ const std::string &value) {
164
+ set_attribute_raw(node, name, value);
165
+ mark_dirty(win);
166
+ }
167
+
168
+ static void collect_text(const xtgui::Node *node, std::string &out) {
169
+ if (node->isText()) {
170
+ out += node->text;
171
+ return;
172
+ }
173
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) collect_text(child.get(), out);
174
+ }
175
+
176
+ static std::vector<std::string> split_classes(const std::string &text) {
177
+ std::vector<std::string> out;
178
+ size_t index = 0;
179
+ while (index < text.size()) {
180
+ while (index < text.size() && std::isspace((unsigned char)text[index])) index++;
181
+ size_t start = index;
182
+ while (index < text.size() && !std::isspace((unsigned char)text[index])) index++;
183
+ if (index > start) out.push_back(text.substr(start, index - start));
184
+ }
185
+ return out;
186
+ }
187
+
188
+ static std::string join_classes(const std::vector<std::string> &names) {
189
+ std::string out;
190
+ for (size_t i = 0; i < names.size(); i++) {
191
+ if (i > 0) out += " ";
192
+ out += names[i];
193
+ }
194
+ return out;
195
+ }
196
+
197
+ static std::string class_name_of(const xtgui::Node *node) {
198
+ const std::string *value = node->attr("class");
199
+ return value != nullptr ? *value : std::string();
200
+ }
201
+
202
+ static void set_class_name(XtGuiWindow *win, xtgui::Node *node, const std::string &value) {
203
+ if (value.empty()) {
204
+ remove_attribute_raw(node, "class");
205
+ mark_dirty(win);
206
+ } else {
207
+ set_attribute(win, node, "class", value);
208
+ }
209
+ }
210
+
211
+ /* -- inline style helpers ------------------------------------------------- */
212
+
213
+ static std::string inline_style_of(const xtgui::Node *node) {
214
+ const std::string *value = node->attr("style");
215
+ return value != nullptr ? *value : std::string();
216
+ }
217
+
218
+ static void write_declarations(xtgui::Node *node, const std::vector<xtgui::Declaration> &decls) {
219
+ std::string out;
220
+ for (const xtgui::Declaration &decl : decls) {
221
+ if (!out.empty()) out += ";";
222
+ out += decl.property + ":" + decl.value + (decl.important ? " !important" : "");
223
+ }
224
+ if (out.empty()) {
225
+ remove_attribute_raw(node, "style");
226
+ } else {
227
+ set_attribute_raw(node, "style", out);
228
+ }
229
+ }
230
+
231
+ static void set_style_property(XtGuiWindow *win, xtgui::Node *node, const std::string &name,
232
+ const std::string &value) {
233
+ std::vector<xtgui::Declaration> decls = xtgui::xt_css_parse_declarations(inline_style_of(node));
234
+ bool found = false;
235
+ for (xtgui::Declaration &decl : decls) {
236
+ if (decl.property == name) {
237
+ decl.value = value;
238
+ decl.important = false;
239
+ found = true;
240
+ }
241
+ }
242
+ if (!found) decls.push_back({name, value, false});
243
+ write_declarations(node, decls);
244
+ mark_dirty(win);
245
+ }
246
+
247
+ static std::string get_style_property(const xtgui::Node *node, const std::string &name) {
248
+ std::vector<xtgui::Declaration> decls = xtgui::xt_css_parse_declarations(inline_style_of(node));
249
+ std::string value;
250
+ for (const xtgui::Declaration &decl : decls) {
251
+ if (decl.property == name) value = decl.value;
252
+ }
253
+ return value;
254
+ }
255
+
256
+ static void remove_style_property(XtGuiWindow *win, xtgui::Node *node, const std::string &name) {
257
+ std::vector<xtgui::Declaration> decls = xtgui::xt_css_parse_declarations(inline_style_of(node));
258
+ std::vector<xtgui::Declaration> kept;
259
+ for (const xtgui::Declaration &decl : decls) {
260
+ if (decl.property != name) kept.push_back(decl);
261
+ }
262
+ write_declarations(node, kept);
263
+ mark_dirty(win);
264
+ }
265
+
266
+ /* -- queries -------------------------------------------------------------- */
267
+
268
+ static const xtgui::Node *find_by_tag(const xtgui::Node *node, const std::string &tag) {
269
+ if (node->isElement() && node->tag == tag) return node;
270
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
271
+ if (const xtgui::Node *found = find_by_tag(child.get(), tag)) return found;
272
+ }
273
+ return nullptr;
274
+ }
275
+
276
+ static const xtgui::Node *find_by_id(const xtgui::Node *node, const std::string &id) {
277
+ if (node->isElement()) {
278
+ const std::string *value = node->attr("id");
279
+ if (value != nullptr && *value == id) return node;
280
+ }
281
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
282
+ if (const xtgui::Node *found = find_by_id(child.get(), id)) return found;
283
+ }
284
+ return nullptr;
285
+ }
286
+
287
+ static void collect_matches(const xtgui::Node *node,
288
+ const std::vector<xtgui::ComplexSelector> &selectors,
289
+ std::vector<const xtgui::Node *> &out) {
290
+ if (node->isElement()) {
291
+ for (const xtgui::ComplexSelector &selector : selectors) {
292
+ if (xtgui::xt_css_match(node, selector)) {
293
+ out.push_back(node);
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
299
+ collect_matches(child.get(), selectors, out);
300
+ }
301
+ }
302
+
303
+ /* -- event machinery ------------------------------------------------------ */
304
+
305
+ static xt_value make_dom_event(XtGuiWindow *win, const char *type, const xtgui::Node *target,
306
+ xt_value legacy) {
307
+ xt_value event = xt_object_new_with_proto(event_proto());
308
+ xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr(type));
309
+ xt_object_set(event, xt_string_from_cstr("target"), xt_gui_node_handle(win, target));
310
+ xt_object_set(event, xt_string_from_cstr("defaultPrevented"), XT_FALSE);
311
+ xt_object_set(event, xt_string_from_cstr("bubbles"), XT_TRUE);
312
+ if (XT_IS_OBJECT(legacy)) {
313
+ static const char *const fields[] = {
314
+ "x", "y", "clientX", "clientY", "button", "clicks",
315
+ "deltaX", "deltaY", "key", "code", "repeat", "ctrl",
316
+ "shift", "alt", "meta",
317
+ };
318
+ for (const char *field : fields) {
319
+ xt_value value = xt_object_get_cstr(legacy, field);
320
+ if (!XT_IS_UNDEFINED(value)) xt_object_set(event, xt_string_from_cstr(field), value);
321
+ }
322
+ }
323
+ return event;
324
+ }
325
+
326
+ static void remove_listener(XtGuiWindow *win, xtgui::Node *node, const std::string &type,
327
+ xt_value fn, bool capture) {
328
+ auto found = win->node_listeners.find(node);
329
+ if (found == win->node_listeners.end()) return;
330
+ std::vector<XtGuiNodeListener> &list = found->second;
331
+ for (auto it = list.begin(); it != list.end(); ++it) {
332
+ if (it->type == type && it->capture == capture && xt_truthy(xt_seq(it->fn, fn))) {
333
+ list.erase(it);
334
+ return;
335
+ }
336
+ }
337
+ }
338
+
339
+ static bool call_node_listeners(XtGuiWindow *win, xtgui::Node *node, const char *type,
340
+ xt_value event, bool capture) {
341
+ auto found = win->node_listeners.find(node);
342
+ if (found == win->node_listeners.end()) return false;
343
+ std::vector<XtGuiNodeListener> snapshot = found->second;
344
+ for (const XtGuiNodeListener &listener : snapshot) {
345
+ if (listener.type != type || listener.capture != capture) continue;
346
+ if (listener.once) remove_listener(win, node, listener.type, listener.fn, listener.capture);
347
+ xt_value self = xt_gui_node_handle(win, node);
348
+ xt_object_set(event, xt_string_from_cstr("currentTarget"), self);
349
+ xt_value args[1] = {event};
350
+ xt_call_with_this(listener.fn, self, 1, args);
351
+ if (value_truthy(xt_object_get_cstr(event, "__stopImmediate"))) return true;
352
+ if (value_truthy(xt_object_get_cstr(event, "__stop"))) return true;
353
+ }
354
+ return value_truthy(xt_object_get_cstr(event, "__stop"));
355
+ }
356
+
357
+ void xt_gui_emit_dom_event(XtGuiWindow *win, const char *type, const xtgui::Node *target,
358
+ xt_value legacy) {
359
+ if (win == nullptr || win->document == nullptr || target == nullptr) return;
360
+ if (win->node_listeners.empty()) return;
361
+ std::vector<xtgui::Node *> path;
362
+ for (xtgui::Node *node = const_cast<xtgui::Node *>(target); node != nullptr; node = node->parent) {
363
+ path.push_back(node);
364
+ }
365
+ xt_value event = make_dom_event(win, type, target, legacy);
366
+ /* Capture phase: root -> target. */
367
+ for (auto it = path.rbegin(); it != path.rend(); ++it) {
368
+ if (call_node_listeners(win, *it, type, event, true)) return;
369
+ }
370
+ /* Bubble phase: target -> root. */
371
+ for (auto it = path.begin(); it != path.end(); ++it) {
372
+ if (call_node_listeners(win, *it, type, event, false)) return;
373
+ }
374
+ }
375
+
376
+ void xt_gui_dispatch_to_node(XtGuiWindow *win, const xtgui::Node *target, const char *type) {
377
+ if (win == nullptr || target == nullptr) return;
378
+ xt_value legacy = xt_object_new();
379
+ xt_object_set(legacy, xt_string_from_cstr("type"), xt_string_from_cstr(type));
380
+ std::string describe = xtgui::xt_dom_describe(target);
381
+ xt_object_set(legacy, xt_string_from_cstr("target"), xt_string_from_cstr(describe.c_str()));
382
+ xt_gui_emit_dom_event(win, type, target, legacy);
383
+ }
384
+
385
+ /* -- document methods ----------------------------------------------------- */
386
+
387
+ #define METHOD(name) static xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
388
+
389
+ METHOD(doc_query_selector) {
390
+ (void)env;
391
+ XtGuiWindow *win = resolve_window(self);
392
+ if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
393
+ return xt_gui_node_handle(win, win->document->querySelector(to_string_value(xt_arg(argc, argv, 0))));
394
+ }
395
+
396
+ METHOD(doc_query_selector_all) {
397
+ (void)env;
398
+ XtGuiWindow *win = resolve_window(self);
399
+ if (win == nullptr || win->document == nullptr || argc < 1) return xt_array_new(0, NULL);
400
+ std::vector<const xtgui::Node *> matches =
401
+ win->document->querySelectorAll(to_string_value(xt_arg(argc, argv, 0)));
402
+ xt_value array = xt_array_new(0, NULL);
403
+ for (const xtgui::Node *node : matches) xt_array_push(array, xt_gui_node_handle(win, node));
404
+ return array;
405
+ }
406
+
407
+ METHOD(doc_get_element_by_id) {
408
+ (void)env;
409
+ XtGuiWindow *win = resolve_window(self);
410
+ if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
411
+ return xt_gui_node_handle(
412
+ win, find_by_id(win->document->root(), to_string_value(xt_arg(argc, argv, 0))));
413
+ }
414
+
415
+ METHOD(doc_create_element) {
416
+ (void)env;
417
+ XtGuiWindow *win = resolve_window(self);
418
+ if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
419
+ std::string tag = to_string_value(xt_arg(argc, argv, 0));
420
+ for (char &c : tag) c = (char)std::tolower((unsigned char)c);
421
+ return xt_gui_node_handle(win, win->document->createElement(tag));
422
+ }
423
+
424
+ METHOD(doc_create_text_node) {
425
+ (void)env;
426
+ XtGuiWindow *win = resolve_window(self);
427
+ if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
428
+ return xt_gui_node_handle(win, win->document->createTextNode(to_string_value(xt_arg(argc, argv, 0))));
429
+ }
430
+
431
+ METHOD(doc_body_get) {
432
+ (void)env;
433
+ (void)argc;
434
+ (void)argv;
435
+ XtGuiWindow *win = resolve_window(self);
436
+ if (win == nullptr || win->document == nullptr) return XT_UNDEFINED;
437
+ return xt_gui_node_handle(win, find_by_tag(win->document->root(), "body"));
438
+ }
439
+
440
+ METHOD(doc_document_element_get) {
441
+ (void)env;
442
+ (void)argc;
443
+ (void)argv;
444
+ XtGuiWindow *win = resolve_window(self);
445
+ if (win == nullptr || win->document == nullptr) return XT_UNDEFINED;
446
+ return xt_gui_node_handle(win, find_by_tag(win->document->root(), "html"));
447
+ }
448
+
449
+ /* -- node getters --------------------------------------------------------- */
450
+
451
+ METHOD(node_tag_name_get) {
452
+ (void)env;
453
+ (void)argc;
454
+ (void)argv;
455
+ XtGuiWindow *win = nullptr;
456
+ xtgui::Node *node = resolve_node(self, &win);
457
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
458
+ return xt_string_from_cstr(to_upper(node->tag).c_str());
459
+ }
460
+
461
+ METHOD(node_node_type_get) {
462
+ (void)env;
463
+ (void)argc;
464
+ (void)argv;
465
+ xtgui::Node *node = resolve_node(self, nullptr);
466
+ if (node == nullptr) return XT_UNDEFINED;
467
+ if (node->isElement()) return xt_number(1);
468
+ if (node->isText()) return xt_number(3);
469
+ return xt_number(9);
470
+ }
471
+
472
+ METHOD(node_id_get) {
473
+ (void)env;
474
+ (void)argc;
475
+ (void)argv;
476
+ xtgui::Node *node = resolve_node(self, nullptr);
477
+ if (node == nullptr || !node->isElement()) return xt_string_from_cstr("");
478
+ const std::string *value = node->attr("id");
479
+ return xt_string_from_cstr(value != nullptr ? value->c_str() : "");
480
+ }
481
+
482
+ METHOD(node_id_set) {
483
+ (void)env;
484
+ XtGuiWindow *win = nullptr;
485
+ xtgui::Node *node = resolve_node(self, &win);
486
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
487
+ set_attribute(win, node, "id", argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "");
488
+ return XT_UNDEFINED;
489
+ }
490
+
491
+ METHOD(node_class_name_get) {
492
+ (void)env;
493
+ (void)argc;
494
+ (void)argv;
495
+ xtgui::Node *node = resolve_node(self, nullptr);
496
+ if (node == nullptr || !node->isElement()) return xt_string_from_cstr("");
497
+ return xt_string_from_cstr(class_name_of(node).c_str());
498
+ }
499
+
500
+ METHOD(node_class_name_set) {
501
+ (void)env;
502
+ XtGuiWindow *win = nullptr;
503
+ xtgui::Node *node = resolve_node(self, &win);
504
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
505
+ set_class_name(win, node, argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "");
506
+ return XT_UNDEFINED;
507
+ }
508
+
509
+ METHOD(node_text_content_get) {
510
+ (void)env;
511
+ (void)argc;
512
+ (void)argv;
513
+ xtgui::Node *node = resolve_node(self, nullptr);
514
+ if (node == nullptr) return XT_UNDEFINED;
515
+ std::string out;
516
+ collect_text(node, out);
517
+ return xt_string_from_cstr(out.c_str());
518
+ }
519
+
520
+ METHOD(node_text_content_set) {
521
+ (void)env;
522
+ XtGuiWindow *win = nullptr;
523
+ xtgui::Node *node = resolve_node(self, &win);
524
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
525
+ std::string text = argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "";
526
+ std::vector<xtgui::Node *> existing;
527
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) existing.push_back(child.get());
528
+ for (xtgui::Node *child : existing) win->document->removeChild(node, child);
529
+ if (!text.empty()) node->addText(text);
530
+ mark_dirty(win);
531
+ return XT_UNDEFINED;
532
+ }
533
+
534
+ METHOD(node_inner_html_get) {
535
+ (void)env;
536
+ (void)argc;
537
+ (void)argv;
538
+ xtgui::Node *node = resolve_node(self, nullptr);
539
+ if (node == nullptr) return XT_UNDEFINED;
540
+ std::string out;
541
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
542
+ out += xtgui::xt_dom_to_string(child.get());
543
+ }
544
+ return xt_string_from_cstr(out.c_str());
545
+ }
546
+
547
+ METHOD(node_inner_html_set) {
548
+ (void)env;
549
+ XtGuiWindow *win = nullptr;
550
+ xtgui::Node *node = resolve_node(self, &win);
551
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
552
+ std::string html = argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "";
553
+ std::vector<xtgui::Node *> existing;
554
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) existing.push_back(child.get());
555
+ for (xtgui::Node *child : existing) win->document->removeChild(node, child);
556
+ std::unique_ptr<xtgui::Node> fragment = xtgui::xt_html_parse(html);
557
+ for (std::unique_ptr<xtgui::Node> &child : fragment->children) node->append(std::move(child));
558
+ mark_dirty(win);
559
+ return XT_UNDEFINED;
560
+ }
561
+
562
+ METHOD(node_parent_node_get) {
563
+ (void)env;
564
+ (void)argc;
565
+ (void)argv;
566
+ XtGuiWindow *win = nullptr;
567
+ xtgui::Node *node = resolve_node(self, &win);
568
+ if (node == nullptr || node->parent == nullptr || node->parent->isDocument()) return XT_UNDEFINED;
569
+ return xt_gui_node_handle(win, node->parent);
570
+ }
571
+
572
+ METHOD(node_parent_element_get) {
573
+ (void)env;
574
+ (void)argc;
575
+ (void)argv;
576
+ XtGuiWindow *win = nullptr;
577
+ xtgui::Node *node = resolve_node(self, &win);
578
+ if (node == nullptr || node->parent == nullptr || !node->parent->isElement()) return XT_UNDEFINED;
579
+ return xt_gui_node_handle(win, node->parent);
580
+ }
581
+
582
+ METHOD(node_children_get) {
583
+ (void)env;
584
+ (void)argc;
585
+ (void)argv;
586
+ XtGuiWindow *win = nullptr;
587
+ xtgui::Node *node = resolve_node(self, &win);
588
+ xt_value array = xt_array_new(0, NULL);
589
+ if (node == nullptr) return array;
590
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
591
+ if (child->isElement()) xt_array_push(array, xt_gui_node_handle(win, child.get()));
592
+ }
593
+ return array;
594
+ }
595
+
596
+ METHOD(node_child_nodes_get) {
597
+ (void)env;
598
+ (void)argc;
599
+ (void)argv;
600
+ XtGuiWindow *win = nullptr;
601
+ xtgui::Node *node = resolve_node(self, &win);
602
+ xt_value array = xt_array_new(0, NULL);
603
+ if (node == nullptr) return array;
604
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
605
+ xt_array_push(array, xt_gui_node_handle(win, child.get()));
606
+ }
607
+ return array;
608
+ }
609
+
610
+ METHOD(node_child_element_count_get) {
611
+ (void)env;
612
+ (void)argc;
613
+ (void)argv;
614
+ xtgui::Node *node = resolve_node(self, nullptr);
615
+ if (node == nullptr) return xt_number(0);
616
+ double count = 0;
617
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
618
+ if (child->isElement()) count += 1;
619
+ }
620
+ return xt_number(count);
621
+ }
622
+
623
+ METHOD(node_first_element_child_get) {
624
+ (void)env;
625
+ (void)argc;
626
+ (void)argv;
627
+ XtGuiWindow *win = nullptr;
628
+ xtgui::Node *node = resolve_node(self, &win);
629
+ if (node == nullptr) return XT_UNDEFINED;
630
+ return xt_gui_node_handle(win, node->firstElementChild());
631
+ }
632
+
633
+ METHOD(node_last_element_child_get) {
634
+ (void)env;
635
+ (void)argc;
636
+ (void)argv;
637
+ XtGuiWindow *win = nullptr;
638
+ xtgui::Node *node = resolve_node(self, &win);
639
+ if (node == nullptr) return XT_UNDEFINED;
640
+ for (auto it = node->children.rbegin(); it != node->children.rend(); ++it) {
641
+ if ((*it)->isElement()) return xt_gui_node_handle(win, it->get());
642
+ }
643
+ return XT_UNDEFINED;
644
+ }
645
+
646
+ METHOD(node_next_element_sibling_get) {
647
+ (void)env;
648
+ (void)argc;
649
+ (void)argv;
650
+ XtGuiWindow *win = nullptr;
651
+ xtgui::Node *node = resolve_node(self, &win);
652
+ if (node == nullptr || node->parent == nullptr) return XT_UNDEFINED;
653
+ bool found = false;
654
+ for (const std::unique_ptr<xtgui::Node> &sibling : node->parent->children) {
655
+ if (sibling.get() == node) {
656
+ found = true;
657
+ continue;
658
+ }
659
+ if (found && sibling->isElement()) return xt_gui_node_handle(win, sibling.get());
660
+ }
661
+ return XT_UNDEFINED;
662
+ }
663
+
664
+ METHOD(node_previous_element_sibling_get) {
665
+ (void)env;
666
+ (void)argc;
667
+ (void)argv;
668
+ XtGuiWindow *win = nullptr;
669
+ xtgui::Node *node = resolve_node(self, &win);
670
+ if (node == nullptr || node->parent == nullptr) return XT_UNDEFINED;
671
+ xtgui::Node *previous = nullptr;
672
+ for (const std::unique_ptr<xtgui::Node> &sibling : node->parent->children) {
673
+ if (sibling.get() == node) break;
674
+ if (sibling->isElement()) previous = sibling.get();
675
+ }
676
+ return xt_gui_node_handle(win, previous);
677
+ }
678
+
679
+ METHOD(node_is_connected_get) {
680
+ (void)env;
681
+ (void)argc;
682
+ (void)argv;
683
+ XtGuiWindow *win = nullptr;
684
+ xtgui::Node *node = resolve_node(self, &win);
685
+ if (node == nullptr || win == nullptr || win->document == nullptr) return XT_FALSE;
686
+ return win->document->root()->contains(node) ? XT_TRUE : XT_FALSE;
687
+ }
688
+
689
+ METHOD(node_class_list_get) {
690
+ (void)env;
691
+ (void)argc;
692
+ (void)argv;
693
+ xt_value cached = xt_object_get_cstr(self, "__xt_gui_class_list");
694
+ if (XT_IS_OBJECT(cached)) return cached;
695
+ xt_value list = xt_object_new_with_proto(class_list_proto());
696
+ xt_object_set(list, xt_string_from_cstr("__xt_gui_node"), self);
697
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_class_list"), list);
698
+ return list;
699
+ }
700
+
701
+ METHOD(node_style_get) {
702
+ (void)env;
703
+ (void)argc;
704
+ (void)argv;
705
+ xt_value cached = xt_object_get_cstr(self, "__xt_gui_style");
706
+ if (XT_IS_OBJECT(cached)) return cached;
707
+ xt_value style = xt_object_new_with_proto(style_proto());
708
+ xt_object_set(style, xt_string_from_cstr("__xt_gui_node"), self);
709
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_style"), style);
710
+ return style;
711
+ }
712
+
713
+ /* -- node methods --------------------------------------------------------- */
714
+
715
+ METHOD(node_get_attribute) {
716
+ (void)env;
717
+ xtgui::Node *node = resolve_node(self, nullptr);
718
+ if (node == nullptr || !node->isElement() || argc < 1) return XT_UNDEFINED;
719
+ const std::string *value = node->attr(to_string_value(xt_arg(argc, argv, 0)));
720
+ if (value == nullptr) return XT_UNDEFINED;
721
+ return xt_string_from_cstr(value->c_str());
722
+ }
723
+
724
+ METHOD(node_set_attribute) {
725
+ (void)env;
726
+ XtGuiWindow *win = nullptr;
727
+ xtgui::Node *node = resolve_node(self, &win);
728
+ if (node == nullptr || !node->isElement() || argc < 2) return XT_UNDEFINED;
729
+ set_attribute(win, node, to_string_value(xt_arg(argc, argv, 0)),
730
+ to_string_value(xt_arg(argc, argv, 1)));
731
+ return XT_UNDEFINED;
732
+ }
733
+
734
+ METHOD(node_remove_attribute) {
735
+ (void)env;
736
+ XtGuiWindow *win = nullptr;
737
+ xtgui::Node *node = resolve_node(self, &win);
738
+ if (node == nullptr || !node->isElement() || argc < 1) return XT_UNDEFINED;
739
+ remove_attribute_raw(node, to_string_value(xt_arg(argc, argv, 0)));
740
+ mark_dirty(win);
741
+ return XT_UNDEFINED;
742
+ }
743
+
744
+ METHOD(node_has_attribute) {
745
+ (void)env;
746
+ xtgui::Node *node = resolve_node(self, nullptr);
747
+ if (node == nullptr || !node->isElement() || argc < 1) return XT_FALSE;
748
+ return node->attr(to_string_value(xt_arg(argc, argv, 0))) != nullptr ? XT_TRUE : XT_FALSE;
749
+ }
750
+
751
+ METHOD(node_query_selector) {
752
+ (void)env;
753
+ XtGuiWindow *win = nullptr;
754
+ xtgui::Node *node = resolve_node(self, &win);
755
+ if (node == nullptr || argc < 1) return XT_UNDEFINED;
756
+ std::vector<xtgui::ComplexSelector> selectors =
757
+ xtgui::xt_css_parse_selector_list(to_string_value(xt_arg(argc, argv, 0)));
758
+ if (selectors.empty()) return XT_UNDEFINED;
759
+ std::vector<const xtgui::Node *> matches;
760
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
761
+ collect_matches(child.get(), selectors, matches);
762
+ }
763
+ return matches.empty() ? XT_UNDEFINED : xt_gui_node_handle(win, matches.front());
764
+ }
765
+
766
+ METHOD(node_query_selector_all) {
767
+ (void)env;
768
+ XtGuiWindow *win = nullptr;
769
+ xtgui::Node *node = resolve_node(self, &win);
770
+ xt_value array = xt_array_new(0, NULL);
771
+ if (node == nullptr || argc < 1) return array;
772
+ std::vector<xtgui::ComplexSelector> selectors =
773
+ xtgui::xt_css_parse_selector_list(to_string_value(xt_arg(argc, argv, 0)));
774
+ std::vector<const xtgui::Node *> matches;
775
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
776
+ collect_matches(child.get(), selectors, matches);
777
+ }
778
+ for (const xtgui::Node *match : matches) xt_array_push(array, xt_gui_node_handle(win, match));
779
+ return array;
780
+ }
781
+
782
+ METHOD(node_matches) {
783
+ (void)env;
784
+ xtgui::Node *node = resolve_node(self, nullptr);
785
+ if (node == nullptr || !node->isElement() || argc < 1) return XT_FALSE;
786
+ std::vector<xtgui::ComplexSelector> selectors =
787
+ xtgui::xt_css_parse_selector_list(to_string_value(xt_arg(argc, argv, 0)));
788
+ for (const xtgui::ComplexSelector &selector : selectors) {
789
+ if (xtgui::xt_css_match(node, selector)) return XT_TRUE;
790
+ }
791
+ return XT_FALSE;
792
+ }
793
+
794
+ METHOD(node_append_child) {
795
+ (void)env;
796
+ XtGuiWindow *win = nullptr;
797
+ xtgui::Node *node = resolve_node(self, &win);
798
+ if (node == nullptr || win == nullptr || argc < 1) return XT_UNDEFINED;
799
+ xtgui::Node *child = resolve_node(xt_arg(argc, argv, 0), nullptr);
800
+ if (child == nullptr) return XT_UNDEFINED;
801
+ return xt_gui_node_handle(win, win->document->appendChild(node, child));
802
+ }
803
+
804
+ METHOD(node_insert_before) {
805
+ (void)env;
806
+ XtGuiWindow *win = nullptr;
807
+ xtgui::Node *node = resolve_node(self, &win);
808
+ if (node == nullptr || win == nullptr || argc < 1) return XT_UNDEFINED;
809
+ xtgui::Node *child = resolve_node(xt_arg(argc, argv, 0), nullptr);
810
+ xtgui::Node *reference = argc >= 2 ? resolve_node(xt_arg(argc, argv, 1), nullptr) : nullptr;
811
+ if (child == nullptr) return XT_UNDEFINED;
812
+ return xt_gui_node_handle(win, win->document->insertBefore(node, child, reference));
813
+ }
814
+
815
+ METHOD(node_remove_child) {
816
+ (void)env;
817
+ XtGuiWindow *win = nullptr;
818
+ xtgui::Node *node = resolve_node(self, &win);
819
+ if (node == nullptr || win == nullptr || argc < 1) return XT_UNDEFINED;
820
+ xtgui::Node *child = resolve_node(xt_arg(argc, argv, 0), nullptr);
821
+ if (child == nullptr) return XT_UNDEFINED;
822
+ win->document->removeChild(node, child);
823
+ return xt_gui_node_handle(win, child);
824
+ }
825
+
826
+ METHOD(node_replace_child) {
827
+ (void)env;
828
+ XtGuiWindow *win = nullptr;
829
+ xtgui::Node *node = resolve_node(self, &win);
830
+ if (node == nullptr || win == nullptr || argc < 2) return XT_UNDEFINED;
831
+ xtgui::Node *newChild = resolve_node(xt_arg(argc, argv, 0), nullptr);
832
+ xtgui::Node *oldChild = resolve_node(xt_arg(argc, argv, 1), nullptr);
833
+ if (newChild == nullptr || oldChild == nullptr) return XT_UNDEFINED;
834
+ return xt_gui_node_handle(win, win->document->replaceChild(node, newChild, oldChild));
835
+ }
836
+
837
+ METHOD(node_remove) {
838
+ (void)env;
839
+ (void)argc;
840
+ (void)argv;
841
+ XtGuiWindow *win = nullptr;
842
+ xtgui::Node *node = resolve_node(self, &win);
843
+ if (node == nullptr || win == nullptr) return XT_UNDEFINED;
844
+ win->document->detach(node);
845
+ win->struct_dirty = 1;
846
+ return XT_UNDEFINED;
847
+ }
848
+
849
+ static xtgui::Node *clone_node(XtGuiWindow *win, const xtgui::Node *source, bool deep) {
850
+ if (source->isText()) return win->document->createTextNode(source->text);
851
+ if (!source->isElement()) return nullptr;
852
+ xtgui::Node *copy = win->document->createElement(source->tag);
853
+ for (const xtgui::Attr &attr : source->attrs) copy->attrs.push_back(attr);
854
+ if (deep) {
855
+ for (const std::unique_ptr<xtgui::Node> &child : source->children) {
856
+ xtgui::Node *childCopy = clone_node(win, child.get(), true);
857
+ if (childCopy != nullptr) win->document->appendChild(copy, childCopy);
858
+ }
859
+ }
860
+ return copy;
861
+ }
862
+
863
+ METHOD(node_clone_node) {
864
+ (void)env;
865
+ XtGuiWindow *win = nullptr;
866
+ xtgui::Node *node = resolve_node(self, &win);
867
+ if (node == nullptr || win == nullptr) return XT_UNDEFINED;
868
+ bool deep = argc >= 1 ? value_truthy(xt_arg(argc, argv, 0)) : false;
869
+ return xt_gui_node_handle(win, clone_node(win, node, deep));
870
+ }
871
+
872
+ METHOD(node_get_bounding_rect) {
873
+ (void)env;
874
+ (void)argc;
875
+ (void)argv;
876
+ XtGuiWindow *win = nullptr;
877
+ xtgui::Node *node = resolve_node(self, &win);
878
+ if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
879
+ const xtgui::LayoutBox *box = win->document->boxOf(node);
880
+ if (box == nullptr) return XT_UNDEFINED;
881
+ xt_value object = xt_object_new();
882
+ xt_object_set(object, xt_string_from_cstr("x"), xt_number(box->x));
883
+ xt_object_set(object, xt_string_from_cstr("y"), xt_number(box->y));
884
+ xt_object_set(object, xt_string_from_cstr("width"), xt_number(box->width));
885
+ xt_object_set(object, xt_string_from_cstr("height"), xt_number(box->height));
886
+ return object;
887
+ }
888
+
889
+ /** Laid-out border-box size, rounded like the browser `offsetWidth`/
890
+ * `offsetHeight` (0 when the element has no box yet). Applies pending
891
+ * mutations first so a read after a write is up to date. */
892
+ static xt_value node_offset_dimension(xt_value self, bool width) {
893
+ XtGuiWindow *win = nullptr;
894
+ xtgui::Node *node = resolve_node(self, &win);
895
+ if (node == nullptr || win == nullptr || win->document == nullptr) return xt_number(0);
896
+ xt_gui_flush_dom(win);
897
+ const xtgui::LayoutBox *box = win->document->boxOf(node);
898
+ if (box == nullptr) return xt_number(0);
899
+ return xt_number((double)std::lround(width ? box->width : box->height));
900
+ }
901
+
902
+ METHOD(node_offset_width_get) {
903
+ (void)env;
904
+ (void)argc;
905
+ (void)argv;
906
+ return node_offset_dimension(self, true);
907
+ }
908
+
909
+ METHOD(node_offset_height_get) {
910
+ (void)env;
911
+ (void)argc;
912
+ (void)argv;
913
+ return node_offset_dimension(self, false);
914
+ }
915
+
916
+ METHOD(node_contains) {
917
+ (void)env;
918
+ xtgui::Node *node = resolve_node(self, nullptr);
919
+ if (node == nullptr || argc < 1) return XT_FALSE;
920
+ xtgui::Node *other = resolve_node(xt_arg(argc, argv, 0), nullptr);
921
+ if (other == nullptr) return XT_FALSE;
922
+ return node->contains(other) ? XT_TRUE : XT_FALSE;
923
+ }
924
+
925
+ METHOD(node_focus) {
926
+ (void)env;
927
+ (void)argc;
928
+ (void)argv;
929
+ XtGuiWindow *win = nullptr;
930
+ xtgui::Node *node = resolve_node(self, &win);
931
+ if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
932
+ if (win->document->setFocus(node)) win->geometry.dirty = 1;
933
+ return XT_UNDEFINED;
934
+ }
935
+
936
+ METHOD(node_blur) {
937
+ (void)env;
938
+ (void)argc;
939
+ (void)argv;
940
+ XtGuiWindow *win = nullptr;
941
+ xtgui::Node *node = resolve_node(self, &win);
942
+ if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
943
+ if (win->document->focus() == node && win->document->setFocus(nullptr)) win->geometry.dirty = 1;
944
+ return XT_UNDEFINED;
945
+ }
946
+
947
+ METHOD(node_click) {
948
+ (void)env;
949
+ (void)argc;
950
+ (void)argv;
951
+ XtGuiWindow *win = nullptr;
952
+ xtgui::Node *node = resolve_node(self, &win);
953
+ if (node == nullptr || win == nullptr) return XT_UNDEFINED;
954
+ xt_gui_dispatch_to_node(win, node, "click");
955
+ return XT_UNDEFINED;
956
+ }
957
+
958
+ METHOD(node_add_event_listener) {
959
+ (void)env;
960
+ XtGuiWindow *win = nullptr;
961
+ xtgui::Node *node = resolve_node(self, &win);
962
+ if (node == nullptr || win == nullptr || argc < 2) return self;
963
+ xt_value fn = xt_arg(argc, argv, 1);
964
+ if (!XT_IS_FUNCTION(fn)) return self;
965
+ bool capture = false;
966
+ bool once = false;
967
+ xt_value options = xt_arg(argc, argv, 2);
968
+ if (XT_IS_OBJECT(options)) {
969
+ capture = value_truthy(xt_object_get_cstr(options, "capture"));
970
+ once = value_truthy(xt_object_get_cstr(options, "once"));
971
+ } else if (XT_IS_BOOL(options)) {
972
+ capture = value_truthy(options);
973
+ }
974
+ XtGuiNodeListener listener;
975
+ listener.type = to_string_value(xt_arg(argc, argv, 0));
976
+ listener.fn = fn;
977
+ listener.once = once;
978
+ listener.capture = capture;
979
+ win->node_listeners[node].push_back(listener);
980
+ return self;
981
+ }
982
+
983
+ METHOD(node_remove_event_listener) {
984
+ (void)env;
985
+ XtGuiWindow *win = nullptr;
986
+ xtgui::Node *node = resolve_node(self, &win);
987
+ if (node == nullptr || win == nullptr || argc < 2) return self;
988
+ bool capture = false;
989
+ xt_value options = xt_arg(argc, argv, 2);
990
+ if (XT_IS_OBJECT(options)) capture = value_truthy(xt_object_get_cstr(options, "capture"));
991
+ else if (XT_IS_BOOL(options)) capture = value_truthy(options);
992
+ remove_listener(win, node, to_string_value(xt_arg(argc, argv, 0)), xt_arg(argc, argv, 1), capture);
993
+ return self;
994
+ }
995
+
996
+ METHOD(node_dispatch_event) {
997
+ (void)env;
998
+ XtGuiWindow *win = nullptr;
999
+ xtgui::Node *node = resolve_node(self, &win);
1000
+ if (node == nullptr || win == nullptr || argc < 1) return XT_FALSE;
1001
+ xt_value event = xt_arg(argc, argv, 0);
1002
+ std::string type = XT_IS_OBJECT(event) ? to_string_value(xt_object_get_cstr(event, "type"))
1003
+ : to_string_value(event);
1004
+ xt_gui_emit_dom_event(win, type.c_str(), node, event);
1005
+ return XT_TRUE;
1006
+ }
1007
+
1008
+ /* -- event methods -------------------------------------------------------- */
1009
+
1010
+ METHOD(event_prevent_default) {
1011
+ (void)env;
1012
+ (void)argc;
1013
+ (void)argv;
1014
+ xt_object_set(self, xt_string_from_cstr("defaultPrevented"), XT_TRUE);
1015
+ return XT_UNDEFINED;
1016
+ }
1017
+
1018
+ METHOD(event_stop_propagation) {
1019
+ (void)env;
1020
+ (void)argc;
1021
+ (void)argv;
1022
+ xt_object_set(self, xt_string_from_cstr("__stop"), XT_TRUE);
1023
+ return XT_UNDEFINED;
1024
+ }
1025
+
1026
+ METHOD(event_stop_immediate_propagation) {
1027
+ (void)env;
1028
+ (void)argc;
1029
+ (void)argv;
1030
+ xt_object_set(self, xt_string_from_cstr("__stop"), XT_TRUE);
1031
+ xt_object_set(self, xt_string_from_cstr("__stopImmediate"), XT_TRUE);
1032
+ return XT_UNDEFINED;
1033
+ }
1034
+
1035
+ /* -- classList methods ---------------------------------------------------- */
1036
+
1037
+ static xtgui::Node *class_list_node(xt_value self, XtGuiWindow **winOut) {
1038
+ return resolve_node(xt_object_get_cstr(self, "__xt_gui_node"), winOut);
1039
+ }
1040
+
1041
+ METHOD(class_list_add) {
1042
+ (void)env;
1043
+ XtGuiWindow *win = nullptr;
1044
+ xtgui::Node *node = class_list_node(self, &win);
1045
+ if (node == nullptr) return self;
1046
+ std::vector<std::string> names = split_classes(class_name_of(node));
1047
+ bool changed = false;
1048
+ for (int32_t i = 0; i < argc; i++) {
1049
+ std::string name = to_string_value(xt_arg(argc, argv, i));
1050
+ if (name.empty() || std::find(names.begin(), names.end(), name) != names.end()) continue;
1051
+ names.push_back(name);
1052
+ changed = true;
1053
+ }
1054
+ if (changed) set_class_name(win, node, join_classes(names));
1055
+ return self;
1056
+ }
1057
+
1058
+ METHOD(class_list_remove) {
1059
+ (void)env;
1060
+ XtGuiWindow *win = nullptr;
1061
+ xtgui::Node *node = class_list_node(self, &win);
1062
+ if (node == nullptr) return self;
1063
+ std::vector<std::string> names = split_classes(class_name_of(node));
1064
+ bool changed = false;
1065
+ for (int32_t i = 0; i < argc; i++) {
1066
+ std::string name = to_string_value(xt_arg(argc, argv, i));
1067
+ auto it = std::find(names.begin(), names.end(), name);
1068
+ if (it != names.end()) {
1069
+ names.erase(it);
1070
+ changed = true;
1071
+ }
1072
+ }
1073
+ if (changed) set_class_name(win, node, join_classes(names));
1074
+ return self;
1075
+ }
1076
+
1077
+ METHOD(class_list_toggle) {
1078
+ (void)env;
1079
+ XtGuiWindow *win = nullptr;
1080
+ xtgui::Node *node = class_list_node(self, &win);
1081
+ if (node == nullptr || argc < 1) return XT_FALSE;
1082
+ std::string name = to_string_value(xt_arg(argc, argv, 0));
1083
+ std::vector<std::string> names = split_classes(class_name_of(node));
1084
+ auto it = std::find(names.begin(), names.end(), name);
1085
+ if (it != names.end()) {
1086
+ names.erase(it);
1087
+ set_class_name(win, node, join_classes(names));
1088
+ return XT_FALSE;
1089
+ }
1090
+ if (!name.empty()) {
1091
+ names.push_back(name);
1092
+ set_class_name(win, node, join_classes(names));
1093
+ }
1094
+ return XT_TRUE;
1095
+ }
1096
+
1097
+ METHOD(class_list_contains) {
1098
+ (void)env;
1099
+ xtgui::Node *node = class_list_node(self, nullptr);
1100
+ if (node == nullptr || argc < 1) return XT_FALSE;
1101
+ std::string name = to_string_value(xt_arg(argc, argv, 0));
1102
+ std::vector<std::string> names = split_classes(class_name_of(node));
1103
+ return std::find(names.begin(), names.end(), name) != names.end() ? XT_TRUE : XT_FALSE;
1104
+ }
1105
+
1106
+ /* -- style methods -------------------------------------------------------- */
1107
+
1108
+ static xtgui::Node *style_node(xt_value self, XtGuiWindow **winOut) {
1109
+ return resolve_node(xt_object_get_cstr(self, "__xt_gui_node"), winOut);
1110
+ }
1111
+
1112
+ METHOD(style_set_property) {
1113
+ (void)env;
1114
+ XtGuiWindow *win = nullptr;
1115
+ xtgui::Node *node = style_node(self, &win);
1116
+ if (node == nullptr || argc < 2) return XT_UNDEFINED;
1117
+ set_style_property(win, node, to_string_value(xt_arg(argc, argv, 0)),
1118
+ to_string_value(xt_arg(argc, argv, 1)));
1119
+ return XT_UNDEFINED;
1120
+ }
1121
+
1122
+ METHOD(style_get_property_value) {
1123
+ (void)env;
1124
+ xtgui::Node *node = style_node(self, nullptr);
1125
+ if (node == nullptr || argc < 1) return xt_string_from_cstr("");
1126
+ return xt_string_from_cstr(
1127
+ get_style_property(node, to_string_value(xt_arg(argc, argv, 0))).c_str());
1128
+ }
1129
+
1130
+ METHOD(style_remove_property) {
1131
+ (void)env;
1132
+ XtGuiWindow *win = nullptr;
1133
+ xtgui::Node *node = style_node(self, &win);
1134
+ if (node == nullptr || argc < 1) return XT_UNDEFINED;
1135
+ remove_style_property(win, node, to_string_value(xt_arg(argc, argv, 0)));
1136
+ return XT_UNDEFINED;
1137
+ }
1138
+
1139
+ /* -- prototype construction (lazy) ---------------------------------------- */
1140
+
1141
+ static xt_value node_proto() {
1142
+ if (XT_IS_OBJECT(g_node_proto)) return g_node_proto;
1143
+ xt_value proto = xt_object_new();
1144
+ /* Getters. */
1145
+ define_getter(proto, "tagName", (void *)node_tag_name_get);
1146
+ define_getter(proto, "nodeName", (void *)node_tag_name_get);
1147
+ define_getter(proto, "nodeType", (void *)node_node_type_get);
1148
+ define_getter(proto, "id", (void *)node_id_get);
1149
+ define_getter(proto, "className", (void *)node_class_name_get);
1150
+ define_getter(proto, "textContent", (void *)node_text_content_get);
1151
+ define_getter(proto, "innerText", (void *)node_text_content_get);
1152
+ define_getter(proto, "innerHTML", (void *)node_inner_html_get);
1153
+ define_getter(proto, "parentNode", (void *)node_parent_node_get);
1154
+ define_getter(proto, "parentElement", (void *)node_parent_element_get);
1155
+ define_getter(proto, "children", (void *)node_children_get);
1156
+ define_getter(proto, "childNodes", (void *)node_child_nodes_get);
1157
+ define_getter(proto, "childElementCount", (void *)node_child_element_count_get);
1158
+ define_getter(proto, "firstElementChild", (void *)node_first_element_child_get);
1159
+ define_getter(proto, "lastElementChild", (void *)node_last_element_child_get);
1160
+ define_getter(proto, "nextElementSibling", (void *)node_next_element_sibling_get);
1161
+ define_getter(proto, "previousElementSibling", (void *)node_previous_element_sibling_get);
1162
+ define_getter(proto, "isConnected", (void *)node_is_connected_get);
1163
+ define_getter(proto, "classList", (void *)node_class_list_get);
1164
+ define_getter(proto, "style", (void *)node_style_get);
1165
+ define_getter(proto, "offsetWidth", (void *)node_offset_width_get);
1166
+ define_getter(proto, "offsetHeight", (void *)node_offset_height_get);
1167
+ /* Setters. */
1168
+ define_setter(proto, "id", (void *)node_id_set);
1169
+ define_setter(proto, "className", (void *)node_class_name_set);
1170
+ define_setter(proto, "textContent", (void *)node_text_content_set);
1171
+ define_setter(proto, "innerText", (void *)node_text_content_set);
1172
+ define_setter(proto, "innerHTML", (void *)node_inner_html_set);
1173
+ /* Methods. */
1174
+ define_method(proto, "getAttribute", (void *)node_get_attribute);
1175
+ define_method(proto, "setAttribute", (void *)node_set_attribute);
1176
+ define_method(proto, "removeAttribute", (void *)node_remove_attribute);
1177
+ define_method(proto, "hasAttribute", (void *)node_has_attribute);
1178
+ define_method(proto, "querySelector", (void *)node_query_selector);
1179
+ define_method(proto, "querySelectorAll", (void *)node_query_selector_all);
1180
+ define_method(proto, "matches", (void *)node_matches);
1181
+ define_method(proto, "appendChild", (void *)node_append_child);
1182
+ define_method(proto, "insertBefore", (void *)node_insert_before);
1183
+ define_method(proto, "removeChild", (void *)node_remove_child);
1184
+ define_method(proto, "replaceChild", (void *)node_replace_child);
1185
+ define_method(proto, "remove", (void *)node_remove);
1186
+ define_method(proto, "cloneNode", (void *)node_clone_node);
1187
+ define_method(proto, "getBoundingClientRect", (void *)node_get_bounding_rect);
1188
+ define_method(proto, "contains", (void *)node_contains);
1189
+ define_method(proto, "focus", (void *)node_focus);
1190
+ define_method(proto, "blur", (void *)node_blur);
1191
+ define_method(proto, "click", (void *)node_click);
1192
+ define_method(proto, "addEventListener", (void *)node_add_event_listener);
1193
+ define_method(proto, "removeEventListener", (void *)node_remove_event_listener);
1194
+ define_method(proto, "dispatchEvent", (void *)node_dispatch_event);
1195
+ g_node_proto = proto;
1196
+ return proto;
1197
+ }
1198
+
1199
+ static xt_value document_proto() {
1200
+ if (XT_IS_OBJECT(g_document_proto)) return g_document_proto;
1201
+ xt_value proto = xt_object_new_with_proto(node_proto());
1202
+ define_getter(proto, "body", (void *)doc_body_get);
1203
+ define_getter(proto, "documentElement", (void *)doc_document_element_get);
1204
+ define_method(proto, "querySelector", (void *)doc_query_selector);
1205
+ define_method(proto, "querySelectorAll", (void *)doc_query_selector_all);
1206
+ define_method(proto, "getElementById", (void *)doc_get_element_by_id);
1207
+ define_method(proto, "createElement", (void *)doc_create_element);
1208
+ define_method(proto, "createTextNode", (void *)doc_create_text_node);
1209
+ /* `document.addEventListener` / element listener plumbing come from the node
1210
+ * prototype and operate on the document root node. */
1211
+ define_method(proto, "addEventListener", (void *)node_add_event_listener);
1212
+ define_method(proto, "removeEventListener", (void *)node_remove_event_listener);
1213
+ define_method(proto, "dispatchEvent", (void *)node_dispatch_event);
1214
+ g_document_proto = proto;
1215
+ return proto;
1216
+ }
1217
+
1218
+ static xt_value event_proto() {
1219
+ if (XT_IS_OBJECT(g_event_proto)) return g_event_proto;
1220
+ xt_value proto = xt_object_new();
1221
+ define_method(proto, "preventDefault", (void *)event_prevent_default);
1222
+ define_method(proto, "stopPropagation", (void *)event_stop_propagation);
1223
+ define_method(proto, "stopImmediatePropagation", (void *)event_stop_immediate_propagation);
1224
+ g_event_proto = proto;
1225
+ return proto;
1226
+ }
1227
+
1228
+ static xt_value class_list_proto() {
1229
+ if (XT_IS_OBJECT(g_class_list_proto)) return g_class_list_proto;
1230
+ xt_value proto = xt_object_new();
1231
+ define_method(proto, "add", (void *)class_list_add);
1232
+ define_method(proto, "remove", (void *)class_list_remove);
1233
+ define_method(proto, "toggle", (void *)class_list_toggle);
1234
+ define_method(proto, "contains", (void *)class_list_contains);
1235
+ g_class_list_proto = proto;
1236
+ return proto;
1237
+ }
1238
+
1239
+ static xt_value style_proto() {
1240
+ if (XT_IS_OBJECT(g_style_proto)) return g_style_proto;
1241
+ xt_value proto = xt_object_new();
1242
+ define_method(proto, "setProperty", (void *)style_set_property);
1243
+ define_method(proto, "getPropertyValue", (void *)style_get_property_value);
1244
+ define_method(proto, "removeProperty", (void *)style_remove_property);
1245
+ g_style_proto = proto;
1246
+ return proto;
1247
+ }