Reference
Changelog
What changed in each published version of @tltdsh/mx, newest first.
v1.5#
Nested arrays in children
Breaking
- Types: importing the package no longer declares
mx,dom,defineandcomponentsas 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)afterhost.render(node)used to rewritenodeinto the banner, and now a new<p>is inserted in front of it. - A form control named
appendno longer breaks rendering into that form. New children are placed withinsertBefore, which a form control cannot shadow.
Types
aria-modalandaria-roledescription.MxChildincludes plain arrays, nested ones too, somx.ul(items.map(item => mx.li(item)))type-checks.- Every typed attribute also takes
nullorfalseto remove it.aria-expanded,aria-selected,aria-checkedandaria-pressedtakenull,trueandString(flag), and still rejectfalse. MxElementaccepts any other key, sothis._xfields and methods mixed in from the definition type-check inside$.$returns a description, an array, ornull,falseor 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 anMxElementinstead of a plainElement, andsummary,figure,time,dt,dd,coland other common tags have their own element types ondom.define(name)accepts a missing definition, as the runtime does.
Docs
- The README and the package's AGENTS.md say that
value,checkedandselectedset the property only inrender(), list plain arrays among the inputsrender()accepts, and show the classic-script type opt-in.
v1.4#
Keyed lists keep focus and scroll position
Changed
- Kept
dom()nodes are placed withinsertBefore, 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 positionalmx()description never reuses it.
Fixed
- Dropping
.textContentor.innerHTMLin favor of children keeps the new children. They used to be wiped right after rendering. value: undefinedclears a form control likenulldoes, instead of showing the text "undefined".- Re-rendering
value: nullno 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-invalidandaria-readonly.
Reconciler fixes and stricter ARIA types
Breaking
- Types:
aria-expanded,aria-selected,aria-checkedandaria-pressedonly accept strings.falseremoves an attribute, and a screen reader reads a missingaria-expandeddifferently from"false", so passString(flag).
Changed
dom()applies attributes after children, likerender()does, sodom.select({ '.value': 'b' }, ...options)selectsb.
Fixed
- Dropping
.src,.titleor a function-valued attribute removes the attribute instead of leaving the text "null". - Attributes named after
Object.prototypemembers, such astoString, no longer break the element. - Trailing children are removed through the parent, so a form holding a control named
removeor a component with its ownremove()method is removed correctly. value,checkedand.propwrites 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-orientationandaria-sort.- camelCase SVG tags such as
clipPathandlinearGradientleft themxtag list, since the proxy turns them into invalid kebab-case tags.dom()SVG tags other thansvgare typed as plainElement.
Docs
- The README says the module needs a browser, since it reads
documentwhen it loads.
v1.3#
Reconciler fixes and more ARIA types
Fixed
- Attributes named
toString,valueOforhasOwnPropertyare removed when a later render drops them. - An attributes object with its own
constructorkey is read as attributes, not rendered as a child. - Children of
foreignObjectare 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-busyandaria-current.
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-ownsandaria-haspopup.
ARIA state types
Types
roleautocompletes the common ARIA roles and still accepts any string.aria-hidden,aria-expanded,aria-disabled,aria-selected,aria-checkedandaria-pressed.
v1.2#
camelCase component names
Fixed
define('myWidget', ...)registersmy-widget, somx.myWidget()finds it. The name used to be lowercased tomywidget.- Attributes whose names start with
_are removed when a later render drops them. - A key that switches from a function or
.propto a plain string keeps the string.titleused to end up as "null".
Types
blocking.
Types
is.
true means "true" everywhere
Changed
dom()and$attrs()writetrueas the string"true", likerender(). They used to write an empty attribute.
Fixed
- A component whose
$returnsnull,undefinedorfalserenders 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.
- Replaces 1.2.2, which was withdrawn from npm.
Types
part.
Form control fixes
Changed
value,checkedandselectedare 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
0andfalseor1andtruereaches the DOM. dom()and$attrs()write1as "1". It used to become an empty attribute.- Removing
valuefrom a button or option no longer leavesvalue="null".
Types
nonce.
Types
autocapitalize.
v1.1#
Types
aria-label,aria-labelledby,aria-describedby,aria-placeholderandaria-errormessage.
Dialog and popover events
Types
onbeforetoggle,oncloseandoncancel, andontogglereceives aToggleEvent.
Docs
- The bundled AGENTS.md and CLAUDE.md follow the current conventions, and the README links the main docs pages.
v1.0#
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.mjsends its statement with a semicolon again, for bundler compatibility.
Docs
- 1.0.9: ships CLAUDE.md next to AGENTS.md.
Popover and input types
Types
popover,popovertarget,popovertargetaction,inert,inputmode,enterkeyhint,fetchpriority,ontoggleandopen.
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.
Types and an ES module
New
- 1.0.1: TypeScript types in
mx.d.ts, an ES module build inmx.mjs, an exports map forimportandrequire, and a README.
Docs
- 1.0.2: README examples.
First release
New
- One file,
mx.min.js, with four globals:mx,dom,defineandcomponents.
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 versionGlobal 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')