@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 +12 -0
- package/api-surface.md +0 -6
- package/package.json +6 -6
- package/src/client/bridge.ts +19 -4
- package/src/components.ts +5 -2
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.
|
|
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.
|
|
51
|
-
"@zerotal/core": "1.7.
|
|
52
|
-
"@zerotal/orm": "1.7.
|
|
53
|
-
"@zerotal/validator": "1.7.
|
|
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.
|
|
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",
|
package/src/client/bridge.ts
CHANGED
|
@@ -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
|
|
2643
|
+
_mark(el, true);
|
|
2627
2644
|
return;
|
|
2628
2645
|
}
|
|
2629
2646
|
const exact = el.hasAttribute("flow:current.exact");
|
|
2630
|
-
|
|
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
|
-
|
|
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
|
|
1488
|
+
panelOut["x-trap.noscroll"] = shownExpr; // focus trap + scroll lock, as Modal
|
|
1486
1489
|
if (title) panelOut["aria-labelledby"] = titleId;
|
|
1487
1490
|
}
|
|
1488
1491
|
|