@haklex/rich-renderer-codeblock 0.0.81 → 0.0.83

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.mjs CHANGED
@@ -1,246 +1,278 @@
1
- import { normalizeLanguage } from "./constants.mjs";
2
- import { _ as semanticClassNames, d as lang, f as langInput, i as body, n as LanguageIcon, r as hasLanguageIcon } from "./icons-CnFW5y6g.js";
3
- import { n as CodeBlockCard, t as CodeBlockRenderer } from "./CodeBlockRenderer-ECu_hR7l.js";
4
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
5
- import { Compartment, EditorSelection, EditorState, Prec } from "@codemirror/state";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { history, defaultKeymap, historyKeymap, indentWithTab } from "@codemirror/commands";
3
+ import { Compartment, Prec, EditorState, EditorSelection } from "@codemirror/state";
6
4
  import { EditorView, keymap, lineNumbers } from "@codemirror/view";
7
- import { getThemeExtensions, isOnFirstLine, isOnLastLine, loadLanguageExtension } from "@haklex/cm-editor";
8
- import { useColorScheme, useVariant } from "@haklex/rich-editor";
9
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
- import { jsx, jsxs } from "react/jsx-runtime";
11
- import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@haklex/rich-editor-ui";
5
+ import { isOnFirstLine, isOnLastLine, getThemeExtensions, loadLanguageExtension } from "@haklex/cm-editor";
6
+ import { useVariant, useColorScheme } from "@haklex/rich-editor";
7
+ import { useCallback, useMemo, useState, useRef, useEffect } from "react";
8
+ import { a as CodeBlockCard } from "./CodeBlockRenderer-DSXP75Xm.js";
9
+ import { C } from "./CodeBlockRenderer-DSXP75Xm.js";
10
+ import { normalizeLanguage } from "./constants.mjs";
11
+ import { Combobox, ComboboxInput, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem } from "@haklex/rich-editor-ui";
12
12
  import { bundledLanguagesInfo } from "shiki/bundle/web";
13
- //#region src/LanguageCombobox.tsx
14
- var languageItems = bundledLanguagesInfo.map((info) => ({
15
- id: info.id,
16
- name: info.name
13
+ import "@iconify/utils";
14
+ import "@iconify-json/material-icon-theme";
15
+ import { l as lang, s as semanticClassNames, h as hasLanguageIcon, a as LanguageIcon, b as langInput, c as lined, d as linedWithNumbers, e as body, f as bodyReadonly } from "./language-UrxzhGSS.js";
16
+ const languageItems = bundledLanguagesInfo.map((info) => ({
17
+ id: info.id,
18
+ name: info.name
17
19
  }));
18
20
  function LanguageCombobox({ language, onLanguageChange }) {
19
- const normalizedLanguage = normalizeLanguage(language);
20
- const handleValueChange = useCallback((value) => {
21
- if (value) onLanguageChange?.(value.id);
22
- }, [onLanguageChange]);
23
- const selectedValue = useMemo(() => languageItems.find((item) => item.id === normalizedLanguage) ?? null, [normalizedLanguage]);
24
- return /* @__PURE__ */ jsxs("div", {
25
- className: `${lang} ${semanticClassNames.lang}`,
26
- children: [hasLanguageIcon(normalizedLanguage) && /* @__PURE__ */ jsx(LanguageIcon, {
27
- language: normalizedLanguage,
28
- size: 14
29
- }), /* @__PURE__ */ jsxs(Combobox, {
30
- itemToStringLabel: (item) => item.id,
31
- itemToStringValue: (item) => item.id,
32
- items: languageItems,
33
- value: selectedValue,
34
- onValueChange: handleValueChange,
35
- children: [/* @__PURE__ */ jsx(ComboboxInput, {
36
- className: langInput,
37
- placeholder: "language",
38
- onKeyDown: (e) => {
39
- e.stopPropagation();
40
- }
41
- }), /* @__PURE__ */ jsxs(ComboboxContent, {
42
- align: "end",
43
- side: "top",
44
- sideOffset: 8,
45
- children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: "No languages found." }), /* @__PURE__ */ jsx(ComboboxList, { children: (item) => /* @__PURE__ */ jsxs(ComboboxItem, {
46
- value: item,
47
- children: [/* @__PURE__ */ jsx(LanguageIcon, {
48
- language: item.id,
49
- size: 16
50
- }), item.name]
51
- }, item.id) })]
52
- })]
53
- })]
54
- });
21
+ const normalizedLanguage = normalizeLanguage(language);
22
+ const handleValueChange = useCallback(
23
+ (value) => {
24
+ if (value) {
25
+ onLanguageChange?.(value.id);
26
+ }
27
+ },
28
+ [onLanguageChange]
29
+ );
30
+ const selectedValue = useMemo(
31
+ () => languageItems.find((item) => item.id === normalizedLanguage) ?? null,
32
+ [normalizedLanguage]
33
+ );
34
+ return /* @__PURE__ */ jsxs("div", { className: `${lang} ${semanticClassNames.lang}`, children: [
35
+ hasLanguageIcon(normalizedLanguage) && /* @__PURE__ */ jsx(LanguageIcon, { language: normalizedLanguage, size: 14 }),
36
+ /* @__PURE__ */ jsxs(
37
+ Combobox,
38
+ {
39
+ itemToStringLabel: (item) => item.id,
40
+ itemToStringValue: (item) => item.id,
41
+ items: languageItems,
42
+ value: selectedValue,
43
+ onValueChange: handleValueChange,
44
+ children: [
45
+ /* @__PURE__ */ jsx(
46
+ ComboboxInput,
47
+ {
48
+ className: langInput,
49
+ placeholder: "language",
50
+ onKeyDown: (e) => {
51
+ e.stopPropagation();
52
+ }
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsxs(ComboboxContent, { align: "end", side: "top", sideOffset: 8, children: [
56
+ /* @__PURE__ */ jsx(ComboboxEmpty, { children: "No languages found." }),
57
+ /* @__PURE__ */ jsx(ComboboxList, { children: (item) => /* @__PURE__ */ jsxs(ComboboxItem, { value: item, children: [
58
+ /* @__PURE__ */ jsx(LanguageIcon, { language: item.id, size: 16 }),
59
+ item.name
60
+ ] }, item.id) })
61
+ ] })
62
+ ]
63
+ }
64
+ )
65
+ ] });
55
66
  }
56
- //#endregion
57
- //#region src/CodeBlockEditRenderer.tsx
58
67
  function stopHandledEvent(event) {
59
- event.preventDefault();
60
- event.stopPropagation();
61
- event.stopImmediatePropagation();
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ event.stopImmediatePropagation();
62
71
  }
63
- var CodeBlockEditRenderer = ({ code, language, showLineNumbers: showLineNumbersProp, editable = false, selected = false, cursorPlacement = "start", onCodeChange, onLanguageChange, onDelete, onExitBlock }) => {
64
- const variant = useVariant();
65
- const colorScheme = useColorScheme();
66
- const showLineNumbers = showLineNumbersProp ?? variant !== "comment";
67
- const normalizedLanguage = normalizeLanguage(language);
68
- const [mounted, setMounted] = useState(false);
69
- const containerRef = useRef(null);
70
- const editorRef = useRef(null);
71
- const suppressChangeRef = useRef(false);
72
- const editableRef = useRef(editable);
73
- editableRef.current = editable;
74
- const onCodeChangeRef = useRef(onCodeChange);
75
- onCodeChangeRef.current = onCodeChange;
76
- const onDeleteRef = useRef(onDelete);
77
- onDeleteRef.current = onDelete;
78
- const onExitBlockRef = useRef(onExitBlock);
79
- onExitBlockRef.current = onExitBlock;
80
- const languageCompartmentRef = useRef(null);
81
- const editableCompartmentRef = useRef(null);
82
- const lineNumbersCompartmentRef = useRef(null);
83
- const themeCompartmentRef = useRef(null);
84
- if (!languageCompartmentRef.current) languageCompartmentRef.current = new Compartment();
85
- if (!editableCompartmentRef.current) editableCompartmentRef.current = new Compartment();
86
- if (!lineNumbersCompartmentRef.current) lineNumbersCompartmentRef.current = new Compartment();
87
- if (!themeCompartmentRef.current) themeCompartmentRef.current = new Compartment();
88
- const keyboardBoundaryHandler = useMemo(() => Prec.high(EditorView.domEventHandlers({ keydown: (event, view) => {
89
- if (!editableRef.current) return false;
90
- event.stopPropagation();
91
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
92
- stopHandledEvent(event);
93
- onExitBlockRef.current?.("after");
94
- return true;
95
- }
96
- if (event.key === "Backspace" && view.state.doc.length === 0) {
97
- stopHandledEvent(event);
98
- onDeleteRef.current?.();
99
- return true;
100
- }
101
- if (event.key === "ArrowUp" && isOnFirstLine(view)) {
102
- stopHandledEvent(event);
103
- onExitBlockRef.current?.("before");
104
- return true;
105
- }
106
- if (event.key === "ArrowDown" && isOnLastLine(view)) {
107
- stopHandledEvent(event);
108
- onExitBlockRef.current?.("after");
109
- return true;
110
- }
111
- return false;
112
- } })), []);
113
- useEffect(() => {
114
- const container = containerRef.current;
115
- if (!container) return;
116
- const editor = new EditorView({
117
- parent: container,
118
- state: EditorState.create({
119
- doc: code,
120
- extensions: [
121
- history(),
122
- keymap.of([
123
- ...defaultKeymap,
124
- ...historyKeymap,
125
- indentWithTab
126
- ]),
127
- EditorView.updateListener.of((update) => {
128
- if (!update.docChanged || suppressChangeRef.current) return;
129
- onCodeChangeRef.current?.(update.state.doc.toString());
130
- }),
131
- keyboardBoundaryHandler,
132
- editableCompartmentRef.current.of([EditorView.editable.of(editable), EditorState.readOnly.of(!editable)]),
133
- lineNumbersCompartmentRef.current.of(showLineNumbers ? lineNumbers() : []),
134
- themeCompartmentRef.current.of(getThemeExtensions(colorScheme)),
135
- languageCompartmentRef.current.of([])
136
- ]
137
- })
138
- });
139
- editorRef.current = editor;
140
- setMounted(true);
141
- return () => {
142
- editor.destroy();
143
- editorRef.current = null;
144
- setMounted(false);
145
- };
146
- }, []);
147
- useEffect(() => {
148
- const editor = editorRef.current;
149
- if (!editor) return;
150
- editor.dispatch({ effects: editableCompartmentRef.current.reconfigure([EditorView.editable.of(editable), EditorState.readOnly.of(!editable)]) });
151
- }, [editable]);
152
- useEffect(() => {
153
- const editor = editorRef.current;
154
- if (!editor) return;
155
- editor.dispatch({ effects: lineNumbersCompartmentRef.current.reconfigure(showLineNumbers ? lineNumbers() : []) });
156
- }, [showLineNumbers]);
157
- useEffect(() => {
158
- const editor = editorRef.current;
159
- if (!editor) return;
160
- editor.dispatch({ effects: themeCompartmentRef.current.reconfigure(getThemeExtensions(colorScheme)) });
161
- }, [colorScheme]);
162
- useEffect(() => {
163
- const editor = editorRef.current;
164
- if (!editor) return;
165
- const current = editor.state.doc.toString();
166
- if (current === code) return;
167
- suppressChangeRef.current = true;
168
- editor.dispatch({ changes: {
169
- from: 0,
170
- to: current.length,
171
- insert: code
172
- } });
173
- suppressChangeRef.current = false;
174
- }, [code]);
175
- useEffect(() => {
176
- const editor = editorRef.current;
177
- if (!editor) return;
178
- let cancelled = false;
179
- (async () => {
180
- const extension = await loadLanguageExtension(normalizedLanguage);
181
- if (cancelled) return;
182
- editor.dispatch({ effects: languageCompartmentRef.current.reconfigure(extension) });
183
- })();
184
- return () => {
185
- cancelled = true;
186
- };
187
- }, [normalizedLanguage]);
188
- useEffect(() => {
189
- if (!editable || !selected) return;
190
- const editor = editorRef.current;
191
- if (!editor) return;
192
- const raf = requestAnimationFrame(() => {
193
- const nextCursor = cursorPlacement === "end" ? editor.state.doc.length : 0;
194
- editor.focus();
195
- editor.dispatch({
196
- selection: EditorSelection.cursor(nextCursor),
197
- scrollIntoView: true
198
- });
199
- });
200
- return () => cancelAnimationFrame(raf);
201
- }, [
202
- cursorPlacement,
203
- editable,
204
- mounted,
205
- selected
206
- ]);
207
- const fallbackLines = useMemo(() => code.split("\n"), [code]);
208
- const fallbackClassName = [
209
- showLineNumbers && "_1pn9r4qc",
210
- showLineNumbers && semanticClassNames.lined,
211
- showLineNumbers && "_1pn9r4qd",
212
- showLineNumbers && semanticClassNames.linedWithNumbers
213
- ].filter(Boolean).join(" ");
214
- const bodyClassName = [
215
- body,
216
- semanticClassNames.body,
217
- !editable && "_1pn9r4q9",
218
- !editable && semanticClassNames.bodyReadonly
219
- ].filter(Boolean).join(" ");
220
- return /* @__PURE__ */ jsxs(CodeBlockCard, {
221
- code,
222
- collapsible: !editable,
223
- language,
224
- langSlot: editable ? /* @__PURE__ */ jsx(LanguageCombobox, {
225
- language,
226
- onLanguageChange
227
- }) : void 0,
228
- children: [!mounted && /* @__PURE__ */ jsx("pre", {
229
- className: fallbackClassName,
230
- children: /* @__PURE__ */ jsx("code", { children: fallbackLines.map((line, i) => /* @__PURE__ */ jsx("span", {
231
- className: "line",
232
- children: line
233
- }, `${line}-${i}`)) })
234
- }), /* @__PURE__ */ jsx("div", {
235
- className: bodyClassName,
236
- ref: containerRef,
237
- style: !mounted ? {
238
- height: 0,
239
- overflow: "hidden",
240
- visibility: "hidden"
241
- } : void 0
242
- })]
243
- });
72
+ const CodeBlockEditRenderer = ({
73
+ code,
74
+ language,
75
+ showLineNumbers: showLineNumbersProp,
76
+ editable = false,
77
+ selected = false,
78
+ cursorPlacement = "start",
79
+ onCodeChange,
80
+ onLanguageChange,
81
+ onDelete,
82
+ onExitBlock
83
+ }) => {
84
+ const variant = useVariant();
85
+ const colorScheme = useColorScheme();
86
+ const showLineNumbers = showLineNumbersProp ?? variant !== "comment";
87
+ const normalizedLanguage = normalizeLanguage(language);
88
+ const [mounted, setMounted] = useState(false);
89
+ const containerRef = useRef(null);
90
+ const editorRef = useRef(null);
91
+ const suppressChangeRef = useRef(false);
92
+ const editableRef = useRef(editable);
93
+ editableRef.current = editable;
94
+ const onCodeChangeRef = useRef(onCodeChange);
95
+ onCodeChangeRef.current = onCodeChange;
96
+ const onDeleteRef = useRef(onDelete);
97
+ onDeleteRef.current = onDelete;
98
+ const onExitBlockRef = useRef(onExitBlock);
99
+ onExitBlockRef.current = onExitBlock;
100
+ const languageCompartmentRef = useRef(null);
101
+ const editableCompartmentRef = useRef(null);
102
+ const lineNumbersCompartmentRef = useRef(null);
103
+ const themeCompartmentRef = useRef(null);
104
+ if (!languageCompartmentRef.current) languageCompartmentRef.current = new Compartment();
105
+ if (!editableCompartmentRef.current) editableCompartmentRef.current = new Compartment();
106
+ if (!lineNumbersCompartmentRef.current) lineNumbersCompartmentRef.current = new Compartment();
107
+ if (!themeCompartmentRef.current) themeCompartmentRef.current = new Compartment();
108
+ const keyboardBoundaryHandler = useMemo(
109
+ () => Prec.high(
110
+ EditorView.domEventHandlers({
111
+ keydown: (event, view) => {
112
+ if (!editableRef.current) return false;
113
+ event.stopPropagation();
114
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
115
+ stopHandledEvent(event);
116
+ onExitBlockRef.current?.("after");
117
+ return true;
118
+ }
119
+ if (event.key === "Backspace" && view.state.doc.length === 0) {
120
+ stopHandledEvent(event);
121
+ onDeleteRef.current?.();
122
+ return true;
123
+ }
124
+ if (event.key === "ArrowUp" && isOnFirstLine(view)) {
125
+ stopHandledEvent(event);
126
+ onExitBlockRef.current?.("before");
127
+ return true;
128
+ }
129
+ if (event.key === "ArrowDown" && isOnLastLine(view)) {
130
+ stopHandledEvent(event);
131
+ onExitBlockRef.current?.("after");
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ })
137
+ ),
138
+ []
139
+ );
140
+ useEffect(() => {
141
+ const container = containerRef.current;
142
+ if (!container) return;
143
+ const editor = new EditorView({
144
+ parent: container,
145
+ state: EditorState.create({
146
+ doc: code,
147
+ extensions: [
148
+ history(),
149
+ keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
150
+ EditorView.updateListener.of((update) => {
151
+ if (!update.docChanged || suppressChangeRef.current) return;
152
+ onCodeChangeRef.current?.(update.state.doc.toString());
153
+ }),
154
+ keyboardBoundaryHandler,
155
+ editableCompartmentRef.current.of([
156
+ EditorView.editable.of(editable),
157
+ EditorState.readOnly.of(!editable)
158
+ ]),
159
+ lineNumbersCompartmentRef.current.of(showLineNumbers ? lineNumbers() : []),
160
+ themeCompartmentRef.current.of(getThemeExtensions(colorScheme)),
161
+ languageCompartmentRef.current.of([])
162
+ ]
163
+ })
164
+ });
165
+ editorRef.current = editor;
166
+ setMounted(true);
167
+ return () => {
168
+ editor.destroy();
169
+ editorRef.current = null;
170
+ setMounted(false);
171
+ };
172
+ }, []);
173
+ useEffect(() => {
174
+ const editor = editorRef.current;
175
+ if (!editor) return;
176
+ editor.dispatch({
177
+ effects: editableCompartmentRef.current.reconfigure([
178
+ EditorView.editable.of(editable),
179
+ EditorState.readOnly.of(!editable)
180
+ ])
181
+ });
182
+ }, [editable]);
183
+ useEffect(() => {
184
+ const editor = editorRef.current;
185
+ if (!editor) return;
186
+ editor.dispatch({
187
+ effects: lineNumbersCompartmentRef.current.reconfigure(showLineNumbers ? lineNumbers() : [])
188
+ });
189
+ }, [showLineNumbers]);
190
+ useEffect(() => {
191
+ const editor = editorRef.current;
192
+ if (!editor) return;
193
+ editor.dispatch({
194
+ effects: themeCompartmentRef.current.reconfigure(getThemeExtensions(colorScheme))
195
+ });
196
+ }, [colorScheme]);
197
+ useEffect(() => {
198
+ const editor = editorRef.current;
199
+ if (!editor) return;
200
+ const current = editor.state.doc.toString();
201
+ if (current === code) return;
202
+ suppressChangeRef.current = true;
203
+ editor.dispatch({
204
+ changes: {
205
+ from: 0,
206
+ to: current.length,
207
+ insert: code
208
+ }
209
+ });
210
+ suppressChangeRef.current = false;
211
+ }, [code]);
212
+ useEffect(() => {
213
+ const editor = editorRef.current;
214
+ if (!editor) return;
215
+ let cancelled = false;
216
+ (async () => {
217
+ const extension = await loadLanguageExtension(normalizedLanguage);
218
+ if (cancelled) return;
219
+ editor.dispatch({
220
+ effects: languageCompartmentRef.current.reconfigure(extension)
221
+ });
222
+ })();
223
+ return () => {
224
+ cancelled = true;
225
+ };
226
+ }, [normalizedLanguage]);
227
+ useEffect(() => {
228
+ if (!editable || !selected) return;
229
+ const editor = editorRef.current;
230
+ if (!editor) return;
231
+ const raf = requestAnimationFrame(() => {
232
+ const nextCursor = cursorPlacement === "end" ? editor.state.doc.length : 0;
233
+ editor.focus();
234
+ editor.dispatch({
235
+ selection: EditorSelection.cursor(nextCursor),
236
+ scrollIntoView: true
237
+ });
238
+ });
239
+ return () => cancelAnimationFrame(raf);
240
+ }, [cursorPlacement, editable, mounted, selected]);
241
+ const fallbackLines = useMemo(() => code.split("\n"), [code]);
242
+ const fallbackClassName = [
243
+ showLineNumbers && lined,
244
+ showLineNumbers && semanticClassNames.lined,
245
+ showLineNumbers && linedWithNumbers,
246
+ showLineNumbers && semanticClassNames.linedWithNumbers
247
+ ].filter(Boolean).join(" ");
248
+ const bodyClassName = [
249
+ body,
250
+ semanticClassNames.body,
251
+ !editable && bodyReadonly,
252
+ !editable && semanticClassNames.bodyReadonly
253
+ ].filter(Boolean).join(" ");
254
+ return /* @__PURE__ */ jsxs(
255
+ CodeBlockCard,
256
+ {
257
+ code,
258
+ collapsible: !editable,
259
+ language,
260
+ langSlot: editable ? /* @__PURE__ */ jsx(LanguageCombobox, { language, onLanguageChange }) : void 0,
261
+ children: [
262
+ !mounted && /* @__PURE__ */ jsx("pre", { className: fallbackClassName, children: /* @__PURE__ */ jsx("code", { children: fallbackLines.map((line, i) => /* @__PURE__ */ jsx("span", { className: "line", children: line }, `${line}-${i}`)) }) }),
263
+ /* @__PURE__ */ jsx(
264
+ "div",
265
+ {
266
+ className: bodyClassName,
267
+ ref: containerRef,
268
+ style: !mounted ? { height: 0, overflow: "hidden", visibility: "hidden" } : void 0
269
+ }
270
+ )
271
+ ]
272
+ }
273
+ );
274
+ };
275
+ export {
276
+ CodeBlockEditRenderer,
277
+ C as CodeBlockRenderer
244
278
  };
245
- //#endregion
246
- export { CodeBlockEditRenderer, CodeBlockRenderer };