@lapismd/mira-react 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,2169 @@
1
+ // src/mira.tsx
2
+ import {
3
+ createMiraCodeMirrorExtensions,
4
+ createMiraEditorController,
5
+ openImageFilePicker
6
+ } from "@lapismd/mira/core";
7
+ import {
8
+ executeMiraCommand,
9
+ isMiraCommandEnabled,
10
+ mountMiraExtensionStyles,
11
+ resolveMiraExtensions
12
+ } from "@lapismd/mira/extensions";
13
+ import {
14
+ forwardRef,
15
+ useCallback,
16
+ useEffect as useEffect2,
17
+ useImperativeHandle,
18
+ useMemo,
19
+ useRef as useRef3,
20
+ useState
21
+ } from "react";
22
+
23
+ // src/hooks.ts
24
+ import { useLayoutEffect, useRef } from "react";
25
+ function useLatestRef(value) {
26
+ const ref = useRef(value);
27
+ useLayoutEffect(() => {
28
+ ref.current = value;
29
+ });
30
+ return ref;
31
+ }
32
+ function cx(...values) {
33
+ return values.filter(Boolean).join(" ");
34
+ }
35
+
36
+ // src/appearance.ts
37
+ function normalizeMiraTheme(theme) {
38
+ return theme?.trim() ? theme : void 0;
39
+ }
40
+ function miraColorModeAttribute(colorMode) {
41
+ return colorMode === "inherit" ? void 0 : colorMode;
42
+ }
43
+ function miraColorModeClassName(colorMode) {
44
+ if (colorMode === "dark") return "dark theme-dark";
45
+ if (colorMode === "light") return "light theme-light";
46
+ return void 0;
47
+ }
48
+
49
+ // src/preview-host.tsx
50
+ import { useEffect, useRef as useRef2 } from "react";
51
+ import { mount, unmount } from "svelte";
52
+ import { MarkdownOutline, MarkdownPreview } from "@lapismd/mira/preview";
53
+ import { jsx, jsxs } from "react/jsx-runtime";
54
+ function MarkdownPreviewHost({
55
+ assetResolver,
56
+ extensions = [],
57
+ fileAdapter,
58
+ frontmatterConfig,
59
+ frontmatterOpen = true,
60
+ headingIds = false,
61
+ headingIdPrefix = "",
62
+ htmlPolicy = "trusted",
63
+ emoji = false,
64
+ outline = false,
65
+ outlineVariant = "floating",
66
+ linkResolver,
67
+ onChange,
68
+ onFrontmatterChange,
69
+ sourcePath,
70
+ value
71
+ }) {
72
+ const hostRef = useRef2(null);
73
+ const outlineRef = useRef2(null);
74
+ useEffect(() => {
75
+ if (!hostRef.current) {
76
+ return;
77
+ }
78
+ const component = mount(MarkdownPreview, {
79
+ target: hostRef.current,
80
+ props: {
81
+ assetResolver,
82
+ class: "markdown-reading-view",
83
+ extensions,
84
+ fileAdapter,
85
+ frontmatterConfig,
86
+ frontmatterOpen,
87
+ headingIds,
88
+ headingIdPrefix,
89
+ htmlPolicy,
90
+ emoji,
91
+ linkResolver,
92
+ onChange,
93
+ onFrontmatterChange,
94
+ sourcePath,
95
+ value
96
+ }
97
+ });
98
+ return () => {
99
+ unmount(component);
100
+ };
101
+ }, [
102
+ assetResolver,
103
+ extensions,
104
+ fileAdapter,
105
+ frontmatterConfig,
106
+ frontmatterOpen,
107
+ headingIds,
108
+ headingIdPrefix,
109
+ htmlPolicy,
110
+ emoji,
111
+ linkResolver,
112
+ onChange,
113
+ onFrontmatterChange,
114
+ sourcePath,
115
+ value
116
+ ]);
117
+ useEffect(() => {
118
+ if (!outline || !outlineRef.current) {
119
+ return;
120
+ }
121
+ const component = mount(MarkdownOutline, {
122
+ target: outlineRef.current,
123
+ props: {
124
+ headingIdPrefix,
125
+ root: hostRef.current,
126
+ variant: outlineVariant,
127
+ value
128
+ }
129
+ });
130
+ return () => {
131
+ unmount(component);
132
+ };
133
+ }, [headingIdPrefix, outline, outlineVariant, value]);
134
+ return /* @__PURE__ */ jsxs(
135
+ "div",
136
+ {
137
+ className: `mira-react-preview-host ${outline ? `mira-react-preview-host--outline-${outlineVariant}` : ""}`.trim(),
138
+ children: [
139
+ /* @__PURE__ */ jsx("div", { className: "mira-react-preview-host__preview", ref: hostRef }),
140
+ outline ? /* @__PURE__ */ jsx("div", { className: "mira-react-preview-host__outline", ref: outlineRef }) : null
141
+ ]
142
+ }
143
+ );
144
+ }
145
+
146
+ // src/mira.tsx
147
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
148
+ var defaultMode = "live-preview";
149
+ var Mira = forwardRef(function Mira2({
150
+ assetResolver,
151
+ authoring,
152
+ className,
153
+ defaultMode: defaultModeProp = defaultMode,
154
+ defaultReadonly = false,
155
+ defaultValue = "",
156
+ extensions = [],
157
+ fileAdapter,
158
+ imageConfig,
159
+ frontmatterOpen = true,
160
+ frontmatterConfig,
161
+ headingIds = false,
162
+ headingIdPrefix = "",
163
+ htmlPolicy = "trusted",
164
+ emoji = false,
165
+ outline = false,
166
+ outlineVariant = "floating",
167
+ blockControls = false,
168
+ indentGuides = true,
169
+ indentWithTabs = true,
170
+ indentWidth = 4,
171
+ lineWrapping = true,
172
+ linkResolver,
173
+ mode: modeProp,
174
+ onChange,
175
+ onFrontmatterChange,
176
+ onModeChange,
177
+ onReadonlyChange,
178
+ placeholder = "Start writing Markdown...",
179
+ readonly: readonlyProp,
180
+ sourcePath,
181
+ spellcheck = true,
182
+ theme,
183
+ colorMode = "inherit",
184
+ toolbar = true,
185
+ value: valueProp
186
+ }, ref) {
187
+ const editorHostRef = useRef3(null);
188
+ const previewPaneRef = useRef3(null);
189
+ const rootRef = useRef3(null);
190
+ const controllerRef = useRef3(null);
191
+ const cleanupExtensionMountsRef = useRef3([]);
192
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
193
+ const [uncontrolledMode, setUncontrolledMode] = useState(defaultModeProp);
194
+ const [uncontrolledReadonly, setUncontrolledReadonly] = useState(defaultReadonly);
195
+ const value = valueProp ?? uncontrolledValue;
196
+ const mode = modeProp ?? uncontrolledMode;
197
+ const readonly = readonlyProp ?? uncontrolledReadonly;
198
+ const valueRef = useLatestRef(value);
199
+ const modeRef = useLatestRef(mode);
200
+ const readonlyRef = useLatestRef(readonly);
201
+ const onChangeRef = useLatestRef(onChange);
202
+ const onModeChangeRef = useLatestRef(onModeChange);
203
+ const onReadonlyChangeRef = useLatestRef(onReadonlyChange);
204
+ const onFrontmatterChangeRef = useLatestRef(onFrontmatterChange);
205
+ const resolvedExtensions = useMemo(
206
+ () => resolveMiraExtensions(extensions, {
207
+ mode,
208
+ readonly,
209
+ sourcePath
210
+ }),
211
+ [extensions, mode, readonly, sourcePath]
212
+ );
213
+ const commitValue = useCallback(
214
+ (nextValue) => {
215
+ if (valueProp === void 0) {
216
+ setUncontrolledValue(nextValue);
217
+ }
218
+ onChangeRef.current?.(nextValue);
219
+ },
220
+ [onChangeRef, valueProp]
221
+ );
222
+ const setMode = useCallback(
223
+ (nextMode) => {
224
+ if (modeProp === void 0) {
225
+ setUncontrolledMode(nextMode);
226
+ }
227
+ onModeChangeRef.current?.(nextMode);
228
+ },
229
+ [modeProp, onModeChangeRef]
230
+ );
231
+ const setReadonly = useCallback(
232
+ (nextReadonly) => {
233
+ if (readonlyProp === void 0) {
234
+ setUncontrolledReadonly(nextReadonly);
235
+ }
236
+ onReadonlyChangeRef.current?.(nextReadonly);
237
+ },
238
+ [onReadonlyChangeRef, readonlyProp]
239
+ );
240
+ const handleInsertMarkdown = useCallback(
241
+ (markdown, selection) => {
242
+ const controller = controllerRef.current;
243
+ controller?.replaceSelection(markdown, selection);
244
+ controller?.focus();
245
+ },
246
+ []
247
+ );
248
+ const handleInsertImage = useCallback(() => {
249
+ const controller = controllerRef.current;
250
+ if (controller) {
251
+ openImageFilePicker(controller.view, imageConfig);
252
+ }
253
+ }, [imageConfig]);
254
+ const handleApplyMarkdownAction = useCallback(
255
+ (action) => {
256
+ if (readonlyRef.current || modeRef.current === "preview") {
257
+ return false;
258
+ }
259
+ return controllerRef.current?.applyMarkdownAction(action) ?? false;
260
+ },
261
+ [modeRef, readonlyRef]
262
+ );
263
+ const createExtensionRuntimeContext = useCallback(
264
+ (activeController = controllerRef.current, view = activeController?.view) => ({
265
+ view,
266
+ mode: modeRef.current,
267
+ readonly: readonlyRef.current,
268
+ sourcePath,
269
+ focus: () => activeController?.focus(),
270
+ getValue: () => activeController?.getValue() ?? valueRef.current,
271
+ insertImage: handleInsertImage,
272
+ insertMarkdown: handleInsertMarkdown,
273
+ setValue(nextValue) {
274
+ if (valueProp === void 0) {
275
+ setUncontrolledValue(nextValue);
276
+ }
277
+ activeController?.setValue(nextValue);
278
+ }
279
+ }),
280
+ [
281
+ handleInsertImage,
282
+ handleInsertMarkdown,
283
+ modeRef,
284
+ readonlyRef,
285
+ sourcePath,
286
+ valueProp,
287
+ valueRef
288
+ ]
289
+ );
290
+ const buildCodeMirrorExtensions = useCallback(() => {
291
+ return createMiraCodeMirrorExtensions({
292
+ mode: modeRef.current,
293
+ readonly: readonlyRef.current,
294
+ placeholder,
295
+ lineWrapping,
296
+ spellcheck,
297
+ blockControls,
298
+ indentGuides,
299
+ indentWithTabs,
300
+ indentWidth,
301
+ extensions,
302
+ sourcePath,
303
+ linkResolver,
304
+ assetResolver,
305
+ fileAdapter,
306
+ imageConfig,
307
+ authoring,
308
+ frontmatterOpen,
309
+ frontmatterConfig,
310
+ runtimeContext: (view) => createExtensionRuntimeContext(controllerRef.current, view),
311
+ onChange(replacement, from, to, nextValue) {
312
+ const controller = controllerRef.current;
313
+ if (controller) {
314
+ controller.view.dispatch({
315
+ changes: { from, insert: replacement, to }
316
+ });
317
+ } else {
318
+ commitValue(nextValue);
319
+ }
320
+ },
321
+ onFrontmatterChange: (...args) => onFrontmatterChangeRef.current?.(...args)
322
+ });
323
+ }, [
324
+ assetResolver,
325
+ authoring,
326
+ blockControls,
327
+ commitValue,
328
+ createExtensionRuntimeContext,
329
+ extensions,
330
+ fileAdapter,
331
+ frontmatterOpen,
332
+ frontmatterConfig,
333
+ indentGuides,
334
+ indentWithTabs,
335
+ indentWidth,
336
+ imageConfig,
337
+ lineWrapping,
338
+ linkResolver,
339
+ modeRef,
340
+ onFrontmatterChangeRef,
341
+ placeholder,
342
+ readonlyRef,
343
+ sourcePath,
344
+ spellcheck
345
+ ]);
346
+ const runExtensionMounts = useCallback(
347
+ (activeController) => {
348
+ for (const cleanup of cleanupExtensionMountsRef.current) {
349
+ cleanup();
350
+ }
351
+ cleanupExtensionMountsRef.current = [];
352
+ for (const mountExtension of resolvedExtensions.onMount) {
353
+ const cleanup = mountExtension(
354
+ createExtensionRuntimeContext(
355
+ activeController,
356
+ activeController.view
357
+ )
358
+ );
359
+ if (typeof cleanup === "function") {
360
+ cleanupExtensionMountsRef.current.push(cleanup);
361
+ }
362
+ }
363
+ },
364
+ [createExtensionRuntimeContext, resolvedExtensions.onMount]
365
+ );
366
+ const findCommand = useCallback(
367
+ (commandId) => {
368
+ for (let index = resolvedExtensions.commands.length - 1; index >= 0; index -= 1) {
369
+ const command = resolvedExtensions.commands[index];
370
+ if (command?.id === commandId) {
371
+ return command;
372
+ }
373
+ }
374
+ return void 0;
375
+ },
376
+ [resolvedExtensions.commands]
377
+ );
378
+ const isCommandEnabled = useCallback(
379
+ (commandId) => {
380
+ const command = findCommand(commandId);
381
+ return Boolean(
382
+ command && isMiraCommandEnabled(command, createExtensionRuntimeContext())
383
+ );
384
+ },
385
+ [createExtensionRuntimeContext, findCommand]
386
+ );
387
+ const executeCommand = useCallback(
388
+ (commandId) => executeMiraCommand(
389
+ resolvedExtensions.commands,
390
+ commandId,
391
+ createExtensionRuntimeContext()
392
+ ),
393
+ [createExtensionRuntimeContext, resolvedExtensions.commands]
394
+ );
395
+ useEffect2(() => {
396
+ if (!editorHostRef.current) {
397
+ return;
398
+ }
399
+ const activeController = createMiraEditorController({
400
+ codeMirrorExtensions: buildCodeMirrorExtensions(),
401
+ onChange(nextValue) {
402
+ commitValue(nextValue);
403
+ },
404
+ value
405
+ });
406
+ activeController.mount(editorHostRef.current);
407
+ controllerRef.current = activeController;
408
+ runExtensionMounts(activeController);
409
+ return () => {
410
+ for (const cleanup of cleanupExtensionMountsRef.current) {
411
+ cleanup();
412
+ }
413
+ cleanupExtensionMountsRef.current = [];
414
+ activeController.destroy();
415
+ controllerRef.current = null;
416
+ };
417
+ }, []);
418
+ useEffect2(() => {
419
+ const controller = controllerRef.current;
420
+ if (controller && value !== controller.getValue()) {
421
+ controller.setValue(value);
422
+ }
423
+ }, [value]);
424
+ useEffect2(
425
+ () => mountMiraExtensionStyles(resolvedExtensions.styles),
426
+ [resolvedExtensions.styles]
427
+ );
428
+ useEffect2(() => {
429
+ const controller = controllerRef.current;
430
+ if (!controller) {
431
+ return;
432
+ }
433
+ controller.update({
434
+ codeMirrorExtensions: buildCodeMirrorExtensions()
435
+ });
436
+ runExtensionMounts(controller);
437
+ }, [buildCodeMirrorExtensions, runExtensionMounts]);
438
+ const handlePreviewChange = useCallback(
439
+ (replacement, from, to) => {
440
+ const currentValue = valueRef.current;
441
+ const nextValue = `${currentValue.slice(0, from)}${replacement}${currentValue.slice(to)}`;
442
+ commitValue(nextValue);
443
+ },
444
+ [commitValue, valueRef]
445
+ );
446
+ useImperativeHandle(
447
+ ref,
448
+ () => ({
449
+ applyMarkdownAction: handleApplyMarkdownAction,
450
+ executeCommand,
451
+ focus() {
452
+ controllerRef.current?.focus();
453
+ },
454
+ getMarkdown() {
455
+ return controllerRef.current?.getValue() ?? valueRef.current;
456
+ },
457
+ getCommands() {
458
+ return resolvedExtensions.commands;
459
+ },
460
+ getMode() {
461
+ return modeRef.current;
462
+ },
463
+ getSelection() {
464
+ return controllerRef.current?.getSelection() ?? null;
465
+ },
466
+ insertMarkdown: handleInsertMarkdown,
467
+ insertImage: handleInsertImage,
468
+ isCommandEnabled,
469
+ setMarkdown(markdown) {
470
+ if (valueProp === void 0) {
471
+ setUncontrolledValue(markdown);
472
+ }
473
+ controllerRef.current?.setValue(markdown);
474
+ },
475
+ setMode,
476
+ setReadonly,
477
+ setSelection(selection) {
478
+ controllerRef.current?.setSelection(selection);
479
+ }
480
+ }),
481
+ [
482
+ handleInsertImage,
483
+ handleApplyMarkdownAction,
484
+ handleInsertMarkdown,
485
+ executeCommand,
486
+ isCommandEnabled,
487
+ modeRef,
488
+ resolvedExtensions.commands,
489
+ setMode,
490
+ setReadonly,
491
+ valueProp,
492
+ valueRef
493
+ ]
494
+ );
495
+ const showEditor = mode !== "preview";
496
+ const showPreview = mode === "preview" || mode === "split";
497
+ const themeAttribute = normalizeMiraTheme(theme);
498
+ const colorModeAttribute = miraColorModeAttribute(colorMode);
499
+ const colorModeClass = miraColorModeClassName(colorMode);
500
+ useEffect2(() => {
501
+ const controller = controllerRef.current;
502
+ const previewPane = previewPaneRef.current;
503
+ if (mode !== "split" || !controller || !previewPane) {
504
+ return;
505
+ }
506
+ return syncSplitScroll(controller.view.scrollDOM, previewPane);
507
+ }, [mode, showPreview]);
508
+ return /* @__PURE__ */ jsxs2(
509
+ "div",
510
+ {
511
+ className: cx("mira", colorModeClass, className),
512
+ "data-mira-theme": themeAttribute,
513
+ "data-mira-color-mode": colorModeAttribute,
514
+ "data-mode": mode,
515
+ "data-readonly": readonly,
516
+ ref: rootRef,
517
+ children: [
518
+ toolbar ? /* @__PURE__ */ jsxs2("div", { className: "mira__toolbar", "aria-label": "Markdown editor toolbar", children: [
519
+ /* @__PURE__ */ jsx2("div", { className: "mira-toggle-group", "aria-label": "Editor mode", children: ["source", "live-preview", "preview", "split"].map(
520
+ (modeOption) => /* @__PURE__ */ jsx2(
521
+ "button",
522
+ {
523
+ className: "mira-toggle-group__item",
524
+ "data-state": mode === modeOption ? "on" : "off",
525
+ onClick: () => setMode(modeOption),
526
+ type: "button",
527
+ children: modeOption === "live-preview" ? "Live" : modeOption
528
+ },
529
+ modeOption
530
+ )
531
+ ) }),
532
+ /* @__PURE__ */ jsx2("div", { className: "mira-separator--vertical mira__toolbar-separator" }),
533
+ /* @__PURE__ */ jsxs2("div", { className: "mira__actions", children: [
534
+ /* @__PURE__ */ jsx2(
535
+ "button",
536
+ {
537
+ className: "mira-ui-button mira-ui-button--ghost mira-ui-button--sm",
538
+ "aria-label": "Bold",
539
+ disabled: readonly || mode === "preview",
540
+ onClick: () => handleApplyMarkdownAction("bold"),
541
+ type: "button",
542
+ children: "B"
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsx2(
546
+ "button",
547
+ {
548
+ className: "mira-ui-button mira-ui-button--ghost mira-ui-button--sm",
549
+ "aria-label": "Italic",
550
+ disabled: readonly || mode === "preview",
551
+ onClick: () => handleApplyMarkdownAction("italic"),
552
+ type: "button",
553
+ children: "I"
554
+ }
555
+ ),
556
+ /* @__PURE__ */ jsx2(
557
+ "button",
558
+ {
559
+ className: "mira-ui-button mira-ui-button--ghost mira-ui-button--sm",
560
+ "aria-label": "Link",
561
+ disabled: readonly || mode === "preview",
562
+ onClick: () => handleApplyMarkdownAction("link"),
563
+ type: "button",
564
+ children: "Link"
565
+ }
566
+ ),
567
+ /* @__PURE__ */ jsx2(
568
+ "button",
569
+ {
570
+ className: "mira-ui-button mira-ui-button--ghost mira-ui-button--sm",
571
+ disabled: readonly || mode === "preview",
572
+ onClick: handleInsertImage,
573
+ type: "button",
574
+ children: "Image"
575
+ }
576
+ )
577
+ ] })
578
+ ] }) : null,
579
+ /* @__PURE__ */ jsxs2("div", { className: "mira__body", children: [
580
+ /* @__PURE__ */ jsx2(
581
+ "section",
582
+ {
583
+ "aria-hidden": showEditor ? "false" : "true",
584
+ className: cx(
585
+ "mira__pane mira__pane--editor markdown-editor-surface",
586
+ mode === "source" ? "markdown-view__editor--source markdown-source-mode" : "markdown-view__editor--live-preview markdown-live-preview-mode"
587
+ ),
588
+ "data-visible": showEditor,
589
+ children: /* @__PURE__ */ jsx2("div", { className: "mira__editor-host", ref: editorHostRef })
590
+ }
591
+ ),
592
+ showPreview ? /* @__PURE__ */ jsx2(
593
+ "section",
594
+ {
595
+ className: cx(
596
+ "mira__pane mira__pane--preview",
597
+ outline && outlineVariant === "floating" ? "mira__pane--outline-floating" : void 0
598
+ ),
599
+ ref: previewPaneRef,
600
+ children: /* @__PURE__ */ jsx2(
601
+ MarkdownPreviewHost,
602
+ {
603
+ assetResolver,
604
+ extensions,
605
+ fileAdapter,
606
+ frontmatterConfig,
607
+ frontmatterOpen,
608
+ headingIds: headingIds || outline,
609
+ headingIdPrefix,
610
+ htmlPolicy,
611
+ emoji,
612
+ outline,
613
+ outlineVariant,
614
+ linkResolver,
615
+ onChange: handlePreviewChange,
616
+ onFrontmatterChange,
617
+ sourcePath,
618
+ value
619
+ }
620
+ )
621
+ }
622
+ ) : null
623
+ ] })
624
+ ]
625
+ }
626
+ );
627
+ });
628
+ Mira.displayName = "Mira";
629
+ function syncSplitScroll(editorScroller, previewPane) {
630
+ const previewScroller = previewPane.querySelector(".mira-markdown-preview") ?? previewPane;
631
+ let activeSource = null;
632
+ let releaseFrame = 0;
633
+ const sync = (source, target) => {
634
+ if (activeSource && activeSource !== source) {
635
+ return;
636
+ }
637
+ const sourceMax = source.scrollHeight - source.clientHeight;
638
+ const targetMax = target.scrollHeight - target.clientHeight;
639
+ if (sourceMax <= 0 || targetMax <= 0) {
640
+ return;
641
+ }
642
+ activeSource = source;
643
+ target.scrollTop = source.scrollTop / sourceMax * targetMax;
644
+ cancelAnimationFrame(releaseFrame);
645
+ releaseFrame = requestAnimationFrame(() => {
646
+ activeSource = null;
647
+ });
648
+ };
649
+ const syncPreview = () => sync(editorScroller, previewScroller);
650
+ const syncEditor = () => sync(previewScroller, editorScroller);
651
+ editorScroller.addEventListener("scroll", syncPreview, { passive: true });
652
+ previewScroller.addEventListener("scroll", syncEditor, { passive: true });
653
+ return () => {
654
+ cancelAnimationFrame(releaseFrame);
655
+ editorScroller.removeEventListener("scroll", syncPreview);
656
+ previewScroller.removeEventListener("scroll", syncEditor);
657
+ };
658
+ }
659
+
660
+ // src/mira-editor.tsx
661
+ import {
662
+ forwardRef as forwardRef2,
663
+ useCallback as useCallback3,
664
+ useImperativeHandle as useImperativeHandle2,
665
+ useMemo as useMemo3,
666
+ useRef as useRef5,
667
+ useState as useState3
668
+ } from "react";
669
+ import { createRoot } from "react-dom/client";
670
+ import {
671
+ Check as Check2,
672
+ Code as Code2,
673
+ Command,
674
+ Image as Image2,
675
+ Link as Link2,
676
+ Play,
677
+ RotateCcw,
678
+ Save,
679
+ Sparkles,
680
+ Table2 as Table22,
681
+ WandSparkles
682
+ } from "lucide-react";
683
+ import {
684
+ defineMiraExtension,
685
+ resolveMiraExtensions as resolveMiraExtensions2
686
+ } from "@lapismd/mira/extensions";
687
+
688
+ // src/mira-editor-toolbar.tsx
689
+ import {
690
+ Bold,
691
+ BookOpen,
692
+ Braces,
693
+ Check,
694
+ Code,
695
+ CodeXml,
696
+ Columns2,
697
+ Ellipsis,
698
+ FileCode,
699
+ Heading1,
700
+ Image,
701
+ Italic,
702
+ Link,
703
+ List,
704
+ ListChecks,
705
+ ListOrdered,
706
+ PencilLine,
707
+ Quote,
708
+ Strikethrough,
709
+ Table2,
710
+ TableCellsSplit,
711
+ Workflow
712
+ } from "lucide-react";
713
+ import {
714
+ useCallback as useCallback2,
715
+ useEffect as useEffect3,
716
+ useMemo as useMemo2,
717
+ useRef as useRef4,
718
+ useState as useState2
719
+ } from "react";
720
+ import {
721
+ isMiraEditMode,
722
+ markdownActionForMiraToolbarItem,
723
+ miraEditorToolbarItemLabels,
724
+ miraViewOptionsLabel,
725
+ miraViewToggleLabel,
726
+ resolveMiraModeAfterSplit,
727
+ resolveMiraViewModeMenuItems,
728
+ resolveMiraViewToggleMode,
729
+ templateForMiraToolbarItem
730
+ } from "@lapismd/mira-editor";
731
+
732
+ // src/features.ts
733
+ import { mermaidExtension } from "@lapismd/mira-plugin-mermaid";
734
+ import {
735
+ resolveMiraEditorBlockControls as resolveBaseMiraEditorBlockControls,
736
+ resolveMiraEditorSlashCommands as resolveBaseMiraEditorSlashCommands
737
+ } from "@lapismd/mira-editor";
738
+ var MiraFeature = {
739
+ Toolbar: "toolbar",
740
+ ModeSwitch: "mode-switch",
741
+ Formatting: "formatting",
742
+ Headings: "headings",
743
+ Lists: "lists",
744
+ Links: "links",
745
+ Tables: "tables",
746
+ GridTables: "grid-tables",
747
+ Mermaid: "mermaid",
748
+ Code: "code",
749
+ Math: "math",
750
+ Frontmatter: "frontmatter",
751
+ Images: "images",
752
+ Embeds: "embeds",
753
+ Wikilinks: "wikilinks",
754
+ Tags: "tags",
755
+ SlashCommands: "slash-commands",
756
+ BlockControls: "block-controls",
757
+ SourceMode: "source-mode",
758
+ LivePreviewMode: "live-preview-mode",
759
+ PreviewMode: "preview-mode",
760
+ SplitMode: "split-mode"
761
+ };
762
+ var defaultMiraEditorFeatures = {
763
+ [MiraFeature.Toolbar]: true,
764
+ [MiraFeature.ModeSwitch]: true,
765
+ [MiraFeature.Formatting]: true,
766
+ [MiraFeature.Headings]: true,
767
+ [MiraFeature.Lists]: true,
768
+ [MiraFeature.Links]: true,
769
+ [MiraFeature.Tables]: true,
770
+ [MiraFeature.GridTables]: true,
771
+ [MiraFeature.Mermaid]: true,
772
+ [MiraFeature.Code]: true,
773
+ [MiraFeature.Math]: true,
774
+ [MiraFeature.Frontmatter]: true,
775
+ [MiraFeature.Images]: true,
776
+ [MiraFeature.Embeds]: true,
777
+ [MiraFeature.Wikilinks]: true,
778
+ [MiraFeature.Tags]: true,
779
+ [MiraFeature.SlashCommands]: true,
780
+ [MiraFeature.BlockControls]: true,
781
+ [MiraFeature.SourceMode]: true,
782
+ [MiraFeature.LivePreviewMode]: true,
783
+ [MiraFeature.PreviewMode]: true,
784
+ [MiraFeature.SplitMode]: true
785
+ };
786
+ var defaultMiraEditorEditMode = "live-preview";
787
+ var defaultToolbarItems = [
788
+ "heading",
789
+ "bold",
790
+ "italic",
791
+ "strikethrough",
792
+ "inlineCode",
793
+ "quote",
794
+ "bulletList",
795
+ "numberedList",
796
+ "taskList",
797
+ "link",
798
+ "image",
799
+ "table",
800
+ "gridTable",
801
+ "code",
802
+ "math",
803
+ "mermaid"
804
+ ];
805
+ function resolveMiraEditorFeatures(features = {}) {
806
+ return {
807
+ ...defaultMiraEditorFeatures,
808
+ ...features
809
+ };
810
+ }
811
+ function resolveMiraEditorBlockControls(options = {}) {
812
+ const blockConfig = options.featureConfigs?.[MiraFeature.BlockControls];
813
+ return resolveBaseMiraEditorBlockControls({
814
+ features: options.features,
815
+ featureConfigs: blockConfig ? {
816
+ [MiraFeature.BlockControls]: blockConfig
817
+ } : void 0
818
+ });
819
+ }
820
+ function createMiraEditorExtensions({
821
+ featureConfigs = {},
822
+ features = {}
823
+ } = {}) {
824
+ const resolvedFeatures = resolveMiraEditorFeatures(features);
825
+ const extensions = [];
826
+ if (resolvedFeatures[MiraFeature.Mermaid] && featureConfigs[MiraFeature.Mermaid]?.enabled !== false) {
827
+ extensions.push(mermaidExtension());
828
+ }
829
+ if (resolvedFeatures[MiraFeature.SlashCommands]) {
830
+ const slashCommands = resolveMiraEditorSlashCommands({
831
+ featureConfigs,
832
+ features
833
+ });
834
+ if (slashCommands.length > 0) {
835
+ extensions.push({
836
+ name: "default-slash-commands",
837
+ slashCommands
838
+ });
839
+ }
840
+ }
841
+ return extensions;
842
+ }
843
+ function resolveMiraEditorSlashCommands({
844
+ featureConfigs = {},
845
+ features = {}
846
+ } = {}) {
847
+ return resolveBaseMiraEditorSlashCommands({
848
+ featureConfigs,
849
+ features
850
+ });
851
+ }
852
+ function resolveMiraEditorToolbarItems({
853
+ featureConfigs = {},
854
+ features = {}
855
+ } = {}) {
856
+ const resolvedFeatures = resolveMiraEditorFeatures(features);
857
+ const configuredItems = featureConfigs[MiraFeature.Toolbar]?.items ?? defaultToolbarItems;
858
+ return configuredItems.filter(
859
+ (item) => isToolbarItemAvailable(item, resolvedFeatures)
860
+ );
861
+ }
862
+ function resolveMiraEditorToolbarActions({
863
+ featureConfigs = {},
864
+ toolbarActions = []
865
+ } = {}) {
866
+ return [
867
+ ...featureConfigs[MiraFeature.Toolbar]?.actions ?? [],
868
+ ...toolbarActions
869
+ ];
870
+ }
871
+ function resolveMiraEditorToolbarDefinitions({
872
+ featureConfigs = {},
873
+ toolbars = []
874
+ } = {}) {
875
+ return [
876
+ ...featureConfigs[MiraFeature.Toolbar]?.toolbars ?? [],
877
+ ...toolbars
878
+ ];
879
+ }
880
+ function resolveMiraEditorModes(features = {}) {
881
+ const resolvedFeatures = resolveMiraEditorFeatures(features);
882
+ const modes = [];
883
+ if (resolvedFeatures[MiraFeature.SourceMode]) {
884
+ modes.push("source");
885
+ }
886
+ if (resolvedFeatures[MiraFeature.LivePreviewMode]) {
887
+ modes.push("live-preview");
888
+ }
889
+ if (resolvedFeatures[MiraFeature.PreviewMode]) {
890
+ modes.push("preview");
891
+ }
892
+ if (resolvedFeatures[MiraFeature.SplitMode]) {
893
+ modes.push("split");
894
+ }
895
+ return modes;
896
+ }
897
+ function resolveMiraEditorEditMode(defaultEditMode = defaultMiraEditorEditMode, modeOptions = resolveMiraEditorModes()) {
898
+ if (modeOptions.includes(defaultEditMode)) {
899
+ return defaultEditMode;
900
+ }
901
+ if (modeOptions.includes(defaultMiraEditorEditMode)) {
902
+ return defaultMiraEditorEditMode;
903
+ }
904
+ if (modeOptions.includes("source")) {
905
+ return "source";
906
+ }
907
+ return defaultMiraEditorEditMode;
908
+ }
909
+ function isToolbarItemAvailable(item, features) {
910
+ switch (item) {
911
+ case "bold":
912
+ case "italic":
913
+ case "strikethrough":
914
+ return features[MiraFeature.Formatting];
915
+ case "heading":
916
+ return features[MiraFeature.Headings];
917
+ case "quote":
918
+ case "bulletList":
919
+ case "numberedList":
920
+ case "taskList":
921
+ return features[MiraFeature.Lists];
922
+ case "link":
923
+ return features[MiraFeature.Links];
924
+ case "image":
925
+ return features[MiraFeature.Images];
926
+ case "table":
927
+ return features[MiraFeature.Tables];
928
+ case "gridTable":
929
+ return features[MiraFeature.GridTables];
930
+ case "mermaid":
931
+ return features[MiraFeature.Mermaid];
932
+ case "inlineCode":
933
+ case "code":
934
+ return features[MiraFeature.Code];
935
+ case "math":
936
+ return features[MiraFeature.Math];
937
+ }
938
+ }
939
+
940
+ // src/mira-editor-toolbar.tsx
941
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
942
+ var modeIcons = {
943
+ source: FileCode,
944
+ "live-preview": PencilLine,
945
+ preview: BookOpen,
946
+ split: Columns2
947
+ };
948
+ var toolbarItemIcons = {
949
+ heading: Heading1,
950
+ bold: Bold,
951
+ italic: Italic,
952
+ strikethrough: Strikethrough,
953
+ inlineCode: CodeXml,
954
+ quote: Quote,
955
+ bulletList: List,
956
+ numberedList: ListOrdered,
957
+ taskList: ListChecks,
958
+ link: Link,
959
+ image: Image,
960
+ table: Table2,
961
+ gridTable: TableCellsSplit,
962
+ code: Code,
963
+ math: Braces,
964
+ mermaid: Workflow
965
+ };
966
+ function MiraEditorToolbar({
967
+ className,
968
+ context,
969
+ defaultEditMode = defaultMiraEditorEditMode,
970
+ featureConfigs = {},
971
+ features = {},
972
+ indentGuides = true,
973
+ indentWithTabs = true,
974
+ indentWidth = 4,
975
+ mode = "live-preview",
976
+ modeOptions: modeOptionsProp,
977
+ onIndentGuidesChange,
978
+ onIndentWidthChange,
979
+ onIndentWithTabsChange,
980
+ onInsertMarkdown,
981
+ onInsertImage,
982
+ onModeChange,
983
+ readonly = false,
984
+ showModeSwitch = true,
985
+ toolbarActions = [],
986
+ toolbars = [],
987
+ value = ""
988
+ }) {
989
+ const rootRef = useRef4(null);
990
+ const [openMenu, setOpenMenu] = useState2(null);
991
+ const [lastNonSplitMode, setLastNonSplitMode] = useState2(defaultEditMode);
992
+ const [localIndentGuides, setLocalIndentGuides] = useState2(indentGuides);
993
+ const [localIndentWithTabs, setLocalIndentWithTabs] = useState2(indentWithTabs);
994
+ const [localIndentWidth, setLocalIndentWidth] = useState2(indentWidth);
995
+ const resolvedFeatures = useMemo2(
996
+ () => resolveMiraEditorFeatures(features),
997
+ [features]
998
+ );
999
+ const modeOptions = useMemo2(
1000
+ () => modeOptionsProp ?? resolveMiraEditorModes(features),
1001
+ [features, modeOptionsProp]
1002
+ );
1003
+ const toolbarItems = useMemo2(
1004
+ () => resolveMiraEditorToolbarItems({ featureConfigs, features }),
1005
+ [featureConfigs, features]
1006
+ );
1007
+ const customToolbarActions = useMemo2(
1008
+ () => resolveMiraEditorToolbarActions({
1009
+ featureConfigs,
1010
+ toolbarActions
1011
+ }).filter(hasTopToolbarPlacement),
1012
+ [featureConfigs, toolbarActions]
1013
+ );
1014
+ const customToolbars = useMemo2(
1015
+ () => resolveMiraEditorToolbarDefinitions({
1016
+ featureConfigs,
1017
+ toolbars
1018
+ }).map((toolbar) => ({
1019
+ ...toolbar,
1020
+ items: toolbar.items.filter(hasTopToolbarPlacement)
1021
+ })),
1022
+ [featureConfigs, toolbars]
1023
+ );
1024
+ function hasTopToolbarPlacement(action) {
1025
+ return action.type === "dropdown" || !action.placements || action.placements.includes("toolbar");
1026
+ }
1027
+ const startToolbars = useMemo2(
1028
+ () => customToolbars.filter((toolbar) => toolbar.align !== "end"),
1029
+ [customToolbars]
1030
+ );
1031
+ const endToolbars = useMemo2(
1032
+ () => customToolbars.filter((toolbar) => toolbar.align === "end"),
1033
+ [customToolbars]
1034
+ );
1035
+ const modeSwitchVisible = showModeSwitch && resolvedFeatures[MiraFeature.ModeSwitch] && modeOptions.length > 1;
1036
+ const resolvedDefaultEditMode = useMemo2(
1037
+ () => resolveMiraEditorEditMode(defaultEditMode, modeOptions),
1038
+ [defaultEditMode, modeOptions]
1039
+ );
1040
+ const editModeOptions = useMemo2(
1041
+ () => modeOptions.filter((modeOption) => isMiraEditMode(modeOption)),
1042
+ [modeOptions]
1043
+ );
1044
+ const viewModeMenuItems = useMemo2(
1045
+ () => resolveMiraViewModeMenuItems({
1046
+ mode,
1047
+ modeOptions,
1048
+ resolvedDefaultEditMode
1049
+ }).map((item) => ({
1050
+ ...item,
1051
+ icon: modeIcons[item.mode]
1052
+ })),
1053
+ [mode, modeOptions, resolvedDefaultEditMode]
1054
+ );
1055
+ const viewModeToggleVisible = modeSwitchVisible && modeOptions.includes("preview") && editModeOptions.length > 0;
1056
+ const splitModeVisible = modeSwitchVisible && modeOptions.includes("split");
1057
+ const editorSettingsMenuVisible = true;
1058
+ const viewModeOverflowVisible = modeSwitchVisible && viewModeMenuItems.length > 0 || editorSettingsMenuVisible;
1059
+ const toolbarHasStartContent = startToolbars.length > 0 || toolbarItems.length > 0 || customToolbarActions.length > 0;
1060
+ const fallbackContext = useCallback2(
1061
+ () => ({
1062
+ focus: () => void 0,
1063
+ getIndentGuides: () => localIndentGuides,
1064
+ getIndentWidth: () => localIndentWidth,
1065
+ getIndentWithTabs: () => localIndentWithTabs,
1066
+ getMarkdown: () => value,
1067
+ getMode: () => mode,
1068
+ getSelection: () => null,
1069
+ insertMarkdown(markdown) {
1070
+ onInsertMarkdown?.(markdown);
1071
+ },
1072
+ insertImage() {
1073
+ onInsertImage?.();
1074
+ },
1075
+ setIndentGuides(nextEnabled) {
1076
+ setLocalIndentGuides(nextEnabled);
1077
+ onIndentGuidesChange?.(nextEnabled);
1078
+ },
1079
+ setIndentWidth(nextWidth) {
1080
+ setLocalIndentWidth(nextWidth);
1081
+ onIndentWidthChange?.(nextWidth);
1082
+ },
1083
+ setIndentWithTabs(nextEnabled) {
1084
+ setLocalIndentWithTabs(nextEnabled);
1085
+ onIndentWithTabsChange?.(nextEnabled);
1086
+ },
1087
+ mode,
1088
+ readonly,
1089
+ setMarkdown: () => void 0,
1090
+ setMode(nextMode) {
1091
+ onModeChange?.(nextMode);
1092
+ },
1093
+ setReadonly: () => void 0,
1094
+ setSelection: () => void 0,
1095
+ value
1096
+ }),
1097
+ [
1098
+ localIndentGuides,
1099
+ localIndentWidth,
1100
+ localIndentWithTabs,
1101
+ mode,
1102
+ onIndentGuidesChange,
1103
+ onIndentWidthChange,
1104
+ onIndentWithTabsChange,
1105
+ onInsertImage,
1106
+ onInsertMarkdown,
1107
+ onModeChange,
1108
+ readonly,
1109
+ value
1110
+ ]
1111
+ );
1112
+ const actionContext = useCallback2(
1113
+ () => context ?? fallbackContext(),
1114
+ [context, fallbackContext]
1115
+ );
1116
+ const runToolbarItem = useCallback2(
1117
+ (item) => {
1118
+ const activeContext = actionContext();
1119
+ if (item === "image" && activeContext.insertImage) {
1120
+ activeContext.insertImage();
1121
+ return;
1122
+ }
1123
+ const action = markdownActionForMiraToolbarItem(item);
1124
+ if (action && activeContext.applyMarkdownAction) {
1125
+ activeContext.applyMarkdownAction(action);
1126
+ return;
1127
+ }
1128
+ activeContext.insertMarkdown(templateForMiraToolbarItem(item));
1129
+ },
1130
+ [actionContext]
1131
+ );
1132
+ const dynamicBoolean = useCallback2(
1133
+ (value2) => {
1134
+ if (typeof value2 === "function") {
1135
+ return value2(actionContext());
1136
+ }
1137
+ return value2 ?? false;
1138
+ },
1139
+ [actionContext]
1140
+ );
1141
+ const applyMode = useCallback2(
1142
+ (nextMode) => {
1143
+ if (!modeOptions.includes(nextMode)) {
1144
+ return;
1145
+ }
1146
+ context?.setMode(nextMode);
1147
+ onModeChange?.(nextMode);
1148
+ setOpenMenu(null);
1149
+ },
1150
+ [context, modeOptions, onModeChange]
1151
+ );
1152
+ const getIndentGuidesSetting = useCallback2(
1153
+ () => actionContext().getIndentGuides?.() ?? localIndentGuides,
1154
+ [actionContext, localIndentGuides]
1155
+ );
1156
+ const getIndentWidthSetting = useCallback2(
1157
+ () => actionContext().getIndentWidth?.() ?? localIndentWidth,
1158
+ [actionContext, localIndentWidth]
1159
+ );
1160
+ const getIndentWithTabsSetting = useCallback2(
1161
+ () => actionContext().getIndentWithTabs?.() ?? localIndentWithTabs,
1162
+ [actionContext, localIndentWithTabs]
1163
+ );
1164
+ const setIndentGuidesSetting = useCallback2(
1165
+ (nextEnabled) => {
1166
+ const ctx = actionContext();
1167
+ if (ctx.setIndentGuides) {
1168
+ ctx.setIndentGuides(nextEnabled);
1169
+ } else {
1170
+ setLocalIndentGuides(nextEnabled);
1171
+ onIndentGuidesChange?.(nextEnabled);
1172
+ }
1173
+ },
1174
+ [actionContext, onIndentGuidesChange]
1175
+ );
1176
+ const setIndentWidthSetting = useCallback2(
1177
+ (nextWidth) => {
1178
+ const ctx = actionContext();
1179
+ if (ctx.setIndentWidth) {
1180
+ ctx.setIndentWidth(nextWidth);
1181
+ } else {
1182
+ setLocalIndentWidth(nextWidth);
1183
+ onIndentWidthChange?.(nextWidth);
1184
+ }
1185
+ },
1186
+ [actionContext, onIndentWidthChange]
1187
+ );
1188
+ const setIndentWithTabsSetting = useCallback2(
1189
+ (nextEnabled) => {
1190
+ const ctx = actionContext();
1191
+ if (ctx.setIndentWithTabs) {
1192
+ ctx.setIndentWithTabs(nextEnabled);
1193
+ } else {
1194
+ setLocalIndentWithTabs(nextEnabled);
1195
+ onIndentWithTabsChange?.(nextEnabled);
1196
+ }
1197
+ },
1198
+ [actionContext, onIndentWithTabsChange]
1199
+ );
1200
+ useEffect3(() => {
1201
+ setLocalIndentGuides(indentGuides);
1202
+ }, [indentGuides]);
1203
+ useEffect3(() => {
1204
+ setLocalIndentWithTabs(indentWithTabs);
1205
+ }, [indentWithTabs]);
1206
+ useEffect3(() => {
1207
+ setLocalIndentWidth(indentWidth);
1208
+ }, [indentWidth]);
1209
+ const modeAfterSplit = useCallback2(() => {
1210
+ return resolveMiraModeAfterSplit({
1211
+ lastNonSplitMode,
1212
+ modeOptions,
1213
+ resolvedDefaultEditMode
1214
+ });
1215
+ }, [lastNonSplitMode, modeOptions, resolvedDefaultEditMode]);
1216
+ const handleSplitMode = useCallback2(() => {
1217
+ applyMode(mode === "split" ? modeAfterSplit() : "split");
1218
+ }, [applyMode, mode, modeAfterSplit]);
1219
+ useEffect3(() => {
1220
+ if (mode !== "split" && modeOptions.includes(mode)) {
1221
+ setLastNonSplitMode(mode);
1222
+ }
1223
+ }, [mode, modeOptions]);
1224
+ useEffect3(() => {
1225
+ if (!openMenu) {
1226
+ return;
1227
+ }
1228
+ function handlePointerDown(event) {
1229
+ if (!rootRef.current?.contains(event.target)) {
1230
+ setOpenMenu(null);
1231
+ }
1232
+ }
1233
+ function handleKeyDown(event) {
1234
+ if (event.key === "Escape") {
1235
+ setOpenMenu(null);
1236
+ }
1237
+ }
1238
+ document.addEventListener("pointerdown", handlePointerDown);
1239
+ document.addEventListener("keydown", handleKeyDown);
1240
+ return () => {
1241
+ document.removeEventListener("pointerdown", handlePointerDown);
1242
+ document.removeEventListener("keydown", handleKeyDown);
1243
+ };
1244
+ }, [openMenu]);
1245
+ const renderAction = useCallback2(
1246
+ (action, align = "start") => {
1247
+ if (isDropdownAction(action)) {
1248
+ return /* @__PURE__ */ jsx3(
1249
+ DropdownAction,
1250
+ {
1251
+ action,
1252
+ align,
1253
+ dynamicBoolean,
1254
+ isOpen: openMenu === action.id,
1255
+ menuItemIcon: (item) => menuItemIcon(item, dynamicBoolean),
1256
+ onMenuItemRun: (item) => {
1257
+ runMenuItem(item, actionContext, dynamicBoolean);
1258
+ setOpenMenu(null);
1259
+ },
1260
+ onToggle: () => setOpenMenu(
1261
+ (current) => current === action.id ? null : action.id
1262
+ )
1263
+ },
1264
+ action.id
1265
+ );
1266
+ }
1267
+ const Icon = action.icon;
1268
+ return /* @__PURE__ */ jsx3(
1269
+ "button",
1270
+ {
1271
+ "aria-label": action.label,
1272
+ "aria-pressed": dynamicBoolean(action.pressed) ? "true" : void 0,
1273
+ className: "mira-toolbar__button",
1274
+ disabled: dynamicBoolean(action.disabled),
1275
+ onClick: () => action.run(actionContext()),
1276
+ title: action.tooltip ?? action.label,
1277
+ type: "button",
1278
+ children: /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "mira-editor__icon" })
1279
+ },
1280
+ action.id
1281
+ );
1282
+ },
1283
+ [actionContext, dynamicBoolean, openMenu]
1284
+ );
1285
+ return /* @__PURE__ */ jsx3(
1286
+ "div",
1287
+ {
1288
+ className: "mira-editor-toolbar__event-root",
1289
+ ref: rootRef,
1290
+ role: "presentation",
1291
+ children: /* @__PURE__ */ jsxs3(
1292
+ "div",
1293
+ {
1294
+ "aria-label": "Markdown editor toolbar",
1295
+ className: cx(
1296
+ "mira-toolbar mira-editor-toolbar mira-editor__toolbar",
1297
+ className
1298
+ ),
1299
+ role: "toolbar",
1300
+ children: [
1301
+ startToolbars.map((toolbar) => /* @__PURE__ */ jsx3(ToolbarSection, { toolbar, children: toolbar.items.map((action) => renderAction(action)) }, toolbar.id)),
1302
+ startToolbars.length > 0 ? /* @__PURE__ */ jsx3(Separator, {}) : null,
1303
+ toolbarItems.length > 0 ? /* @__PURE__ */ jsx3(
1304
+ "div",
1305
+ {
1306
+ "aria-label": "Insert",
1307
+ className: "mira-editor__toolbar-section",
1308
+ role: "group",
1309
+ children: toolbarItems.map((item) => {
1310
+ const Icon = toolbarItemIcons[item];
1311
+ return /* @__PURE__ */ jsx3(
1312
+ "button",
1313
+ {
1314
+ "aria-label": miraEditorToolbarItemLabels[item],
1315
+ className: "mira-toolbar__button",
1316
+ disabled: readonly || mode === "preview",
1317
+ onClick: () => runToolbarItem(item),
1318
+ title: miraEditorToolbarItemLabels[item],
1319
+ type: "button",
1320
+ children: /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "mira-editor__icon" })
1321
+ },
1322
+ item
1323
+ );
1324
+ })
1325
+ }
1326
+ ) : null,
1327
+ customToolbarActions.length > 0 ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1328
+ toolbarItems.length > 0 ? /* @__PURE__ */ jsx3(Separator, {}) : null,
1329
+ /* @__PURE__ */ jsx3(
1330
+ "div",
1331
+ {
1332
+ "aria-label": "Custom actions",
1333
+ className: "mira-editor__toolbar-section",
1334
+ role: "group",
1335
+ children: customToolbarActions.map((action) => renderAction(action))
1336
+ }
1337
+ )
1338
+ ] }) : null,
1339
+ /* @__PURE__ */ jsx3("div", { className: "mira-editor-toolbar__spacer" }),
1340
+ endToolbars.map((toolbar) => /* @__PURE__ */ jsx3(ToolbarSection, { toolbar, children: toolbar.items.map((action) => renderAction(action, "end")) }, toolbar.id)),
1341
+ viewModeToggleVisible || splitModeVisible || viewModeOverflowVisible ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1342
+ toolbarHasStartContent || endToolbars.length > 0 ? /* @__PURE__ */ jsx3(Separator, {}) : null,
1343
+ /* @__PURE__ */ jsxs3(
1344
+ "div",
1345
+ {
1346
+ "aria-label": "View controls",
1347
+ className: "mira-editor__toolbar-section",
1348
+ role: "group",
1349
+ children: [
1350
+ viewModeToggleVisible ? /* @__PURE__ */ jsx3(
1351
+ ViewModeToggle,
1352
+ {
1353
+ mode,
1354
+ onApplyMode: applyMode,
1355
+ resolvedDefaultEditMode
1356
+ }
1357
+ ) : null,
1358
+ splitModeVisible ? /* @__PURE__ */ jsx3(
1359
+ "button",
1360
+ {
1361
+ "aria-label": "Split",
1362
+ "aria-pressed": mode === "split" ? "true" : void 0,
1363
+ className: "mira-toolbar__button",
1364
+ onClick: handleSplitMode,
1365
+ title: "Split",
1366
+ type: "button",
1367
+ children: /* @__PURE__ */ jsx3(Columns2, { "aria-hidden": "true", className: "mira-editor__icon" })
1368
+ }
1369
+ ) : null,
1370
+ viewModeOverflowVisible ? /* @__PURE__ */ jsx3(
1371
+ ViewModeDropdown,
1372
+ {
1373
+ getIndentGuides: getIndentGuidesSetting,
1374
+ getIndentWidth: getIndentWidthSetting,
1375
+ getIndentWithTabs: getIndentWithTabsSetting,
1376
+ items: viewModeMenuItems,
1377
+ isOpen: openMenu === "view-mode",
1378
+ onApplyMode: applyMode,
1379
+ onSetIndentGuides: setIndentGuidesSetting,
1380
+ onSetIndentWidth: setIndentWidthSetting,
1381
+ onSetIndentWithTabs: setIndentWithTabsSetting,
1382
+ onToggle: () => setOpenMenu(
1383
+ (current) => current === "view-mode" ? null : "view-mode"
1384
+ )
1385
+ }
1386
+ ) : null
1387
+ ]
1388
+ }
1389
+ )
1390
+ ] }) : null
1391
+ ]
1392
+ }
1393
+ )
1394
+ }
1395
+ );
1396
+ }
1397
+ function ToolbarSection({
1398
+ children,
1399
+ toolbar
1400
+ }) {
1401
+ return /* @__PURE__ */ jsx3(
1402
+ "div",
1403
+ {
1404
+ "aria-label": toolbar.label ?? toolbar.id,
1405
+ className: "mira-editor__toolbar-section",
1406
+ role: "group",
1407
+ children
1408
+ }
1409
+ );
1410
+ }
1411
+ function Separator() {
1412
+ return /* @__PURE__ */ jsx3("div", { className: "mira-separator--vertical mira-editor__separator" });
1413
+ }
1414
+ function DropdownAction({
1415
+ action,
1416
+ align,
1417
+ dynamicBoolean,
1418
+ isOpen,
1419
+ menuItemIcon: resolveMenuItemIcon,
1420
+ onMenuItemRun,
1421
+ onToggle
1422
+ }) {
1423
+ const Icon = action.icon;
1424
+ return /* @__PURE__ */ jsxs3("div", { className: "mira-react-dropdown", children: [
1425
+ /* @__PURE__ */ jsx3(
1426
+ "button",
1427
+ {
1428
+ "aria-expanded": isOpen,
1429
+ "aria-haspopup": "menu",
1430
+ "aria-label": action.label,
1431
+ className: "mira-toolbar__button",
1432
+ disabled: dynamicBoolean(action.disabled),
1433
+ onClick: onToggle,
1434
+ title: action.tooltip ?? action.label,
1435
+ type: "button",
1436
+ children: /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "mira-editor__icon" })
1437
+ }
1438
+ ),
1439
+ isOpen ? /* @__PURE__ */ jsx3(
1440
+ "div",
1441
+ {
1442
+ className: cx(
1443
+ "mira-react-dropdown__content",
1444
+ align === "end" && "mira-react-dropdown__content--end"
1445
+ ),
1446
+ "data-slot": "dropdown-menu-content",
1447
+ role: "menu",
1448
+ children: action.items.map((item, index) => /* @__PURE__ */ jsx3(
1449
+ MenuItem,
1450
+ {
1451
+ disabled: isMenuItemDisabled(item, dynamicBoolean),
1452
+ icon: resolveMenuItemIcon(item),
1453
+ item,
1454
+ onRun: onMenuItemRun
1455
+ },
1456
+ menuItemKey(item, index)
1457
+ ))
1458
+ }
1459
+ ) : null
1460
+ ] });
1461
+ }
1462
+ function ViewModeToggle({
1463
+ mode,
1464
+ onApplyMode,
1465
+ resolvedDefaultEditMode
1466
+ }) {
1467
+ const isPreview = mode === "preview";
1468
+ const Icon = isPreview ? PencilLine : BookOpen;
1469
+ const label = miraViewToggleLabel(mode);
1470
+ return /* @__PURE__ */ jsx3(
1471
+ "button",
1472
+ {
1473
+ "aria-label": label,
1474
+ className: "mira-toolbar__button",
1475
+ onClick: () => onApplyMode(resolveMiraViewToggleMode(mode, resolvedDefaultEditMode)),
1476
+ title: label,
1477
+ type: "button",
1478
+ children: /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "mira-editor__icon" })
1479
+ }
1480
+ );
1481
+ }
1482
+ function ViewModeDropdown({
1483
+ getIndentGuides,
1484
+ getIndentWidth,
1485
+ getIndentWithTabs,
1486
+ isOpen,
1487
+ items,
1488
+ onApplyMode,
1489
+ onSetIndentGuides,
1490
+ onSetIndentWidth,
1491
+ onSetIndentWithTabs,
1492
+ onToggle
1493
+ }) {
1494
+ const tabSizeOptions = [2, 4, 8];
1495
+ return /* @__PURE__ */ jsxs3("div", { className: "mira-react-dropdown", children: [
1496
+ /* @__PURE__ */ jsx3(
1497
+ "button",
1498
+ {
1499
+ "aria-expanded": isOpen,
1500
+ "aria-haspopup": "menu",
1501
+ "aria-label": miraViewOptionsLabel,
1502
+ className: "mira-toolbar__button",
1503
+ onClick: onToggle,
1504
+ title: miraViewOptionsLabel,
1505
+ type: "button",
1506
+ children: /* @__PURE__ */ jsx3(Ellipsis, { "aria-hidden": "true", className: "mira-editor__icon" })
1507
+ }
1508
+ ),
1509
+ isOpen ? /* @__PURE__ */ jsxs3(
1510
+ "div",
1511
+ {
1512
+ className: "mira-react-dropdown__content mira-react-dropdown__content--end",
1513
+ "data-slot": "dropdown-menu-content",
1514
+ role: "menu",
1515
+ children: [
1516
+ items.map((item) => {
1517
+ const Icon = item.icon;
1518
+ return /* @__PURE__ */ jsxs3(
1519
+ "button",
1520
+ {
1521
+ className: "mira-react-menu-button",
1522
+ "data-slot": "dropdown-menu-item",
1523
+ onClick: () => onApplyMode(item.mode),
1524
+ role: "menuitem",
1525
+ type: "button",
1526
+ children: [
1527
+ /* @__PURE__ */ jsx3(
1528
+ Icon,
1529
+ {
1530
+ "aria-hidden": "true",
1531
+ className: "mira-editor-toolbar__menu-icon"
1532
+ }
1533
+ ),
1534
+ /* @__PURE__ */ jsx3("span", { children: item.label }),
1535
+ item.checked ? /* @__PURE__ */ jsx3(
1536
+ Check,
1537
+ {
1538
+ "aria-hidden": "true",
1539
+ className: "mira-editor-toolbar__menu-check"
1540
+ }
1541
+ ) : null
1542
+ ]
1543
+ },
1544
+ item.mode
1545
+ );
1546
+ }),
1547
+ items.length > 0 ? /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-separator", role: "separator" }) : null,
1548
+ /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-label", children: "Editor" }),
1549
+ /* @__PURE__ */ jsxs3(
1550
+ "button",
1551
+ {
1552
+ className: "mira-react-menu-button",
1553
+ "data-slot": "dropdown-menu-item",
1554
+ onClick: () => onSetIndentGuides(!getIndentGuides()),
1555
+ role: "menuitem",
1556
+ type: "button",
1557
+ children: [
1558
+ getIndentGuides() ? /* @__PURE__ */ jsx3(
1559
+ Check,
1560
+ {
1561
+ "aria-hidden": "true",
1562
+ className: "mira-editor-toolbar__menu-icon"
1563
+ }
1564
+ ) : null,
1565
+ /* @__PURE__ */ jsx3("span", { children: "Indentation guides" })
1566
+ ]
1567
+ }
1568
+ ),
1569
+ /* @__PURE__ */ jsxs3(
1570
+ "button",
1571
+ {
1572
+ className: "mira-react-menu-button",
1573
+ "data-slot": "dropdown-menu-item",
1574
+ onClick: () => onSetIndentWithTabs(!getIndentWithTabs()),
1575
+ role: "menuitem",
1576
+ type: "button",
1577
+ children: [
1578
+ getIndentWithTabs() ? /* @__PURE__ */ jsx3(
1579
+ Check,
1580
+ {
1581
+ "aria-hidden": "true",
1582
+ className: "mira-editor-toolbar__menu-icon"
1583
+ }
1584
+ ) : null,
1585
+ /* @__PURE__ */ jsx3("span", { children: "Use tabs for indentation" })
1586
+ ]
1587
+ }
1588
+ ),
1589
+ /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-separator", role: "separator" }),
1590
+ /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-label", children: "Tab size" }),
1591
+ tabSizeOptions.map((size) => /* @__PURE__ */ jsxs3(
1592
+ "button",
1593
+ {
1594
+ className: "mira-react-menu-button",
1595
+ "data-slot": "dropdown-menu-item",
1596
+ onClick: () => onSetIndentWidth(size),
1597
+ role: "menuitem",
1598
+ type: "button",
1599
+ children: [
1600
+ getIndentWidth() === size ? /* @__PURE__ */ jsx3(
1601
+ Check,
1602
+ {
1603
+ "aria-hidden": "true",
1604
+ className: "mira-editor-toolbar__menu-icon"
1605
+ }
1606
+ ) : null,
1607
+ /* @__PURE__ */ jsxs3("span", { children: [
1608
+ size,
1609
+ " spaces"
1610
+ ] })
1611
+ ]
1612
+ },
1613
+ size
1614
+ ))
1615
+ ]
1616
+ }
1617
+ ) : null
1618
+ ] });
1619
+ }
1620
+ function MenuItem({
1621
+ disabled,
1622
+ icon: Icon,
1623
+ item,
1624
+ onRun
1625
+ }) {
1626
+ if (item.type === "separator") {
1627
+ return /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-separator", role: "separator" });
1628
+ }
1629
+ if (item.type === "label") {
1630
+ return /* @__PURE__ */ jsx3("div", { "data-slot": "dropdown-menu-label", children: item.label });
1631
+ }
1632
+ return /* @__PURE__ */ jsxs3(
1633
+ "button",
1634
+ {
1635
+ className: "mira-react-menu-button",
1636
+ "data-disabled": disabled ? "" : void 0,
1637
+ "data-slot": "dropdown-menu-item",
1638
+ disabled,
1639
+ onClick: () => onRun(item),
1640
+ role: "menuitem",
1641
+ type: "button",
1642
+ children: [
1643
+ Icon ? /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "mira-editor-toolbar__menu-icon" }) : null,
1644
+ /* @__PURE__ */ jsx3("span", { children: item.label }),
1645
+ item.shortcut ? /* @__PURE__ */ jsx3("span", { "data-slot": "dropdown-menu-shortcut", children: item.shortcut }) : null
1646
+ ]
1647
+ }
1648
+ );
1649
+ }
1650
+ function isDropdownAction(action) {
1651
+ return action.type === "dropdown";
1652
+ }
1653
+ function menuItemKey(item, index) {
1654
+ const fallback = item.type === "separator" ? index : item.label;
1655
+ return `${item.type ?? "item"}-${item.id ?? fallback}`;
1656
+ }
1657
+ function isMenuItemDisabled(item, dynamicBoolean) {
1658
+ return item.type !== "label" && item.type !== "separator" && dynamicBoolean(item.disabled);
1659
+ }
1660
+ function isMenuItemChecked(item, dynamicBoolean) {
1661
+ return item.type !== "label" && item.type !== "separator" && dynamicBoolean(item.checked);
1662
+ }
1663
+ function menuItemIcon(item, dynamicBoolean) {
1664
+ if (item.type === "label" || item.type === "separator") {
1665
+ return void 0;
1666
+ }
1667
+ return item.icon ?? (isMenuItemChecked(item, dynamicBoolean) ? Check : void 0);
1668
+ }
1669
+ function runMenuItem(item, actionContext, dynamicBoolean) {
1670
+ if (item.type === "label" || item.type === "separator" || isMenuItemDisabled(item, dynamicBoolean)) {
1671
+ return;
1672
+ }
1673
+ item.run(actionContext());
1674
+ }
1675
+
1676
+ // src/mira-editor.tsx
1677
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1678
+ var extensionToolbarIcons = {
1679
+ check: Check2,
1680
+ code: Code2,
1681
+ command: Command,
1682
+ image: Image2,
1683
+ link: Link2,
1684
+ play: Play,
1685
+ "rotate-ccw": RotateCcw,
1686
+ save: Save,
1687
+ sparkles: Sparkles,
1688
+ table: Table22,
1689
+ "wand-sparkles": WandSparkles
1690
+ };
1691
+ function isBlockMenuButton(action) {
1692
+ return action.type !== "dropdown" && action.placements?.includes("block-menu") === true;
1693
+ }
1694
+ var MiraEditor = forwardRef2(
1695
+ function MiraEditor2({
1696
+ assetResolver,
1697
+ authoring,
1698
+ className,
1699
+ defaultEditMode = defaultMiraEditorEditMode,
1700
+ defaultMode: defaultMode2 = defaultEditMode,
1701
+ defaultReadonly = false,
1702
+ defaultValue = "",
1703
+ editorClassName,
1704
+ extensions = [],
1705
+ fileAdapter,
1706
+ imageConfig,
1707
+ featureConfigs = {},
1708
+ features = {},
1709
+ frontmatterConfig,
1710
+ frontmatterOpen = true,
1711
+ headingIds = false,
1712
+ headingIdPrefix = "",
1713
+ htmlPolicy = "trusted",
1714
+ emoji = false,
1715
+ outline = false,
1716
+ outlineVariant = "floating",
1717
+ indentGuides: indentGuidesProp,
1718
+ indentWithTabs: indentWithTabsProp,
1719
+ indentWidth: indentWidthProp,
1720
+ lineWrapping = true,
1721
+ linkResolver,
1722
+ mode: modeProp,
1723
+ onChange,
1724
+ onFrontmatterChange,
1725
+ onIndentGuidesChange,
1726
+ onIndentWidthChange,
1727
+ onIndentWithTabsChange,
1728
+ onModeChange,
1729
+ onReadonlyChange,
1730
+ placeholder = "Start writing Markdown...",
1731
+ readonly: readonlyProp,
1732
+ sourcePath,
1733
+ spellcheck = true,
1734
+ theme,
1735
+ colorMode = "inherit",
1736
+ toolbarActions = [],
1737
+ toolbars = [],
1738
+ value: valueProp
1739
+ }, ref) {
1740
+ const editorRef = useRef5(null);
1741
+ const toolbarContextRef = useRef5(
1742
+ null
1743
+ );
1744
+ const [uncontrolledValue, setUncontrolledValue] = useState3(defaultValue);
1745
+ const [uncontrolledMode, setUncontrolledMode] = useState3(defaultMode2);
1746
+ const [uncontrolledReadonly, setUncontrolledReadonly] = useState3(defaultReadonly);
1747
+ const [uncontrolledIndentGuides, setUncontrolledIndentGuides] = useState3(true);
1748
+ const [uncontrolledIndentWithTabs, setUncontrolledIndentWithTabs] = useState3(true);
1749
+ const [uncontrolledIndentWidth, setUncontrolledIndentWidth] = useState3(4);
1750
+ const value = valueProp ?? uncontrolledValue;
1751
+ const mode = modeProp ?? uncontrolledMode;
1752
+ const readonly = readonlyProp ?? uncontrolledReadonly;
1753
+ const indentGuides = indentGuidesProp ?? uncontrolledIndentGuides;
1754
+ const indentWithTabs = indentWithTabsProp ?? uncontrolledIndentWithTabs;
1755
+ const indentWidth = indentWidthProp ?? uncontrolledIndentWidth;
1756
+ const resolvedFeatures = useMemo3(
1757
+ () => resolveMiraEditorFeatures(features),
1758
+ [features]
1759
+ );
1760
+ const modeOptions = useMemo3(
1761
+ () => resolveMiraEditorModes(features),
1762
+ [features]
1763
+ );
1764
+ const resolvedDefaultEditMode = useMemo3(
1765
+ () => resolveMiraEditorEditMode(defaultEditMode, modeOptions),
1766
+ [defaultEditMode, modeOptions]
1767
+ );
1768
+ const frameworkBlockMenuActions = useMemo3(
1769
+ () => [
1770
+ ...resolveMiraEditorToolbarActions({
1771
+ featureConfigs,
1772
+ toolbarActions
1773
+ }).filter(isBlockMenuButton).map((action) => ({ action, group: action.group ?? "Actions" })),
1774
+ ...resolveMiraEditorToolbarDefinitions({
1775
+ featureConfigs,
1776
+ toolbars
1777
+ }).flatMap(
1778
+ (toolbar) => toolbar.items.filter(isBlockMenuButton).map((action) => ({
1779
+ action,
1780
+ group: action.group ?? toolbar.label ?? "Actions"
1781
+ }))
1782
+ )
1783
+ ],
1784
+ [featureConfigs, toolbarActions, toolbars]
1785
+ );
1786
+ const frameworkBlockMenuExtension = useMemo3(
1787
+ () => frameworkBlockMenuActions.length > 0 ? defineMiraExtension({
1788
+ name: "mira-editor-react-block-menu-actions",
1789
+ blockActions: frameworkBlockMenuActions.map(
1790
+ ({ action, group }) => ({
1791
+ id: `mira-editor-${action.id}`,
1792
+ label: action.label,
1793
+ group,
1794
+ placements: ["block-menu"],
1795
+ shortcut: action.shortcut,
1796
+ disabled(context) {
1797
+ const base = toolbarContextRef.current;
1798
+ if (!base) {
1799
+ return true;
1800
+ }
1801
+ const disabled = action.disabled;
1802
+ const actionContext = createBlockToolbarActionContext(
1803
+ base,
1804
+ context
1805
+ );
1806
+ return typeof disabled === "function" ? disabled(actionContext) : disabled ?? false;
1807
+ },
1808
+ renderIcon(target) {
1809
+ const root = createRoot(target);
1810
+ const Icon = action.icon;
1811
+ root.render(
1812
+ /* @__PURE__ */ jsx4(Icon, { "aria-hidden": "true", className: "mira-editor__icon" })
1813
+ );
1814
+ return () => root.unmount();
1815
+ },
1816
+ run(context) {
1817
+ const base = toolbarContextRef.current;
1818
+ if (base) {
1819
+ action.run(
1820
+ createBlockToolbarActionContext(base, context)
1821
+ );
1822
+ }
1823
+ }
1824
+ })
1825
+ )
1826
+ }) : null,
1827
+ [frameworkBlockMenuActions]
1828
+ );
1829
+ const activeExtensions = useMemo3(
1830
+ () => [
1831
+ ...createMiraEditorExtensions({ featureConfigs, features }),
1832
+ ...frameworkBlockMenuExtension ? [frameworkBlockMenuExtension] : [],
1833
+ ...extensions
1834
+ ],
1835
+ [extensions, featureConfigs, features, frameworkBlockMenuExtension]
1836
+ );
1837
+ const blockControls = useMemo3(
1838
+ () => resolveMiraEditorBlockControls({ featureConfigs, features }),
1839
+ [featureConfigs, features]
1840
+ );
1841
+ const resolvedExtensionContributions = useMemo3(
1842
+ () => resolveMiraExtensions2(activeExtensions, {
1843
+ mode,
1844
+ readonly,
1845
+ sourcePath
1846
+ }),
1847
+ [activeExtensions, mode, readonly, sourcePath]
1848
+ );
1849
+ const extensionToolbars = useMemo3(() => {
1850
+ const groups = /* @__PURE__ */ new Map();
1851
+ for (const item of resolvedExtensionContributions.toolbarItems) {
1852
+ if (item.placements && !item.placements.includes("toolbar")) {
1853
+ continue;
1854
+ }
1855
+ const align = item.align ?? "start";
1856
+ const label = item.group ?? "Extensions";
1857
+ const key = `${align}:${label}`;
1858
+ let group = groups.get(key);
1859
+ if (!group) {
1860
+ group = {
1861
+ id: `extension-${slugify(label)}-${align}`,
1862
+ label,
1863
+ align,
1864
+ items: []
1865
+ };
1866
+ groups.set(key, group);
1867
+ }
1868
+ const Icon = extensionToolbarIcons[item.icon ?? "command"] ?? Command;
1869
+ group.items.push({
1870
+ id: `extension-${item.id}`,
1871
+ label: item.label,
1872
+ tooltip: item.tooltip,
1873
+ icon: Icon,
1874
+ disabled: () => editorRef.current ? !editorRef.current.isCommandEnabled(item.command) : false,
1875
+ run: () => {
1876
+ editorRef.current?.executeCommand(item.command);
1877
+ }
1878
+ });
1879
+ }
1880
+ return [...groups.values()];
1881
+ }, [resolvedExtensionContributions.toolbarItems]);
1882
+ const handleChange = useCallback3(
1883
+ (nextValue) => {
1884
+ if (valueProp === void 0) {
1885
+ setUncontrolledValue(nextValue);
1886
+ }
1887
+ onChange?.(nextValue);
1888
+ },
1889
+ [onChange, valueProp]
1890
+ );
1891
+ const applyMode = useCallback3(
1892
+ (nextMode) => {
1893
+ if (!modeOptions.includes(nextMode)) {
1894
+ return;
1895
+ }
1896
+ if (modeProp === void 0) {
1897
+ setUncontrolledMode(nextMode);
1898
+ }
1899
+ onModeChange?.(nextMode);
1900
+ },
1901
+ [modeOptions, modeProp, onModeChange]
1902
+ );
1903
+ const applyReadonly = useCallback3(
1904
+ (nextReadonly) => {
1905
+ if (readonlyProp === void 0) {
1906
+ setUncontrolledReadonly(nextReadonly);
1907
+ }
1908
+ onReadonlyChange?.(nextReadonly);
1909
+ },
1910
+ [onReadonlyChange, readonlyProp]
1911
+ );
1912
+ const applyIndentGuides = useCallback3(
1913
+ (nextEnabled) => {
1914
+ if (indentGuidesProp === void 0) {
1915
+ setUncontrolledIndentGuides(nextEnabled);
1916
+ }
1917
+ onIndentGuidesChange?.(nextEnabled);
1918
+ },
1919
+ [indentGuidesProp, onIndentGuidesChange]
1920
+ );
1921
+ const applyIndentWithTabs = useCallback3(
1922
+ (nextEnabled) => {
1923
+ if (indentWithTabsProp === void 0) {
1924
+ setUncontrolledIndentWithTabs(nextEnabled);
1925
+ }
1926
+ onIndentWithTabsChange?.(nextEnabled);
1927
+ },
1928
+ [indentWithTabsProp, onIndentWithTabsChange]
1929
+ );
1930
+ const applyIndentWidth = useCallback3(
1931
+ (nextWidth) => {
1932
+ if (indentWidthProp === void 0) {
1933
+ setUncontrolledIndentWidth(nextWidth);
1934
+ }
1935
+ onIndentWidthChange?.(nextWidth);
1936
+ },
1937
+ [indentWidthProp, onIndentWidthChange]
1938
+ );
1939
+ const handleSetMarkdown = useCallback3(
1940
+ (markdown) => {
1941
+ if (valueProp === void 0) {
1942
+ setUncontrolledValue(markdown);
1943
+ }
1944
+ editorRef.current?.setMarkdown(markdown);
1945
+ },
1946
+ [valueProp]
1947
+ );
1948
+ const handleSetMode = useCallback3(
1949
+ (nextMode) => {
1950
+ applyMode(nextMode);
1951
+ editorRef.current?.setMode(nextMode);
1952
+ },
1953
+ [applyMode]
1954
+ );
1955
+ const handleSetReadonly = useCallback3(
1956
+ (nextReadonly) => {
1957
+ applyReadonly(nextReadonly);
1958
+ editorRef.current?.setReadonly(nextReadonly);
1959
+ },
1960
+ [applyReadonly]
1961
+ );
1962
+ const toolbarContext = useMemo3(
1963
+ () => ({
1964
+ applyMarkdownAction: (action) => editorRef.current?.applyMarkdownAction(action) ?? false,
1965
+ focus: () => editorRef.current?.focus(),
1966
+ getIndentGuides: () => indentGuides,
1967
+ getIndentWidth: () => indentWidth,
1968
+ getIndentWithTabs: () => indentWithTabs,
1969
+ getMarkdown: () => editorRef.current?.getMarkdown() ?? value,
1970
+ getMode: () => editorRef.current?.getMode() ?? mode,
1971
+ getSelection: () => editorRef.current?.getSelection() ?? null,
1972
+ insertImage: () => editorRef.current?.insertImage(),
1973
+ insertMarkdown: (markdown) => editorRef.current?.insertMarkdown(markdown),
1974
+ mode,
1975
+ readonly,
1976
+ setIndentGuides: applyIndentGuides,
1977
+ setIndentWidth: applyIndentWidth,
1978
+ setIndentWithTabs: applyIndentWithTabs,
1979
+ setMarkdown: handleSetMarkdown,
1980
+ setMode: handleSetMode,
1981
+ setReadonly: handleSetReadonly,
1982
+ setSelection: (selection) => editorRef.current?.setSelection(selection),
1983
+ value
1984
+ }),
1985
+ [
1986
+ handleSetMarkdown,
1987
+ handleSetMode,
1988
+ handleSetReadonly,
1989
+ applyIndentGuides,
1990
+ applyIndentWidth,
1991
+ applyIndentWithTabs,
1992
+ indentGuides,
1993
+ indentWidth,
1994
+ indentWithTabs,
1995
+ mode,
1996
+ readonly,
1997
+ value
1998
+ ]
1999
+ );
2000
+ toolbarContextRef.current = toolbarContext;
2001
+ useImperativeHandle2(
2002
+ ref,
2003
+ () => ({
2004
+ applyMarkdownAction(action) {
2005
+ if (readonly || mode === "preview") {
2006
+ return false;
2007
+ }
2008
+ return editorRef.current?.applyMarkdownAction(action) ?? false;
2009
+ },
2010
+ executeCommand(commandId) {
2011
+ return editorRef.current?.executeCommand(commandId) ?? false;
2012
+ },
2013
+ focus() {
2014
+ editorRef.current?.focus();
2015
+ },
2016
+ getMarkdown() {
2017
+ return editorRef.current?.getMarkdown() ?? value;
2018
+ },
2019
+ getCommands() {
2020
+ return editorRef.current?.getCommands() ?? resolvedExtensionContributions.commands;
2021
+ },
2022
+ getMode() {
2023
+ return editorRef.current?.getMode() ?? mode;
2024
+ },
2025
+ getSelection() {
2026
+ return editorRef.current?.getSelection() ?? null;
2027
+ },
2028
+ insertMarkdown(markdown) {
2029
+ editorRef.current?.insertMarkdown(markdown);
2030
+ },
2031
+ insertImage() {
2032
+ editorRef.current?.insertImage();
2033
+ },
2034
+ isCommandEnabled(commandId) {
2035
+ return editorRef.current?.isCommandEnabled(commandId) ?? false;
2036
+ },
2037
+ setMarkdown: handleSetMarkdown,
2038
+ setMode: handleSetMode,
2039
+ setReadonly: handleSetReadonly,
2040
+ setSelection(selection) {
2041
+ editorRef.current?.setSelection(selection);
2042
+ }
2043
+ }),
2044
+ [
2045
+ handleSetMarkdown,
2046
+ handleSetMode,
2047
+ handleSetReadonly,
2048
+ mode,
2049
+ readonly,
2050
+ resolvedExtensionContributions.commands,
2051
+ value
2052
+ ]
2053
+ );
2054
+ return /* @__PURE__ */ jsxs4(
2055
+ "div",
2056
+ {
2057
+ className: cx(
2058
+ "mira-editor",
2059
+ miraColorModeClassName(colorMode),
2060
+ className
2061
+ ),
2062
+ "data-mira-theme": normalizeMiraTheme(theme),
2063
+ "data-mira-color-mode": miraColorModeAttribute(colorMode),
2064
+ "data-mode": mode,
2065
+ "data-readonly": readonly,
2066
+ children: [
2067
+ resolvedFeatures[MiraFeature.Toolbar] ? /* @__PURE__ */ jsx4(
2068
+ MiraEditorToolbar,
2069
+ {
2070
+ context: toolbarContext,
2071
+ defaultEditMode: resolvedDefaultEditMode,
2072
+ featureConfigs,
2073
+ features,
2074
+ indentGuides,
2075
+ indentWidth,
2076
+ indentWithTabs,
2077
+ mode,
2078
+ modeOptions,
2079
+ onIndentGuidesChange: applyIndentGuides,
2080
+ onIndentWidthChange: applyIndentWidth,
2081
+ onIndentWithTabsChange: applyIndentWithTabs,
2082
+ readonly,
2083
+ toolbarActions,
2084
+ toolbars: [...extensionToolbars, ...toolbars],
2085
+ value
2086
+ }
2087
+ ) : null,
2088
+ /* @__PURE__ */ jsx4("div", { className: "mira-editor__editor", children: /* @__PURE__ */ jsx4(
2089
+ Mira,
2090
+ {
2091
+ assetResolver,
2092
+ authoring,
2093
+ blockControls,
2094
+ className: editorClassName,
2095
+ defaultMode: defaultMode2,
2096
+ defaultReadonly,
2097
+ defaultValue,
2098
+ extensions: activeExtensions,
2099
+ fileAdapter,
2100
+ imageConfig,
2101
+ frontmatterConfig,
2102
+ frontmatterOpen: resolvedFeatures[MiraFeature.Frontmatter] && frontmatterOpen,
2103
+ headingIds,
2104
+ headingIdPrefix,
2105
+ htmlPolicy,
2106
+ emoji,
2107
+ outline,
2108
+ outlineVariant,
2109
+ indentGuides,
2110
+ indentWidth,
2111
+ indentWithTabs,
2112
+ lineWrapping,
2113
+ linkResolver,
2114
+ mode,
2115
+ onChange: handleChange,
2116
+ onFrontmatterChange,
2117
+ onModeChange: applyMode,
2118
+ onReadonlyChange: applyReadonly,
2119
+ placeholder,
2120
+ readonly,
2121
+ ref: editorRef,
2122
+ sourcePath,
2123
+ spellcheck,
2124
+ theme,
2125
+ colorMode,
2126
+ toolbar: false,
2127
+ value
2128
+ }
2129
+ ) })
2130
+ ]
2131
+ }
2132
+ );
2133
+ }
2134
+ );
2135
+ MiraEditor.displayName = "MiraEditor";
2136
+ function createBlockToolbarActionContext(base, context) {
2137
+ return {
2138
+ ...base,
2139
+ block: context.block,
2140
+ blocks: context.blocks,
2141
+ handle: context.handle,
2142
+ affectedRange: context.affectedRange,
2143
+ replaceRange: context.replaceRange
2144
+ };
2145
+ }
2146
+ function slugify(value) {
2147
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || "extensions";
2148
+ }
2149
+
2150
+ // src/index.ts
2151
+ var index_default = Mira;
2152
+ export {
2153
+ Mira,
2154
+ MiraEditor,
2155
+ MiraEditorToolbar,
2156
+ MiraFeature,
2157
+ createMiraEditorExtensions,
2158
+ index_default as default,
2159
+ defaultMiraEditorEditMode,
2160
+ defaultMiraEditorFeatures,
2161
+ resolveMiraEditorBlockControls,
2162
+ resolveMiraEditorEditMode,
2163
+ resolveMiraEditorFeatures,
2164
+ resolveMiraEditorModes,
2165
+ resolveMiraEditorSlashCommands,
2166
+ resolveMiraEditorToolbarActions,
2167
+ resolveMiraEditorToolbarDefinitions,
2168
+ resolveMiraEditorToolbarItems
2169
+ };