@zerotal/flow 1.7.4 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,18 @@ change.
13
13
 
14
14
  ## [1.7.3] — 2026-08-20
15
15
 
16
+ ### Changed
17
+
18
+ - **BREAKING — `this.title(…)` is removed.** Declare `static title` instead — a string, or a
19
+ function of the component: `static title = (c: SearchPage) => \`Search: ${c.query}\``. It is
20
+ resolved on the server for every render and every patch, so a title that depends on state
21
+ follows it without an action remembering to update it. `_titleValue` and the per-frame reset
22
+ go with it.
23
+
24
+ A call to `this.title(…)` on a component that declares its own `title` field now sets that
25
+ field rather than the document title, which is silent — search for `this.title(` before
26
+ upgrading.
27
+
16
28
  ### Fixed
17
29
 
18
30
  - **A bound password field silently discarded every keystroke.** The client-writable set was
package/api-surface.md CHANGED
@@ -281,8 +281,6 @@ function registerComponent = (PageClass: typeof Component, path?: string) => voi
281
281
 
282
282
  function registerFlowEvent = <K extends EventName>(name: K, guard: (payload: unknown) => payload is FlowEvents[K]) => void
283
283
 
284
- function registerFlowModel = (name: string, ModelClass: typeof BaseModel) => void
285
-
286
284
  function registerForm = (FormClass: new () => Form) => void
287
285
 
288
286
  function registerSynth = (synth: Synth) => void
@@ -1089,12 +1087,8 @@ function __escAttr = (v: unknown) => string
1089
1087
 
1090
1088
  ## ./store `(./src/store.ts)`
1091
1089
 
1092
- function clientStore = () => Record<string, unknown>
1093
-
1094
1090
  function defineStore = (initial: Record<string, unknown>) => void
1095
1091
 
1096
- function initClientStore = (alpine: AlpineStores) => Record<string, unknown>
1097
-
1098
1092
  interface FlowStore = {}
1099
1093
 
1100
1094
  ## ./testing `(./src/testing.ts)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/flow",
3
- "version": "1.7.4",
3
+ "version": "1.7.5",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -47,15 +47,15 @@
47
47
  "@alpinejs/morph": "^3.14.9",
48
48
  "@alpinejs/persist": "^3.14.9",
49
49
  "@alpinejs/resize": "^3.14.9",
50
- "@zerotal/client": "1.7.4",
51
- "@zerotal/core": "1.7.4",
52
- "@zerotal/orm": "1.7.4",
53
- "@zerotal/validator": "1.7.4",
50
+ "@zerotal/client": "1.7.5",
51
+ "@zerotal/core": "1.7.5",
52
+ "@zerotal/orm": "1.7.5",
53
+ "@zerotal/validator": "1.7.5",
54
54
  "alpinejs": "^3.14.9",
55
55
  "typescript": "^5.8.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@zerotal/broadcasting": "1.7.4",
58
+ "@zerotal/broadcasting": "1.7.5",
59
59
  "@types/alpinejs": "^3.13.10",
60
60
  "@types/alpinejs__anchor": "^3.13.1",
61
61
  "@types/alpinejs__collapse": "^3.13.4",
@@ -2612,6 +2612,23 @@ function _linkActive(href: string, path: string, exact = false): boolean {
2612
2612
  return h === path || (h.length > 1 && path.startsWith(h + "/"));
2613
2613
  }
2614
2614
 
2615
+ /**
2616
+ * Mark a navigate link as the current page, for both kinds of reader.
2617
+ *
2618
+ * `data-current` styles it; `aria-current` is the only way anyone using a screen
2619
+ * reader learns which of thirty nav items is the page they are on. Setting one
2620
+ * without the other marks the link only for people who can see it.
2621
+ */
2622
+ function _mark(el: Element, active: boolean): void {
2623
+ if (active) {
2624
+ el.setAttribute("data-current", "");
2625
+ el.setAttribute("aria-current", "page");
2626
+ } else {
2627
+ el.removeAttribute("data-current");
2628
+ el.removeAttribute("aria-current");
2629
+ }
2630
+ }
2631
+
2615
2632
  function _updateCurrentLinks(): void {
2616
2633
  const path = window.location.pathname;
2617
2634
 
@@ -2623,13 +2640,11 @@ function _updateCurrentLinks(): void {
2623
2640
  document.querySelectorAll("a[flow\\:navigate]").forEach((el) => {
2624
2641
  if (el.hasAttribute("flow:current.ignore")) return;
2625
2642
  if (el.hasAttribute("flow:current.force")) {
2626
- el.setAttribute("data-current", "");
2643
+ _mark(el, true);
2627
2644
  return;
2628
2645
  }
2629
2646
  const exact = el.hasAttribute("flow:current.exact");
2630
- if (_linkActive(el.getAttribute("href") ?? "", path, exact))
2631
- el.setAttribute("data-current", "");
2632
- else el.removeAttribute("data-current");
2647
+ _mark(el, _linkActive(el.getAttribute("href") ?? "", path, exact));
2633
2648
  });
2634
2649
 
2635
2650
  document.querySelectorAll("a[flow\\:navigate][flow\\:current]").forEach((el) => {
package/src/components.ts CHANGED
@@ -250,7 +250,10 @@ export function Modal(props: ModalProps): HtmlNode {
250
250
  if (name) {
251
251
  overlay["flow:show"] = name;
252
252
  overlay["data-flow-modal"] = name;
253
- overlay["x-trap"] = `$flow.${name}`; // trap focus while open (@alpinejs/focus)
253
+ // `.noscroll` locks the body while the dialog is open. Without it focus is
254
+ // trapped correctly and the page behind still scrolls under a finger or a
255
+ // wheel, which on a phone reads as the dialog having broken the page.
256
+ overlay["x-trap.noscroll"] = `$flow.${name}`; // focus trap + scroll lock (@alpinejs/focus)
254
257
  if (title) overlay["aria-labelledby"] = titleId;
255
258
  }
256
259
  if (!open) overlay["style"] = { display: "none" };
@@ -1482,7 +1485,7 @@ export function Drawer(props: DrawerProps): HtmlNode {
1482
1485
  };
1483
1486
  if (name) {
1484
1487
  panelOut["data-flow-modal"] = name; // Escape-to-close (shared bridge handler)
1485
- panelOut["x-trap"] = shownExpr; // trap focus while open
1488
+ panelOut["x-trap.noscroll"] = shownExpr; // focus trap + scroll lock, as Modal
1486
1489
  if (title) panelOut["aria-labelledby"] = titleId;
1487
1490
  }
1488
1491