@sveltia/ui 0.67.0 → 0.67.1

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.
@@ -7,7 +7,7 @@ export const SHIKI_VERSION: "4.4.3";
7
7
  /**
8
8
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
9
9
  */
10
- export const UI_VERSION: "0.67.0";
10
+ export const UI_VERSION: "0.67.1";
11
11
  /**
12
12
  * Available syntax highlighting languages, sorted by display name.
13
13
  * @type {{ id: string, name: string, aliases?: string[] }[]}
@@ -10,7 +10,7 @@ export const SHIKI_VERSION = "4.4.3";
10
10
  /**
11
11
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
12
12
  */
13
- export const UI_VERSION = "0.67.0";
13
+ export const UI_VERSION = "0.67.1";
14
14
 
15
15
  /**
16
16
  * Available syntax highlighting languages, sorted by display name.
@@ -148,6 +148,9 @@
148
148
 
149
149
  await focusEditor(editorStore.editor);
150
150
  editorStore.editor.dispatchCommand(TOGGLE_LINK_COMMAND, anchorURL);
151
+ } else if (editorStore.editor) {
152
+ // The dialog leaves the focus alone (see `restoreFocus` below), so bring it back here
153
+ await focusEditor(editorStore.editor);
151
154
  }
152
155
 
153
156
  anchorURL = '';
@@ -202,6 +205,7 @@
202
205
  bind:value={anchorURL}
203
206
  okDisabled={!anchorURL}
204
207
  okLabel={dialogMode === 'create' ? _('_sui.insert') : _('_sui.update')}
208
+ restoreFocus={false}
205
209
  onClose={(event) => {
206
210
  onDialogClose(event);
207
211
  }}
@@ -40,6 +40,7 @@
40
40
  lightDismiss = false,
41
41
  escapeDismiss = true,
42
42
  keepContent = false,
43
+ restoreFocus = true,
43
44
  children,
44
45
  extraContent,
45
46
  onOpening,
@@ -152,26 +153,44 @@
152
153
  */
153
154
  let lastActiveElement;
154
155
 
156
+ /**
157
+ * Timer used to defer the focus restoration in {@link moveFocusBack}.
158
+ * @type {number | undefined}
159
+ */
160
+ let restoreFocusTimer;
161
+
155
162
  /**
156
163
  * Move the focus back to the element that had it before the modal was opened. This is done
157
164
  * manually rather than relying on the browser’s own focus restoration, because the modal is
158
165
  * closed while `<body>` is `inert`, which prevents the focus from being restored.
159
166
  */
160
- const restoreFocus = () => {
161
- const { activeElement } = document;
167
+ const moveFocusBack = () => {
162
168
  const element = lastActiveElement;
163
169
 
164
170
  lastActiveElement = undefined;
165
171
 
166
- if (!element?.isConnected) {
172
+ if (!restoreFocus || !element) {
167
173
  return;
168
174
  }
169
175
 
170
- // Only take the focus back if it’s still inside the modal, or nowhere because the modal took it
171
- // down with itself. If it has already moved on, pulling it back would undo what happened.
172
- if (!activeElement || activeElement === document.body || dialog?.contains(activeElement)) {
173
- element.focus();
174
- }
176
+ // Wait for the current task to finish before moving the focus. The modal is typically closed
177
+ // from within an event handler, and the event that triggered it is still being processed:
178
+ // Firefox dispatches `keypress` to whatever holds the focus at that point, so restoring it
179
+ // synchronously would let the Enter key that submitted the modal activate the button that
180
+ // opened it, immediately reopening the modal.
181
+ restoreFocusTimer = window.setTimeout(() => {
182
+ const { activeElement } = document;
183
+
184
+ if (!element.isConnected) {
185
+ return;
186
+ }
187
+
188
+ // Only take the focus back if it’s still inside the modal, or nowhere because the modal took
189
+ // it down with itself. If it has already moved on, pulling it back would undo what happened.
190
+ if (!activeElement || activeElement === document.body || dialog?.contains(activeElement)) {
191
+ element.focus();
192
+ }
193
+ });
175
194
  };
176
195
 
177
196
  /**
@@ -235,6 +254,9 @@
235
254
  const gen = generation;
236
255
  const { activeElement } = document;
237
256
 
257
+ // Cancel a pending restoration from a previous close, which would otherwise pull the focus out
258
+ // of the modal that’s being opened right now
259
+ window.clearTimeout(restoreFocusTimer);
238
260
  lastActiveElement = activeElement instanceof HTMLElement ? activeElement : undefined;
239
261
 
240
262
  onOpening?.(new CustomEvent('Opening'));
@@ -291,7 +313,7 @@
291
313
  document.body.inert = false;
292
314
  }
293
315
 
294
- restoreFocus();
316
+ moveFocusBack();
295
317
  setActiveClass = false;
296
318
  setOpenClass = false;
297
319
 
@@ -340,6 +362,7 @@
340
362
 
341
363
  // onUnmount
342
364
  return () => {
365
+ window.clearTimeout(restoreFocusTimer);
343
366
  dialog?.close();
344
367
  unmount(placeholder);
345
368
  };
@@ -153,6 +153,13 @@ export type ModalProps = {
153
153
  * open, and unmounted once the closing transition is complete.
154
154
  */
155
155
  keepContent?: boolean | undefined;
156
+ /**
157
+ * Whether to move the focus back to the element that had it
158
+ * before the modal was opened. Default: `true`. Disable this if the consumer takes care of the
159
+ * focus itself once the modal is closed, such as when the modal is opened from a rich text editor,
160
+ * where refocusing the element would reset the caret and discard the user’s selection.
161
+ */
162
+ restoreFocus?: boolean | undefined;
156
163
  /**
157
164
  * A reference to the `<dialog>` element.
158
165
  */
package/dist/typedefs.js CHANGED
@@ -57,6 +57,10 @@
57
57
  * @property {boolean} [keepContent] Whether to keep the `<dialog>` element and its content in the
58
58
  * DOM tree while the modal is not displayed. By default, these are mounted only while the modal is
59
59
  * open, and unmounted once the closing transition is complete.
60
+ * @property {boolean} [restoreFocus] Whether to move the focus back to the element that had it
61
+ * before the modal was opened. Default: `true`. Disable this if the consumer takes care of the
62
+ * focus itself once the modal is closed, such as when the modal is opened from a rich text editor,
63
+ * where refocusing the element would reset the caret and discard the user’s selection.
60
64
  * @property {HTMLDialogElement} [dialog] A reference to the `<dialog>` element.
61
65
  * @property {Snippet} [children] Primary slot content.
62
66
  * @property {Snippet} [extraContent] Extra slot content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.67.0",
3
+ "version": "0.67.1",
4
4
  "description": "A collection of Svelte components and utilities for building user interfaces.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -66,14 +66,14 @@
66
66
  "eslint-plugin-package-json": "^1.7.1",
67
67
  "eslint-plugin-svelte": "^3.23.0",
68
68
  "globals": "^17.11.0",
69
- "happy-dom": "^20.11.2",
69
+ "happy-dom": "^20.11.6",
70
70
  "oxlint": "^1.79.0",
71
71
  "postcss": "^8.5.26",
72
72
  "postcss-html": "^2.0.0",
73
73
  "prettier": "^3.9.6",
74
74
  "prettier-plugin-svelte": "^4.1.1",
75
- "rolldown": "^1.2.4",
76
- "sass": "^1.102.0",
75
+ "rolldown": "^1.2.5",
76
+ "sass": "^1.103.0",
77
77
  "shiki": "^4.4.3",
78
78
  "stylelint": "^17.14.1",
79
79
  "stylelint-config-recommended-scss": "^17.0.1",