|
|
@@ -0,0 +1,143 @@
|
|
|
+# What's new — the Statum client is now four modules
|
|
|
+
|
|
|
+The single-file client (`js/statum.js`, served as
|
|
|
+`/_statum/resource/statum.js`) is gone. The browser client now ships as four
|
|
|
+ES5, dependency-free scripts with a strict load order:
|
|
|
+
|
|
|
+```html
|
|
|
+<script src="/_statum/resource/statum-core.js"></script>
|
|
|
+<script src="/_statum/resource/statum-document.js"></script>
|
|
|
+<script src="/_statum/resource/statum-markdown.js"></script>
|
|
|
+<script src="/_statum/resource/statum-forms.js"></script>
|
|
|
+```
|
|
|
+
|
|
|
+**Load order is a contract.** `statum-core.js` must be first: it owns the slot
|
|
|
+protocol and installs the seams (`statum.registerAttribute`, the render
|
|
|
+driver, the internal bridge) that every later module registers into. The
|
|
|
+canonical order is core → document → markdown → forms.
|
|
|
+
|
|
|
+You may cherry-pick trailing modules (core first, always): core + document
|
|
|
+gives you the full state-binding walk (`stm-text`, `stm-html`, `stm-if`,
|
|
|
+`stm-loop`, `stm-action`, …) with no markdown engine and no form extras.
|
|
|
+Note that `stm-markdown` *and* `stm-h-demotion` live in the markdown module,
|
|
|
+so a core+document page sees those attributes do nothing — remove them, or
|
|
|
+load the markdown module.
|
|
|
+
|
|
|
+Legacy pages that still say `<script pstm-res-src="statum.js"></script>`
|
|
|
+keep working: `statum-mkpstm` expands that reference at build time into the
|
|
|
+four tags above (one build warning per page names the expansion). A page
|
|
|
+mixing the legacy reference with explicit module tags is deduplicated — each
|
|
|
+module appears exactly once, in canonical order — and the warning says so.
|
|
|
+The single-file artifact itself no longer exists; in new pages, just write
|
|
|
+the four tags.
|
|
|
+
|
|
|
+Depth on all of this: `model.md` (wire protocol, directives, every
|
|
|
+attribute) and `getting-started.md` (tutorial + full JS API).
|
|
|
+
|
|
|
+## stm-prefill — state into controls, without clobbering edits
|
|
|
+
|
|
|
+`stm-prefill` writes a state expression into form controls as **properties**,
|
|
|
+not attributes, every render cycle:
|
|
|
+
|
|
|
+```html
|
|
|
+<input type="text" name="name" stm-prefill="profile.name">
|
|
|
+<select name="theme" stm-prefill="profile.theme">…</select>
|
|
|
+<input type="checkbox" name="news" stm-prefill="profile.news">
|
|
|
+```
|
|
|
+
|
|
|
+The control matrix:
|
|
|
+
|
|
|
+- **Text-like inputs and `textarea`** — writes `.value` (`null`/`undefined`
|
|
|
+ become `''`; `undefined` leaves the element untouched).
|
|
|
+- **Select** — single: writes `.value`; `multiple`: sets each option's
|
|
|
+ `.selected` from an array of string values.
|
|
|
+- **Checkbox** — writes `.checked`, coerced truthy/falsy.
|
|
|
+- **Radio** — per-element: give every member of the group the same
|
|
|
+ expression and the one whose `value` matches is checked.
|
|
|
+
|
|
|
+ ```html
|
|
|
+ <input type="radio" name="cadence" value="daily" stm-prefill="profile.cadence">
|
|
|
+ <input type="radio" name="cadence" value="weekly" stm-prefill="profile.cadence">
|
|
|
+ ```
|
|
|
+
|
|
|
+- **File inputs are skipped**: assigning `.value` from a script is forbidden
|
|
|
+ by the platform, so there is nothing sensible to write.
|
|
|
+
|
|
|
+**Dirty tracking is the point.** The moment a user types in or changes a
|
|
|
+control (capture-phase `input`/`change` listeners), it is *dirty* and
|
|
|
+stm-prefill never overwrites it again — the edit outranks the state
|
|
|
+snapshot. A form `reset` (a reset button, `statum.forms.reset`, or the
|
|
|
+server's `form-reset` directive — all funnel through the same `reset` event)
|
|
|
+un-dirties the form's controls so state can re-apply. Because dirtiness is
|
|
|
+per-element (a `WeakSet`), a form inside a keyed `stm-loop` instance keeps
|
|
|
+its dirty state across re-renders: the instance owns its nodes.
|
|
|
+
|
|
|
+## stm-clear-on-success and statum.forms
|
|
|
+
|
|
|
+A form whose own `stm-action` succeeds can clear itself:
|
|
|
+
|
|
|
+```html
|
|
|
+<form stm-action="profile.saveAction" stm-clear-on-success>…</form>
|
|
|
+```
|
|
|
+
|
|
|
+The clear waits for the action's promise and resets the form only when it
|
|
|
+resolved (a failed action leaves the fields as they are). The reset also
|
|
|
+un-dirties the controls, so the next render cycle re-applies `stm-prefill`
|
|
|
+against the fresh state — and `stm-clear-on-success` is scoped to exactly
|
|
|
+this form; no other form on the page is touched.
|
|
|
+
|
|
|
+The `statum.forms` API:
|
|
|
+
|
|
|
+```js
|
|
|
+statum.forms.reset(formOrElement); // reset the form owning the element
|
|
|
+statum.forms.markDirty(el); // force an element dirty (prefill skips it)
|
|
|
+statum.forms.isDirty(el); // query dirtiness
|
|
|
+```
|
|
|
+
|
|
|
+## statum.markdown — the renderer as an API
|
|
|
+
|
|
|
+The CommonMark engine (unchanged, just moved) is now callable directly, on
|
|
|
+the same code path the `stm-markdown` binding uses:
|
|
|
+
|
|
|
+```js
|
|
|
+statum.markdown.render(src); // → safe HTML string
|
|
|
+statum.markdown.renderTo(el, src); // replaces el's content
|
|
|
+```
|
|
|
+
|
|
|
+Escape-first posture unchanged: raw source HTML is neutralised and
|
|
|
+`javascript:`/`data:` URLs never survive as hrefs.
|
|
|
+
|
|
|
+## statum.registerAttribute — write your own bindings
|
|
|
+
|
|
|
+Module authors extend the render walk without touching core:
|
|
|
+
|
|
|
+```js
|
|
|
+statum.registerAttribute('stm-foo', function (el, scope) {
|
|
|
+ // called once per element per render cycle when el carries stm-foo
|
|
|
+}, { after: 'stm-html' }); // or before: 'stm-class.'
|
|
|
+```
|
|
|
+
|
|
|
+A trailing dot makes the name a **prefix matcher** (`'stm-class.'` matches
|
|
|
+`stm-class.hidden`); the handler then iterates the element's attributes
|
|
|
+itself. `after`/`before` name another registered attribute and insert
|
|
|
+relative to it — order matters (`stm-prefill` runs before `stm-class.*`;
|
|
|
+markdown renders before `stm-h-demotion` demotes). A constraint naming an
|
|
|
+unregistered attribute warns and keeps registration order; handlers get no
|
|
|
+per-handler try/catch — a throwing handler aborts that render cycle, exactly
|
|
|
+like the built-ins.
|
|
|
+
|
|
|
+## The reenter directive
|
|
|
+
|
|
|
+A response can ask the client to re-fetch the page's entrypoint for the
|
|
|
+current URL and apply its directives in place — no page reload:
|
|
|
+
|
|
|
+```json
|
|
|
+{ "type": "reenter" }
|
|
|
+```
|
|
|
+
|
|
|
+Use it after an action that invalidates page-level state. `reenter` runs
|
|
|
+after `form-reset`/`form-submit` and before `navigate`/`post` (navigation
|
|
|
+still wins if both appear), and the refresh is awaited — the originating
|
|
|
+action's promise (and so `stm-clear-on-success` and `statum.act`) resolves
|
|
|
+only after the re-entered state has been applied. A failed refresh is logged
|
|
|
+and swallowed.
|