简体   繁体   English

当焦点更改为 React Select 时,React Slate 中的文本选择不再标记

[英]Text selection in React Slate no longer marked when focus changes to React Select

I don't know if this is a common issue or a mistake on our part but maybe someone has an idea: We're building an HTML editor with react and slate.我不知道这是一个常见问题还是我们的错误,但也许有人有一个想法:我们正在构建一个带有 react 和 slate 的 HTML 编辑器。 The User can select a textbox and then change attributes.用户可以 select 一个文本框然后更改属性。 This works fine for simple buttons.这适用于简单的按钮。 However, when I open a dropdown (react-select) to for example change font size, the selected text is no longer marked.但是,当我打开一个下拉列表(react-select)以更改字体大小时,所选文本不再被标记。 Slate keeps the selection so the changes take effect but it's a bad UX like that. Slate 保留选择以使更改生效,但这样的用户体验很糟糕。

Imho this should be a slate feature to keep the text marked but maybe it's something I need to apply myself.恕我直言,这应该是保持文本标记的石板功能,但也许这是我需要自己应用的东西。

some snippets, don't know if they help:一些片段,不知道他们是否有帮助:

The Editor component initializes the font style plugins and takes care of serialization. Editor 组件初始化字体样式插件并负责序列化。

class Editor extends React.Component {
  constructor(props) {
    super(props);

    this.config = {
      ...mergePluginConfig(PLUGIN_CONFIG, props),
      getEditor: () => this.editor,
      getValue: () => this.state.value,
    };
    this.plugins = initializePlugins(this.config);
    this.htmlSerializer = new HtmlSerializer({
      rules: getSerializationRulesFromPlugins(this.plugins),
    });
    this.schema = getSchemaFromPlugins(this.plugins);
    this.state = {
      value: this.htmlSerializer.deserialize(props.value)
    };



ref = editor => {
    this.editor = editor;
  };


render() {
    return (
      <div>
        <Toolbar>
            <div className="control">
                {renderToolbarElementWithPlugins(this.plugins, 'font-size')}
            </div>
        <!--- more tools --->
      <SlateEditor
            autoFocus={true}
            spellCheck={true}
            placeholder={this.props.placeholder}
            ref={this.ref}
            value={this.state.value}
            onChange={this.onChange}
            onKeyDown={this.onKeyDown}
            plugins={this.plugins}
            schema={this.schema}
       />


onChange = ({ value }) => {
    const {startInline, endInline, document, selection, fragment} = value;
    // holds the correct information
    console.log(fragment.text);
    // ...
    this.setState({ value });
    this.props.onChange(this.htmlSerializer.serialize(value));
 };

This is the font-size plugin that is initialized with the others and will be displayed in the toolbar:这是与其他插件一起初始化的字体大小插件,将显示在工具栏中:

export default function initializeFontSizePlugin(options) {
  // this takes care of detecting the current value and applying selected change to the value. 
  // it does not change selection
  const plugin = createStyleBasedMarkPlugin(...); 
  const fontSizeOptions = options.fontSizeOptions || [];

  const handleFontSizeChange = ({value}) => {
    plugin.reapplyFontSize({value: rendererFontSize(value)});
  };

  return {
    ...plugin,

    renderToolbarElement() {
      const {isMixed, fontSize} = plugin.detectFontSize();

      return <Select
        isCreatable={true}
        name='font-size'
        value={isMixed ? undefined : displayFontSize(fontSize)}
        onChange={handleFontSizeChange}
        options={fontSizeOptions}
      />;
    }
  };
}

My current solution is to focus slate as soon as select opens and then tell select to be open but that feels hackish and has drawbacks (see below)我目前的解决方案是在 select 打开后立即关注 slate,然后告诉 select 打开,但这感觉很糟糕并且有缺点(见下文)

const handleFontSizeChange = ({value}) => {
    plugin.reapplyFontSize({value: rendererFontSize(value)});
    handleMenuClose();
  };

  let menuIsOpen = false;
  let firstOpen = false;

  const handleMenuOpen = (editor) => {
    firstOpen = true;
    if(!menuIsOpen) {
      setTimeout(() => {
        if (editor) {
          editor.focus();
        }
        menuIsOpen = true;
      }, 1);
    }
  }
  const handleMenuClose = (editor) => {
    if(!firstOpen) {
      setTimeout(() => {
        if(menuIsOpen) {
          menuIsOpen = false;
          if (editor) {
            editor.focus();
          }
        }
      }, 1);
    } else {
      firstOpen = false;
    }  
  }

<Select
    onMenuOpen={handleMenuOpen.bind(this)}
    onMenuClose={handleMenuClose.bind(this)}
    menuIsOpen={menuIsOpen}

I have to use the timeout to get outside the react lifecycle and I have to add an additional flag since losing focus on the select component will also close it.我必须使用超时来超出反应生命周期,并且我必须添加一个额外的标志,因为失去对 select 组件的关注也会关闭它。 Like I said that has drawbacks: - a little flicker on the selected text during the focus switch - the dropdown box in select has the wrong coloring (not focused obviously) - switching to another dropdown (like alignment) won't close the other since that already has no focus:就像我说的那样有缺点: - 在焦点切换期间所选文本有点闪烁 - select 中的下拉框颜色错误(明显没有聚焦) - 切换到另一个下拉框(如对齐)不会关闭另一个,因为已经没有焦点:

Additional info: We have to work with slate and slate-react at version 0.47 as higher versions are not supported by slate-html-serializer which we need.附加信息:我们必须在 0.47 版本中使用 slate 和slate-react ,因为我们需要的slate-html-serializer不支持更高版本。 Maybe this has already been solved in a higher version?也许这已经在更高版本中解决了?

So, I have a somewhat working version but I'd much more prefer a solution where slate takes care of the selection "natively" without me having to handle focus.所以,我有一个有点工作的版本,但我更喜欢一个解决方案,其中 slate 可以“本地”处理选择,而无需我处理焦点。 It should be possible I think without the selected text flickering and off colors.我认为应该有可能没有选定的文本flickering和关闭 colors。

Slate doesn't hold the selection when you focus out of the editor due to a dropdown being opened.由于打开了下拉菜单,当您将注意力移出编辑器时,Slate 不会保留选择。 Now with buttons it is different as it reapplies the selection现在有了按钮,它就不同了,因为它重新应用了选择

Since you now have to manually apply and get selections a way to do this is to store editor selection in state when you are trying to open the menu from select.由于您现在必须手动应用和获取选择,因此当您尝试从 select 打开菜单时,将编辑器选择存储在 state 中。 When menu is open, reapply selection using Transforms.setSelection and get the fontSize which you can store in state again to show the focussed value in dropdown打开菜单后,使用Transforms.setSelection重新应用选择并获取可以存储在 state 中的 fontSize 以在下拉列表中显示焦点值

Now once you apply the change, you need to apply the selection again现在,一旦应用更改,您需要再次应用选择

You can follow the concept used in this PR您可以遵循此 PR中使用的概念

const [selection, setSelection] = useState();
const [menuIsOpen, setMenuIsOpen] = useState(false);
const [fontSize, setFontSize] = useState(plugins.detectFontSize());
const handleFontSizeChange = ({value}) => {
    plugin.reapplyFontSize({value: rendererFontSize(value)});
    handleMenuClose();
  };
}
const handleMenuOpen = (editor) => {
    setSelection(editor.selection);
    setMenuIsOpen(true);
    Transforms.setSelection() // pass in the required params here
    setFontSize(plugins.detectFontSize());
}
const handleMenuClose = (editor) => {
    setMenuIsOpen(false);
    Transforms.setSelection() // pass in the required params here based on selection state
}

<Select
    onMenuOpen={handleMenuOpen.bind(this)}
    onMenuClose={handleMenuClose.bind(this)}
    menuIsOpen={menuIsOpen}
    value={fontSize}
    options={options}
/>

Also have a look at this github issue regarding focus and selection另请查看有关焦点和选择的github 问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM