Repository navigation
Releases: pinpointzero73/domma
Release list
Domma v1.0.1
v1.0.1 - Context menus stay open while scrolling (2026-10-27)
Fixed
- Scrolling a long context menu or one of its submenus keeps it open. Scrolling the surrounding page still closes the menu.
- Context menus contain scroll gestures and remain within the viewport on smaller screens.
Domma v1.0.0
v1.0.0 - Domma 1.0 (2026-10-26)
Changed
- The API is stable. From 1.0, a change that breaks working code needs a new major version: patch releases fix bugs, minor releases add features. 1.0.0 contains no code changes from 0.60.0. It marks the end of the road-to-1.0 work, done in 0.54 to 0.60:
- 0.54: globals and
noConflict, imports that are safe in Node, packaging. - 0.55: one set of conventions (
open/close,destroy(),E.get,dm:events, one instance per call withall()), and each web component is a thin wrapper over itsE.*component. - 0.56: dropzone, range slider, command palette, split pane, virtual list, i18n and focus trap.
- 0.57: focus trapping in modals, slideovers, dialogs and popovers, and type declarations generated from the source.
- 0.58: the deprecated forms removed.
- 0.59 / 0.60: the type catalogue.
- 0.54: globals and
- The docs describe the current API. The README and Components guide still described the timeline alias and the array-shaped factory results as working; both were removed in 0.58.
Domma v0.60.0
v0.60.0 - Added: every component's options in the type catalogue (2026-10-25)
Added
- The type catalogue lists every
E.*component's options.elementsindomma-js/types/catalogue.jsonmaps each factory that takes an options object to its options type -progression,rangeSlider,datePickerand the rest, not only the components$.setup()can create. Domma CMS uses it to describe its shortcodes' settings.
Domma v0.59.0
v0.59.0 - Added: a type catalogue for editors and tools (2026-10-25)
Added
- A type catalogue for tools that cannot run TypeScript.
assets/types/catalogue.json(domma-js/types/catalogue.json) lists every component$.setup()can create, its factory and its options: each option's kind (boolean, number, string, the values of an enum, ...) and its doc line, read from the generated declarations.npm run build:typeswrites it andnpm run check:typesfails when it is stale. Domma CMS's page editor uses it to check a page's config.
Fixed
$.setup()givescookieConsent,notificationandalarmtheir options. They were called with the selector in place of the options, so a config's options for them were dropped.- Setup config types say what works.
initialand eventtext/html/attrvalues take numbers and booleans (they are converted to text), and aformcomponent's options are typed asFormSetupOptions- its own options plusschemaanddata.
Domma v0.58.2
v0.58.2 - Fixes: command palette recents survive a reload (2026-10-25)
Fixed
- Command palette recents from async pages and sources. A command that came from an async
childrenpage or asourcesearch was remembered only by id, so after a reload nothing could find it and it vanished from Recent. A link command (anhref, noaction) now keeps a copy of what it needs to be shown and followed again, under<recentKey>:saved;clearRecent()removes both keys.
Added
resolveRecent(id)option onE.commandPalette: returns the command for a remembered id the list does not hold (one that runs code, which cannot be saved). A command it returns under a different id is ignored.
Domma v0.58.1
v0.58.1 - Fixes: range slider reset and tooltip attribute, wizard Previous, table rows in .html(), focus trap layers (2026-10-25)
Fixed
- Range slider and form reset. Inputs the slider creates (a container target, or the second thumb), and a caller's input without a
valueattribute, now get the starting value as theirvalueattribute, so a form reset puts the slider back where it started instead of the midpoint.destroy()gives the caller's input back its own attribute. - Range slider no longer wears
data-tooltip. Its tooltip setting moved todata-tooltip-modeon.dm-range-slider, because[data-tooltip]is what pages scan to attachE.tooltip()- a slider picked up a tooltip reading "hover". Restyled CSS that targeted.dm-range-slider[data-tooltip=…]should use[data-tooltip-mode=…]. F.wizardPrevious keeps what was typed. Leaving a step by Previous,prevStep(),nextStep()orgoToStep()saves that step's values first (without validating), so coming back shows them andgetStepData()includes them..html()into a table part. Rows written into a<tbody>,<thead>,<tfoot>or<table>, and cells into a<tr>, are no longer dropped by DOMPurify, which parses as if into<body>. They are sanitised in their table context, with the same rules as before.- Focus trap
allowOutsideFocus: 'layers'. Aposition: fixedelement that was already on the page and stacks below the trap (a fixed site header under a modal) is now page, not a floating layer, so focus can no longer leave an overlay for it. Toasts, popovers and anything opened after the trap still count as layers. New helper:stackLevel(el).
Changed
- Kickstart types. The MPA kickstart (CLI
initandadd-ai, and the browser builder's AI files) now shipsfrontend/types/copied from the declarations generated from the source (assets/types), replacing a hand-kept copy that had drifted.build:typesruns beforebuild:kickstart-files.
Domma v0.58.0
v0.58.0 - Deprecated APIs removed (2026-10-24)
Removed (breaking)
Everything 0.55 marked as deprecated is gone. Domma CMS 1.10.14 (with Docs 2.14.1) and Docs Pro 0.11.2 no longer use them; update those before this release.
| Removed | Use instead |
|---|---|
Modal's unprefixed open / close DOM events on modal.element |
modal.on('close', fn) / modal.once('close', fn), or the bubbling dm:modal:close (detail {instance, args}) |
Popover's popover:show / shown / hide / hidden DOM events |
pop.on('shown', fn), or bubbling, cancelable dm:popover:<name>; e.detail.instance replaces e.detail.popover |
Sortable's sortable:start / move / sort / drop / cancel / end DOM events |
.on('sort', fn), or dm:sortable:<name>; the old detail is e.detail.args[0] |
Using E.tooltip(), E.buttonGroup() or E.listGroup() as an array ([i], length, forEach, map, iteration) |
.all() on the result, or E.tooltip.all(sel) / E.buttonGroup.all(sel) / E.listGroup.all(sel) |
E.buttonGroup() / E.listGroup() returning [] when nothing matches |
They return null, like E.tooltip(); .all(sel) still returns [] |
E.timeline(selector, options) |
E.progression(selector, {mode: 'timeline', ...options}) |
Domma.theme.setVariant(), toggle(), listVariants() |
Domma.theme.set('ocean-dark'), listThemes() / listBases() |
ThemeRoller loadPreset() |
Domma.theme.set(name) |
show() / hide() / isVisible() stay as permanent aliases of open() / close() / isOpen(). window.DommaOptions.deprecationWarnings is still read, for future deprecations.
Domma v0.57.0
v0.57.0 - Focus trapping in overlays (2026-10-24)
Focus stays in modals, slideovers, dialogs and popovers
E.modal,E.slideoverandE.alert/E.confirm/E.promptnow keep Tab and Shift+Tab inside while they
are open, and put focus back where it was when they close. The modal's cycle runs through its X, the content
you put in it and the footer buttons in the order they show. A slideover focuses its panel on open, so the
first Tab reaches its first control; a field you focus afteropen()keeps focus.- New options
trapFocus(defaulttrue) andreturnFocus(defaulttrue) on all three. A modal or slideover
without a backdrop is not trapped unless you passtrapFocus: true, since the page beside it stays usable. - Overlays stack: a modal from a slideover, a confirm from a modal, a slideover from a slideover. The newest
holds Tab; closing it hands focus back to where it was in the one beneath, which holds Tab again. An overlay
closed underneath a newer one leaves focus alone and passes its own return target up. - Toasts and their buttons, popovers, context menus, date pickers and other floating panels opened from an
overlay keep focus and clicks; Tab moves through them and back into the overlay. The page behind, and an
overlay beneath, stay out of reach. Mark anything else that should take focus withdata-focus-trap-allow. - Tab is left alone when something used it first: a textarea that indents on Tab, a grid that moves between
cells, an editor's own Tab handling (anything that callspreventDefault()). - Popovers keep their non-modal Tab behaviour. Inside an overlay, Tab off the end of one goes to the next
control in that overlay, not the page behind.trapFocus: trueon a popover now uses the focus trap. - Escape and the close buttons still call
close(), so a guard you put onclosestill runs.destroy()
removes the trap. Nothing is madeinert. - Focus trap: Tab is decided as the key reaches
window, so handlers on the way can claim it; new options
allowOutsideFocus(false|true|'layers'| function) andregister; a trap whose containers left the
page lets go of focus.
Types generated from the source
- The TypeScript declarations (
assets/types/, the package'stypes) are now generated from the JSDoc insrc/
bynpm run build:types(part ofnpm run build) instead of being written by hand. They cover what they did
before and more: the router (R), auth (A), forms (F), effects, blueprint helpers (B), web components,
everyE.*factory and its instance, and the Schema Builder.H.get<T>()and the other HTTP methods are
generic,$()events are typed by name ('click'gives aMouseEvent), andisDomma()is a type guard. - Every alias Domma puts on
windowis now declared as a global (E,H,I,T,R,A,F,Bjoined
$,_,M,D,S), plusFLfrom domma-flags. - Types now follow the code where the old hand-written ones did not: names and members that never existed are
gone (HttpResponse,ThemeChangeEvent,BackToTopScrollEvent,BackToTopShowEvent,H.setDefaults,
S.setPrefix, several DataTable methods), and signatures that were wrong are fixed (Tfilter/filterBy,
PageInfo.page,theme.preview(theme)). Module values such asutilsorelementsare type-only exports, since
the ES module never exported them at runtime. - The separate hand-kept copy in
public/assets/ide/phpstorm/is retired; point an IDE atassets/types/index.d.ts. npm run check:types(inmake checkand CI) fails when the committed declarations differ from a fresh
generation, and type-checks a small consumer undertsc --strict. See docs/Types.md.
Fixes
-
A click, key or input handler inside a modal, slideover or dialog may move focus outside it on purpose - an "Insert" button that focuses the page editor behind its slideover, inserts, then closes keeps working (
allowScriptFocus, on for the overlays). Focus that escapes any other way is still brought back. -
A date picker or dropdown opened from inside a slideover or modal draws above it instead of behind it.
-
A.createUserInfo()renders the signed-in user and wires its logout button; it had no constructor, so it never did. The button keeps working after a login or logout re-renders it, anddestroy()stops listening for auth changes.
Domma v0.56.0
v0.56.0 - New components: dropzone, range slider, command palette, focus trap, split pane, virtual list and translations (2026-10-24)
i18n
- New
Domma.i18nmodule, withDomma.t(key, params)as the shortcut: messages as nested JSON
(flattened to dot keys, merged onadd()),load()from a URL viaH.getor a loader (cached,
concurrent loads shared), a fallback chain (fr-CA->fr-> fallbacks). - Plurals and select as objects of forms (
{one, few, many, other, '=0'},{select: 'gender', ...})
chosen byIntl.PluralRules, ordinals included. No ICU parser to ship. t()returns plain text;html()keeps the message's markup and escapes every param. Messages
are never evaluated.number(),currency(),date()(Date, timestamp, string orD()),relative()andlist()
over cachedIntlformatters in the current locale.translate(root)fillsdata-i18n,data-i18n-html(sanitised) anddata-i18n-attr, with
data-i18n-params; changing locale sets<html lang dir>(rtl for Arabic, Hebrew, Persian,
Urdu...) and re-translates the page.detect()picks a locale from storage, the browser or
<html lang>and can remember it withS.- Missing keys return the key (or
default), firemissingand anonMissinghook, and warn once;
window.DommaOptions = {i18nWarnings: false}silences them. Eventschange,missing,loaded
viai18n.on()and asdm:i18n:*on document. data-i18n-attrnever writes event handlers (on*),style,srcdocorformaction, and URL
attributes (href,src,action, ...) only take http(s),mailto:,tel:or relative values -
so neither a message nor a param can add script. Refusals warn once.- Types in
assets/types/i18n.d.ts; guide indocs/I18n.md; showcase pageshowcase/i18n/.
Focus trap
E.focusTrap(container, options) keeps Tab, Shift+Tab and stray focus inside one or more containers while it is
active. Tabbables are found live (shadow roots included), a container with nothing tabbable takes focus itself, and
traps stack - opening a second pauses the first, closing it hands focus back. Options for initial and returned focus,
Escape, outside clicks and inert on the rest of the page. Also importable on its own from
src/components/focus-trap.js for other components.
Command palette
E.commandPalette(options) is a Cmd/Ctrl+K launcher: fuzzy search with highlighted matches, groups, recent commands
(remembered with S), shortcut hints, nested pages (arrays or async), and an async source that is debounced and
abortable with loading, empty and error states. Full keyboard support, the ARIA combobox + listbox pattern, a focus
trap, theme tokens, reduced motion and a full-width panel on phones.
Split Pane
E.splitPane(container, options) makes resizable panes out of a container's children, with draggable,
focusable role="separator" gutters between them. Side by side or stacked, 2 or more panes, nests.
Min/max sizes in px or %, snapOffset, collapsible panes (double-click, a gutter button, Enter, or
collapse(i) / expand(i)), sizes remembered with persistKey, RTL, and iframes in panes cannot swallow a
drag. Events: beforedrag (cancelable), dragstart, drag, dragend, resize, collapse, expand.
Showcase: showcase/elements/split-pane/.
Virtual List
E.virtualList(container, options) renders only the visible rows of a long list in a scrolling container -
100,000 fixed-height rows keep about fifteen rows in the page. Fixed itemHeight or 'auto' with measured,
cached heights and a stable scroll position; keyField re-uses rows; prepend() keeps your place;
scrollToIndex(i, {align}). Infinite loading with loadMore() (one load at a time, loading / end / error rows,
retry). Roving-tabindex keyboard navigation (arrows, Page Up/Down, Home/End, Enter) that never loses the
focused row. Strings from render() are sanitised unless safe: false. Events: render, scroll,
loadmore, loaded, end, error, itemclick. Showcase: showcase/elements/virtual-list/.
Dropzone
E.dropzone(target, options) turns an element into a place to drop, browse for or paste files. A real
<input type="file"> (yours, or one it adds) carries them, so inside a <form> the files submit with it - the
input's file list is kept in step with what the area shows. Rules: accept, maxSize, minSize, maxFiles and
a cancelable onBeforeAdd; refusals fire reject(entry, 'type' | 'size' | 'count') and are announced to screen
readers. Images get thumbnails. With upload: {url, field, headers, data, concurrency} each file is sent on its own
with a progress bar, automatic retry, a Retry button and cancel(). A file dropped just outside the area no
longer navigates away from the page while a drag is under way. F.create takes type: 'dropzone'.
E.dropzone('#attachments', {accept: 'image/*,.pdf', maxSize: 5 * 1024 * 1024, maxFiles: 4});Range slider
E.rangeSlider(target, options) (alias E.slider) - one value or a [low, high] range between two thumbs, with
ticks, labelled marks, a tooltip, vertical orientation and right-to-left support. Full keyboard (arrows, Page Up /
Page Down, Home / End), ARIA sliders with aria-valuetext from the labels formatter, and pointer dragging with a
touch-sized hit area. Real <input type="range"> elements carry the value and fire native input / change, so
forms, FormData and model bindings see it. F.create takes type: 'slider'.
E.rangeSlider('#price', {range: true, min: 0, max: 1000, step: 10, value: [100, 600], labels: v => '£' + v});Domma v0.55.0
v0.55.0 - API conventions, one implementation per component (2026-10-24)
API conventions (non-breaking)
- Every
onXoption on the UI components now has a matching.on('x', fn)event and a bubblingdm:<component>:xDOM event withdetail = {instance, args}. Options run exactly as before. - Overlays (dropdown, sidebar, autocomplete, pillbox, slideover, date picker, context menu, cookie consent) all answer
open(),close(),toggle()andisOpen();show(),hide()andisVisible()stay as permanent aliases. Popover gainsopen(),close()andisVisible(). - The aliases, close buttons, backdrops and Escape all call
close()by name, so a "discard changes?" guard patched ontoinstance.closecovers every way out. - Slideover, dropdown, sidebar, date picker, autocomplete, pillbox and context menu fire a cancelable
beforeclose(andbeforeopen): returnfalsefromonBeforeCloseor a listener, or callpreventDefault(), to keep it open.destroy()always closes. E.buttonGroup()andE.listGroup()return the first instance when the selector matches several elements, with.all()(andE.buttonGroup.all()/E.listGroup.all()) for the list. Using the result as an array still works, with a one-time warning, until 1.0.E.get(el)finds skeletons, avatars, avatar groups and choosers as well as every component;E.popover.getInstance,E.skeleton.get,E.avatar.getandE.avatarGroup.getgive the same answer.E.chooser()with a missing host returns an inert chooser instead ofnull, and choosers have anelementproperty.E.notify({title, body, ...})works as well asE.notify(title, options).- Alarms:
removeAlarm(id)is the name;remove(id)keeps working. - Fixes: a sidebar's floating toggle is removed on
destroy(); a destroyed cookie consent is no longer handed back byE.cookieConsent(); destroying a slideover in the frame it opened no longer throws.
See "Conventions" in docs/Components.md.
One implementation per component
E.card, E.modal, E.tooltip, E.badge, E.numberBadge, E.toast, E.loader (with showLoader, hideLoader, fullscreenLoader), E.backToTop, E.createModal and E.showModal now each have one implementation, a component like the rest (src/components/). The <domma-*> tags are thin shells that build that same component on themselves.
- The element you pass is the component's element. Nothing replaces it any more, so references you hold, listeners bound to it,
#idlookups andE.get(el)all keep working. Every instance has a realoptionsobject. - Theme colours reach every one of them. Inside the old shadow roots,
--dm-primary,--dm-success, the greys and the shadows were declared in terms of themselves, which made them invalid, so each component drew its hard-coded fallback whatever the theme. In Chrome underocean-dark, the page's primary was#48cae4and a badge's wasrgb(79, 70, 229). Card, badge, number badge, tooltip, toast, loader and back-to-top are now plain page markup styled byelements.css. The modal keeps a shadow root (on your element) and now uses the theme. - Events follow the shared conventions: every
onXoption has a matching.on('x')anddm:<component>:xDOM event. The old unprefixedclose,show,clickandremoveevents no longer leak out of nested components, and a badge click fires once. - Modal:
onClosedfires, and factory modals honouronOpened,onClosed,headerClass,bodyClassandclassName. New cancelableonBeforeOpen/onBeforeClose(also.on('beforeclose')anddm:modal:beforeclose). The X, backdrop and Escape callclose()by name, so a replacedclose()is honoured, and Escape closes only the top modal. The unprefixedopenandcloseDOM events are still dispatched on the element, once per open or close, until 1.0.show()/hide()stay as aliases ofopen()/close().showModal()resolves withnullwhen the modal is closed without a button. - Modal content (
content,setContent()) is sanitised as$().html()does, which removes inputs and buttons. Pass{safe: false}for trusted markup. Selector mode keeps your.modal-header/.modal-bodymarkup and wires its.modal-close,[data-close]and[data-dismiss="modal"]buttons. - Toast: the message is always text unless you pass
html: true, which is now sanitised (safe: falsefor trusted markup).E.toast('123')shows "123".actionis a shorthand for a singleactionsentry, and an action'sonClickis called. - Tooltip:
E.tooltip(selector)sets up every match, not just the first. It returns the first instance, andall()(orE.tooltip.all()) gives every one; using the result as an array still works, with a one-time warning, until 1.0. A tooltip whose target is removed from the page hides with it. The tooltip appears beside the target without wrapping it. Atitleis used as the content when there is nocontent(the browser's own tooltip is held back), and keyboard focus shows hover tooltips. - Badge keeps child markup such as icons, and card keeps loose text. Card's
hoverablegives the.card-hoverborder highlight, andcolor: 'primary'adds.card-primary. fullscreenLoader()reuses one overlay, andhideLoader('#dm-loader-fullscreen')hides it. Loader options given as strings (centered: 'false') are read correctly.E.backToTop('#my-button')now uses that button, and inside the container it watches, it sticks to that container's corner. Any other selector still adds a button to the body.- The
<domma-*>tags are registered on first use or when the page contains one. Their methods (badge.setVariant(),nb.increment()) are on the tag, and a changed attribute updates the component.<domma-toast>is gone (useE.toast). So is thelegacy-wrappersmodule with itscreateXWrapperexports, which nothing in Domma used.