@scalar/use-codemirror 0.14.9 → 0.14.10

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.
@@ -1,362 +1,319 @@
1
- import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
2
- import { history, historyKeymap, indentWithTab, insertNewline } from "@codemirror/commands";
3
- import { css } from "@codemirror/lang-css";
4
- import { html } from "@codemirror/lang-html";
5
- import { json } from "@codemirror/lang-json";
6
- import { xml } from "@codemirror/lang-xml";
7
- import { yaml } from "@codemirror/lang-yaml";
8
- import {
9
- bracketMatching,
10
- defaultHighlightStyle,
11
- foldGutter,
12
- indentOnInput,
13
- syntaxHighlighting
14
- } from "@codemirror/language";
15
- import { linter } from "@codemirror/lint";
16
- import { StateEffect } from "@codemirror/state";
17
- import {
18
- EditorView,
19
- highlightSpecialChars,
20
- keymap,
21
- lineNumbers as lineNumbersExtension,
22
- placeholder as placeholderExtension
23
- } from "@codemirror/view";
24
- import { ScalarIcon } from "@scalar/components";
25
- import { computed, h, onBeforeUnmount, ref, render, toValue, watch } from "vue";
26
- import { customTheme } from "../themes/index.js";
27
- import { variables } from "./variables.js";
28
- const hasProvider = (params) => "provider" in params && !!toValue(params.provider);
1
+ import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
2
+ import { history, historyKeymap, indentWithTab, insertNewline } from '@codemirror/commands';
3
+ import { css } from '@codemirror/lang-css';
4
+ import { html } from '@codemirror/lang-html';
5
+ import { json } from '@codemirror/lang-json';
6
+ import { xml } from '@codemirror/lang-xml';
7
+ import { yaml } from '@codemirror/lang-yaml';
8
+ import { bracketMatching, defaultHighlightStyle, foldGutter, indentOnInput, syntaxHighlighting, } from '@codemirror/language';
9
+ import { linter } from '@codemirror/lint';
10
+ import { StateEffect } from '@codemirror/state';
11
+ import { EditorView, highlightSpecialChars, keymap, lineNumbers as lineNumbersExtension, placeholder as placeholderExtension, } from '@codemirror/view';
12
+ import { computed, onBeforeUnmount, ref, toValue, watch } from 'vue';
13
+ const CHEVRON_DOWN = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m18 10-6 6-6-6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>';
14
+ const CHEVRON_RIGHT = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m9 18 6-6-6-6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>';
15
+ import { customTheme } from '../themes/index.js';
16
+ import { variables } from './variables.js';
17
+ /** Check if the hook has a provider. In provider mode we ignore the content variable */
18
+ const hasProvider = (params) => 'provider' in params && !!toValue(params.provider);
29
19
  const selectAllKeyBinding = {
30
- key: "Mod-a",
31
- run: (view) => {
32
- view.dispatch({
33
- selection: { anchor: 0, head: view.state.doc.length },
34
- scrollIntoView: false
35
- });
36
- return true;
37
- }
38
- };
39
- const useCodeMirror = (params) => {
40
- const codeMirror = ref(null);
41
- const setCodeMirrorContent = (newValue = "") => {
42
- if (!codeMirror.value) {
43
- return;
44
- }
45
- if (codeMirror.value.state.doc.toString() === newValue) {
46
- return;
47
- }
48
- codeMirror.value.dispatch({
49
- changes: {
50
- from: 0,
51
- to: codeMirror.value.state.doc.length,
52
- insert: newValue
53
- },
54
- selection: {
55
- anchor: Math.min(codeMirror.value.state.selection.main.anchor, newValue.length)
56
- }
57
- });
58
- };
59
- const extensionConfig = computed(() => ({
60
- onChange: params.onChange,
61
- onBlur: params.onBlur,
62
- onFocus: params.onFocus,
63
- disableTabIndent: toValue(params.disableTabIndent),
64
- language: toValue(params.language),
65
- classes: toValue(params.classes),
66
- readOnly: toValue(params.readOnly),
67
- lineNumbers: toValue(params.lineNumbers),
68
- withVariables: toValue(params.withVariables),
69
- forceFoldGutter: toValue(params.forceFoldGutter),
70
- disableEnter: toValue(params.disableEnter),
71
- disableCloseBrackets: toValue(params.disableCloseBrackets),
72
- withoutTheme: toValue(params.withoutTheme),
73
- lint: toValue(params.lint),
74
- additionalExtensions: toValue(params.extensions),
75
- placeholder: toValue(params.placeholder)
76
- }));
77
- watch(
78
- params.codeMirrorRef,
79
- () => {
80
- codeMirror.value?.destroy();
81
- mountCodeMirror();
20
+ key: 'Mod-a',
21
+ run: (view) => {
22
+ // Select the entire content
23
+ view.dispatch({
24
+ selection: { anchor: 0, head: view.state.doc.length },
25
+ scrollIntoView: false,
26
+ });
27
+ return true;
82
28
  },
83
- { immediate: true }
84
- );
85
- onBeforeUnmount(() => codeMirror.value?.destroy());
86
- function mountCodeMirror() {
87
- if (params.codeMirrorRef.value) {
88
- const provider = hasProvider(params) ? toValue(params.provider) : null;
89
- const extensions = getCodeMirrorExtensions({
90
- ...extensionConfig.value,
91
- provider
92
- });
93
- codeMirror.value = new EditorView({
94
- parent: params.codeMirrorRef.value,
95
- extensions
96
- });
97
- if (!hasProvider(params)) {
98
- setCodeMirrorContent(toValue(params.content));
99
- }
100
- }
101
- }
102
- watch(
103
- () => hasProvider(params) ? toValue(params.provider) : null,
104
- () => {
105
- if (hasProvider(params)) {
29
+ };
30
+ /** Reactive CodeMirror Integration */
31
+ export const useCodeMirror = (params) => {
32
+ const codeMirror = ref(null);
33
+ /** Set the codemirror content value */
34
+ const setCodeMirrorContent = (newValue = '') => {
35
+ if (!codeMirror.value) {
36
+ return;
37
+ }
38
+ // No need to set the CodeMirror content if nothing has changed
39
+ if (codeMirror.value.state.doc.toString() === newValue) {
40
+ return;
41
+ }
42
+ codeMirror.value.dispatch({
43
+ changes: {
44
+ from: 0,
45
+ to: codeMirror.value.state.doc.length,
46
+ insert: newValue,
47
+ },
48
+ selection: {
49
+ anchor: Math.min(codeMirror.value.state.selection.main.anchor, newValue.length),
50
+ },
51
+ });
52
+ };
53
+ // All options except provider
54
+ const extensionConfig = computed(() => ({
55
+ onChange: params.onChange,
56
+ onBlur: params.onBlur,
57
+ onFocus: params.onFocus,
58
+ disableTabIndent: toValue(params.disableTabIndent),
59
+ language: toValue(params.language),
60
+ classes: toValue(params.classes),
61
+ readOnly: toValue(params.readOnly),
62
+ lineNumbers: toValue(params.lineNumbers),
63
+ withVariables: toValue(params.withVariables),
64
+ forceFoldGutter: toValue(params.forceFoldGutter),
65
+ disableEnter: toValue(params.disableEnter),
66
+ disableCloseBrackets: toValue(params.disableCloseBrackets),
67
+ withoutTheme: toValue(params.withoutTheme),
68
+ lint: toValue(params.lint),
69
+ additionalExtensions: toValue(params.extensions),
70
+ placeholder: toValue(params.placeholder),
71
+ }));
72
+ // Unmounts CodeMirror if it's mounted already, and mounts CodeMirror, if the given ref exists.
73
+ watch(params.codeMirrorRef, () => {
106
74
  codeMirror.value?.destroy();
107
75
  mountCodeMirror();
108
- }
76
+ }, { immediate: true });
77
+ // Cleanup codemirror
78
+ onBeforeUnmount(() => codeMirror.value?.destroy());
79
+ // Initializes CodeMirror.
80
+ function mountCodeMirror() {
81
+ if (params.codeMirrorRef.value) {
82
+ const provider = hasProvider(params) ? toValue(params.provider) : null;
83
+ const extensions = getCodeMirrorExtensions({
84
+ ...extensionConfig.value,
85
+ provider,
86
+ });
87
+ codeMirror.value = new EditorView({
88
+ parent: params.codeMirrorRef.value,
89
+ extensions,
90
+ });
91
+ // Set the initial content if a provider is not in use
92
+ if (!hasProvider(params)) {
93
+ setCodeMirrorContent(toValue(params.content));
94
+ }
95
+ }
109
96
  }
110
- );
111
- watch(
112
- extensionConfig,
113
- () => {
114
- if (!codeMirror.value) {
115
- return;
116
- }
117
- const provider = hasProvider(params) ? toValue(params.provider) : null;
118
- const extensions = getCodeMirrorExtensions({
119
- ...extensionConfig.value,
120
- provider
121
- });
122
- requestAnimationFrame(() => {
123
- codeMirror.value?.dispatch({
124
- effects: StateEffect.reconfigure.of(extensions)
97
+ // ---------------------------------------------------------------------------
98
+ // Provider must be watched separately because we need to restart codemirror if the provider changes
99
+ watch(() => (hasProvider(params) ? toValue(params.provider) : null), () => {
100
+ if (hasProvider(params)) {
101
+ codeMirror.value?.destroy();
102
+ mountCodeMirror();
103
+ }
104
+ });
105
+ // Update the extensions whenever parameters changes
106
+ watch(extensionConfig, () => {
107
+ if (!codeMirror.value) {
108
+ return;
109
+ }
110
+ // If a provider is
111
+ const provider = hasProvider(params) ? toValue(params.provider) : null;
112
+ const extensions = getCodeMirrorExtensions({
113
+ ...extensionConfig.value,
114
+ provider,
125
115
  });
126
- });
127
- },
128
- { immediate: true }
129
- );
130
- watch(
131
- () => toValue(params.content),
132
- () => {
133
- if (hasProvider(params)) {
134
- return;
135
- }
136
- setCodeMirrorContent(toValue(params.content));
137
- },
138
- { immediate: true }
139
- );
140
- return {
141
- /** Replaces the current content with the given value. */
142
- setCodeMirrorContent,
143
- /** Codemirror instance */
144
- codeMirror
145
- };
116
+ requestAnimationFrame(() => {
117
+ codeMirror.value?.dispatch({
118
+ effects: StateEffect.reconfigure.of(extensions),
119
+ });
120
+ });
121
+ }, { immediate: true });
122
+ // ---------------------------------------------------------------------------
123
+ // Keep the content in sync when the content is managed externally
124
+ watch(() => toValue(params.content), () => {
125
+ // When a provider is in use we do not map the content value back to the codemirror instance
126
+ if (hasProvider(params)) {
127
+ return;
128
+ }
129
+ setCodeMirrorContent(toValue(params.content));
130
+ }, { immediate: true });
131
+ return {
132
+ /** Replaces the current content with the given value. */
133
+ setCodeMirrorContent,
134
+ /** Codemirror instance */
135
+ codeMirror,
136
+ };
146
137
  };
138
+ // ---------------------------------------------------------------------------
147
139
  const languageExtensions = {
148
- html,
149
- json,
150
- yaml,
151
- css,
152
- xml
140
+ html: html,
141
+ json: json,
142
+ yaml: yaml,
143
+ css: css,
144
+ xml: xml,
153
145
  };
154
- function getCodeMirrorExtensions({
155
- onChange,
156
- onBlur,
157
- onFocus,
158
- provider,
159
- language,
160
- classes = [],
161
- readOnly = false,
162
- lineNumbers = false,
163
- withVariables = false,
164
- forceFoldGutter = false,
165
- disableEnter = false,
166
- disableCloseBrackets = false,
167
- disableTabIndent = false,
168
- withoutTheme = false,
169
- lint = false,
170
- additionalExtensions = [],
171
- placeholder
172
- }) {
173
- const extensions = [
174
- highlightSpecialChars(),
175
- history(),
176
- keymap.of(historyKeymap),
177
- syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
178
- EditorView.theme({
179
- ".cm-line": {
180
- lineHeight: "22px",
181
- padding: "0 2px 0 4px"
182
- },
183
- ".cm-gutterElement": {
184
- lineHeight: "22px"
185
- },
186
- ".cm-tooltip": {
187
- border: "1px solid #f5c6cb",
188
- fontSize: "12px"
189
- },
190
- ".cm-tooltip-lint": {
191
- backgroundColor: "#ffffff"
192
- },
193
- ".cm-diagnostic-error": {
194
- borderLeft: "0",
195
- color: "#dc1b19"
196
- },
197
- ".cm-foldPlaceholder": {
198
- background: "var(--scalar-background-1)",
199
- border: "none",
200
- fontFamily: "var(--scalar-font)"
201
- }
202
- }),
203
- // Listen to updates
204
- EditorView.updateListener.of((v) => {
205
- if (!v.docChanged) {
206
- return;
207
- }
208
- onChange?.(v.state.doc.toString());
209
- }),
210
- EditorView.domEventHandlers({
211
- blur: (_event, view) => {
212
- onBlur?.(view.state.doc.toString());
213
- },
214
- focus: (_event, view) => {
215
- onFocus?.(view.state.doc.toString());
216
- }
217
- }),
218
- // Add Classes
219
- EditorView.editorAttributes.of({ class: classes.join(" ") }),
220
- ...additionalExtensions
221
- ];
222
- if (provider) {
223
- extensions.push(provider);
224
- }
225
- if (!withoutTheme) {
226
- extensions.push(customTheme);
227
- }
228
- if (readOnly) {
229
- extensions.push(EditorView.editable.of(false));
230
- } else {
231
- extensions.push(
232
- indentOnInput(),
233
- bracketMatching(),
234
- autocompletion(),
235
- keymap.of([...completionKeymap, selectAllKeyBinding]),
236
- bracketMatching()
237
- );
238
- if (!disableCloseBrackets) {
239
- extensions.push(closeBrackets(), keymap.of([...closeBracketsKeymap]));
146
+ /** Generate the list of extension from parameters */
147
+ function getCodeMirrorExtensions({ onChange, onBlur, onFocus, provider, language, classes = [], readOnly = false, lineNumbers = false, withVariables = false, forceFoldGutter = false, disableEnter = false, disableCloseBrackets = false, disableTabIndent = false, withoutTheme = false, lint = false, additionalExtensions = [], placeholder, }) {
148
+ const extensions = [
149
+ highlightSpecialChars(),
150
+ history(),
151
+ keymap.of(historyKeymap),
152
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
153
+ EditorView.theme({
154
+ '.cm-line': {
155
+ lineHeight: '22px',
156
+ padding: '0 2px 0 4px',
157
+ },
158
+ '.cm-gutterElement': {
159
+ lineHeight: '22px',
160
+ },
161
+ '.cm-tooltip': {
162
+ border: '1px solid #f5c6cb',
163
+ fontSize: '12px',
164
+ },
165
+ '.cm-tooltip-lint': {
166
+ backgroundColor: '#ffffff',
167
+ },
168
+ '.cm-diagnostic-error': {
169
+ borderLeft: '0',
170
+ color: '#dc1b19',
171
+ },
172
+ '.cm-foldPlaceholder': {
173
+ background: 'var(--scalar-background-1)',
174
+ border: 'none',
175
+ fontFamily: 'var(--scalar-font)',
176
+ },
177
+ }),
178
+ // Listen to updates
179
+ EditorView.updateListener.of((v) => {
180
+ if (!v.docChanged) {
181
+ return;
182
+ }
183
+ onChange?.(v.state.doc.toString());
184
+ }),
185
+ EditorView.domEventHandlers({
186
+ blur: (_event, view) => {
187
+ onBlur?.(view.state.doc.toString());
188
+ },
189
+ focus: (_event, view) => {
190
+ onFocus?.(view.state.doc.toString());
191
+ },
192
+ }),
193
+ // Add Classes
194
+ EditorView.editorAttributes.of({ class: classes.join(' ') }),
195
+ ...additionalExtensions,
196
+ ];
197
+ // Enable the provider
198
+ if (provider) {
199
+ extensions.push(provider);
240
200
  }
241
- if (disableTabIndent) {
242
- extensions.push(
243
- keymap.of([
244
- {
245
- key: "Tab",
246
- run: () => false,
247
- // Prevent default Tab behavior
248
- shift: () => false
249
- // Prevent default Shift+Tab behavior
250
- }
251
- ])
252
- );
253
- } else {
254
- extensions.push(keymap.of([indentWithTab]));
201
+ // Add the theme as needed
202
+ if (!withoutTheme) {
203
+ extensions.push(customTheme);
255
204
  }
256
- }
257
- if (placeholder) {
258
- extensions.push(placeholderExtension(placeholder));
259
- }
260
- if (lineNumbers) {
261
- extensions.push(lineNumbersExtension());
262
- }
263
- if (forceFoldGutter) {
264
- extensions.push(
265
- foldGutter({
266
- markerDOM: (open) => {
267
- const icon = document.createElement("div");
268
- icon.classList.add("cm-foldMarker");
269
- const vnode = h(ScalarIcon, {
270
- icon: open ? "ChevronDown" : "ChevronRight",
271
- size: "md"
272
- });
273
- render(vnode, icon);
274
- return icon;
275
- }
276
- })
277
- );
278
- }
279
- if (language && languageExtensions[language]) {
280
- extensions.push(languageExtensions[language]());
281
- if (!forceFoldGutter) {
282
- extensions.push(
283
- foldGutter({
284
- markerDOM: (open) => {
285
- const icon = document.createElement("div");
286
- icon.classList.add("cm-foldMarker");
287
- const vnode = h(ScalarIcon, {
288
- icon: open ? "ChevronDown" : "ChevronRight",
289
- size: "md"
290
- });
291
- render(vnode, icon);
292
- return icon;
293
- }
294
- })
295
- );
205
+ // Read only
206
+ if (readOnly) {
207
+ extensions.push(EditorView.editable.of(false));
296
208
  }
297
- }
298
- if (lint && language === "json") {
299
- const jsonLinter = linter((view) => {
300
- const diagnostics = [];
301
- const content = view.state.doc.toString();
302
- if (content.trim()) {
303
- try {
304
- JSON.parse(content);
305
- } catch (e) {
306
- if (e instanceof Error) {
307
- diagnostics.push({
308
- from: 0,
309
- to: view.state.doc.length,
310
- severity: "error",
311
- message: e.message
312
- });
313
- }
209
+ else {
210
+ extensions.push(indentOnInput(), bracketMatching(), autocompletion(), keymap.of([...completionKeymap, selectAllKeyBinding]), bracketMatching());
211
+ if (!disableCloseBrackets) {
212
+ extensions.push(closeBrackets(), keymap.of([...closeBracketsKeymap]));
314
213
  }
315
- }
316
- return diagnostics;
317
- });
318
- extensions.push(jsonLinter);
319
- }
320
- if (withVariables) {
321
- extensions.push(variables());
322
- }
323
- if (disableEnter) {
324
- extensions.push(
325
- keymap.of([
326
- {
327
- key: "Enter",
328
- run: () => {
329
- return true;
330
- }
331
- },
332
- {
333
- key: "Ctrl-Enter",
334
- mac: "Cmd-Enter",
335
- run: () => {
336
- return true;
337
- }
338
- },
339
- {
340
- key: "Shift-Enter",
341
- run: () => {
342
- return true;
343
- }
214
+ if (disableTabIndent) {
215
+ extensions.push(keymap.of([
216
+ {
217
+ key: 'Tab',
218
+ run: () => false, // Prevent default Tab behavior
219
+ shift: () => false, // Prevent default Shift+Tab behavior
220
+ },
221
+ ]));
344
222
  }
345
- ])
346
- );
347
- } else {
348
- extensions.push(
349
- keymap.of([
350
- {
351
- key: "Enter",
352
- run: insertNewline
223
+ else {
224
+ extensions.push(keymap.of([indentWithTab]));
225
+ }
226
+ }
227
+ // Add placeholder extension if placeholder is provided
228
+ if (placeholder) {
229
+ extensions.push(placeholderExtension(placeholder));
230
+ }
231
+ // Line numbers
232
+ if (lineNumbers) {
233
+ extensions.push(lineNumbersExtension());
234
+ }
235
+ if (forceFoldGutter) {
236
+ extensions.push(foldGutter({
237
+ markerDOM: (open) => {
238
+ const icon = document.createElement('div');
239
+ icon.classList.add('cm-foldMarker');
240
+ icon.innerHTML = open ? CHEVRON_DOWN : CHEVRON_RIGHT;
241
+ return icon;
242
+ },
243
+ }));
244
+ }
245
+ // Syntax highlighting
246
+ if (language && languageExtensions[language]) {
247
+ extensions.push(languageExtensions[language]());
248
+ if (!forceFoldGutter) {
249
+ extensions.push(foldGutter({
250
+ markerDOM: (open) => {
251
+ const icon = document.createElement('div');
252
+ icon.classList.add('cm-foldMarker');
253
+ icon.innerHTML = open ? CHEVRON_DOWN : CHEVRON_RIGHT;
254
+ return icon;
255
+ },
256
+ }));
353
257
  }
354
- ])
355
- );
356
- }
357
- return extensions;
258
+ }
259
+ // JSON Linter
260
+ if (lint && language === 'json') {
261
+ const jsonLinter = linter((view) => {
262
+ const diagnostics = [];
263
+ const content = view.state.doc.toString();
264
+ if (content.trim()) {
265
+ try {
266
+ JSON.parse(content);
267
+ }
268
+ catch (e) {
269
+ if (e instanceof Error) {
270
+ diagnostics.push({
271
+ from: 0,
272
+ to: view.state.doc.length,
273
+ severity: 'error',
274
+ message: e.message,
275
+ });
276
+ }
277
+ }
278
+ }
279
+ return diagnostics;
280
+ });
281
+ extensions.push(jsonLinter);
282
+ }
283
+ // Highlight variables
284
+ if (withVariables) {
285
+ extensions.push(variables());
286
+ }
287
+ if (disableEnter) {
288
+ extensions.push(keymap.of([
289
+ {
290
+ key: 'Enter',
291
+ run: () => {
292
+ return true;
293
+ },
294
+ },
295
+ {
296
+ key: 'Ctrl-Enter',
297
+ mac: 'Cmd-Enter',
298
+ run: () => {
299
+ return true;
300
+ },
301
+ },
302
+ {
303
+ key: 'Shift-Enter',
304
+ run: () => {
305
+ return true;
306
+ },
307
+ },
308
+ ]));
309
+ }
310
+ else {
311
+ extensions.push(keymap.of([
312
+ {
313
+ key: 'Enter',
314
+ run: insertNewline,
315
+ },
316
+ ]));
317
+ }
318
+ return extensions;
358
319
  }
359
- export {
360
- useCodeMirror
361
- };
362
- //# sourceMappingURL=useCodeMirror.js.map