@ultimat3/render 27.2.2 → 27.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/render",
3
- "version": "27.2.2",
3
+ "version": "27.2.3",
4
4
  "description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,11 +40,11 @@
40
40
  "test": "bun test"
41
41
  },
42
42
  "dependencies": {
43
- "@ultimat3/cache": "27.2.2",
44
- "@ultimat3/core": "27.2.2",
45
- "@ultimat3/http": "27.2.2",
46
- "@ultimat3/i18n": "27.2.2",
47
- "@ultimat3/seo": "27.2.2",
43
+ "@ultimat3/cache": "27.2.3",
44
+ "@ultimat3/core": "27.2.3",
45
+ "@ultimat3/http": "27.2.3",
46
+ "@ultimat3/i18n": "27.2.3",
47
+ "@ultimat3/seo": "27.2.3",
48
48
  "sass": "1.105.1"
49
49
  }
50
50
  }
@@ -146,6 +146,29 @@ export function modalController(
146
146
  const onClose = (event: Event): void => {
147
147
  if (event.target === dialog && dialog !== undefined && !dialog.open) close();
148
148
  };
149
+ // A press on the backdrop. `closedby="any"` has the browser close the dialog itself (and fire
150
+ // `close`, read above); a browser without it reports the press as a click whose target is the
151
+ // dialog and whose point is outside its box — a click on the dialog's own padding is inside it
152
+ // and closes nothing, and so does one a keyboard made, which lands on a control. The press must
153
+ // START outside too: one that began inside (selecting text, dragging out of a field) and was
154
+ // released on the backdrop also clicks the dialog, and must not throw the visitor's work away.
155
+ // Idempotent with the browser's own: a close already under way is not started twice.
156
+ const outside = (event: Event): boolean => {
157
+ const open = dialog;
158
+ if (open === undefined || event.target !== open) return false;
159
+ const { clientX: x, clientY: y } = event as MouseEvent;
160
+ const box = open.getBoundingClientRect();
161
+ return x < box.left || x > box.right || y < box.top || y > box.bottom;
162
+ };
163
+ let pressedOutside = false;
164
+ const onPointerDown = (event: Event): void => {
165
+ pressedOutside = outside(event);
166
+ };
167
+ const onClick = (event: Event): void => {
168
+ const began = pressedOutside;
169
+ pressedOutside = false;
170
+ if (began && outside(event)) close();
171
+ };
149
172
 
150
173
  const present = async (
151
174
  next: Document,
@@ -165,8 +188,12 @@ export function modalController(
165
188
  title = doc.title;
166
189
  open = doc.createElement('dialog');
167
190
  open.setAttribute(NAVIGATION_MODAL_ATTRIBUTE, '');
191
+ // Light dismiss: Escape AND a press outside close it, as every modal does (`Dialog` included).
192
+ open.setAttribute('closedby', 'any');
168
193
  open.addEventListener('cancel', onCancel);
169
194
  open.addEventListener('close', onClose);
195
+ open.addEventListener('pointerdown', onPointerDown);
196
+ open.addEventListener('click', onClick);
170
197
  doc.body.append(open);
171
198
  } else {
172
199
  // One modal at a time: the next address replaces this one's content, in the same dialog.