Don't like q-component keywords? Rather call them a-block or something else?
Now you can use our script builder to customize the keywords for your qhtml instance. Click here to visit the script roller.
QHTML is a compact language and runtime for building web UIs with readable block syntax, reusable components, signals, and live QDOM editing.
- Live demo: https://qhtml.github.io/qhtml6/dist/demo.html
- Dev testbed: https://qhtml.github.io/qhtml6/dist/test.html
- Editor playground: https://qhtml.github.io/qhtml6/dist/editor.html
- Language wiki and more examples: https://github.com/qhtml/qhtml.js
- Added
for (alias in source) { ... }keyword-level iteration syntax. - Added runtime support for
forsource evaluation through inline expression scope (including component-scoped references likethis.component.items). - Added
dist/test.htmlcoverage for multipleforuse cases (array, object/map-style keys, function-returned arrays, primitive source). - Updated docs with
forsyntax and accepted source patterns.
- Clone qhtml6 github repository
git clone https://github.com/qhtml/qhtml6.git- Copy qhtml6 into your project
qhtmlfolder
source ./deploy.sh /path/to/projectIn any .html file in your project directory just include qhtml.js to get started.
<script src="qhtml.js"></script>Optional: component library and UI tools (modal, form, grid, tabs, builder, editor):
Assuming your q-components.qhtml is located in the project folder /path/to/my-project/
<q-html>
q-import { q-components.qhtml }
</q-html>Files:
- Required:
qhtml.js - Recommended:
q-components.qhtml,w3.css - Optional:
q-components.qhtml,w3-tags.js,bs.css+bs-tags.js
<q-html>
h1 { text { Hello QHTML } }
p { text { Your first QHTML render is running. } }
</q-html>Resulting HTML:
<q-html>
<h1>Hello QHTML</h1>
<p>Your first QHTML render is running.</p>
</q-html><q-html>
div {
h2 { text { Product } }
p { text { Lightweight UI syntax. } }
}
</q-html>
Resulting HTML:
<q-html>
<div>
<h2>Product</h2>
<p>Lightweight UI syntax.</p>
</div>
</q-html><q-html>
div,section,h3 { text { Nested } }
</q-html>
Resulting HTML:
<q-html>
<div><section><h3>Nested</h3></section></div>
</q-html><q-html>
div#main.card {
p { text { Card body } }
}
</q-html>
Resulting HTML:
<q-html>
<div id="main" class="card">
<p>Card body</p>
</div>
</q-html>Multiple selectors with shorthand:
<q-html>
div#my-id.my-class,span.my-class,h2#id2 { hello world }
</q-html>
Resulting HTML:
<q-html>
<div id="my-id" class="my-class">
<span class="my-class">
<h2 id="id2">hello world</h2>
</span>
</div>
</q-html>Component instances also support selector shorthand:
q-component my-card { div { text { Card } } }
my-card#card-1.primary { }
<q-html>
a {
href: "https://example.com"
target: "_blank"
text { Open Example }
}
</q-html>
Resulting HTML:
<q-html>
<a href="https://example.com" target="_blank">Open Example</a>
</q-html><q-html>
p {
style { font-size: 20px; margin: 0; }
text { Plain text content }
}
div { html { <strong>Real HTML fragment</strong> } }
</q-html>
Resulting HTML:
<q-html>
<p style="font-size: 20px; margin: 0;">Plain text content</p>
<div><strong>Real HTML fragment</strong></div>
</q-html><q-html>
button {
text { Click }
onclick { this.textContent = "Clicked"; }
}
</q-html>
Resulting HTML:
<q-html>
<button onclick="this.textContent = "Clicked";">Click</button>
</q-html>onReady {} runs after the host’s content is mounted.
<q-html>
onReady { this.setAttribute("data-ready", "1"); }
div { text { Host ready hook executed. } }
</q-html>
Resulting HTML:
<q-html data-ready="1">
<div>Host ready hook executed.</div>
</q-html>Use $("<selector>") inside QHTML runtime JavaScript to query within the current <q-html> root only.
$("#sender")is equivalent tothis.component.qhtmlRoot().querySelector("#sender")(or closest<q-html>root).- For global page lookup, use
document.querySelector(...).
q-component notifier {
function notify(msg) { this.setAttribute("data-msg", msg); }
onReady { $("#sender").sendSignal.connect(this.component.notify) }
}
q-component sender {
q-signal sendSignal(message)
}
Use \{ and \} when you want literal braces inside block bodies.
<q-html>
div {
text { hello \} world }
}
</q-html>
Resulting HTML:
<q-html>
<div>hello } world</div>
</q-html>q-style + q-theme are the preferred styling model for new code.
q-style-class lets a style definition add CSS classes and inline properties together.
q-style panel-style {
q-style-class { w3-container w3-round-large }
backgroundColor: #e0f2fe
color: #0c4a6e
}
Apply it directly:
panel-style,div { text { Styled panel } }
Or through a theme rule:
q-theme app-theme {
.panel { panel-style }
}
Notes:
q-style-classmerges class names into the elementclassattribute.- Inline
q-styledeclarations are still applied viastyle="". - If both class CSS and inline declarations target the same property, inline wins.
q-style panel {
backgroundColor: #eff6ff
color: #1e293b
border: 1px solid #93c5fd
}
panel,div { text { Styled panel } }
q-style card-shell {
q-style-class { w3-card w3-round-large w3-padding }
borderColor: #cbd5e1
}
q-style title-accent { color: #1d4ed8 }
q-style body-muted { backgroundColor: #64748b }
q-theme article-theme {
h3 { title-accent body-muted }
p { body-muted }
}
q-default-theme is a fallback theme. It applies first, and any conflicting q-theme rules in scope replace it.
q-style panel-base { backgroundColor: #eef3fb color: #0f172a }
q-style panel-override { backgroundColor: #ffedd5 color: #7c2d12 }
q-default-theme card-theme {
.card { panel-base }
}
q-theme card-demo-theme {
card-theme { }
.card { panel-override }
}
article-theme {
div {
h3 { text { Title } }
p { text { Description } }
}
}
q-theme base-theme {
button { button-base }
}
q-theme admin-theme {
base-theme { }
.danger { button-danger }
}
q-style button-base {
q-style-class { w3-button w3-round }
backgroundColor: #0f766e
color: #ffffff
}
Notes:
q-style-classmerges into the elementclassattribute.- Inline declarations from
q-styleare written tostyle=""and win on property conflicts. - Themes can be declared once and reused as lightweight styling scopes.
q-component defines a runtime host element with:
q-propertyfieldsfunction ... {}methods onthis.componentq-signal ...signals onthis.componentq-alias ... { ... }computed alias properties onthis.componentslot { name }placeholders for projection
<q-html>
q-component app-card {
q-property { title }
div {
h3 { text { ${this.component.title} } }
slot { body }
}
}
app-card {
title: "Welcome"
body { p { text { Projected content. } } }
}
</q-html>
q-component my-comp {
q-property title
q-property selected: true
}
Each declared q-property automatically exposes a changed signal handler name in the form on<Property>Changed.
q-component counter-box {
q-property count: 0
onCountChanged {
console.log("new count =", event.detail.params.value);
}
function increment() {
this.count = Number(this.count) + 1;
}
}
Notes:
onCountChangedfires only whencountchanges to a different value.- Payload is available at
event.detail.params.valueandevent.detail.args[0]. - Each declared property also gets an implicit runtime signal function (
countChanged,titleChanged, etc), so you can use.connect(...)in addition toon<Property>Changed. on<Property>Changedmatching is case-insensitive (oncountchanged,onCountChanged,onCoUnTcHaNgEdall work).
You can assign typed array and map values directly to component properties. q-array becomes a JavaScript array and q-map becomes a plain JavaScript object on the mounted component instance.
q-component my-comp {
q-property mydivs: q-array { "hello world", 5, q-array { "multi-dimensional", 4 } }
q-property settings: q-map {
title: "Example"
count: 2
flags: q-array { true, false }
nested: q-map { enabled: true }
}
}
my-comp { }
At runtime:
document.querySelector("my-comp").mydivs[0]returns"hello world"document.querySelector("my-comp").mydivs[2]returns["multi-dimensional", 4]document.querySelector("my-comp").settings.nested.enabledreturnstrue
Named q-array and q-map declarations still work, and you can assign them to declared component properties by name.
q-array shared-items { "hello world", 5, q-array { "multi-dimensional", 4 } }
q-map shared-settings {
title: "Shared config"
count: 2
nested: q-map { enabled: true }
}
q-component my-comp {
q-property mydivs: shared-items
q-property settings: shared-settings
}
my-comp { }
q-component my-comp {
q-property mydivs: q-array { "hello world", 5, q-array { "multi-dimensional", 4 } }
q-property settings: q-map { title: "Example", nested: q-map { enabled: true } }
}
my-comp { }
button {
text { Show values }
onclick {
const comp = document.querySelector("my-comp");
alert(comp.mydivs[0]);
console.log(comp.mydivs[2]);
console.log(comp.settings.nested.enabled);
}
}
property <name>: ... also works as shorthand inside q-component, while property <name> { ... } still means "bind this child node to a component property".
q-model normalizes model data and exposes a consistent runtime API (count(), at(), values(), add(), insert(), remove(), subscribe()) regardless of source shape.
q-model my-model { q-array { 5, 10 } }
onReady {
var model = this["my-model"];
model.add(15);
console.log(model.count()); // 3
console.log(model.at(1)); // 10
console.log(model.values()); // [5, 10, 15]
}
q-model-view renders its child template once per model entry using the alias defined by as { ... }.
q-array my-source { 5, 10, q-map { name: "tom" } }
q-model-view {
q-model { my-source }
as { item }
div { text { ${item && item.name ? item.name : item} } }
}
Use for when you want inline repeated template expansion without creating a q-model-view node:
q-component list-demo {
q-property items: q-array { "one", "two", "three" }
ul {
for (item in this.component.items) {
li { text { ${item} } }
}
}
}
Accepted source inputs:
- q-array and JS arrays (for example
this.component.items) - q-map / plain object (iterates keys)
q-modelhelpers such as.values()/.keys()- function return values that evaluate to arrays/objects (for example
this.component.getItems()) - primitive values (treated as single-entry iteration)
Notes:
forexpression scope follows runtime inline expression rules.- For component state, prefer explicit component references (
this.component.<name>).
q-timer is a named top-level construct that declares a runtime timer directly:
q-timer myTimer {
interval: 3000
repeat: true
running: true
onTimeout {
alert("hello world");
}
}
Behavior:
repeat: trueuses nativesetInterval(...).repeat: falseuses nativesetTimeout(...).- The named timer handle is exported globally as
window.<name>(for examplewindow.myTimer). - The same named handle is also exposed to inline expression scope (for example
${myTimer}in runtime-evaluated expressions).
Name collisions / overwrite behavior:
- If multiple
q-timerdeclarations use the same name in the same mounted host, the last declaration wins for the exported name (window.<name>points to the last timer handle). - Earlier same-name timers may still be running if they were already started; only the exported reference is overwritten.
- On host re-render/unmount, runtime-managed keyword timers for that host are cleared and re-created from current declarations.
Recommendation:
- Use unique timer names per host to avoid handle collisions.
q-tree-view consumes model data from the same q-model pipeline and renders nested branches/leaves with native details/summary. For the current end-to-end example, see dist/demo.html.
Use extends when you want one component to inherit reusable behavior from another component instead of wrapping one component inside another.
Inherited component parts include:
q-propertyfunction ... { }q-signalq-aliasonReadyand other lifecycle hooksslot { ... }placeholders- template children / rendered markup
Child components are merged after parent components, so child methods and declarations with the same name win.
q-component counter-base {
q-property count: 0
function increment() {
this.count = Number(this.count) + 1;
this.update(this.qdom().UUID);
}
}
q-component counter-button extends counter-base {
button {
type: "button"
onclick { this.component.increment(); }
text { Count: ${this.component.count} }
}
}
counter-button { }
Use single inheritance when one parent component already represents the exact reusable base behavior you want.
q-component counter-base {
q-property count: 0
function increment() {
this.count = Number(this.count) + 1;
this.update(this.qdom().UUID);
}
}
q-component hello-base {
function hello() { alert("hello world"); }
}
q-component counter-toolbar extends counter-base extends hello-base {
button {
type: "button"
onclick { this.component.increment(); }
text { Count: ${this.component.count} }
}
button {
type: "button"
onclick { this.component.hello(); }
text { Say Hello }
}
}
counter-toolbar { }
Use multiple inheritance when you want to compose a new component from several reusable behavior blocks without adding extra wrapper components just to pass features through.
q-component base-a {
function label() { return "A"; }
}
q-component base-b {
function label() { return "B"; }
}
q-component final-comp extends base-a extends base-b {
function label() { return "child"; }
}
Merge order is left-to-right, then child last:
base-abase-bfinal-comp
So in the example above, final-comp.label() returns child. If the child did not define label(), then base-b.label() would win over base-a.label().
q-wasm is supported inside q-component and exposes this.component.wasm with:
ready(Promise)call(exportName, payload)terminate()
q-component wasm-card {
q-signal computed(result)
q-wasm {
src: "/wasm/demo.wasm"
mode: worker
awaitWasm: true
timeoutMs: 5000
maxPayloadBytes: 65536
exports { init compute }
bind {
compute -> method runCompute
compute -> signal computed
}
}
onReady {
this.component.wasm.ready.then(() => this.component.runCompute({ value: 7 }));
}
}
Notes:
q-wasmis valid only insideq-component.mode: workeris default; if worker mode cannot be used, runtime falls back to main thread.allowImports { ... }is supported in main-thread mode.
This creates a real child node and assigns it to this.component.<name>.
q-component my-comp {
property builder { q-builder { } }
onReady { this.component.builder.setAttribute("data-bound", "1"); }
}
q-component mycomp {
q-alias myotherprop { return document.querySelector("#mydiv").myprop; }
}
q-component mytarget {
q-property myprop: "hello world"
}
mytarget { id: "mydiv" }
mycomp {
div { text { ${this.component.myotherprop} } }
}
q-macro is source expansion, not a rendered node.
It behaves like a reusable inline source generator.
q-macro badge {
slot { label }
return {
span.badge { text { ${this.slot("label")} } }
}
}
div {
badge { label { hello world } }
}
Inside macro output, ${name} resolves using the current scoped references (macro slots).
q-macro scoped-label {
slot { value }
return {
p { text { value=${this.slot("value")} } }
}
}
scoped-label {
value { demo-ref }
}
Use slot { name } for raw slot insertion blocks, and ${name} for inline placeholder insertion.
q-bind is deprecated and treated the same as q-script.
Use q-script for assignment expressions.
Use assignment-form q-script with q-property.
q-component my-component {
q-property myprop: q-script { return "bound-" + (2 + 3) }
div { text { ${this.component.myprop} } }
}
my-component { }
q-script {} runs JavaScript and replaces itself with the returned value:
- If the return looks like QHTML, it is parsed as QHTML.
- Otherwise, it becomes a text node.
<q-html>
div {
q-script { return "p { text { Inserted by q-script } }"; }
}
</q-html>
<q-html>
div {
data-note: q-script { return "n:" + (4 + 1) }
text { q-script { return "script-inline"; } }
}
</q-html>
${expression} is inline expression syntax for string content.
- It resolves when the final HTML string value is rendered.
- It is not a watcher by itself.
- Re-evaluation is explicit (for example, manual setter calls and explicit update flows).
<q-html>
div {
title: "Current user: ${window.currentUser}"
text { Hello ${window.currentUser} }
}
</q-html>
q-component user-card {
q-property name: "Guest"
h3 { text { ${this.component.name} } }
}
q-macro badge {
slot { label }
return { span { text { ${label} } } }
}
q-component ${dynamicName} { } // invalid
q-keyword ${alias} { q-component } // invalid
${tagName} { text { hi } } // invalid
q-component counter-label {
q-property label: q-bind { return "Count: " + window.count; } // same as q-script (deprecated alias)
div { text { ${this.component.label} } }
}
q-keyword remaps a keyword head inside the current scope.
q-keyword component { q-component }
component card-box {
div { text { hello } }
}
card-box { }
Scope is local to the parent block and inherited by children:
div {
q-keyword box { span }
box { text { inside } } // -> span { ... }
}
box { text { outside } } // unchanged (no alias in this scope)
Invalid direct aliasing is rejected:
q-keyword a { q-component }
q-keyword b { a } // error: alias cannot target another alias
-
Note* it is still may be possible to design a system that loops forever using q-keword combined with other features.
-
If you want to create that and freeze your web browser, there are only basic safe guards in place that do not recursively prevent such behavior on all cases.
-
Note * If you define your keyword as
#or.or something like that, there may be some undesired artifacts rendered into the HTML DOM output.
There are two signal forms: component signals (function-style) and QHTML signal definitions.
<q-html>
q-component sender-box {
q-signal sent(message)
button {
text { Send }
onclick { this.component.sent("Hello"); }
}
}
q-component receiver-box {
function onMessage(message) { this.querySelector("#out").textContent = message; }
sender-box { id: "sender" }
p { id: "out" text { Waiting... } }
onReady { this.querySelector("#sender").sent.connect(this.component.onMessage); }
}
receiver-box { }
</q-html>
Defining q-signal menuItemClicked { ... } lets you “call” it by writing menuItemClicked { ... }.
This dispatches a DOM CustomEvent named menuItemClicked with event.detail.slots and event.detail.slotQDom.
<q-html>
q-signal menuItemClicked {
slot { itemId }
}
div {
menuItemClicked { itemId { A } }
p { text { signal-syntax-ok } }
}
</q-html>
q-rewrite is a pre-parse macro that expands calls like name { ... } before the rest of QHTML is parsed.
q-rewrite pill {
slot { label }
span { class: "pill" slot { label } }
}
pill { label { text { OK } } }
q-rewrite choose-class {
slot { active }
return {
q-script {
return this.qdom().slot("active").trim() === "true" ? "on" : "off";
}
}
}
div { class: choose-class { active { true } } }
Mounted <q-html> elements expose .qdom() (the source-of-truth tree). Mutate QDOM, then call .update() to re-render.
Any HTMLElement inside a mounted tree can also call .qdom(), which resolves using the closest q-component host when present, then the nearest <q-html> host.
When q-keyword aliases are active during parse, generated QDOM nodes include a keywords map (effective alias table at parse time).
<q-html id="page">
ul { id: "items" }
</q-html>
<script>
const host = document.querySelector("#page");
const root = host.qdom();
const list = root.find("#items");
list.appendNode(list.createQElement("li", { textContent: "Added via qdom()" }));
host.update();
</script>const host = document.querySelector("q-html");
host.qdom().find("#items").replaceWithQHTML("ul { li { text { Replaced } } }");
host.update();document.querySelector("q-html").qdom().find("#items").rewrite(function () {
return "div { class: 'box' text { Rewritten } }";
});
document.querySelector("q-html").update();const host = document.querySelector("q-html");
const serialized = host.qdom().serialize();
host.qdom().deserialize(serialized, false); // append
host.update();
host.qdom().deserialize(serialized, true); // replace
host.update();const comp = document.querySelector("my-component");
const qnode = comp.qdom(); // component instance qdom
const props = qnode.properties(); // shallow copy of current props
const val = qnode.getProperty("title"); // single prop lookup
qnode.property("title", "New title"); // set via helper
comp.update(); // re-render this component scopethis.component.update(); // this component subtree
this.component.root().update(); // whole <q-html>dist/demo.htmlis the component usage gallery.q-editorsupports authoring live QHTML and previewing output.q-builderprovides visual inspect/edit workflows on mounted<q-html>content.
<q-editor> takes QHTML as literal text content (not nested <q-html>).
<q-editor>
h3 { text { Hello from q-editor } }
</q-editor>q-import records import metadata in the host QDOM (meta.imports / meta.importCacheRefs) and resolves definitions from the import cache at runtime. Imported component/template/signal definitions become available without inlining full imported source into the host QDOM.
<q-html>
q-import { q-components/q-modal.qhtml }
q-modal { title { text { Hello } } body { text { Modal body } } }
</q-html>
q-template instances render their template nodes directly (no runtime host element, no this.component).
<q-html>
q-template badge {
span { class: "badge" slot { label } }
}
badge { label { text { New } } }
</q-html>
window.QHTML_RUNTIME_DEBUG = true;document.querySelector("q-html").update();document.querySelector("q-html").invalidate({ forceBindings: true });q-component my-comp {
q-logger { q-signal q-property }
q-property count: 0
q-signal ping(value)
}
q-loggerscope is lexical:- inside a
q-componentdefinition: applies to all instances of that component - inside a specific instance block: applies to that instance only
- inside a
- Supported categories:
q-property,q-signal,q-component,function,slot,model,instantiation,all
These libraries are now obsolete as their functionality has been fully merged into the core modules through various means.
While they will continue to work, it is recommended to use q-style and q-theme instead for simplicity and ease of implementation.
But for those who want to use the obsolete libraries:
These scripts register custom elements like w3-card and bs-btn so you can use them as tag names. They apply CSS classes to their first non-w3-* / non-bs-* descendant and then remove the custom wrapper elements.
<link rel="stylesheet" href="w3.css" />
<script src="w3-tags.js"></script><q-html>
w3-card,w3-padding {
div { text { This div receives W3 classes. } }
}
</q-html>
<q-html>
q-style padded-card {
q-style-class { w3-card w3-padding }
}
q-theme main-theme {
div { padded-card }
}
main-theme { div { text { This div receives W3 classes } } }
</q-html>
<link rel="stylesheet" href="bs.css" />
<script src="bs-tags.js"></script><q-html>
bs-btn,bs-btn-primary {
button { text { Primary button } }
}
</q-html>
modules/qdom-core/README.mdmodules/qhtml-parser/README.mdmodules/dom-renderer/README.mdmodules/qhtml-runtime/README.mdmodules/release-bundle/README.md
- Added declarative
q-logger { ... }support with scoped categories for runtime debugging (q-property,q-signal,q-component,function,slot,model,instantiation,all). - Added and expanded
dist/test.htmlcoverage for logger categories and multi-scope logger behavior. - Improved
qdom().qmap(...)keyword extraction for component metadata (includingq-propertydeclarations) and instance mapping behavior. - Stabilized parser/runtime updates around
q-model,q-model-view, and signal/property change flows; current non-deprecated tests pass.
- Fixed signal callback host binding so
.connect(function(){ ... })now runs against the live component instance (this) during dispatch. - Fixed
on<signal>attribute handling in component definitions to resolve case-insensitively and route through the same signal.connect(...)path (with DOM-event fallback for non-signal events). - Improved
on<Property>changednormalization so lowercase/mixed-case handlers (for exampleonmypropchanged) map correctly tomypropChanged. - Improved queued-mode declarative signal subscription timing by deferring registration until component UUID availability, and preserving cleanup metadata for detach/replace.
- Updated
dist/test.htmltest 49 to use a lower-overheadq-model-viewrandomization scenario with explicitStart timerscontrol. - Marked unstable test 39 as deprecated; active test board reports all current non-deprecated tests passing.
- Deprecated
q-bind; assignment usage is now treated as an alias ofq-script. - Declared
q-propertysetter changes no longer auto-trigger component/host invalidate-update cycles; refresh/update is explicit. q-propertynow emits per-property signals on value change:on<Property>Changed(for exampleonCountChanged).- Property-changed signal payload is value-first (
event.detail.params.value/event.detail.args[0]) and does not emit when the assigned value is unchanged. - Generic
q-property-changedevent wiring is replaced by per-property signal dispatch. - Runtime event loop mode now defaults to
queued; setwindow.QHTML_EVENT_LOOP_MODE = "compat"beforeqhtml.jsto opt out. - Updated runtime/parser/docs to reflect canonical assignment binding semantics around
q-script. - Refreshed
dist/test.htmlinto a simplified board that runs first-pass checks onQHTMLContentLoadedand re-checks every 5 seconds. - Kept binding-deprecated test numbers in place with explicit
test has been deprecatedmarkers. - Added
q-timer <name> { ... }as a top-level language construct (native runtime timer declaration) instead of component-based timer usage.
- Added typed
q-arrayandq-mapproperty values, including nested anonymous container declarations on the right-hand side of property assignments. - Named
q-array ... { }andq-map ... { }declarations still work and can be assigned to component properties by name. - Added
property <name>: <value>shorthand insideq-component, while preservingproperty <name> { ... }as child-node binding syntax. - Added
q-modelnormalized model API support forq-array,q-map, and script-backed sources. - Added
q-model-viewdelegate rendering (q-model { ... }+as { item }) for model-driven UI blocks. - Updated
q-tree-viewto use the model pipeline and nativedetails/summarystructure.
- Added
q-component ... extends ...inheritance support. - Added multiple inheritance support with ordered merge behavior:
q-component child extends baseA extends baseB { ... }. - Extended components now inherit properties, methods, signals, aliases, lifecycle hooks, slots, and template children from all parent components.
- Fixed q-editor QDom tab lag for large 40+ KB fragments by removing heavy JSON formatting from the display path and using lightweight raw output handling.
- Fixed qdom() updateing bug causing component instances to not have their own property scoping -- now each instance contains a unique property set which is accessible directly from any instance inheriting q-component definitions.
HTMLElement.prototype.qdom()now resolves from the closestq-componentcontext first (when available), then falls back to the nearest<q-html>host context.- Added component-instance QDOM property helpers:
componentInstanceQDom.properties()componentInstanceQDom.getProperty(key)componentInstanceQDom.property(key)componentInstanceQDom.property(key, value)
- Aligned README examples with validated
dist/test.htmlsyntax patterns. - Expanded
dist/test.htmlcoverage for QDOM operations and runtime update paths. - Removed remaining legacy color-system documentation references in favor of
q-style/q-theme. - Added scoped selector shortcut
$("<css selector>")for runtime script contexts (onclick,onReady,q-bind,q-script, component methods/aliases/properties).
- Added
q-style,q-style-class, andq-themesupport for merging and building complex stylesheets with advanced theming capabilities (see section on Styles and Themes). - Added
q-default-themefor fallback theme layers.q-default-themerules apply first, and conflictingq-themerules override them. q-style,q-style-class, andq-themeare actively evolving and may change in future releases.- Added component-level
q-wasmfor loading.wasmmodules with method/signal bindings and worker-first execution.
- Added
q-style-classinsideq-stylefor class + inline style composition. - Added richer
q-themeworkflows:- selector-based style mapping
- theme composition (
q-theme my-theme { base-theme { } ... }) - scoped theme invocation on element trees
- Refactored component examples and q-components to favor
q-style/q-theme. - Updated
q-builderstyle editing flow to focus onq-styleandq-themeblocks.
- Added
q-macrocompile-time inline expansion:q-macro my-macro { slot { in1 } return { div,span,${in1} { hello world } } }my-macro { in1 { h3 } }creates<div><span><h3>hello world </h3></span></div>- Invocations expand before parse (similar timing to
q-scriptreplacement, but macro output is plain qhtml expansion instead of javascript).
- Added scoped
${reference}placeholders:${slotName}resolves from current macro slot scope.- Intended for macro/rewrite scoped slot references.
- Added lazy
${expression}inline runtime interpolation in rendered string contexts (text/attributes). - Parser metadata now includes macro expansion info in
qdom.meta.qMacrosandqdom.meta.macroExpandedSource. - Expanded styling syntax:
- reusable
q-styledefinitions - selector-driven
q-themestyle assignment - scoped theme application to subtrees
- reusable
q-keywordscoped keyword aliasing:q-keyword component { q-component }.- Alias scope is lexical (parent block + descendants), with child-scope override support.
- Alias mappings are stored on parsed QDOM nodes as
node.keywords. - Direct-only alias rules: aliases cannot point to other aliases.
tag#id.classselector shorthand support finalized for elements and component instances.
q-bindevaluates with a DOM-capable runtimethis(closest,querySelector, etc).q-bindevaluation is wrapped in runtimetry/catch(binding failures log, page continues).- Host
onReadydispatch runs through the runtime callback queue (more reliable “ready” timing). - Inline source ingestion preserves literal HTML children in
<q-html>and<q-editor>source. - Runtime logs are gated behind
window.QHTML_RUNTIME_DEBUG(orwindow.QHTML_DEBUG). q-propertyfor explicit component properties.- Function-style component signals:
q-signal mySignal(a, b)plus.connect/.disconnect/.emit. - Component aliases:
q-alias name { return ... }for computed host properties. .qdom().deserialize(serialized, shouldReplaceQDom)append-or-replace import flow.- Scoped updates:
this.component.update()and full host updates:this.component.root().update().