@pantoken/tinymce 0.1.0 → 0.2.0

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.d.mts CHANGED
@@ -1,16 +1,8 @@
1
1
  import { CdnFile, CdnProvider } from "@pantoken/cdn";
2
+ import { PageLayout, pageLayouts } from "@pantoken/plugin-layouts";
2
3
  import { LogoMeta, Product, logos, products } from "@pantoken/plugin-logos";
3
4
  import { Editor } from "tinymce";
4
5
  import { CssDocEntry, CssDocEntry as CssDocEntry$1 } from "@cssdoc/core";
5
- //#region src/content-css.d.ts
6
- /** Resolves a set of {@link CdnFile}s to URLs suitable for TinyMCE's `content_css` init option. */
7
- declare function pantokenContentCssUrls(assets: readonly CdnFile[], provider?: string | CdnProvider): string[];
8
- /**
9
- * Appends a `<link rel="stylesheet">` to the editor's content document `<head>` at runtime.
10
- * Idempotent per URL — calling this twice with the same `url` is a no-op the second time.
11
- */
12
- declare function injectContentStylesheet(editor: Editor, url: string): void;
13
- //#endregion
14
6
  //#region src/types.d.ts
15
7
  /** A starter page template offered by {@link createTemplatesPlugin}. */
16
8
  interface StarterTemplate {
@@ -19,6 +11,17 @@ interface StarterTemplate {
19
11
  /** Raw HTML inserted (replacing the whole document, after a confirm) when chosen. */
20
12
  content: string;
21
13
  }
14
+ /** Called when a picker inserts an item whose CSS isn't yet in the caller's asset list. */
15
+ type MissingAssetHandler = (file: CdnFile) => void;
16
+ //#endregion
17
+ //#region src/content-css.d.ts
18
+ /** Resolves a set of {@link CdnFile}s to URLs suitable for TinyMCE's `content_css` init option. */
19
+ export declare function pantokenContentCssUrls(assets: readonly CdnFile[], provider?: string | CdnProvider): string[];
20
+ /**
21
+ * Appends a `<link rel="stylesheet">` to the editor's content document `<head>` at runtime.
22
+ * Idempotent per URL — calling this twice with the same `url` is a no-op the second time.
23
+ */
24
+ export declare function injectContentStylesheet(editor: Editor, url: string): void;
22
25
  //#endregion
23
26
  //#region src/plugins/templates.d.ts
24
27
  /** Options for {@link createTemplatesPlugin}. */
@@ -29,7 +32,22 @@ interface TemplatesPluginOptions {
29
32
  onInsert?: (template: StarterTemplate) => void;
30
33
  }
31
34
  /** Builds the `tinymce.PluginManager.add` callback for the "Insert template" plugin. */
32
- declare function createTemplatesPlugin(options: TemplatesPluginOptions): (editor: Editor) => {};
35
+ export declare function createTemplatesPlugin(options: TemplatesPluginOptions): (editor: Editor) => {};
36
+ //#endregion
37
+ //#region src/plugins/layouts.d.ts
38
+ /** Options for {@link createLayoutsPlugin}. */
39
+ interface LayoutsPluginOptions {
40
+ /** The page layouts offered in the "Insert layout" picker. Defaults to {@link pageLayouts}. */
41
+ layouts?: readonly PageLayout[];
42
+ /** Called after a layout is inserted (e.g. to refresh a live preview). */
43
+ onInsert?: (layout: PageLayout) => void;
44
+ }
45
+ /** The plugin name to pass in TinyMCE's `plugins`/`toolbar` init options. */
46
+ export declare const LAYOUTS_PLUGIN_NAME = "pantoken_layouts";
47
+ /** The toolbar button/menu item name registered by this plugin. */
48
+ export declare const LAYOUTS_TOOLBAR_NAME = "pantokenLayouts";
49
+ /** Builds the `tinymce.PluginManager.add` callback for the "Insert layout" plugin. */
50
+ export declare function createLayoutsPlugin(options?: LayoutsPluginOptions): (editor: Editor) => {};
33
51
  //#endregion
34
52
  //#region src/icons.d.ts
35
53
  /**
@@ -49,12 +67,12 @@ interface TaggedIcon {
49
67
  * or a manifest published alongside formats/components model.json.
50
68
  * For now, only simple-icons are included.
51
69
  */
52
- declare function loadAllIcons(): Promise<TaggedIcon[]>;
70
+ export declare function loadAllIcons(): Promise<TaggedIcon[]>;
53
71
  /**
54
72
  * Compute the CdnFile for an icon given its source.
55
73
  * Returns `{ package, path }` for use with \@pantoken/cdn's buildFileUrl.
56
74
  */
57
- declare function getIconCdnFile(icon: TaggedIcon): {
75
+ export declare function getIconCdnFile(icon: TaggedIcon): {
58
76
  package: string;
59
77
  path: string;
60
78
  };
@@ -63,20 +81,20 @@ declare function getIconCdnFile(icon: TaggedIcon): {
63
81
  /**
64
82
  * Find a component/utility/custom entry by name.
65
83
  */
66
- declare function findEntry(name: string): CssDocEntry$1 | undefined;
84
+ export declare function findEntry(name: string): CssDocEntry$1 | undefined;
67
85
  /**
68
86
  * List all components (kind: "component").
69
87
  */
70
- declare function listComponents(): CssDocEntry$1[];
88
+ export declare function listComponents(): CssDocEntry$1[];
71
89
  /**
72
90
  * List all utilities (kind: "utility").
73
91
  */
74
- declare function listUtilities(): CssDocEntry$1[];
92
+ export declare function listUtilities(): CssDocEntry$1[];
75
93
  /**
76
94
  * Get all modifier suggestions for a component/utility, filtered by an optional prefix.
77
95
  * Returns array of `{ name, prop, value?, description }` objects.
78
96
  */
79
- declare function getModifierSuggestions(entryName: string, prefix?: string): Array<{
97
+ export declare function getModifierSuggestions(entryName: string, prefix?: string): Array<{
80
98
  name: string;
81
99
  prop: string;
82
100
  value?: string;
@@ -86,7 +104,7 @@ declare function getModifierSuggestions(entryName: string, prefix?: string): Arr
86
104
  * Validate a pantoken class token (e.g., "instui-button", "instui-button.-color-primary").
87
105
  * Returns an array of validation errors (empty if valid).
88
106
  */
89
- declare function validateClassToken(token: string): string[];
107
+ export declare function validateClassToken(token: string): string[];
90
108
  //#endregion
91
109
  //#region src/plugins/components.d.ts
92
110
  /**
@@ -95,7 +113,7 @@ declare function validateClassToken(token: string): string[];
95
113
  interface ComponentsPickerOptions {
96
114
  model: CssDocEntry[];
97
115
  currentAssets: CdnFile[];
98
- onMissingAsset?: (asset: CdnFile) => void;
116
+ onMissingAsset?: MissingAssetHandler;
99
117
  }
100
118
  /**
101
119
  * Create the components picker plugin factory.
@@ -107,7 +125,7 @@ interface ComponentsPickerOptions {
107
125
  * createComponentsPlugin(`{ model, currentAssets, onMissingAsset }`)
108
126
  * );
109
127
  */
110
- declare function createComponentsPlugin(options: ComponentsPickerOptions): (editor: Editor) => void;
128
+ export declare function createComponentsPlugin(options: ComponentsPickerOptions): (editor: Editor) => void;
111
129
  //#endregion
112
130
  //#region src/plugins/icons.d.ts
113
131
  /**
@@ -116,13 +134,13 @@ declare function createComponentsPlugin(options: ComponentsPickerOptions): (edit
116
134
  interface IconsPickerOptions {
117
135
  icons: TaggedIcon[];
118
136
  currentAssets: CdnFile[];
119
- onMissingAsset?: (asset: CdnFile) => void;
137
+ onMissingAsset?: MissingAssetHandler;
120
138
  }
121
139
  /**
122
140
  * Create the icons picker plugin factory.
123
141
  * Returns a function suitable for `tinymce.PluginManager.add()`.
124
142
  */
125
- declare function createIconsPlugin(options: IconsPickerOptions): (editor: Editor) => void;
143
+ export declare function createIconsPlugin(options: IconsPickerOptions): (editor: Editor) => void;
126
144
  //#endregion
127
145
  //#region src/plugins/logos.d.ts
128
146
  /**
@@ -132,13 +150,13 @@ interface LogosPickerOptions {
132
150
  logos: readonly LogoMeta[];
133
151
  products: readonly Product[];
134
152
  currentAssets: CdnFile[];
135
- onMissingAsset?: (asset: CdnFile) => void;
153
+ onMissingAsset?: MissingAssetHandler;
136
154
  }
137
155
  /**
138
156
  * Create the logos picker plugin factory.
139
157
  * Returns a function suitable for `tinymce.PluginManager.add()`.
140
158
  */
141
- declare function createLogosPlugin(options: LogosPickerOptions): (editor: Editor) => void;
159
+ export declare function createLogosPlugin(options: LogosPickerOptions): (editor: Editor) => void;
142
160
  //#endregion
143
161
  //#region src/codemirror/lint.d.ts
144
162
  /**
@@ -160,7 +178,7 @@ interface PantokenLinterOptions {
160
178
  * ],
161
179
  * \});
162
180
  */
163
- declare function pantokenHtmlLinter(_options: PantokenLinterOptions): import("@codemirror/state").Extension;
181
+ export declare function pantokenHtmlLinter(_options: PantokenLinterOptions): import("@codemirror/state").Extension;
164
182
  //#endregion
165
183
  //#region src/codemirror/autocomplete.d.ts
166
184
  /**
@@ -182,6 +200,6 @@ interface AutocompleteOptions {
182
200
  * ],
183
201
  * \});
184
202
  */
185
- declare function pantokenHtmlCompletion(options: AutocompleteOptions): import("@codemirror/state").Extension;
203
+ export declare function pantokenHtmlCompletion(options: AutocompleteOptions): import("@codemirror/state").Extension;
186
204
  //#endregion
187
- export { type CssDocEntry, type LogoMeta, type Product, type TaggedIcon, createComponentsPlugin, createIconsPlugin, createLogosPlugin, createTemplatesPlugin, findEntry, getIconCdnFile, getModifierSuggestions, injectContentStylesheet, listComponents, listUtilities, loadAllIcons, logos, pantokenContentCssUrls, pantokenHtmlCompletion, pantokenHtmlLinter, products, validateClassToken };
205
+ export { type CssDocEntry, type LayoutsPluginOptions, type LogoMeta, type PageLayout, type Product, type TaggedIcon, logos, pageLayouts, products };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { buildFileUrls } from "@pantoken/cdn";
2
+ import { pageLayouts } from "@pantoken/plugin-layouts";
2
3
  import componentsModel from "@pantoken/components/model.json" with { type: "json" };
3
4
  import customComponentsModel from "@pantoken/plugin-custom-components/model.json" with { type: "json" };
4
5
  import simpleIconsManifest from "@pantoken/plugin-simple-icons/manifest.json" with { type: "json" };
@@ -32,6 +33,18 @@ function injectContentStylesheet(editor, url) {
32
33
  link.href = url;
33
34
  head.append(link);
34
35
  }
36
+ /**
37
+ * Tracks `cssFile` in `currentAssets` (once per path, notifying `onMissingAsset`) and injects its
38
+ * stylesheet into the editor's content area — the shared "insert an item whose CSS isn't loaded
39
+ * yet" tail shared by the components/icons/logos pickers.
40
+ */
41
+ function trackAndInjectAsset(editor, cssFile, target) {
42
+ if (!target.currentAssets.find((a) => a.path === cssFile.path)) {
43
+ target.currentAssets.push(cssFile);
44
+ target.onMissingAsset?.(cssFile);
45
+ }
46
+ injectContentStylesheet(editor, `https://unpkg.com/${cssFile.package}@latest/${cssFile.path}`);
47
+ }
35
48
  //#endregion
36
49
  //#region src/plugins/templates.ts
37
50
  /** The toolbar button/menu item name registered by this plugin. */
@@ -89,6 +102,65 @@ function createTemplatesPlugin(options) {
89
102
  };
90
103
  }
91
104
  //#endregion
105
+ //#region src/plugins/layouts.ts
106
+ /** The plugin name to pass in TinyMCE's `plugins`/`toolbar` init options. */
107
+ const LAYOUTS_PLUGIN_NAME = "pantoken_layouts";
108
+ /** The toolbar button/menu item name registered by this plugin. */
109
+ const LAYOUTS_TOOLBAR_NAME = "pantokenLayouts";
110
+ /** Builds the `tinymce.PluginManager.add` callback for the "Insert layout" plugin. */
111
+ function createLayoutsPlugin(options = {}) {
112
+ const { layouts = pageLayouts, onInsert } = options;
113
+ return function pantokenLayoutsPlugin(editor) {
114
+ const openDialog = () => {
115
+ editor.windowManager.open({
116
+ title: "Insert layout",
117
+ body: {
118
+ type: "panel",
119
+ items: [{
120
+ type: "selectbox",
121
+ name: "layout",
122
+ label: "Starter layout",
123
+ items: layouts.map((l) => ({
124
+ value: l.name,
125
+ text: l.title
126
+ }))
127
+ }]
128
+ },
129
+ initialData: { layout: layouts[0]?.name ?? "" },
130
+ buttons: [{
131
+ type: "cancel",
132
+ text: "Cancel"
133
+ }, {
134
+ type: "submit",
135
+ text: "Insert",
136
+ primary: true
137
+ }],
138
+ onSubmit: (api) => {
139
+ const { layout } = api.getData();
140
+ const chosen = layouts.find((l) => l.name === layout);
141
+ api.close();
142
+ if (!chosen) return;
143
+ editor.windowManager.confirm(`Replace the current content with the "${chosen.title}" layout?`, (confirmed) => {
144
+ if (!confirmed) return;
145
+ editor.setContent(chosen.html);
146
+ onInsert?.(chosen);
147
+ });
148
+ }
149
+ });
150
+ };
151
+ editor.ui.registry.addButton(LAYOUTS_TOOLBAR_NAME, {
152
+ text: "Layouts",
153
+ tooltip: "Insert a starter layout",
154
+ onAction: openDialog
155
+ });
156
+ editor.ui.registry.addMenuItem(LAYOUTS_TOOLBAR_NAME, {
157
+ text: "Layout…",
158
+ onAction: openDialog
159
+ });
160
+ return {};
161
+ };
162
+ }
163
+ //#endregion
92
164
  //#region src/cssdoc/model.ts
93
165
  const MERGED_MODEL = [...componentsModel, ...customComponentsModel];
94
166
  const INDEX_BY_NAME = new Map(MERGED_MODEL.map((entry) => [entry.name, entry]));
@@ -200,7 +272,7 @@ function getIconCdnFile(icon) {
200
272
  * );
201
273
  */
202
274
  function createComponentsPlugin(options) {
203
- return (editor) => {
275
+ return function pantokenComponentsPlugin(editor) {
204
276
  const componentList = buildComponentList(options.model);
205
277
  editor.ui.registry.addButton("pantokenComponents", {
206
278
  text: "Components",
@@ -266,15 +338,10 @@ function openComponentsDialog(editor, components, options) {
266
338
  if (component && component.examples.length > 0) {
267
339
  const html = component.examples[0];
268
340
  editor.insertContent(html);
269
- const cssFile = {
341
+ trackAndInjectAsset(editor, {
270
342
  package: "@pantoken/components",
271
343
  path: `dist/${component.name}.css`
272
- };
273
- if (!options.currentAssets.find((a) => a.path === cssFile.path)) {
274
- options.currentAssets.push(cssFile);
275
- if (options.onMissingAsset) options.onMissingAsset(cssFile);
276
- }
277
- injectContentStylesheet(editor, `https://unpkg.com/${cssFile.package}@latest/${cssFile.path}`);
344
+ }, options);
278
345
  }
279
346
  api.close();
280
347
  }
@@ -288,7 +355,7 @@ const ICONS_PER_PAGE = 50;
288
355
  * Returns a function suitable for `tinymce.PluginManager.add()`.
289
356
  */
290
357
  function createIconsPlugin(options) {
291
- return (editor) => {
358
+ return function pantokenIconsPlugin(editor) {
292
359
  editor.ui.registry.addButton("pantokenIcons", {
293
360
  text: "Icons",
294
361
  tooltip: "Insert an icon",
@@ -363,12 +430,7 @@ function renderIconGrid(icons, page, perPage, _onSelect) {
363
430
  function insertIcon(editor, icon, options) {
364
431
  const html = generateIconHtml(icon);
365
432
  editor.insertContent(html);
366
- const cssFile = getIconCdnFile(icon);
367
- if (!options.currentAssets.find((a) => a.path === cssFile.path)) {
368
- options.currentAssets.push(cssFile);
369
- if (options.onMissingAsset) options.onMissingAsset(cssFile);
370
- }
371
- injectContentStylesheet(editor, `https://unpkg.com/${cssFile.package}@latest/${cssFile.path}`);
433
+ trackAndInjectAsset(editor, getIconCdnFile(icon), options);
372
434
  }
373
435
  /**
374
436
  * Generate the HTML snippet for inserting an icon.
@@ -389,7 +451,7 @@ function generateIconHtml(icon) {
389
451
  * Returns a function suitable for `tinymce.PluginManager.add()`.
390
452
  */
391
453
  function createLogosPlugin(options) {
392
- return (editor) => {
454
+ return function pantokenLogosPlugin(editor) {
393
455
  editor.ui.registry.addButton("pantokenLogos", {
394
456
  text: "Logos",
395
457
  tooltip: "Insert a logo",
@@ -405,6 +467,9 @@ function createLogosPlugin(options) {
405
467
  * Open the logos picker dialog.
406
468
  */
407
469
  function openLogosDialog(editor, options) {
470
+ let selectedProduct;
471
+ let selectedLayout;
472
+ let selectedColorMode;
408
473
  const productItems = options.products.map((p) => ({
409
474
  text: p,
410
475
  value: p
@@ -419,7 +484,9 @@ function openLogosDialog(editor, options) {
419
484
  name: "product",
420
485
  label: "Product",
421
486
  items: productItems,
422
- onChange: (_api) => {}
487
+ onChange: (api) => {
488
+ selectedProduct = (api?.target)?.value ?? selectedProduct;
489
+ }
423
490
  },
424
491
  {
425
492
  type: "selectbox",
@@ -440,7 +507,7 @@ function openLogosDialog(editor, options) {
440
507
  }
441
508
  ],
442
509
  onChange: (api) => {
443
- api.target.value;
510
+ selectedLayout = (api?.target)?.value ?? selectedLayout;
444
511
  }
445
512
  },
446
513
  {
@@ -462,7 +529,7 @@ function openLogosDialog(editor, options) {
462
529
  }
463
530
  ],
464
531
  onChange: (api) => {
465
- api.target.value;
532
+ selectedColorMode = (api?.target)?.value ?? selectedColorMode;
466
533
  }
467
534
  }
468
535
  ]
@@ -471,16 +538,35 @@ function openLogosDialog(editor, options) {
471
538
  text: "Insert",
472
539
  type: "submit",
473
540
  primary: true,
474
- disabled: true
541
+ disabled: !selectedProduct
475
542
  }, {
476
543
  text: "Cancel",
477
544
  type: "cancel"
478
545
  }],
479
546
  onSubmit: (api) => {
547
+ if (selectedProduct && selectedLayout && selectedColorMode) insertLogo(editor, selectedProduct, selectedLayout, selectedColorMode, options);
480
548
  api.close();
481
549
  }
482
550
  });
483
551
  }
552
+ /**
553
+ * Insert the selected logo into the editor.
554
+ */
555
+ function insertLogo(editor, productId, layout, colorMode, options) {
556
+ const html = generateLogoHtml(productId, layout, colorMode);
557
+ editor.insertContent(html);
558
+ trackAndInjectAsset(editor, {
559
+ package: "@pantoken/plugin-logos",
560
+ path: `dist/${productId}-${layout}-${colorMode}.css`
561
+ }, options);
562
+ }
563
+ /**
564
+ * Generate the HTML snippet for inserting a logo.
565
+ * Uses an `<img>` tag pointing to the SVG asset with a data attribute for variant.
566
+ */
567
+ function generateLogoHtml(productId, layout, colorMode) {
568
+ return `<img class="pantoken-logo" data-product="${productId}" data-layout="${layout}" data-color-mode="${colorMode}" src="about:blank" alt="${productId} logo" style="max-width: 200px;" />`;
569
+ }
484
570
  //#endregion
485
571
  //#region src/codemirror/lint.ts
486
572
  /**
@@ -563,8 +649,9 @@ function pantokenHtmlCompletion(options) {
563
649
  const partial = lastSpace === -1 ? classString : classString.slice(lastSpace + 1);
564
650
  if (!partial.startsWith("instui-")) return null;
565
651
  const completions = generateCompletions(partial, options.model);
652
+ const lastDash = partial.lastIndexOf("-");
566
653
  return {
567
- from: pos - partial.length,
654
+ from: pos - (partial.length - (lastDash + 1)),
568
655
  options: completions
569
656
  };
570
657
  }] });
@@ -603,4 +690,4 @@ function generateCompletions(partial, _model) {
603
690
  return completions;
604
691
  }
605
692
  //#endregion
606
- export { createComponentsPlugin, createIconsPlugin, createLogosPlugin, createTemplatesPlugin, findEntry, getIconCdnFile, getModifierSuggestions, injectContentStylesheet, listComponents, listUtilities, loadAllIcons, logos, pantokenContentCssUrls, pantokenHtmlCompletion, pantokenHtmlLinter, products, validateClassToken };
693
+ export { LAYOUTS_PLUGIN_NAME, LAYOUTS_TOOLBAR_NAME, createComponentsPlugin, createIconsPlugin, createLayoutsPlugin, createLogosPlugin, createTemplatesPlugin, findEntry, getIconCdnFile, getModifierSuggestions, injectContentStylesheet, listComponents, listUtilities, loadAllIcons, logos, pageLayouts, pantokenContentCssUrls, pantokenHtmlCompletion, pantokenHtmlLinter, products, validateClassToken };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pantoken/tinymce",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared TinyMCE + CodeMirror editor integration for pantoken: content-CSS wiring, an Insert Template plugin, components/icons/logos pickers with dynamic @import injection, and CodeMirror HTML lint/autocomplete driven by cssdoc.",
5
- "homepage": "https://pantoken.iywahl.com",
5
+ "homepage": "https://pantoken.app",
6
6
  "bugs": "https://github.com/thedannywahl/pantoken/issues",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -24,25 +24,26 @@
24
24
  "provenance": true
25
25
  },
26
26
  "dependencies": {
27
- "@cssdoc/core": "^0.14.2",
28
- "@pantoken/cdn": "0.1.0",
29
- "@pantoken/components": "1.0.3",
30
- "@pantoken/plugin-custom-components": "0.3.0",
31
- "@pantoken/plugin-logos": "0.3.6",
32
- "@pantoken/plugin-simple-icons": "0.3.6"
27
+ "@cssdoc/core": "^0.15.2",
28
+ "@pantoken/cdn": "0.2.0",
29
+ "@pantoken/components": "1.1.0",
30
+ "@pantoken/plugin-custom-components": "0.3.1",
31
+ "@pantoken/plugin-layouts": "0.3.0",
32
+ "@pantoken/plugin-logos": "0.4.0",
33
+ "@pantoken/plugin-simple-icons": "0.3.7"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@codemirror/autocomplete": "^6.20.3",
36
37
  "@codemirror/lang-html": "^6.4.12",
37
38
  "@codemirror/lint": "^6.9.7",
38
- "@codemirror/state": "^6.7.1",
39
- "@codemirror/view": "^6.28.0",
40
- "@types/node": "^26.4.0",
39
+ "@codemirror/state": "^6.7.4",
40
+ "@codemirror/view": "^6.43.11",
41
+ "@types/node": "^26.5.0",
41
42
  "codemirror": "^6.0.2",
42
- "tinymce": "^5.9",
43
+ "tinymce": "^5.10.9",
43
44
  "typescript": "^7.0.2",
44
- "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
45
- "vite-plus": "0.3.0"
45
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
46
+ "vite-plus": "0.3.1"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "@codemirror/autocomplete": "^6.20.3",