@mdxeditor/editor 3.54.0 → 3.55.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.ts CHANGED
@@ -1353,6 +1353,18 @@ export declare interface GenericJsxEditorProps extends JsxEditorProps {
1353
1353
  PropertyEditor?: PropertyEditorType;
1354
1354
  }
1355
1355
 
1356
+ /**
1357
+ * Resolves the current language to the select value and items used by CodeMirror language pickers.
1358
+ * Unknown languages are added as a temporary item so the picker remains in sync with the code block value.
1359
+ */
1360
+ export declare function getCodeBlockLanguageSelectData(normalized: NormalizedCodeBlockLanguages, language: string): {
1361
+ value: string;
1362
+ items: {
1363
+ value: string;
1364
+ label: string;
1365
+ }[];
1366
+ };
1367
+
1356
1368
  /**
1357
1369
  * Gets the selected node from the Lexical editor.
1358
1370
  * @group Utils
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { insertJsx$, isMdastJsxNode, jsxPlugin } from "./plugins/jsx/index.js";
18
18
  import { GenericJsxEditor } from "./jsx-editors/GenericJsxEditor.js";
19
19
  import { insertSandpack$, sandpackConfig$, sandpackPlugin } from "./plugins/sandpack/index.js";
20
20
  import { SandpackEditor } from "./plugins/sandpack/SandpackEditor.js";
21
- import { EMPTY_VALUE, codeBlockLanguages$, codeMirrorAutoLoadLanguageSupport$, codeMirrorExtensions$, codeMirrorPlugin, insertCodeMirror$, normalizeCodeBlockLanguages } from "./plugins/codemirror/index.js";
21
+ import { EMPTY_VALUE, codeBlockLanguages$, codeMirrorAutoLoadLanguageSupport$, codeMirrorExtensions$, codeMirrorPlugin, getCodeBlockLanguageSelectData, insertCodeMirror$, normalizeCodeBlockLanguages } from "./plugins/codemirror/index.js";
22
22
  import { COMMON_STATE_CONFIG_EXTENSIONS, CodeMirrorEditor } from "./plugins/codemirror/CodeMirrorEditor.js";
23
23
  import { appendCodeBlockEditorDescriptor$, codeBlockPlugin, defaultCodeBlockLanguage$, insertCodeBlock$ } from "./plugins/codeblock/index.js";
24
24
  import { directivesPlugin, insertDirective$ } from "./plugins/directives/index.js";
@@ -248,6 +248,7 @@ export {
248
248
  fromWithinEditorRead,
249
249
  frontmatterDialogOpen$,
250
250
  frontmatterPlugin,
251
+ getCodeBlockLanguageSelectData,
251
252
  getSelectedNode,
252
253
  getSelectionAsMarkdown,
253
254
  getSelectionRectangle,
@@ -11,8 +11,8 @@ const MdastCodeVisitor = {
11
11
  actions.addAndStepInto(
12
12
  $createCodeBlockNode({
13
13
  code: mdastNode.value,
14
- language: mdastNode.lang,
15
- meta: mdastNode.meta
14
+ language: mdastNode.lang ?? "",
15
+ meta: mdastNode.meta ?? ""
16
16
  })
17
17
  );
18
18
  }
@@ -10,7 +10,7 @@ import { indentWithTab } from "@codemirror/commands";
10
10
  import { basicLight } from "cm6-theme-basic-light";
11
11
  import { basicSetup } from "codemirror";
12
12
  import { $setSelection } from "lexical";
13
- import { codeMirrorExtensions$, codeMirrorAutoLoadLanguageSupport$, codeBlockLanguages$, EMPTY_VALUE } from "./index.js";
13
+ import { codeMirrorExtensions$, codeMirrorAutoLoadLanguageSupport$, codeBlockLanguages$, getCodeBlockLanguageSelectData, EMPTY_VALUE } from "./index.js";
14
14
  import { useCodeMirrorRef } from "../sandpack/useCodeMirrorRef.js";
15
15
  import { Select } from "../toolbar/primitives/select.js";
16
16
  const COMMON_STATE_CONFIG_EXTENSIONS = [];
@@ -28,6 +28,10 @@ const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }) => {
28
28
  const { setCode } = useCodeBlockEditorContext();
29
29
  const editorViewRef = React__default.useRef(null);
30
30
  const elRef = React__default.useRef(null);
31
+ const { value: selectValue, items: selectItems } = React__default.useMemo(
32
+ () => getCodeBlockLanguageSelectData(codeBlockLanguages, language),
33
+ [codeBlockLanguages, language]
34
+ );
31
35
  const setCodeRef = React__default.useRef(setCode);
32
36
  setCodeRef.current = setCode;
33
37
  codeMirrorRef.current = {
@@ -94,7 +98,7 @@ const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }) => {
94
98
  Select,
95
99
  {
96
100
  disabled: readOnly,
97
- value: codeBlockLanguages.keyMap[language] ?? language,
101
+ value: selectValue,
98
102
  onChange: (language2) => {
99
103
  parentEditor.update(() => {
100
104
  lexicalNode.setLanguage(language2 === EMPTY_VALUE ? "" : language2);
@@ -107,7 +111,7 @@ const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }) => {
107
111
  },
108
112
  triggerTitle: t("codeBlock.selectLanguage", "Select code block language"),
109
113
  placeholder: t("codeBlock.inlineLanguage", "Language"),
110
- items: codeBlockLanguages.items
114
+ items: selectItems
111
115
  }
112
116
  ), /* @__PURE__ */ React__default.createElement(
113
117
  "button",
@@ -10,9 +10,10 @@ function normalizeCodeBlockLanguages(input) {
10
10
  const supportMap = {};
11
11
  if (Array.isArray(input)) {
12
12
  for (const lang of input) {
13
- const canonical = ((_a = lang.alias) == null ? void 0 : _a[0]) ?? lang.name.toLowerCase();
13
+ const rawCanonical = ((_a = lang.alias) == null ? void 0 : _a[0]) ?? lang.name.toLowerCase();
14
+ const canonical = rawCanonical || EMPTY_VALUE;
14
15
  items.push({ value: canonical, label: lang.name });
15
- keyMap[canonical] = canonical;
16
+ keyMap[rawCanonical] = canonical;
16
17
  if (lang.alias) {
17
18
  for (const alias of lang.alias) {
18
19
  keyMap[alias] = canonical;
@@ -40,6 +41,16 @@ function normalizeCodeBlockLanguages(input) {
40
41
  }
41
42
  return { items, keyMap, supportMap };
42
43
  }
44
+ function getCodeBlockLanguageSelectData(normalized, language) {
45
+ const value = normalized.keyMap[language] ?? language;
46
+ if (!value || normalized.items.some((item) => item.value === value)) {
47
+ return { value, items: normalized.items };
48
+ }
49
+ return {
50
+ value,
51
+ items: [...normalized.items, { value, label: language }]
52
+ };
53
+ }
43
54
  const codeBlockLanguages$ = Cell(
44
55
  normalizeCodeBlockLanguages({
45
56
  js: "JavaScript",
@@ -79,15 +90,15 @@ const codeMirrorPlugin = realmPlugin({
79
90
  r.pubIn({
80
91
  [codeBlockLanguages$]: normalized,
81
92
  [codeMirrorExtensions$]: (params == null ? void 0 : params.codeMirrorExtensions) ?? [],
82
- [appendCodeBlockEditorDescriptor$]: buildCodeBlockDescriptor(normalized),
93
+ [appendCodeBlockEditorDescriptor$]: buildCodeBlockDescriptor(),
83
94
  [codeMirrorAutoLoadLanguageSupport$]: (params == null ? void 0 : params.autoLoadLanguageSupport) ?? true
84
95
  });
85
96
  }
86
97
  });
87
- function buildCodeBlockDescriptor(normalized) {
98
+ function buildCodeBlockDescriptor(_normalized) {
88
99
  return {
89
- match(language, meta) {
90
- return Object.hasOwn(normalized.keyMap, language ?? "") && !meta;
100
+ match(_language, meta) {
101
+ return !meta;
91
102
  },
92
103
  priority: 1,
93
104
  Editor: CodeMirrorEditor
@@ -99,6 +110,7 @@ export {
99
110
  codeMirrorAutoLoadLanguageSupport$,
100
111
  codeMirrorExtensions$,
101
112
  codeMirrorPlugin,
113
+ getCodeBlockLanguageSelectData,
102
114
  insertCodeMirror$,
103
115
  normalizeCodeBlockLanguages
104
116
  };
@@ -1,6 +1,6 @@
1
1
  const MDX_NODE_TYPES = ["mdxJsxTextElement", "mdxJsxFlowElement"];
2
2
  function isMdastHTMLNode(node) {
3
- return MDX_NODE_TYPES.includes(node.type) && htmlTags.includes(node.name.toLowerCase());
3
+ return MDX_NODE_TYPES.includes(node.type) && htmlTags.includes(node.name);
4
4
  }
5
5
  const htmlTags = [
6
6
  "a",
@@ -1,13 +1,13 @@
1
1
  import { $isLexicalJsxNode } from "./LexicalJsxNode.js";
2
2
  import { isMdastJsxNode } from "./index.js";
3
- import { htmlTags } from "../core/MdastHTMLNode.js";
3
+ import { isHtmlTagName } from "./jsxTagName.js";
4
4
  const LexicalJsxVisitor = {
5
5
  testLexicalNode: $isLexicalJsxNode,
6
6
  visitLexicalNode({ actions, mdastParent, lexicalNode }) {
7
7
  function traverseNestedJsxNodes(node) {
8
8
  if ("children" in node && node.children instanceof Array) {
9
9
  node.children.forEach((child) => {
10
- if (isMdastJsxNode(child) && !htmlTags.includes(child.name.toLowerCase())) {
10
+ if (isMdastJsxNode(child) && !isHtmlTagName(child.name)) {
11
11
  actions.registerReferredComponent(child.name);
12
12
  }
13
13
  traverseNestedJsxNodes(child);
@@ -0,0 +1,7 @@
1
+ import { htmlTags } from "../core/MdastHTMLNode.js";
2
+ function isHtmlTagName(name) {
3
+ return htmlTags.includes(name);
4
+ }
5
+ export {
6
+ isHtmlTagName
7
+ };
@@ -187,7 +187,7 @@ const TableEditor = ({ mdastNode, parentEditor, lexicalTable }) => {
187
187
  );
188
188
  }), readOnly || rowIndex === 0 && /* @__PURE__ */ React__default.createElement("th", { rowSpan: lexicalTable.getRowCount(), "data-tool-cell": true }, /* @__PURE__ */ React__default.createElement("button", { type: "button", className: styles.addColumnButton, onClick: addColumnToRight }, iconComponentFor("add_column"))));
189
189
  })),
190
- readOnly || /* @__PURE__ */ React__default.createElement("tfoot", null, /* @__PURE__ */ React__default.createElement("tr", null, /* @__PURE__ */ React__default.createElement("th", null), /* @__PURE__ */ React__default.createElement("th", { colSpan: lexicalTable.getColCount() }, /* @__PURE__ */ React__default.createElement("button", { type: "button", className: styles.addRowButton, onClick: addRowToBottom }, iconComponentFor("add_row"))), /* @__PURE__ */ React__default.createElement("th", null)))
190
+ readOnly || /* @__PURE__ */ React__default.createElement("tfoot", null, /* @__PURE__ */ React__default.createElement("tr", null, /* @__PURE__ */ React__default.createElement("th", null), /* @__PURE__ */ React__default.createElement("th", { colSpan: lexicalTable.getColCount(), "data-tool-cell": true }, /* @__PURE__ */ React__default.createElement("button", { type: "button", className: styles.addRowButton, onClick: addRowToBottom }, iconComponentFor("add_row"))), /* @__PURE__ */ React__default.createElement("th", null)))
191
191
  );
192
192
  };
193
193
  const Cell = ({ align, ...props }) => {
@@ -2,7 +2,7 @@ import { useCellValues } from "@mdxeditor/gurx";
2
2
  import React__default from "react";
3
3
  import styles from "../../../styles/ui.module.css.js";
4
4
  import { $isCodeBlockNode } from "../../codeblock/CodeBlockNode.js";
5
- import { codeBlockLanguages$, EMPTY_VALUE } from "../../codemirror/index.js";
5
+ import { codeBlockLanguages$, getCodeBlockLanguageSelectData, EMPTY_VALUE } from "../../codemirror/index.js";
6
6
  import { editorInFocus$, activeEditor$, useTranslation } from "../../core/index.js";
7
7
  import { Select } from "../primitives/select.js";
8
8
  const ChangeCodeMirrorLanguage = () => {
@@ -13,14 +13,11 @@ const ChangeCodeMirrorLanguage = () => {
13
13
  return null;
14
14
  }
15
15
  const rawLanguage = codeBlockNode.getLanguage();
16
- let currentLanguage = codeBlockLanguages.keyMap[rawLanguage] ?? rawLanguage;
17
- if (currentLanguage === "") {
18
- currentLanguage = EMPTY_VALUE;
19
- }
16
+ const { value: currentLanguage, items } = getCodeBlockLanguageSelectData(codeBlockLanguages, rawLanguage);
20
17
  return /* @__PURE__ */ React__default.createElement("div", { className: styles.selectWithLabel }, /* @__PURE__ */ React__default.createElement("label", null, t("codeBlock.language", "Code block language")), /* @__PURE__ */ React__default.createElement(
21
18
  Select,
22
19
  {
23
- value: currentLanguage,
20
+ value: currentLanguage || EMPTY_VALUE,
24
21
  onChange: (language) => {
25
22
  theEditor == null ? void 0 : theEditor.update(() => {
26
23
  codeBlockNode.setLanguage(language === EMPTY_VALUE ? "" : language);
@@ -33,7 +30,7 @@ const ChangeCodeMirrorLanguage = () => {
33
30
  },
34
31
  triggerTitle: t("codeBlock.selectLanguage", "Select code block language"),
35
32
  placeholder: t("codeBlock.language", "Code block language"),
36
- items: codeBlockLanguages.items
33
+ items
37
34
  }
38
35
  ));
39
36
  };
@@ -12,6 +12,7 @@ const InsertImage = React__default.forwardRef((_, forwardedRef) => {
12
12
  return /* @__PURE__ */ React__default.createElement(
13
13
  RadixToolbar.Button,
14
14
  {
15
+ "aria-label": t("toolbar.image", "Insert image"),
15
16
  className: styles.toolbarButton,
16
17
  ref: forwardedRef,
17
18
  disabled: readOnly,
@@ -3,7 +3,7 @@ import * as RadixToolbar from "@radix-ui/react-toolbar";
3
3
  import React__default from "react";
4
4
  import classNames from "classnames";
5
5
  import { useCombobox } from "downshift";
6
- import { editorRootElementRef$, readOnly$, iconComponentFor$ } from "../../core/index.js";
6
+ import { editorRootElementRef$, readOnly$, iconComponentFor$, useTranslation } from "../../core/index.js";
7
7
  import styles from "../../../styles/ui.module.css.js";
8
8
  import { TooltipWrap } from "./TooltipWrap.js";
9
9
  import { useCellValues, useCellValue } from "@mdxeditor/gurx";
@@ -18,7 +18,7 @@ const DialogButton = React__default.forwardRef(({ autocompleteSuggestions = [],
18
18
  },
19
19
  [onSubmit]
20
20
  );
21
- return /* @__PURE__ */ React__default.createElement(Dialog.Root, { open, onOpenChange: setOpen }, /* @__PURE__ */ React__default.createElement(Dialog.Trigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(RadixToolbar.Button, { className: styles.toolbarButton, ref: forwardedRef, disabled: readOnly }, /* @__PURE__ */ React__default.createElement(TooltipWrap, { title: tooltipTitle }, buttonContent))), /* @__PURE__ */ React__default.createElement(Dialog.Portal, { container: editorRootElementRef == null ? void 0 : editorRootElementRef.current }, /* @__PURE__ */ React__default.createElement(Dialog.Overlay, { className: styles.dialogOverlay }), /* @__PURE__ */ React__default.createElement(Dialog.Content, { className: styles.dialogContent }, /* @__PURE__ */ React__default.createElement(
21
+ return /* @__PURE__ */ React__default.createElement(Dialog.Root, { open, onOpenChange: setOpen }, /* @__PURE__ */ React__default.createElement(Dialog.Trigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(RadixToolbar.Button, { "aria-label": tooltipTitle, className: styles.toolbarButton, ref: forwardedRef, disabled: readOnly }, /* @__PURE__ */ React__default.createElement(TooltipWrap, { title: tooltipTitle }, buttonContent))), /* @__PURE__ */ React__default.createElement(Dialog.Portal, { container: editorRootElementRef == null ? void 0 : editorRootElementRef.current }, /* @__PURE__ */ React__default.createElement(Dialog.Overlay, { className: styles.dialogOverlay }), /* @__PURE__ */ React__default.createElement(Dialog.Content, { className: styles.dialogContent }, /* @__PURE__ */ React__default.createElement(
22
22
  DialogForm,
23
23
  {
24
24
  submitButtonTitle,
@@ -31,6 +31,7 @@ const DialogButton = React__default.forwardRef(({ autocompleteSuggestions = [],
31
31
  const DialogForm = ({ autocompleteSuggestions, onSubmitCallback, dialogInputPlaceholder, submitButtonTitle }) => {
32
32
  const [items, setItems] = React__default.useState(autocompleteSuggestions.slice(0, MAX_SUGGESTIONS));
33
33
  const iconComponentFor = useCellValue(iconComponentFor$);
34
+ const t = useTranslation();
34
35
  const enableAutoComplete = autocompleteSuggestions.length > 0;
35
36
  const { isOpen, getToggleButtonProps, getMenuProps, getInputProps, highlightedIndex, getItemProps, selectedItem } = useCombobox({
36
37
  initialInputValue: "",
@@ -106,7 +107,7 @@ const DialogForm = ({ autocompleteSuggestions, onSubmitCallback, dialogInputPlac
106
107
  className: classNames(styles.actionButton, styles.primaryActionButton)
107
108
  },
108
109
  iconComponentFor("check")
109
- ), /* @__PURE__ */ React__default.createElement(Dialog.Close, { className: styles.actionButton }, iconComponentFor("close")));
110
+ ), /* @__PURE__ */ React__default.createElement(Dialog.Close, { "aria-label": t("dialog.close", "Close dialog"), className: styles.actionButton }, iconComponentFor("close")));
110
111
  };
111
112
  export {
112
113
  DialogButton
@@ -47,7 +47,7 @@ const SelectButtonTrigger = ({
47
47
  className
48
48
  }) => {
49
49
  const [readOnly, iconComponentFor] = useCellValues(readOnly$, iconComponentFor$);
50
- return /* @__PURE__ */ React__default.createElement(TooltipWrap, { title }, /* @__PURE__ */ React__default.createElement(RadixSelect.Trigger, { className: classNames(styles.toolbarButtonSelectTrigger, className), disabled: readOnly }, children, /* @__PURE__ */ React__default.createElement(RadixSelect.Icon, { className: styles.selectDropdownArrow }, iconComponentFor("arrow_drop_down"))));
50
+ return /* @__PURE__ */ React__default.createElement(TooltipWrap, { title }, /* @__PURE__ */ React__default.createElement(RadixSelect.Trigger, { "aria-label": title, className: classNames(styles.toolbarButtonSelectTrigger, className), disabled: readOnly }, children, /* @__PURE__ */ React__default.createElement(RadixSelect.Icon, { className: styles.selectDropdownArrow }, iconComponentFor("arrow_drop_down"))));
51
51
  };
52
52
  const Select = (props) => {
53
53
  return /* @__PURE__ */ React__default.createElement(RadixSelect.Root, { value: props.value || "", onValueChange: props.onChange, disabled: props.disabled }, /* @__PURE__ */ React__default.createElement(SelectTrigger, { title: props.triggerTitle, placeholder: props.placeholder }), /* @__PURE__ */ React__default.createElement(SelectContent, null, props.items.map((item, index) => {
@@ -15,7 +15,7 @@ function decorateWithRef(Component, decoratedProps) {
15
15
  }
16
16
  function addTooltipToChildren(Component) {
17
17
  return ({ title, children, ...props }) => {
18
- return /* @__PURE__ */ React__default.createElement(Component, { ...props }, /* @__PURE__ */ React__default.createElement(TooltipWrap, { title }, children));
18
+ return /* @__PURE__ */ React__default.createElement(Component, { "aria-label": title, ...props }, /* @__PURE__ */ React__default.createElement(TooltipWrap, { title }, children));
19
19
  };
20
20
  }
21
21
  const Root = ({ readOnly, children, className }) => {
package/package.json CHANGED
@@ -11,17 +11,17 @@
11
11
  "*.css"
12
12
  ],
13
13
  "type": "module",
14
- "version": "3.54.0",
14
+ "version": "3.55.0",
15
15
  "description": "React component for rich text markdown editing",
16
16
  "scripts": {
17
17
  "build": "vite build",
18
18
  "build:docs:api": "typedoc",
19
- "start": "LADLE=true ladle dev",
20
- "dev": "LADLE=true ladle dev",
19
+ "start": "ladle dev",
20
+ "dev": "ladle dev",
21
21
  "typecheck": "tsc --noEmit",
22
22
  "lint": "eslint src --ext .ts,.tsx",
23
- "test": "NODE_ENV=development vitest",
24
- "test:once": "NODE_ENV=development vitest --run",
23
+ "test": "vitest",
24
+ "test:once": "vitest --run",
25
25
  "semantic-release": "semantic-release",
26
26
  "image-upload-backend": "node ./src/examples/file-backend.js",
27
27
  "export-icons": "node ./scripts/export-figma-icons.mjs"
@@ -105,7 +105,7 @@
105
105
  "@babel/plugin-transform-typescript": "^7.23.6",
106
106
  "@babel/preset-react": "^7.23.3",
107
107
  "@babel/preset-typescript": "^7.23.3",
108
- "@ladle/react": "^5.0.2",
108
+ "@ladle/react": "^5.1.1",
109
109
  "@ngneat/falso": "^6.4.0",
110
110
  "@testing-library/dom": "^10.4.1",
111
111
  "@testing-library/jest-dom": "^6.1.6",