@sveltia/ui 0.78.1 → 0.78.2

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.
@@ -876,20 +876,28 @@ export const convertMarkdownToLexical = async (editor, value, enabledTransformer
876
876
  // Pad blank blockquote lines so they are not imported as literal `>` text
877
877
  value = padBlankBlockquoteLines(value);
878
878
 
879
- return new Promise((resolve, reject) => {
880
- editor.update(
881
- () => {
882
- try {
883
- convertFromMarkdownString(value, enabledTransformers);
884
- resolve(exportMarkdown(enabledTransformers));
885
- } catch (ex) {
886
- reject(new Error('Failed to convert Markdown', { cause: ex }));
887
- }
888
- },
889
- // Tell the import from a change made by the user
890
- { tag: IMPORT_UPDATE_TAG },
891
- );
892
- });
879
+ /** @type {unknown} */
880
+ let error;
881
+
882
+ editor.update(
883
+ () => {
884
+ try {
885
+ convertFromMarkdownString(value, enabledTransformers);
886
+ } catch (ex) {
887
+ error = ex;
888
+ }
889
+ },
890
+ // Tell the import from a change made by the user, and commit it right away, so the node
891
+ // transforms, e.g. the one giving a code block its default language, have run by the time the
892
+ // content is exported below
893
+ { tag: IMPORT_UPDATE_TAG, discrete: true },
894
+ );
895
+
896
+ if (error) {
897
+ throw new Error('Failed to convert Markdown', { cause: error });
898
+ }
899
+
900
+ return editor.read(() => exportMarkdown(enabledTransformers));
893
901
  };
894
902
 
895
903
  /**
@@ -92,12 +92,13 @@
92
92
  return;
93
93
  }
94
94
 
95
- const { value: newValue, selection } = /** @type {CustomEvent} */ (event).detail;
95
+ const { value: exportedValue, selection } = /** @type {CustomEvent} */ (event).detail;
96
+ // Keep the value as given when the content is the one last imported, which the editor only
97
+ // writes in its own Markdown style, e.g. `_text_` for `*text*`: that’s not a change, whether
98
+ // the content is untouched or the user has undone their changes to it
99
+ const newValue = editorStore.getImportedValue(exportedValue) ?? exportedValue;
96
100
 
97
- // Keep the value as given when the editor has only rewritten it in its own Markdown style, e.g.
98
- // `_text_` for `*text*`: that’s not a change, and the value would otherwise change as soon as
99
- // it’s set
100
- if (inputValue !== newValue && !editorStore.isRestyledImport(newValue)) {
101
+ if (inputValue !== newValue) {
101
102
  // Temporarily disable rich text to prevent unnecessary Markdown conversion that resets
102
103
  // Lexical nodes and selection, then restore the state
103
104
  editorStore.useRichText = false;
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
3
3
  */
4
- export const UI_VERSION: "0.78.1";
4
+ export const UI_VERSION: "0.78.2";
@@ -3,4 +3,4 @@
3
3
  /**
4
4
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
5
5
  */
6
- export const UI_VERSION = '0.78.1';
6
+ export const UI_VERSION = '0.78.2';
@@ -179,13 +179,12 @@ export const createEditorStore = () => {
179
179
  editorId,
180
180
  convertMarkdown,
181
181
  /**
182
- * Check if the given value, exported by the editor, is only the value last imported, written in
183
- * the editor’s own Markdown style.
182
+ * Get the value last imported if the given value, exported by the editor, is only that value
183
+ * written in the editor’s own Markdown style.
184
184
  * @param {string} value Exported value.
185
- * @returns {boolean} Result.
185
+ * @returns {string | undefined} Imported value, or `undefined` if the content has changed.
186
186
  */
187
- isRestyledImport: (value) =>
188
- !!lastImport && lastImport.source === inputValue && lastImport.exported === value,
187
+ getImportedValue: (value) => (lastImport?.exported === value ? lastImport.source : undefined),
189
188
  /* eslint-enable jsdoc/require-jsdoc */
190
189
  };
191
190
  };
@@ -236,6 +236,12 @@
236
236
  pending = editorStore.pending;
237
237
  });
238
238
 
239
+ // An editor removed right after a change never converts it, so don’t leave a bound `pending` set
240
+ // for good, which would hold up anything waiting for the change
241
+ $effect(() => () => {
242
+ pending = false;
243
+ });
244
+
239
245
  $effect(() => {
240
246
  // The root initializes the editor before these effects first run, and stays initialized
241
247
  /* v8 ignore next */
@@ -88,7 +88,7 @@
88
88
  * @param {object} [options] Options.
89
89
  * @param {boolean} [options.tabStopOnly] Whether to only focus an element in the tab order,
90
90
  * without falling back to any other element.
91
- * @returns {boolean} Whether an element has been focused.
91
+ * @returns {boolean} Whether an element has taken the focus.
92
92
  */
93
93
  const focusContent = ({ tabStopOnly = false } = {}) => {
94
94
  /* v8 ignore next 3 -- the callers check the content first */
@@ -96,19 +96,24 @@
96
96
  return false;
97
97
  }
98
98
 
99
- const target = /** @type {HTMLElement | null} */ (
100
- content.querySelector('[tabindex="0"]:not([aria-disabled="true"])') ??
101
- (tabStopOnly ? null : content.querySelector('[tabindex]:not([aria-disabled="true"])'))
102
- );
99
+ const candidates = /** @type {HTMLElement[]} */ ([
100
+ ...content.querySelectorAll('[tabindex="0"]:not([aria-disabled="true"])'),
101
+ ...(tabStopOnly ? [] : content.querySelectorAll('[tabindex]:not([aria-disabled="true"])')),
102
+ ]);
103
103
 
104
- if (target) {
105
- target.focus();
106
- } else if (!tabStopOnly) {
104
+ // Skip an element that can’t take the focus, e.g. a hidden one
105
+ const focused = candidates.some((element) => {
106
+ element.focus();
107
+
108
+ return document.activeElement === element;
109
+ });
110
+
111
+ if (!focused && !tabStopOnly) {
107
112
  content.tabIndex = -1;
108
113
  content.focus();
109
114
  }
110
115
 
111
- return !!target;
116
+ return focused;
112
117
  };
113
118
 
114
119
  // Keep the `open` prop and the instance in sync both ways. The instance opens the popup when
@@ -1046,11 +1046,11 @@ export type TextEditorStore = {
1046
1046
  */
1047
1047
  convertMarkdown: () => Promise<void>;
1048
1048
  /**
1049
- * Function to check if the given value,
1050
- * exported by the editor, is only the {@link TextEditorStore.inputValue} last imported, written in
1051
- * the editor’s own Markdown style, e.g. `_text_` for `*text*`, rather than a change.
1049
+ * Function to get the
1050
+ * {@link TextEditorStore.inputValue} last imported if the given value, exported by the editor, is
1051
+ * only that value written in the editor’s own Markdown style, e.g. `_text_` for `*text*`.
1052
1052
  */
1053
- isRestyledImport: (value: string) => boolean;
1053
+ getImportedValue: (value: string) => string | undefined;
1054
1054
  };
1055
1055
  export type SelectedItemDetail = {
1056
1056
  /**
package/dist/typedefs.js CHANGED
@@ -445,9 +445,9 @@
445
445
  * @property {boolean} pending Whether the user has changed the rich text content, and the editor
446
446
  * has yet to convert it to Markdown and update {@link TextEditorStore.inputValue}.
447
447
  * @property {() => Promise<void>} convertMarkdown Function to trigger the Lexical converter.
448
- * @property {(value: string) => boolean} isRestyledImport Function to check if the given value,
449
- * exported by the editor, is only the {@link TextEditorStore.inputValue} last imported, written in
450
- * the editor’s own Markdown style, e.g. `_text_` for `*text*`, rather than a change.
448
+ * @property {(value: string) => string | undefined} getImportedValue Function to get the
449
+ * {@link TextEditorStore.inputValue} last imported if the given value, exported by the editor, is
450
+ * only that value written in the editor’s own Markdown style, e.g. `_text_` for `*text*`.
451
451
  */
452
452
 
453
453
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.78.1",
3
+ "version": "0.78.2",
4
4
  "description": "A collection of Svelte components and utilities for building user interfaces.",
5
5
  "repository": {
6
6
  "type": "git",