add 20 browser API builtins to JS runtime and codegen preamble

Iteration 3: closes the browser API gap needed for real web pages.

New builtins in el_runtime.js:
  Extended DOM: dom_set_attr, dom_get_attr, dom_remove_attr, dom_set_html,
    dom_get_html, dom_get_parent, dom_contains_class, dom_get_checked,
    dom_set_checked
  Timers: set_timeout, set_interval, clear_interval
  Local storage: local_storage_get, local_storage_set, local_storage_remove
  Window: window_location, window_redirect, window_on_load
  Debug: console_log

All browser-only functions use _ensureBrowser guard. Timer functions
work in both Node and browser. All new names added to __el export
object, ES named exports, and codegen-js.el destructure preamble.
Spec table updated to document new categories.
This commit is contained in:
Will Anderson
2026-05-04 10:38:20 -05:00
parent 7376349124
commit 437ba0a4dd
4 changed files with 147 additions and 0 deletions
BIN
View File
Binary file not shown.
+136
View File
@@ -630,6 +630,120 @@ function dom_is_null(el) {
return el === null || el === undefined;
}
// ── Extended DOM API (browser-only) ───────────────────────────────────────
function dom_set_attr(el, attr, val) {
_ensureBrowser('dom_set_attr');
if (el != null) el.setAttribute(String(attr), String(val));
}
function dom_get_attr(el, attr) {
_ensureBrowser('dom_get_attr');
if (el == null) return '';
return el.getAttribute(String(attr)) ?? '';
}
function dom_remove_attr(el, attr) {
_ensureBrowser('dom_remove_attr');
if (el != null) el.removeAttribute(String(attr));
}
function dom_set_html(el, html) {
_ensureBrowser('dom_set_html');
if (el != null) el.innerHTML = String(html);
}
function dom_get_html(el) {
_ensureBrowser('dom_get_html');
return el == null ? '' : String(el.innerHTML ?? '');
}
function dom_get_parent(el) {
_ensureBrowser('dom_get_parent');
return el == null ? null : (el.parentElement ?? null);
}
function dom_contains_class(el, cls) {
_ensureBrowser('dom_contains_class');
if (el == null) return false;
return el.classList.contains(String(cls));
}
function dom_get_checked(el) {
_ensureBrowser('dom_get_checked');
return el == null ? false : Boolean(el.checked);
}
function dom_set_checked(el, val) {
_ensureBrowser('dom_set_checked');
if (el != null) el.checked = Boolean(val);
}
// ── Timer API (browser + Node) ─────────────────────────────────────────────
function set_timeout(ms, cb) {
if (typeof setTimeout === 'undefined') {
throw new Error('set_timeout: setTimeout not available in this environment');
}
setTimeout(cb, ms | 0);
}
function set_interval(ms, cb) {
if (typeof setInterval === 'undefined') {
throw new Error('set_interval: setInterval not available in this environment');
}
return setInterval(cb, ms | 0);
}
function clear_interval(handle) {
if (typeof clearInterval !== 'undefined') clearInterval(handle);
}
// ── Local storage (browser-only) ───────────────────────────────────────────
function local_storage_get(key) {
_ensureBrowser('local_storage_get');
return localStorage.getItem(String(key)) ?? '';
}
function local_storage_set(key, val) {
_ensureBrowser('local_storage_set');
localStorage.setItem(String(key), String(val));
}
function local_storage_remove(key) {
_ensureBrowser('local_storage_remove');
localStorage.removeItem(String(key));
}
// ── Window location / navigation (browser-only) ────────────────────────────
function window_location() {
_ensureBrowser('window_location');
return window.location.href;
}
function window_redirect(url) {
_ensureBrowser('window_redirect');
window.location.href = String(url);
}
function window_on_load(cb) {
if (typeof document !== 'undefined') {
document.addEventListener('DOMContentLoaded', cb);
} else if (typeof window !== 'undefined') {
window.addEventListener('load', cb);
}
// In Node: no-op
}
// ── console_log (explicit debug log, distinct from println) ────────────────
function console_log(msg) {
// eslint-disable-next-line no-console
console.log(String(msg));
}
// ── Window export helpers ──────────────────────────────────────────────────
//
// Expose El functions to the browser's global scope so they can be called
@@ -783,6 +897,17 @@ const __el = {
dom_set_prop, dom_get_prop, dom_set_style, dom_add_class, dom_remove_class,
dom_show, dom_hide, dom_listen, dom_query, dom_query_all, dom_create,
dom_append, dom_remove, dom_is_null,
// Extended DOM
dom_set_attr, dom_get_attr, dom_remove_attr, dom_set_html, dom_get_html,
dom_get_parent, dom_contains_class, dom_get_checked, dom_set_checked,
// Timers
set_timeout, set_interval, clear_interval,
// Local storage
local_storage_get, local_storage_set, local_storage_remove,
// Window location
window_location, window_redirect, window_on_load,
// Debug
console_log,
// Window export helpers
window_set, window_get,
// native_js escape hatch
@@ -836,6 +961,17 @@ export {
dom_set_prop, dom_get_prop, dom_set_style, dom_add_class, dom_remove_class,
dom_show, dom_hide, dom_listen, dom_query, dom_query_all, dom_create,
dom_append, dom_remove, dom_is_null,
// Extended DOM
dom_set_attr, dom_get_attr, dom_remove_attr, dom_set_html, dom_get_html,
dom_get_parent, dom_contains_class, dom_get_checked, dom_set_checked,
// Timers
set_timeout, set_interval, clear_interval,
// Local storage
local_storage_get, local_storage_set, local_storage_remove,
// Window location
window_location, window_redirect, window_on_load,
// Debug
console_log,
// Window / native_js
window_set, window_get, native_js, native_js_call,
};
+6
View File
@@ -933,6 +933,12 @@ fn codegen_js(stmts: [Map<String, Any>], source: String) -> String {
js_emit_line(" dom_set_prop, dom_get_prop, dom_set_style, dom_add_class, dom_remove_class,")
js_emit_line(" dom_show, dom_hide, dom_listen, dom_query, dom_query_all, dom_create,")
js_emit_line(" dom_append, dom_remove, dom_is_null,")
js_emit_line(" dom_set_attr, dom_get_attr, dom_remove_attr, dom_set_html, dom_get_html,")
js_emit_line(" dom_get_parent, dom_contains_class, dom_get_checked, dom_set_checked,")
js_emit_line(" set_timeout, set_interval, clear_interval,")
js_emit_line(" local_storage_get, local_storage_set, local_storage_remove,")
js_emit_line(" window_location, window_redirect, window_on_load,")
js_emit_line(" console_log,")
js_emit_line(" window_set, window_get, native_js, native_js_call,")
js_emit_line("} = globalThis.__el;")
js_emit_blank()
+5
View File
@@ -79,6 +79,11 @@ Same function names as `el_runtime.c` wherever possible, so codegen-js can emit
| `state_*` | In-memory `Map` keyed by string |
| `env` | `process.env[k]` in Node, throws in browser |
| DOM bridge (Phase 3) | `dom_get_element`, `dom_get_value`, `dom_set_value`, `dom_get_text`, `dom_set_text`, `dom_set_prop`, `dom_get_prop`, `dom_set_style`, `dom_add_class`, `dom_remove_class`, `dom_show`, `dom_hide`, `dom_listen`, `dom_query`, `dom_query_all`, `dom_create`, `dom_append`, `dom_remove`, `dom_is_null` (browser-only; throw in Node) |
| DOM extended (Phase 4) | `dom_set_attr`, `dom_get_attr`, `dom_remove_attr`, `dom_set_html`, `dom_get_html`, `dom_get_parent`, `dom_contains_class`, `dom_get_checked`, `dom_set_checked` (browser-only) |
| Timers | `set_timeout(ms, cb)`, `set_interval(ms, cb) -> Int`, `clear_interval(handle)` (browser + Node) |
| Local storage | `local_storage_get(key)`, `local_storage_set(key, val)`, `local_storage_remove(key)` (browser-only) |
| Window location | `window_location() -> String`, `window_redirect(url)`, `window_on_load(cb)` (browser-only) |
| Debug | `console_log(msg)` -- explicit console.log, separate from println |
| Window export | `window_set(name, val)`, `window_get(name)` — expose/retrieve values on `window` (or `globalThis` in Node) |
| native_js escape hatch | `native_js(code)` — evaluates a JS expression via `eval`; `native_js_call(obj, method, args)` — calls a method on an object. Use for third-party browser libraries until proper bindings exist |