diff --git a/dist/platform/elc b/dist/platform/elc index 05e4591..0b80847 100755 Binary files a/dist/platform/elc and b/dist/platform/elc differ diff --git a/el-compiler/runtime/el_runtime.js b/el-compiler/runtime/el_runtime.js index 4a8d2ca..88a16e1 100644 --- a/el-compiler/runtime/el_runtime.js +++ b/el-compiler/runtime/el_runtime.js @@ -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, }; diff --git a/el-compiler/src/codegen-js.el b/el-compiler/src/codegen-js.el index f832551..1a9b03e 100644 --- a/el-compiler/src/codegen-js.el +++ b/el-compiler/src/codegen-js.el @@ -933,6 +933,12 @@ fn codegen_js(stmts: [Map], 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() diff --git a/spec/codegen-js.md b/spec/codegen-js.md index 1651dba..7770811 100644 --- a/spec/codegen-js.md +++ b/spec/codegen-js.md @@ -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 |