@threadlabs/looma 0.29.6 → 0.29.7

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.
@@ -0,0 +1,29 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ /** The code block the selection lies in: its position, saved language (`auto` when none), text, and element. */
3
+ export interface LoomaCodeBlockAtSelection {
4
+ readonly pos: number;
5
+ readonly language: string;
6
+ readonly text: string;
7
+ readonly element: HTMLElement;
8
+ }
9
+ /** The code block holding the whole selection, or null when the selection is elsewhere or spans blocks. */
10
+ export declare function codeBlockAtSelection(editor: Editor): LoomaCodeBlockAtSelection | null;
11
+ /**
12
+ * The picker's choices: Auto, naming the detected language when there is one; a saved language the
13
+ * catalog lacks, marked unavailable, so it stays selected; then every catalog language.
14
+ */
15
+ export declare function codeLanguageChoices(language: string, detected: string | null, names: readonly string[]): Array<{
16
+ value: string;
17
+ label: string;
18
+ }>;
19
+ /** Centers the picker over the code block, above it when there is room and below it otherwise. */
20
+ export declare function codeLanguagePickerPlacement(block: DOMRect): {
21
+ top: string;
22
+ left: string;
23
+ transform: string;
24
+ };
25
+ /**
26
+ * Saves `value` as the language of the code block at `pos`, as its own undo step; `auto` clears it.
27
+ * @failure Returns false, and changes nothing, for a language outside `names` or no code block at `pos`.
28
+ */
29
+ export declare function setCodeBlockLanguage(editor: Editor, pos: number, value: string, names: readonly string[]): boolean;
@@ -53,6 +53,7 @@ export interface ActiveTableUiState {
53
53
  headerColumn: boolean;
54
54
  capabilities: TableActionCapabilities;
55
55
  }
56
+ export declare const NO_TABLE_CAPABILITIES: TableActionCapabilities;
56
57
  /**
57
58
  * Text formatting belongs to actual text selections, not table CellSelections.
58
59
  *
@@ -0,0 +1,35 @@
1
+ import { type ActiveTableUiState, type TableActionCapabilities } from "./extensions/table-commands.js";
2
+ import type { TableCellAlignment, TableCellBackground } from "./extensions/table-formatting.js";
3
+ /** What the table's controls show: the toolbar and overlay, and the selected cells' formatting and actions. */
4
+ export interface LoomaTableControls {
5
+ readonly toolbarOpen: boolean;
6
+ readonly overlayOpen: boolean;
7
+ readonly alignment: TableCellAlignment;
8
+ readonly background: TableCellBackground;
9
+ readonly headerRow: boolean;
10
+ readonly headerColumn: boolean;
11
+ readonly capabilities: TableActionCapabilities;
12
+ }
13
+ export declare const LOOMA_TABLE_CONTROLS_CLOSED: LoomaTableControls;
14
+ /**
15
+ * The controls for the table shown: its overlay on wider screens, and the toolbar and the selection's
16
+ * formatting only while the selection is in that table. A table that is only hovered shows its overlay
17
+ * with nothing selected; narrow screens use the editing dock instead of the toolbar and overlay.
18
+ */
19
+ export declare function tableControls(state: ActiveTableUiState, selectionInTable: boolean, narrow: boolean): LoomaTableControls;
20
+ /**
21
+ * Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
22
+ * above the table when there is room and below it otherwise.
23
+ */
24
+ export declare function tableToolbarPlacement(table: DOMRect, viewportWidth: number): {
25
+ top: string;
26
+ left: string;
27
+ maxWidth: string;
28
+ };
29
+ /** The overlay covers the table exactly. */
30
+ export declare const tableOverlayPlacement: (table: DOMRect) => {
31
+ top: string;
32
+ left: string;
33
+ width: string;
34
+ height: string;
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.29.6",
3
+ "version": "0.29.7",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -384,6 +384,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
384
384
  linkBaseUrl: string;
385
385
  disableHighlight: boolean;
386
386
  codeLanguages: Readonly<Record<string, import("highlight.js").LanguageFn>> | null;
387
+ toolbarOpen: boolean;
387
388
  modelValue: JSONContent;
388
389
  slashCommands: LoomaEditorSlashCommands | undefined;
389
390
  mentionQuery: LoomaMentionQuery | undefined;
@@ -401,5 +402,4 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
401
402
  selectionActions: LoomaEditorSelectionAction[];
402
403
  rangeActions: LoomaEditorRangeAction[];
403
404
  toolbarTriggerId: string;
404
- toolbarOpen: boolean;
405
405
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,8 +1,8 @@
1
1
  import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
2
2
  import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
3
3
  import { Editor as Editor$1, EditorContent } from "@tiptap/vue-3";
4
- import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
5
4
  import { Plugin } from "@tiptap/pm/state";
5
+ import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
6
6
  //#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
7
7
  /**
8
8
  * The default document node which represents the top level node of the editor.
@@ -3,12 +3,12 @@ import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ICONS, createLoomaMentionExtension,
3
3
  import { Button, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover, Separator, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
4
4
  import { Editor, Extension, Node as Node$1, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
5
5
  import { Editor as Editor$1 } from "@tiptap/vue-3";
6
- import { closeHistory } from "@tiptap/pm/history";
7
6
  import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
8
7
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
9
8
  import { createLowlight } from "lowlight";
10
9
  import "@tiptap/pm/tables";
11
10
  import "@tiptap/pm/transform";
11
+ import { closeHistory } from "@tiptap/pm/history";
12
12
  import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
13
13
  export * from "@threadlabs/looma/editor";
14
14
  //#region src/vue/editor/primitives.ts
@@ -1562,6 +1562,23 @@ function selectedTableCellElement(editor) {
1562
1562
  }
1563
1563
  //#endregion
1564
1564
  //#region src/editor/extensions/table-commands.ts
1565
+ var NO_TABLE_CAPABILITIES = {
1566
+ canToggleHeaderRow: false,
1567
+ canToggleHeaderColumn: false,
1568
+ canMoveRowUp: false,
1569
+ canMoveRowDown: false,
1570
+ canMoveColumnLeft: false,
1571
+ canMoveColumnRight: false,
1572
+ canAddRowBefore: false,
1573
+ canAddRowAfter: false,
1574
+ canAddColumnBefore: false,
1575
+ canAddColumnAfter: false,
1576
+ canDeleteRow: false,
1577
+ canDeleteColumn: false,
1578
+ canDeleteTable: false,
1579
+ canMergeCells: false,
1580
+ canSplitCell: false
1581
+ };
1565
1582
  /**
1566
1583
  * Moves the caret to the cell an overlay action names when the action comes from a table other
1567
1584
  * than the selected one, so table commands act on that table. A cell menu also selects its cell,
@@ -1879,6 +1896,59 @@ function selectionActionDetail(state, id) {
1879
1896
  after: doc.textBetween(to, Math.min(doc.content.size, to + CONTEXT), "\n")
1880
1897
  };
1881
1898
  }
1899
+ //#endregion
1900
+ //#region src/editor/table-ui.ts
1901
+ var LOOMA_TABLE_CONTROLS_CLOSED = Object.freeze({
1902
+ toolbarOpen: false,
1903
+ overlayOpen: false,
1904
+ alignment: "left",
1905
+ background: null,
1906
+ headerRow: false,
1907
+ headerColumn: false,
1908
+ capabilities: Object.freeze({ ...NO_TABLE_CAPABILITIES })
1909
+ });
1910
+ /**
1911
+ * The controls for the table shown: its overlay on wider screens, and the toolbar and the selection's
1912
+ * formatting only while the selection is in that table. A table that is only hovered shows its overlay
1913
+ * with nothing selected; narrow screens use the editing dock instead of the toolbar and overlay.
1914
+ */
1915
+ function tableControls(state, selectionInTable, narrow) {
1916
+ if (!selectionInTable) return {
1917
+ ...LOOMA_TABLE_CONTROLS_CLOSED,
1918
+ overlayOpen: !narrow
1919
+ };
1920
+ return {
1921
+ toolbarOpen: state.showToolbar && !narrow,
1922
+ overlayOpen: !narrow,
1923
+ alignment: state.cellAlignment,
1924
+ background: state.cellBackground,
1925
+ headerRow: state.headerRow,
1926
+ headerColumn: state.headerColumn,
1927
+ capabilities: state.capabilities
1928
+ };
1929
+ }
1930
+ /** Space, in pixels, the toolbar needs above a table; with less it sits below. */
1931
+ var ROOM_ABOVE$1 = 76;
1932
+ /**
1933
+ * Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
1934
+ * above the table when there is room and below it otherwise.
1935
+ */
1936
+ function tableToolbarPlacement(table, viewportWidth) {
1937
+ const width = Math.min(420, viewportWidth - 24);
1938
+ const left = Math.min(Math.max(12, table.left + (table.width - width) / 2), Math.max(12, viewportWidth - width - 12));
1939
+ return {
1940
+ top: `${table.top > ROOM_ABOVE$1 ? table.top - 52 : table.bottom + 12}px`,
1941
+ left: `${left}px`,
1942
+ maxWidth: `${width}px`
1943
+ };
1944
+ }
1945
+ /** The overlay covers the table exactly. */
1946
+ var tableOverlayPlacement = (table) => ({
1947
+ top: `${table.top}px`,
1948
+ left: `${table.left}px`,
1949
+ width: `${table.width}px`,
1950
+ height: `${table.height}px`
1951
+ });
1882
1952
  /** Whether a viewport this wide gets the editing dock rather than floating tools. */
1883
1953
  var usesMobileDock = (viewportWidth) => viewportWidth <= 767;
1884
1954
  /**
@@ -2485,6 +2555,71 @@ function createToolbarTooltip(onChange) {
2485
2555
  };
2486
2556
  }
2487
2557
  //#endregion
2558
+ //#region src/editor/code-language-picker.ts
2559
+ /** The code block holding the whole selection, or null when the selection is elsewhere or spans blocks. */
2560
+ function codeBlockAtSelection(editor) {
2561
+ const { $from, $to } = editor.state.selection;
2562
+ const block = $from.parent;
2563
+ if (block.type.name !== "codeBlock" || !$from.sameParent($to)) return null;
2564
+ const pos = $from.before();
2565
+ const element = editor.view.nodeDOM(pos);
2566
+ if (!(element instanceof HTMLElement)) return null;
2567
+ return {
2568
+ pos,
2569
+ language: typeof block.attrs.language === "string" && block.attrs.language ? block.attrs.language : "auto",
2570
+ text: block.textContent,
2571
+ element
2572
+ };
2573
+ }
2574
+ /**
2575
+ * The picker's choices: Auto, naming the detected language when there is one; a saved language the
2576
+ * catalog lacks, marked unavailable, so it stays selected; then every catalog language.
2577
+ */
2578
+ function codeLanguageChoices(language, detected, names) {
2579
+ return [
2580
+ {
2581
+ value: "auto",
2582
+ label: detected ? `Auto (${detected.toUpperCase()})` : "Auto"
2583
+ },
2584
+ ...language !== "auto" && !names.includes(language) ? [{
2585
+ value: language,
2586
+ label: `${language.toUpperCase()} (unavailable)`
2587
+ }] : [],
2588
+ ...names.map((name) => ({
2589
+ value: name,
2590
+ label: name.toUpperCase()
2591
+ }))
2592
+ ];
2593
+ }
2594
+ /** Space, in pixels, the picker needs above a code block; with less it sits below. */
2595
+ var ROOM_ABOVE = 76;
2596
+ /** Centers the picker over the code block, above it when there is room and below it otherwise. */
2597
+ function codeLanguagePickerPlacement(block) {
2598
+ const above = block.top > ROOM_ABOVE;
2599
+ return {
2600
+ top: `${above ? block.top - 8 : block.bottom + 8}px`,
2601
+ left: `${block.left + block.width / 2}px`,
2602
+ transform: `translate(-50%, ${above ? "-100%" : "0"})`
2603
+ };
2604
+ }
2605
+ /**
2606
+ * Saves `value` as the language of the code block at `pos`, as its own undo step; `auto` clears it.
2607
+ * @failure Returns false, and changes nothing, for a language outside `names` or no code block at `pos`.
2608
+ */
2609
+ function setCodeBlockLanguage(editor, pos, value, names) {
2610
+ const language = value === "auto" ? null : value;
2611
+ if (language !== null && !names.includes(language)) return false;
2612
+ const node = editor.state.doc.nodeAt(pos);
2613
+ if (node?.type.name !== "codeBlock") return false;
2614
+ editor.view.dispatch(closeHistory(editor.state.tr));
2615
+ editor.view.dispatch(editor.state.tr.setNodeMarkup(pos, void 0, {
2616
+ ...node.attrs,
2617
+ language
2618
+ }));
2619
+ editor.view.dispatch(closeHistory(editor.state.tr));
2620
+ return true;
2621
+ }
2622
+ //#endregion
2488
2623
  //#region src/editor/extensions/expand.ts
2489
2624
  /**
2490
2625
  * Reveals collapsed ancestors before a host navigates to a document position.
@@ -3389,23 +3524,6 @@ var EMPTY_DOCUMENT = {
3389
3524
  };
3390
3525
  var uploadProgressText = (progress) => progress && progress.total > 0 ? `Uploading image… ${Math.round(Math.min(1, progress.loaded / progress.total) * 100)}%` : "Uploading image…";
3391
3526
  var editorInstanceSequence = 0;
3392
- var EMPTY_CAPABILITIES = {
3393
- canToggleHeaderRow: false,
3394
- canToggleHeaderColumn: false,
3395
- canMoveRowUp: false,
3396
- canMoveRowDown: false,
3397
- canMoveColumnLeft: false,
3398
- canMoveColumnRight: false,
3399
- canAddRowBefore: false,
3400
- canAddRowAfter: false,
3401
- canAddColumnBefore: false,
3402
- canAddColumnAfter: false,
3403
- canDeleteRow: false,
3404
- canDeleteColumn: false,
3405
- canDeleteTable: false,
3406
- canMergeCells: false,
3407
- canSplitCell: false
3408
- };
3409
3527
  function slashMenuRows(items) {
3410
3528
  const groups = [];
3411
3529
  for (const [index, item] of items.entries()) {
@@ -3695,7 +3813,7 @@ var LoomaEditor = defineComponent({
3695
3813
  toolbarStyle: {},
3696
3814
  overlayStyle: {},
3697
3815
  menuStyle: {},
3698
- capabilities: { ...EMPTY_CAPABILITIES }
3816
+ capabilities: LOOMA_TABLE_CONTROLS_CLOSED.capabilities
3699
3817
  });
3700
3818
  const openChipEditor = (_instance, position) => {
3701
3819
  if (!chipEditing?.open(position)) return;
@@ -4114,16 +4232,10 @@ var LoomaEditor = defineComponent({
4114
4232
  if (session.setContent(value, lastFocusedSelection) && wasFocused) rememberSelection(instance);
4115
4233
  }, { deep: true });
4116
4234
  const closeTableUi = () => {
4117
- tableUi.toolbarOpen = false;
4118
- tableUi.overlayOpen = false;
4235
+ Object.assign(tableUi, LOOMA_TABLE_CONTROLS_CLOSED);
4119
4236
  tableUi.menuOpen = false;
4120
4237
  tableUi.menuScope = "cell";
4121
- tableUi.alignment = "left";
4122
- tableUi.background = null;
4123
- tableUi.headerRow = false;
4124
- tableUi.headerColumn = false;
4125
4238
  tableUi.geometry = null;
4126
- tableUi.capabilities = { ...EMPTY_CAPABILITIES };
4127
4239
  overlayTableElement = null;
4128
4240
  mobileDock.tableInactive();
4129
4241
  };
@@ -4145,63 +4257,31 @@ var LoomaEditor = defineComponent({
4145
4257
  let codeBlurTimer;
4146
4258
  const updateCodeUi = (focusSettled = false) => {
4147
4259
  const instance = editor.value?.isDestroyed ? void 0 : editor.value;
4148
- const selection = instance?.state.selection;
4149
- const block = selection?.$from.parent;
4150
- const dom = instance && block?.type.name === "codeBlock" && selection.$from.sameParent(selection.$to) ? instance.view.nodeDOM(selection.$from.before()) : null;
4151
- if (!instance || !props.editable || !codeLanguageNames.length || !(dom instanceof HTMLElement)) {
4260
+ const block = instance ? codeBlockAtSelection(instance) : null;
4261
+ if (!instance || !props.editable || !codeLanguageNames.length || !block) {
4152
4262
  codeUi.open = false;
4153
4263
  return;
4154
4264
  }
4155
- loadForCodeBlock(instance, block.attrs.language, block.textContent);
4265
+ loadForCodeBlock(instance, block.language === "auto" ? null : block.language, block.text);
4156
4266
  if (!instance.isFocused && !codeLanguageShell.value?.contains(document.activeElement)) {
4157
4267
  clearTimeout(codeBlurTimer);
4158
4268
  if (codeUi.open && !focusSettled) codeBlurTimer = setTimeout(() => updateCodeUi(true), 0);
4159
4269
  else codeUi.open = false;
4160
4270
  return;
4161
4271
  }
4162
- const rect = dom.getBoundingClientRect();
4163
- const above = rect.top > 76;
4164
4272
  codeUi.open = true;
4165
- codeUi.pos = selection.$from.before();
4166
- codeUi.language = typeof block.attrs.language === "string" && block.attrs.language ? block.attrs.language : "auto";
4167
- codeUi.text = block.textContent;
4168
- codeUi.style = {
4169
- top: `${above ? rect.top - 8 : rect.bottom + 8}px`,
4170
- left: `${rect.left + rect.width / 2}px`,
4171
- transform: `translate(-50%, ${above ? "-100%" : "0"})`
4172
- };
4273
+ codeUi.pos = block.pos;
4274
+ codeUi.language = block.language;
4275
+ codeUi.text = block.text;
4276
+ codeUi.style = codeLanguagePickerPlacement(block.element.getBoundingClientRect());
4173
4277
  };
4174
4278
  const codeLanguageOptions = computed(() => {
4175
4279
  codeUi.grammarVersion;
4176
4280
  const detected = codeUi.open && codeUi.text.trim() ? codeHighlighting.lowlight.highlightAuto(codeUi.text).data?.language : null;
4177
- return [
4178
- {
4179
- value: "auto",
4180
- label: typeof detected === "string" ? `Auto (${detected.toUpperCase()})` : "Auto"
4181
- },
4182
- ...codeUi.language !== "auto" && !codeLanguageNames.includes(codeUi.language) ? [{
4183
- value: codeUi.language,
4184
- label: `${codeUi.language.toUpperCase()} (unavailable)`
4185
- }] : [],
4186
- ...codeLanguageNames.map((name) => ({
4187
- value: name,
4188
- label: name.toUpperCase()
4189
- }))
4190
- ];
4281
+ return codeLanguageChoices(codeUi.language, typeof detected === "string" ? detected : null, codeLanguageNames);
4191
4282
  });
4192
4283
  const chooseCodeLanguage = (value) => {
4193
- const instance = editor.value;
4194
- if (!instance) return;
4195
- const language = value === "auto" ? null : value;
4196
- if (language !== null && !codeLanguageNames.includes(language)) return;
4197
- const node = instance.state.doc.nodeAt(codeUi.pos);
4198
- if (node?.type.name !== "codeBlock") return;
4199
- instance.view.dispatch(closeHistory(instance.state.tr));
4200
- instance.view.dispatch(instance.state.tr.setNodeMarkup(codeUi.pos, void 0, {
4201
- ...node.attrs,
4202
- language
4203
- }));
4204
- instance.view.dispatch(closeHistory(instance.state.tr));
4284
+ if (editor.value) setCodeBlockLanguage(editor.value, codeUi.pos, value, codeLanguageNames);
4205
4285
  };
4206
4286
  const onCodeTransaction = () => updateCodeUi();
4207
4287
  watch(() => props.editable, () => nextTick(onCodeTransaction));
@@ -4230,31 +4310,14 @@ var LoomaEditor = defineComponent({
4230
4310
  overlayTableElement = table;
4231
4311
  if (selectedTableIsActive) mobileDock.tableActive();
4232
4312
  const rect = table.getBoundingClientRect();
4233
- const maxToolbarWidth = Math.min(420, window.innerWidth - 24);
4234
- const toolbarLeft = Math.min(Math.max(12, rect.left + (rect.width - maxToolbarWidth) / 2), Math.max(12, window.innerWidth - maxToolbarWidth - 12));
4235
- tableUi.toolbarOpen = selectedTableIsActive && state.showToolbar && !mobile.value;
4236
- tableUi.overlayOpen = !mobile.value;
4237
- tableUi.alignment = selectedTableIsActive ? state.cellAlignment : "left";
4238
- tableUi.background = selectedTableIsActive ? state.cellBackground : null;
4239
- tableUi.headerRow = selectedTableIsActive && state.headerRow;
4240
- tableUi.headerColumn = selectedTableIsActive && state.headerColumn;
4241
- tableUi.capabilities = selectedTableIsActive ? state.capabilities : { ...EMPTY_CAPABILITIES };
4313
+ Object.assign(tableUi, tableControls(state, selectedTableIsActive, mobile.value));
4242
4314
  const selectedCell = selectedTableIsActive ? selectedTableCellElement(instance) : null;
4243
4315
  const geometry = measureTableOverlayGeometry(table, selectedCell, hoveredTableCell && table.contains(hoveredTableCell) ? hoveredTableCell : null);
4244
4316
  tableUi.rows = Math.max(0, geometry.rowBoundaries.length - 1);
4245
4317
  tableUi.cols = Math.max(0, geometry.columnBoundaries.length - 1);
4246
4318
  tableUi.geometry = geometry;
4247
- tableUi.toolbarStyle = {
4248
- top: `${rect.top > 76 ? rect.top - 52 : rect.bottom + 12}px`,
4249
- left: `${toolbarLeft}px`,
4250
- maxWidth: `${maxToolbarWidth}px`
4251
- };
4252
- tableUi.overlayStyle = {
4253
- top: `${rect.top}px`,
4254
- left: `${rect.left}px`,
4255
- width: `${rect.width}px`,
4256
- height: `${rect.height}px`
4257
- };
4319
+ tableUi.toolbarStyle = tableToolbarPlacement(rect, window.innerWidth);
4320
+ tableUi.overlayStyle = tableOverlayPlacement(rect);
4258
4321
  };
4259
4322
  const onEditorFocus = () => {
4260
4323
  editorFocused.value = true;