Reference

Changelog

What changed in each published version of @tltdsh/mx, newest first.

v1.5#

v1.5.0
Nested arrays in children
Breaking
  • Types: importing the package no longer declares mx, dom, define and components as globals. A classic-script TypeScript project opts in with @tltdsh/mx/classic.
New
  • Nested arrays in children flatten. You can pass a .map() result as a child without spreading it, nested arrays too, and a description inside the array stays one element.
  • The package ships an agent skill in skill/ with the mx rules, the readable engine source and a few examples. mx for AI Agents explains how to install it.
Fixed
  • A description or a string no longer takes over a real node that appears later in the same list. host.render(mx.p('banner'), node) after host.render(node) used to rewrite node into the banner, and now a new <p> is inserted in front of it.
  • A form control named append no longer breaks rendering into that form. New children are placed with insertBefore, which a form control cannot shadow.
Types
  • aria-modal and aria-roledescription.
  • MxChild includes plain arrays, nested ones too, so mx.ul(items.map(item => mx.li(item))) type-checks.
  • Every typed attribute also takes null or false to remove it. aria-expanded, aria-selected, aria-checked and aria-pressed take null, true and String(flag), and still reject false.
  • MxElement accepts any other key, so this._x fields and methods mixed in from the definition type-check inside $.
  • $ returns a description, an array, or null, false or nothing. A string, a number or a bare node, which the runtime mis-renders or throws on, is now a type error.
  • dom.<tag>() for a custom tag or component returns an MxElement instead of a plain Element, and summary, figure, time, dt, dd, col and other common tags have their own element types on dom.
  • define(name) accepts a missing definition, as the runtime does.
Docs
  • The README and the package's AGENTS.md say that value, checked and selected set the property only in render(), list plain arrays among the inputs render() accepts, and show the classic-script type opt-in.

v1.4#

v1.4.1
Keyed lists keep focus and scroll position
Changed
  • Kept dom() nodes are placed with insertBefore, so focus, text selection, scroll position and custom element connection survive a prepend, a swap or removing the first row.
  • A dom() node that is no longer in the list is removed as soon as rendering passes it, so a later positional mx() description never reuses it.
Fixed
  • Dropping .textContent or .innerHTML in favor of children keeps the new children. They used to be wiped right after rendering.
  • value: undefined clears a form control like null does, instead of showing the text "undefined".
  • Re-rendering value: null no longer rewrites the control every time, so a partly typed date or time input keeps its segments.
  • A keyed reorder only touches the nodes that move. A prepend inserts the new rows, removing rows from the front removes just those, and a swap moves one node.
Types
  • aria-required, aria-invalid and aria-readonly.
v1.4.0
Reconciler fixes and stricter ARIA types
Breaking
  • Types: aria-expanded, aria-selected, aria-checked and aria-pressed only accept strings. false removes an attribute, and a screen reader reads a missing aria-expanded differently from "false", so pass String(flag).
Changed
  • dom() applies attributes after children, like render() does, so dom.select({ '.value': 'b' }, ...options) selects b.
Fixed
  • Dropping .src, .title or a function-valued attribute removes the attribute instead of leaving the text "null".
  • Attributes named after Object.prototype members, such as toString, no longer break the element.
  • Trailing children are removed through the parent, so a form holding a control named remove or a component with its own remove() method is removed correctly.
  • value, checked and .prop writes are skipped when the element already holds the value, so a partly typed date or time input survives a re-render.
Types
  • aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext, aria-orientation and aria-sort.
  • camelCase SVG tags such as clipPath and linearGradient left the mx tag list, since the proxy turns them into invalid kebab-case tags. dom() SVG tags other than svg are typed as plain Element.
Docs
  • The README says the module needs a browser, since it reads document when it loads.

v1.3#

v1.3.2
Reconciler fixes and more ARIA types
Fixed
  • Attributes named toString, valueOf or hasOwnProperty are removed when a later render drops them.
  • An attributes object with its own constructor key is read as attributes, not rendered as a child.
  • Children of foreignObject are created as HTML again, not SVG.
  • The minified build caches tag functions like the readable source, so mx.div === mx.div.
  • Engine data lives under a private Symbol, so props named ._$ or ._$$ no longer collide with it.
Types
  • aria-live, aria-atomic, aria-busy and aria-current.
v1.3.1
Text over a comment node
Fixed
  • A text child rendered where an HTML comment sits replaces the comment instead of writing its text into the comment, where it never shows.
Types
  • aria-controls, aria-owns and aria-haspopup.
v1.3.0
ARIA state types
Types
  • role autocompletes the common ARIA roles and still accepts any string.
  • aria-hidden, aria-expanded, aria-disabled, aria-selected, aria-checked and aria-pressed.

v1.2#

v1.2.6
camelCase component names
Fixed
  • define('myWidget', ...) registers my-widget, so mx.myWidget() finds it. The name used to be lowercased to mywidget.
  • Attributes whose names start with _ are removed when a later render drops them.
  • A key that switches from a function or .prop to a plain string keeps the string. title used to end up as "null".
Types
  • blocking.
v1.2.5
Types
  • is.
v1.2.4
true means "true" everywhere
Changed
  • dom() and $attrs() write true as the string "true", like render(). They used to write an empty attribute.
Fixed
  • A component whose $ returns null, undefined or false renders nothing instead of throwing.
  • Passing the same definition object to define() twice no longer breaks the component, and defining a name again keeps the last definition.
Types
  • exportparts.
v1.2.3
  • Replaces 1.2.2, which was withdrawn from npm.
Types
  • part.
v1.2.1
Form control fixes
Changed
  • value, checked and selected are applied on every render, so a control matches the description again after the user edits it.
Fixed
  • '.value' on a <select> is applied after its options render, so the first render selects the right option.
  • The attribute cache compares strictly, so a change between 0 and false or 1 and true reaches the DOM.
  • dom() and $attrs() write 1 as "1". It used to become an empty attribute.
  • Removing value from a button or option no longer leaves value="null".
Types
  • nonce.
v1.2.0
Types
  • autocapitalize.

v1.1#

v1.1.1
Types
  • aria-label, aria-labelledby, aria-describedby, aria-placeholder and aria-errormessage.
v1.1.0
Dialog and popover events
Types
  • onbeforetoggle, onclose and oncancel, and ontoggle receives a ToggleEvent.
Docs
  • The bundled AGENTS.md and CLAUDE.md follow the current conventions, and the README links the main docs pages.

v1.0#

v1.0.8-1.0.10
Agent instructions in the package
Changed
  • 1.0.8: variable names picked for brotli, same behavior. The package ships AGENTS.md, and the README gains a Limits section on the argument count of render(...children).
Fixed
  • 1.0.10: mx.mjs ends its statement with a semicolon again, for bundler compatibility.
Docs
  • 1.0.9: ships CLAUDE.md next to AGENTS.md.
v1.0.7
Popover and input types
Types
  • popover, popovertarget, popovertargetaction, inert, inputmode, enterkeyhint, fetchpriority, ontoggle and open.
v1.0.3-1.0.6
Smaller build
Changed
  • 1.0.3: variable names picked for brotli, same behavior.
Docs
  • 1.0.4: the README shows the jsDelivr and unpkg script tags and a pinned version.
  • 1.0.5: plain ASCII punctuation in the README and type comments.
  • 1.0.6: the README shows a dynamic import() from the CDN.
v1.0.1-1.0.2
Types and an ES module
New
  • 1.0.1: TypeScript types in mx.d.ts, an ES module build in mx.mjs, an exports map for import and require, and a README.
Docs
  • 1.0.2: README examples.
v1.0.0
First release
New
  • One file, mx.min.js, with four globals: mx, dom, define and components.

Upgrading#

Most releases are drop-in. These are the changes that can need an edit in your code.

Nested arrays (1.5.0)

Both forms render the same list from 1.5.0 on. Keep the spread if your code also has to run on 1.4.1 or earlier, where an unspread array renders as text.

JSFragment
mx.ul(items.map(item => mx.li(item)));      // 1.5.0 and later
mx.ul(...items.map(item => mx.li(item)));   // every version

Global types for classic scripts (1.5.0)

If you load mx with a script tag and write TypeScript, reference the classic types once to get the four globals back. ESM imports need nothing.

TSFragment
/// <reference types="@tltdsh/mx/classic" />

ARIA state strings (1.4.0)

JSFragment
mx.button({ 'aria-expanded': String(open) }, 'Menu')
Pin a version

Load an exact version from the CDN and upgrade when you choose.

Installation
Gotchas

Common mistakes and how the reconciler behaves in edge cases.

Read the gotchas