@threadlabs/looma 0.26.11 → 0.26.13

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": "@threadlabs/looma",
3
- "version": "0.26.11",
3
+ "version": "0.26.13",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -57,7 +57,12 @@
57
57
  "./tokens.css": "./tokens.css",
58
58
  "./theme-light.css": "./theme-light.css",
59
59
  "./theme-dark.css": "./theme-dark.css",
60
- "./theme-high-contrast.css": "./theme-high-contrast.css"
60
+ "./theme-high-contrast.css": "./theme-high-contrast.css",
61
+ "./vue/editor/compact": {
62
+ "types": "./vue/editor/compact/index.d.ts",
63
+ "import": "./vue/editor/compact/index.js",
64
+ "default": "./vue/editor/compact/index.js"
65
+ }
61
66
  },
62
67
  "files": [
63
68
  "dist/",
@@ -9957,6 +9957,45 @@ html[data-ui-input-modality="touch"]
9957
9957
  [data-component~="ui-tree-item"][hidden][data-v-8f2cc947] {
9958
9958
  display: none;
9959
9959
  }
9960
+ /* Short-form field primitive: the same field tokens as Textarea. */
9961
+ .looma-compact-editor {
9962
+ min-inline-size: 0;
9963
+ box-sizing: border-box;
9964
+ min-block-size: calc(4.5em + 2 * var(--ui-border-width));
9965
+ border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
9966
+ border-radius: var(--ui-field-radius, var(--ui-radius-md));
9967
+ background: var(--ui-control-surface, var(--ui-surface-elevated));
9968
+ color: var(--ui-text-primary);
9969
+ box-shadow: var(--ui-control-inset);
9970
+ }
9971
+ .looma-compact-editor:focus-within {
9972
+ outline: var(--ui-focus-ring-width, 2px) solid var(--ui-focus-ring, var(--ui-accent-solid));
9973
+ outline-offset: var(--ui-focus-ring-offset, 2px);
9974
+ }
9975
+ .looma-compact-editor .ProseMirror {
9976
+ box-sizing: border-box;
9977
+ min-block-size: 4.5em;
9978
+ max-block-size: 12em;
9979
+ overflow-y: auto;
9980
+ padding: var(--ui-space-2) var(--ui-space-3);
9981
+ outline: none;
9982
+ }
9983
+ .looma-compact-editor, .looma-compact-content {
9984
+ font-size: var(--ui-font-size-sm);
9985
+ font-weight: var(--ui-font-normal, 400);
9986
+ line-height: var(--ui-line-height-md);
9987
+ overflow-wrap: anywhere;
9988
+ }
9989
+ .looma-compact-editor p, .looma-compact-content p { margin: 0; }
9990
+ .looma-compact-editor p + p, .looma-compact-content p + p { margin-block-start: var(--ui-space-2); }
9991
+ .looma-compact-editor strong, .looma-compact-content strong { font-weight: var(--ui-font-bold); }
9992
+ .looma-compact-editor code, .looma-compact-content code {
9993
+ font-family: var(--ui-font-mono);
9994
+ padding-inline: var(--ui-space-1);
9995
+ border-radius: var(--ui-radius-sm);
9996
+ background: var(--ui-surface-muted);
9997
+ }
9998
+ @media (pointer: coarse) { .looma-compact-editor { font-size: var(--ui-font-size-md); } }
9960
9999
  /* The editor surface owns its renderer canvas. Existing hooks retain their defaults. */
9961
10000
  [data-component~="ui-editor-surface"] .ProseMirror {
9962
10001
  min-height: var(--ui-editor-content-min-height, var(--_ui-editor-content-min-height, 300px));
@@ -0,0 +1,3 @@
1
+ import type { AnyExtension } from "@tiptap/core";
2
+ /** Minimal short-form schema; additional consumer extensions are opt-in. */
3
+ export declare function getCompactEditorExtensions(): AnyExtension[];
@@ -0,0 +1,84 @@
1
+ import { type PropType } from "vue";
2
+ import type { AnyExtension, JSONContent } from "@tiptap/core";
3
+ import { Editor } from "@tiptap/vue-3";
4
+ import "./compact-editor.css";
5
+ export { getCompactEditorExtensions } from "./extensions.js";
6
+ /**
7
+ * Toolbar-free editing for short rich text. Owns editor lifecycle and standard
8
+ * formatting/history shortcuts; the host owns persistence and optional extensions.
9
+ * Importing this entry is SSR-safe and does not load the full document editor.
10
+ * @ownership The component owns the editor; callers own content and extensions.
11
+ * @lifecycle Mount creates one editor; unmount destroys it and prop watchers.
12
+ * @failure Persistence failures are host-owned; the controlled document can restore input.
13
+ */
14
+ export declare const LoomaCompactEditor: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
15
+ modelValue: {
16
+ type: PropType<JSONContent>;
17
+ default: () => JSONContent;
18
+ };
19
+ label: {
20
+ type: StringConstructor;
21
+ default: string;
22
+ };
23
+ id: {
24
+ type: StringConstructor;
25
+ default: string;
26
+ };
27
+ editable: {
28
+ type: BooleanConstructor;
29
+ default: boolean;
30
+ };
31
+ extensions: {
32
+ type: PropType<AnyExtension[]>;
33
+ default: () => never[];
34
+ };
35
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
36
+ [key: string]: any;
37
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
38
+ "update:modelValue": (_value: JSONContent) => true;
39
+ ready: (_editor: Editor) => true;
40
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
41
+ modelValue: {
42
+ type: PropType<JSONContent>;
43
+ default: () => JSONContent;
44
+ };
45
+ label: {
46
+ type: StringConstructor;
47
+ default: string;
48
+ };
49
+ id: {
50
+ type: StringConstructor;
51
+ default: string;
52
+ };
53
+ editable: {
54
+ type: BooleanConstructor;
55
+ default: boolean;
56
+ };
57
+ extensions: {
58
+ type: PropType<AnyExtension[]>;
59
+ default: () => never[];
60
+ };
61
+ }>> & Readonly<{
62
+ "onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
63
+ onReady?: ((_editor: Editor) => any) | undefined;
64
+ }>, {
65
+ label: string;
66
+ id: string;
67
+ modelValue: JSONContent;
68
+ editable: boolean;
69
+ extensions: AnyExtension[];
70
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
71
+ /** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
72
+ export declare const LoomaCompactContent: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
73
+ content: {
74
+ type: PropType<JSONContent>;
75
+ required: true;
76
+ };
77
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
78
+ [key: string]: any;
79
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
80
+ content: {
81
+ type: PropType<JSONContent>;
82
+ required: true;
83
+ };
84
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,517 @@
1
+ import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
2
+ import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
3
+ import { Editor, EditorContent } from "@tiptap/vue-3";
4
+ import { history, redo, undo } from "@tiptap/pm/history";
5
+ //#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
6
+ /**
7
+ * The default document node which represents the top level node of the editor.
8
+ * @see https://tiptap.dev/api/nodes/document
9
+ */
10
+ var Document = Node.create({
11
+ name: "doc",
12
+ topNode: true,
13
+ content: "block+"
14
+ });
15
+ //#endregion
16
+ //#region ../../node_modules/.pnpm/@tiptap+extension-paragraph@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-paragraph/dist/index.js
17
+ /**
18
+ * This extension allows you to create paragraphs.
19
+ * @see https://www.tiptap.dev/api/nodes/paragraph
20
+ */
21
+ var Paragraph = Node.create({
22
+ name: "paragraph",
23
+ priority: 1e3,
24
+ addOptions() {
25
+ return { HTMLAttributes: {} };
26
+ },
27
+ group: "block",
28
+ content: "inline*",
29
+ parseHTML() {
30
+ return [{ tag: "p" }];
31
+ },
32
+ renderHTML({ HTMLAttributes }) {
33
+ return [
34
+ "p",
35
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
36
+ 0
37
+ ];
38
+ },
39
+ addCommands() {
40
+ return { setParagraph: () => ({ commands }) => {
41
+ return commands.setNode(this.name);
42
+ } };
43
+ },
44
+ addKeyboardShortcuts() {
45
+ return { "Mod-Alt-0": () => this.editor.commands.setParagraph() };
46
+ }
47
+ });
48
+ //#endregion
49
+ //#region ../../node_modules/.pnpm/@tiptap+extension-text@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-text/dist/index.js
50
+ /**
51
+ * This extension allows you to create text nodes.
52
+ * @see https://www.tiptap.dev/api/nodes/text
53
+ */
54
+ var Text = Node.create({
55
+ name: "text",
56
+ group: "inline"
57
+ });
58
+ //#endregion
59
+ //#region ../../node_modules/.pnpm/@tiptap+extension-bold@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-bold/dist/index.js
60
+ /**
61
+ * Matches bold text via `**` as input.
62
+ */
63
+ var starInputRegex$1 = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
64
+ /**
65
+ * Matches bold text via `**` while pasting.
66
+ */
67
+ var starPasteRegex$1 = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
68
+ /**
69
+ * Matches bold text via `__` as input.
70
+ */
71
+ var underscoreInputRegex$1 = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
72
+ /**
73
+ * Matches bold text via `__` while pasting.
74
+ */
75
+ var underscorePasteRegex$1 = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
76
+ /**
77
+ * This extension allows you to mark text as bold.
78
+ * @see https://tiptap.dev/api/marks/bold
79
+ */
80
+ var Bold = Mark.create({
81
+ name: "bold",
82
+ addOptions() {
83
+ return { HTMLAttributes: {} };
84
+ },
85
+ parseHTML() {
86
+ return [
87
+ { tag: "strong" },
88
+ {
89
+ tag: "b",
90
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
91
+ },
92
+ {
93
+ style: "font-weight=400",
94
+ clearMark: (mark) => mark.type.name === this.name
95
+ },
96
+ {
97
+ style: "font-weight",
98
+ getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
99
+ }
100
+ ];
101
+ },
102
+ renderHTML({ HTMLAttributes }) {
103
+ return [
104
+ "strong",
105
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
106
+ 0
107
+ ];
108
+ },
109
+ addCommands() {
110
+ return {
111
+ setBold: () => ({ commands }) => {
112
+ return commands.setMark(this.name);
113
+ },
114
+ toggleBold: () => ({ commands }) => {
115
+ return commands.toggleMark(this.name);
116
+ },
117
+ unsetBold: () => ({ commands }) => {
118
+ return commands.unsetMark(this.name);
119
+ }
120
+ };
121
+ },
122
+ addKeyboardShortcuts() {
123
+ return {
124
+ "Mod-b": () => this.editor.commands.toggleBold(),
125
+ "Mod-B": () => this.editor.commands.toggleBold()
126
+ };
127
+ },
128
+ addInputRules() {
129
+ return [markInputRule({
130
+ find: starInputRegex$1,
131
+ type: this.type
132
+ }), markInputRule({
133
+ find: underscoreInputRegex$1,
134
+ type: this.type
135
+ })];
136
+ },
137
+ addPasteRules() {
138
+ return [markPasteRule({
139
+ find: starPasteRegex$1,
140
+ type: this.type
141
+ }), markPasteRule({
142
+ find: underscorePasteRegex$1,
143
+ type: this.type
144
+ })];
145
+ }
146
+ });
147
+ //#endregion
148
+ //#region ../../node_modules/.pnpm/@tiptap+extension-italic@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-italic/dist/index.js
149
+ /**
150
+ * Matches an italic to a *italic* on input.
151
+ */
152
+ var starInputRegex = /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/;
153
+ /**
154
+ * Matches an italic to a *italic* on paste.
155
+ */
156
+ var starPasteRegex = /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))/g;
157
+ /**
158
+ * Matches an italic to a _italic_ on input.
159
+ */
160
+ var underscoreInputRegex = /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))$/;
161
+ /**
162
+ * Matches an italic to a _italic_ on paste.
163
+ */
164
+ var underscorePasteRegex = /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))/g;
165
+ /**
166
+ * This extension allows you to create italic text.
167
+ * @see https://www.tiptap.dev/api/marks/italic
168
+ */
169
+ var Italic = Mark.create({
170
+ name: "italic",
171
+ addOptions() {
172
+ return { HTMLAttributes: {} };
173
+ },
174
+ parseHTML() {
175
+ return [
176
+ { tag: "em" },
177
+ {
178
+ tag: "i",
179
+ getAttrs: (node) => node.style.fontStyle !== "normal" && null
180
+ },
181
+ {
182
+ style: "font-style=normal",
183
+ clearMark: (mark) => mark.type.name === this.name
184
+ },
185
+ { style: "font-style=italic" }
186
+ ];
187
+ },
188
+ renderHTML({ HTMLAttributes }) {
189
+ return [
190
+ "em",
191
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
192
+ 0
193
+ ];
194
+ },
195
+ addCommands() {
196
+ return {
197
+ setItalic: () => ({ commands }) => {
198
+ return commands.setMark(this.name);
199
+ },
200
+ toggleItalic: () => ({ commands }) => {
201
+ return commands.toggleMark(this.name);
202
+ },
203
+ unsetItalic: () => ({ commands }) => {
204
+ return commands.unsetMark(this.name);
205
+ }
206
+ };
207
+ },
208
+ addKeyboardShortcuts() {
209
+ return {
210
+ "Mod-i": () => this.editor.commands.toggleItalic(),
211
+ "Mod-I": () => this.editor.commands.toggleItalic()
212
+ };
213
+ },
214
+ addInputRules() {
215
+ return [markInputRule({
216
+ find: starInputRegex,
217
+ type: this.type
218
+ }), markInputRule({
219
+ find: underscoreInputRegex,
220
+ type: this.type
221
+ })];
222
+ },
223
+ addPasteRules() {
224
+ return [markPasteRule({
225
+ find: starPasteRegex,
226
+ type: this.type
227
+ }), markPasteRule({
228
+ find: underscorePasteRegex,
229
+ type: this.type
230
+ })];
231
+ }
232
+ });
233
+ //#endregion
234
+ //#region ../../node_modules/.pnpm/@tiptap+extension-code@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-code/dist/index.js
235
+ /**
236
+ * Regular expressions to match inline code blocks enclosed in backticks.
237
+ * It matches:
238
+ * - An opening backtick, followed by
239
+ * - Any text that doesn't include a backtick (captured for marking), followed by
240
+ * - A closing backtick.
241
+ * This ensures that any text between backticks is formatted as code,
242
+ * regardless of the surrounding characters (exception being another backtick).
243
+ */
244
+ var inputRegex = /(^|[^`])`([^`]+)`(?!`)/;
245
+ /**
246
+ * Matches inline code while pasting.
247
+ */
248
+ var pasteRegex = /(^|[^`])`([^`]+)`(?!`)/g;
249
+ /**
250
+ * This extension allows you to mark text as inline code.
251
+ * @see https://tiptap.dev/api/marks/code
252
+ */
253
+ var Code = Mark.create({
254
+ name: "code",
255
+ addOptions() {
256
+ return { HTMLAttributes: {} };
257
+ },
258
+ excludes: "_",
259
+ code: true,
260
+ exitable: true,
261
+ parseHTML() {
262
+ return [{ tag: "code" }];
263
+ },
264
+ renderHTML({ HTMLAttributes }) {
265
+ return [
266
+ "code",
267
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
268
+ 0
269
+ ];
270
+ },
271
+ addCommands() {
272
+ return {
273
+ setCode: () => ({ commands }) => {
274
+ return commands.setMark(this.name);
275
+ },
276
+ toggleCode: () => ({ commands }) => {
277
+ return commands.toggleMark(this.name);
278
+ },
279
+ unsetCode: () => ({ commands }) => {
280
+ return commands.unsetMark(this.name);
281
+ }
282
+ };
283
+ },
284
+ addKeyboardShortcuts() {
285
+ return { "Mod-e": () => this.editor.commands.toggleCode() };
286
+ },
287
+ addInputRules() {
288
+ return [markInputRule({
289
+ find: inputRegex,
290
+ type: this.type
291
+ })];
292
+ },
293
+ addPasteRules() {
294
+ return [markPasteRule({
295
+ find: pasteRegex,
296
+ type: this.type
297
+ })];
298
+ }
299
+ });
300
+ //#endregion
301
+ //#region ../../node_modules/.pnpm/@tiptap+extension-hard-break@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-hard-break/dist/index.js
302
+ /**
303
+ * This extension allows you to insert hard breaks.
304
+ * @see https://www.tiptap.dev/api/nodes/hard-break
305
+ */
306
+ var HardBreak = Node.create({
307
+ name: "hardBreak",
308
+ addOptions() {
309
+ return {
310
+ keepMarks: true,
311
+ HTMLAttributes: {}
312
+ };
313
+ },
314
+ inline: true,
315
+ group: "inline",
316
+ selectable: false,
317
+ linebreakReplacement: true,
318
+ parseHTML() {
319
+ return [{ tag: "br" }];
320
+ },
321
+ renderHTML({ HTMLAttributes }) {
322
+ return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
323
+ },
324
+ renderText() {
325
+ return "\n";
326
+ },
327
+ addCommands() {
328
+ return { setHardBreak: () => ({ commands, chain, state, editor }) => {
329
+ return commands.first([() => commands.exitCode(), () => commands.command(() => {
330
+ const { selection, storedMarks } = state;
331
+ if (selection.$from.parent.type.spec.isolating) return false;
332
+ const { keepMarks } = this.options;
333
+ const { splittableMarks } = editor.extensionManager;
334
+ const marks = storedMarks || selection.$to.parentOffset && selection.$from.marks();
335
+ return chain().insertContent({ type: this.name }).command(({ tr, dispatch }) => {
336
+ if (dispatch && marks && keepMarks) {
337
+ const filteredMarks = marks.filter((mark) => splittableMarks.includes(mark.type.name));
338
+ tr.ensureMarks(filteredMarks);
339
+ }
340
+ return true;
341
+ }).run();
342
+ })]);
343
+ } };
344
+ },
345
+ addKeyboardShortcuts() {
346
+ return {
347
+ "Mod-Enter": () => this.editor.commands.setHardBreak(),
348
+ "Shift-Enter": () => this.editor.commands.setHardBreak()
349
+ };
350
+ }
351
+ });
352
+ //#endregion
353
+ //#region ../../node_modules/.pnpm/@tiptap+extension-history@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/@tiptap/extension-history/dist/index.js
354
+ /**
355
+ * This extension allows you to undo and redo recent changes.
356
+ * @see https://www.tiptap.dev/api/extensions/history
357
+ *
358
+ * **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove
359
+ * the `history` extension, as it is not compatible with the `collaboration` extension.
360
+ *
361
+ * `@tiptap/extension-collaboration` uses its own history implementation.
362
+ */
363
+ var History = Extension.create({
364
+ name: "history",
365
+ addOptions() {
366
+ return {
367
+ depth: 100,
368
+ newGroupDelay: 500
369
+ };
370
+ },
371
+ addCommands() {
372
+ return {
373
+ undo: () => ({ state, dispatch }) => {
374
+ return undo(state, dispatch);
375
+ },
376
+ redo: () => ({ state, dispatch }) => {
377
+ return redo(state, dispatch);
378
+ }
379
+ };
380
+ },
381
+ addProseMirrorPlugins() {
382
+ return [history(this.options)];
383
+ },
384
+ addKeyboardShortcuts() {
385
+ return {
386
+ "Mod-z": () => this.editor.commands.undo(),
387
+ "Shift-Mod-z": () => this.editor.commands.redo(),
388
+ "Mod-y": () => this.editor.commands.redo(),
389
+ "Mod-я": () => this.editor.commands.undo(),
390
+ "Shift-Mod-я": () => this.editor.commands.redo()
391
+ };
392
+ }
393
+ });
394
+ //#endregion
395
+ //#region src/vue/editor/compact/extensions.ts
396
+ /** Minimal short-form schema; additional consumer extensions are opt-in. */
397
+ function getCompactEditorExtensions() {
398
+ return [
399
+ Document,
400
+ Paragraph,
401
+ Text,
402
+ Bold,
403
+ Italic,
404
+ Code,
405
+ HardBreak,
406
+ History
407
+ ];
408
+ }
409
+ //#endregion
410
+ //#region src/vue/editor/compact/index.ts
411
+ var emptyDocument = () => ({
412
+ type: "doc",
413
+ content: [{ type: "paragraph" }]
414
+ });
415
+ /**
416
+ * Toolbar-free editing for short rich text. Owns editor lifecycle and standard
417
+ * formatting/history shortcuts; the host owns persistence and optional extensions.
418
+ * Importing this entry is SSR-safe and does not load the full document editor.
419
+ * @ownership The component owns the editor; callers own content and extensions.
420
+ * @lifecycle Mount creates one editor; unmount destroys it and prop watchers.
421
+ * @failure Persistence failures are host-owned; the controlled document can restore input.
422
+ */
423
+ var LoomaCompactEditor = defineComponent({
424
+ name: "LoomaCompactEditor",
425
+ inheritAttrs: false,
426
+ props: {
427
+ modelValue: {
428
+ type: Object,
429
+ default: emptyDocument
430
+ },
431
+ label: {
432
+ type: String,
433
+ default: "Message"
434
+ },
435
+ id: {
436
+ type: String,
437
+ default: ""
438
+ },
439
+ editable: {
440
+ type: Boolean,
441
+ default: true
442
+ },
443
+ extensions: {
444
+ type: Array,
445
+ default: () => []
446
+ }
447
+ },
448
+ emits: {
449
+ "update:modelValue": (_value) => true,
450
+ ready: (_editor) => true
451
+ },
452
+ setup(props, { attrs, emit, expose }) {
453
+ const editor = shallowRef();
454
+ onMounted(() => {
455
+ editor.value = new Editor({
456
+ extensions: [...getCompactEditorExtensions(), ...props.extensions],
457
+ content: props.modelValue,
458
+ editable: props.editable,
459
+ editorProps: { attributes: () => ({
460
+ role: "textbox",
461
+ "aria-multiline": "true",
462
+ "aria-label": props.label,
463
+ "aria-readonly": String(!props.editable),
464
+ id: props.id
465
+ }) },
466
+ onUpdate: ({ editor }) => emit("update:modelValue", editor.getJSON())
467
+ });
468
+ emit("ready", editor.value);
469
+ });
470
+ watch(() => props.modelValue, (value) => {
471
+ if (editor.value && JSON.stringify(value) !== JSON.stringify(editor.value.getJSON())) editor.value.commands.setContent(value, false);
472
+ }, { deep: true });
473
+ watch(() => [
474
+ props.id,
475
+ props.label,
476
+ props.editable
477
+ ], () => editor.value?.setEditable(props.editable, false));
478
+ onBeforeUnmount(() => editor.value?.destroy());
479
+ expose({
480
+ focus: () => editor.value?.commands.focus(),
481
+ getEditor: () => editor.value
482
+ });
483
+ return () => h("div", {
484
+ ...attrs,
485
+ id: void 0,
486
+ class: ["looma-compact-editor", attrs.class]
487
+ }, [editor.value ? h(EditorContent, { editor: editor.value }) : null]);
488
+ }
489
+ });
490
+ function renderNode(node) {
491
+ if (node.type === "hardBreak") return h("br");
492
+ if (node.type === "text") {
493
+ let text = node.text ?? "";
494
+ for (const mark of node.marks ?? []) {
495
+ const tag = {
496
+ bold: "strong",
497
+ italic: "em",
498
+ code: "code"
499
+ }[mark.type];
500
+ if (tag) text = h(tag, {}, [text]);
501
+ }
502
+ return text;
503
+ }
504
+ const children = (node.content ?? []).map(renderNode);
505
+ return node.type === "paragraph" ? h("p", {}, children) : children;
506
+ }
507
+ /** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
508
+ var LoomaCompactContent = defineComponent({
509
+ name: "LoomaCompactContent",
510
+ props: { content: {
511
+ type: Object,
512
+ required: true
513
+ } },
514
+ setup: (props) => () => h("div", { class: "looma-compact-content" }, [renderNode(props.content)])
515
+ });
516
+ //#endregion
517
+ export { LoomaCompactContent, LoomaCompactEditor, getCompactEditorExtensions };