@jsenv/navi 0.29.151 → 0.29.153

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.
@@ -138,6 +138,17 @@ consistency across the app, not from any single call site.
138
138
  `smallTouchScreenSignal` in an app to change a dialog's size, before passing
139
139
  `expandX={false}` to stop a dialog sprawling, and before writing CSS to make a
140
140
  dialog fit the screen.
141
+ - `docs/popup_open.md` — what opens a `Dialog`/`Popover` and who owns the fact
142
+ that it is open: why a popup with no `open` prop is the default (it refuses to
143
+ close while a control inside it is mid-action), the attributes that open one
144
+ (`command` + `commandfor`, `value` saying what it is opened ON, reaching
145
+ `onOpen` before the content is built), and `triggerNaviCommand` as the LAST
146
+ RESORT for a decision that is not a press — with the rule that decides most
147
+ reviews: the `event` it takes is the gesture you were handed, threaded down to
148
+ the call, never a `new CustomEvent()` built on the spot (that is for a timer,
149
+ an action settling, a signal changing, and nothing else). Read it before
150
+ calling `triggerNaviCommand`, before passing `open` to a popup, and before an
151
+ `onOpen`/`onClose` writing a signal.
141
152
  - `docs/autofocus.md` — who gets the keyboard when a popup opens or a slide
142
153
  arrives, and whether a ring shows on it: the ladder navi walks, what
143
154
  `autoFocus` means on a surface (`true` = the surface takes it, for a popup
@@ -30,7 +30,15 @@ validation message on the **requester** (the submit button, not the form):
30
30
 
31
31
  - `errorMapping(error)` turns the raw error into what to show: a string, an
32
32
  `Error`, an element, or `{ message, target }` to point the message at another
33
- element.
33
+ element. Two returns matter as much as those:
34
+ - **`return error` — the error untouched — is how a mapping says "not mine"**.
35
+ It is then shown exactly as if there were no mapping at all, so a mapping
36
+ only has to know about the cases it dresses.
37
+ - **returning nothing displays nothing**, on purpose: that is how one
38
+ particular error is swallowed, per error, where `actionErrorEffect="none"`
39
+ says it once for the whole control. Beware of it as a default — a mapping
40
+ whose last branch falls off the end swallows everything it did not
41
+ recognize, and the person sees the button return to idle with no message.
34
42
  - The message is a constraint on the control (`navi_action_error`) rather than a
35
43
  render of its own, and it **auto-resets on the next action**: hitting submit
36
44
  again clears it and re-submits, instead of the form being stuck invalid.
@@ -47,6 +55,49 @@ validation message on the **requester** (the submit button, not the form):
47
55
  `action` — `onActionStart`, `onActionEnd`, `onActionAborted` — are in
48
56
  [actions.md](./actions.md#the-instance-a-control-runs).
49
57
 
58
+ #### A refusal that carries a link
59
+
60
+ The callout is not limited to a sentence. When the refusal has somewhere to go —
61
+ "this slot is already taken by that game" — the mapping returns an element and
62
+ the callout holds it, link included:
63
+
64
+ ```jsx
65
+ <Form
66
+ action={GAME.POST.bindParams(values)}
67
+ errorMapping={(error) => {
68
+ const conflict = error.conflict;
69
+ if (!conflict) return error; // not mine: shown as-is
70
+ return (
71
+ <>
72
+ Ce créneau est déjà pris par{" "}
73
+ <Link route={GAME_ROUTE} routeParams={{ id: conflict.game_id }}>
74
+ cette partie
75
+ </Link>
76
+ </>
77
+ );
78
+ }}
79
+ >
80
+ ```
81
+
82
+ That is the whole thing: the message arrives on the button that was pressed, it
83
+ survives whatever ran before it (a `command` waits for its own action), and it
84
+ clears itself on the next submit. A refusal with a destination does **not** need
85
+ a render of its own in the page.
86
+
87
+ Two consequences of the message being an element:
88
+
89
+ - it is rendered as a **preact tree of its own** into the callout, not as part
90
+ of the app tree, so it receives no context from the app. Anything reading
91
+ global signals — a navi `<Link route>`, a store — works; anything expecting an
92
+ app `Context.Provider` above it does not, and has to be given what it needs
93
+ through props;
94
+ - it has no one-line form, so nothing derives a `title` from it — the callout is
95
+ the only place it is shown. A string or an `Error` message still writes the
96
+ control's `title` when the control has none of its own.
97
+
98
+ [form_error_mapping_jsx demo](../src/control/demos/form_error_mapping_jsx/demo.html)
99
+ shows a mapping answering with a different component per error kind.
100
+
50
101
  ### A screen's data failed — a route action
51
102
 
52
103
  Nobody clicked; the page simply cannot be drawn. It is the page, or a piece of
@@ -5,6 +5,7 @@ What opens a `Dialog` or a `Popover`, and who owns the fact that it is open.
5
5
  - [The popup owns its open state](#the-popup-owns-its-open-state)
6
6
  - [A button opens it: the attributes](#a-button-opens-it-the-attributes)
7
7
  - [Something else opens it: `triggerNaviCommand`](#something-else-opens-it-triggernavicommand)
8
+ - [The event is forwarded, not invented](#the-event-is-forwarded-not-invented)
8
9
  - [Which element receives the command](#which-element-receives-the-command)
9
10
  - [The anchor](#the-anchor)
10
11
  - [Opening it ON something](#opening-it-on-something)
@@ -75,10 +76,15 @@ press:
75
76
 
76
77
  ## Something else opens it: `triggerNaviCommand`
77
78
 
78
- The attributes fire on every click of the element that carries them. As soon as
79
- the opening is a decision rather than a click — a long press, the end of a drag,
80
- a double-click, a keyboard shortcut, a server answer, an `IntersectionObserver` —
81
- the decision has to be made in JS, and the command triggered from there:
79
+ The attributes fire on every click of the element that carries them, and they
80
+ cover more than "this button opens that dialog": `commandfor` says to whom,
81
+ `value` says what it is about, `--navi-x:argument` says how. Before writing any
82
+ JS here, check none of those is the answer — `triggerNaviCommand` is the last
83
+ resort, not the general way to run a command.
84
+
85
+ What it is for is a decision rather than a click — a long press, the end of a
86
+ drag, a double-click, a keyboard shortcut, a server answer, an
87
+ `IntersectionObserver`:
82
88
 
83
89
  ```jsx
84
90
  import { triggerNaviCommand } from "@jsenv/navi";
@@ -100,16 +106,41 @@ This is the same entry point the attributes go through: same target resolution,
100
106
  same command proxies, same events. The popup stays uncontrolled, and keeps its
101
107
  say over closing.
102
108
 
109
+ ### The event is forwarded, not invented
110
+
103
111
  `event` is what caused the decision, and it is mandatory — triggering a command
104
112
  without one throws. It is chained into the request event, and that chain is what
105
113
  lets the popup handle focus correctly (which element to give focus back to,
106
114
  whether a mousedown's click must be swallowed) and what the debug panel groups
107
115
  the whole sequence under.
108
116
 
109
- Almost always there is a DOM event to hand over, because almost always something
110
- was done to the page. When there genuinely was not — a timer firing, an action
111
- settling, a signal changing — say so with a `CustomEvent` named after what
112
- happened rather than leaving the origin unsaid:
117
+ So the event to pass is **the one that is already there**, threaded down through
118
+ every function between the handler and the call. A `CustomEvent` built on the
119
+ spot satisfies the signature and defeats its purpose: the origin is a name
120
+ instead of a gesture, and everything read off the real event is gone.
121
+
122
+ ```js
123
+ // ✗ the handler drops the event, the command is told a story instead
124
+ const openMenu = () => {
125
+ const openEvent = new CustomEvent("open");
126
+ triggerNaviCommand(popoverRef.current, "--navi-open", openEvent);
127
+ };
128
+
129
+ // ✓ the handler passes on what it was given
130
+ const openMenu = (event) => {
131
+ triggerNaviCommand(popoverRef.current, "--navi-open", event);
132
+ };
133
+ ```
134
+
135
+ A helper several presses share takes the event as a parameter like any other;
136
+ the same holds one layer deeper, when the decision is taken by something navi
137
+ handed an event to — an `interactions` detector, an `onOpen`, an action's
138
+ callback. Follow it back: there is a gesture at the start of nearly every
139
+ sequence.
140
+
141
+ The exception is the sequence that genuinely started on its own — a timer
142
+ firing, an action settling, a signal changing. Then say what happened with a
143
+ `CustomEvent` named after it, rather than leaving the origin unsaid:
113
144
 
114
145
  ```js
115
146
  import { chainEvent, triggerNaviCommand } from "@jsenv/navi";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.151",
3
+ "version": "0.29.153",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -49,6 +49,7 @@
49
49
  "access": "public"
50
50
  },
51
51
  "sideEffects": [
52
+ "./src/navi_instance.js",
52
53
  "./src/navi_css_vars.js",
53
54
  "./src/navi_z_indexes.js",
54
55
  "./src/control/interaction/*.js",