likes
comments
collection
share

如何给Monaco Editor的滚动条自定义样式?上次我做了一个简易的公式编辑器,demo结束,大家反应不错,准备正式

作者站长头像
站长
· 阅读数 103

今日需求

上次我做了一个简易的公式编辑器,demo结束,大家反应不错,准备正式用在React项目里了,这篇文章将给一个完整的公式编辑器自定义滚动条的样式。

完成效果将是如下这样:

如何给Monaco Editor的滚动条自定义样式?上次我做了一个简易的公式编辑器,demo结束,大家反应不错,准备正式

实现

1. 完整地自定义语言设置

由于这个输入框只是为了输入公式,所以自定义语言也没有那么复杂,只是需要将单引号和双引号都算作string,上次的文章只是个简单的例子,这里我贴上我的源码:

// Formula.tsx
import Editor from '@monaco-editor/react';
import React, { useCallback, useRef, useImperativeHandle, forwardRef } from 'react';
import {
  constants,
  functions,
  keywords,
  operators,
  typeKeywords,
} from './constants';

const Formula = forwardRef((props, ref) => {
  const { value, onChange, ...restProps } = props;

  const editorRef = useRef(null);

  useImperativeHandle(ref, () => editorRef.current);

  const handleEditorDidMount = useCallback((editor, monaco) => {
    editorRef.current = editor;
    monaco.languages.register({ id: 'smPython' });
    monaco.languages.setMonarchTokensProvider('smPython', {
      keywords,
      typeKeywords,
      operators,
      functions,
      constants,
      symbols: /[=><!~?:&|+\-*\/\^%]+/,
      escapes:
        /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
      tokenizer: {
        root: [
          [
            /[a-zA-Z_]\w*/,
            {
              cases: {
                '@typeKeywords': 'keyword',
                '@keywords': 'keyword',
                '@default': 'identifier',
              },
            },
          ],
          [/[A-Z][\w\$]*/, 'type.identifier'], // to show class names nicely
          // whitespace
          { include: '@whitespace' },
          // delimiters and operators
          [/[{}()\[\]]/, '@brackets'],
          [/[<>](?!@symbols)/, '@brackets'],
          [/@symbols/, { cases: { '@operators': 'operator', '@default': '' } }],
          // @ annotations.
          // As an example, we emit a debugging log message on these tokens.
          // Note: message are supressed during the first load -- change some lines to see them.
          [
            /@\s*[a-zA-Z_\$][\w\$]*/,
            { token: 'annotation', log: 'annotation token: $0' },
          ],
          // numbers
          [/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
          [/0[xX][0-9a-fA-F]+/, 'number.hex'],
          [/\d+/, 'number'],
          // delimiter: after number because of .\d floats
          [/[;,.]/, 'delimiter'],
          // strings
          [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
          [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string
          [/"/, 'string', '@string_double'],
          [/'/, 'string', '@string_single'],
        ],
        comment: [
          [/[^\/*]+/, 'comment'],
          [/\/\*/, 'comment', '@push'], // nested comment
          ['\\*/', 'comment', '@pop'],
          [/[\/*]/, 'comment'],
        ],
        string_double: [
          [/[^\\"]+/, 'string'],
          [/@escapes/, 'string.escape'],
          [/\\./, 'string.escape.invalid'],
          [/"/, 'string', '@pop'],
        ],
        string_single: [
          [/[^\\']+/, 'string'],
          [/@escapes/, 'string.escape'],
          [/\\./, 'string.escape.invalid'],
          [/'/, 'string', '@pop'],
        ],
        whitespace: [
          [/[ \t\r\n]+/, 'white'],
          [/\/\*/, 'comment', '@comment'],
          [/\/\/.*$/, 'comment'],
        ],
      },
    });
    monaco.languages.registerCompletionItemProvider('smPython', {
      provideCompletionItems: (model, position) => {
        const suggestions = [
          ...keywords.map(i => {
            return {
              label: i,
              kind: monaco.languages.CompletionItemKind.Keyword,
              insertText: i,
            };
          }),
          ...typeKeywords.map(i => {
            return {
              label: i,
              kind: monaco.languages.CompletionItemKind.Keyword,
              insertText: i,
            };
          }),
          ...functions.map(i => {
            return {
              label: i,
              kind: monaco.languages.CompletionItemKind.Function,
              insertText: i,
            };
          }),
          ...operators.map(i => {
            return {
              label: i,
              kind: monaco.languages.CompletionItemKind.Operator,
              insertText: i,
            };
          }),
        ];
        return { suggestions };
      },
    });
    monaco.languages.registerCompletionItemProvider('smPython', {
      // Run this function when one of below characters is typed (and anything after a space)
      triggerCharacters: ['+', '-', '/', '>', '<', '=', '('],
      provideCompletionItems: function (model, position, token) {
        const suggestions = [
          ...constants.map(c => {
            return {
              label: c,
              kind: monaco.languages.CompletionItemKind.Constant,
              insertText: c,
            };
          }),
        ];
        return { suggestions };
      },
    });
  }, []);

  const handleChange = useCallback(
    (newValue, event) => {
      onChange({
        ...event,
        target: {
          ...editorRef.current,
          value: newValue,
        },
      });
    },
    [onChange]
  );

  return (
    <Editor
      width="100%"
      height="46px"
      language="smPython"
      value={value}
      theme="light"
      options={{
        lineNumbers: 'off',
        lineDecorationsWidth: 0,
        glyphMargin: false,
        folding: false,
        minimap: {
          enabled: false,
        },
      }}
      wrapperProps={restProps}
      onMount={handleEditorDidMount}
      onChange={handleChange}
    />
  );
});

export default Formula;

2. Scrollbar Options

Editor组件里将scrollbar滚动条的设置传进去,主要是显示与否,大小之类的:

// Formula.tsx
<Editor
  width="100%"
  height="46px"
  language="smPython"
  value={value}
  theme="light"
  options={{
    lineNumbers: 'off',
    lineDecorationsWidth: 0,
    glyphMargin: false,
    folding: false,
    minimap: {
      enabled: false,
    },
    scrollbar: {
      horizontal: "visible",
      horizontalHasArrows: false,
      horizontalScrollbarSize: 14,
      horizontalSliderSize: 6,
      vertical: "visible",
      verticalHasArrows: false,
      verticalScrollbarSize: 14,
      verticalSliderSize: 6,
    }
  }}
  wrapperProps={restProps}
  onMount={handleEditorDidMount}
  onChange={handleChange}
/>

我暂时把horizontalvertical都设为visible,这样方便我在页面上检查元素。

2. 设置CSS

在全局css里设置:

/* global.css */
.monaco-editor .monaco-scrollable-element .scrollbar.horizontal,
.monaco-editor .monaco-scrollable-element .scrollbar.vertical {
  background-color: transparent;
  cursor: default;
}
.monaco-editor .monaco-scrollable-element > .scrollbar > .slider {
  border: 4px solid transparent;
  background-clip: content-box !important;
  border-radius: 7px;
  background: #c8d5e1;
}
.monaco-editor .monaco-scrollable-element > .scrollbar > .slider:hover {
  background: #a8bbcf;
}
.monaco-editor .monaco-scrollable-element > .scrollbar > .slider.active {
  background: #87a2bd;
}

3. 用MUI组件库封装

// FormulaField.tsx

<FormControl className={className} sx={{ height: '101px' }}> // 自定义
    <InputLabel
      htmlFor="smql-formula"
      required={required}
      {...(isTouched && errorType && { error: true })}
    >
      {label}
    </InputLabel>
    <OutlinedInput
      id="smql-formula"
      label={label}
      sx={{ width: '100%', height: '80px' }} // 自定义
      required={required}
      inputRef={ref}
      value={value}
      inputComponent={Formula}    // 刚刚定义的Formula组件
      onChange={handleInputChange}
      onBlur={handleBlur}
    />
    <FormHelperText>  // 公式输入框往往需要一个链接,引导用户按文档使用
        <a
          style={{
            color: '#0076d4',
            textDecoration: 'none',
            cursor: 'pointer',
            display: 'inline',
          }}
          href={helperLink}
          target="_blank"
          rel="noopener noreferrer"
        >
          {'More Help  '}
          <OpenInNewIcon
            sx={{ fontSize: 14, verticalAlign: 'middle' }}
          />
        </a>
    </FormHelperText>
</FormControl>

4. Clean Code

最后记得把第2步里的visible去掉,就可以看到这样的效果啦:\ 如何给Monaco Editor的滚动条自定义样式?上次我做了一个简易的公式编辑器,demo结束,大家反应不错,准备正式

引用

转载自:https://juejin.cn/post/7414424159521177626
评论
请登录