简体   繁体   English

Office UI Fabric TextField焦点/光标问题

[英]Office UI Fabric TextField focus/cursor issues

I have a CodePen that illustrates the issue here: https://codepen.io/elegault/pen/QzZwLO 我有一个CodePen可以在此处说明问题: https ://codepen.io/elegault/pen/QzZwLO

Scenario : A DetailsList component and a search box (the TextField component). 场景 :一个DetailsList组件和一个搜索框(TextField组件)。 The list items can be filtered as the user types in the search box. 可以根据用户在搜索框中输入的内容来过滤列表项。 Any selected project will still be selected in the search results IF it is in the search results. 如果已在搜索结果中选择了任何项目,则仍将在搜索结果中选择该项目。 If it is not in the search results and a subsequent search does include that selection, it will be re-selected. 如果它不在搜索结果中,并且随后的搜索确实包括该选择,则将重新选择它。 (NOTE: the Office UI Fabric team seems aware that this should be handled natively, but I'm not sure of the plans to add this functionality, as per this GitHub issue ). (注意:Office UI Fabric团队似乎意识到应该以本机处理,但是根据GitHub问题 ,我不确定添加此功能的计划。)

Problem : after every key press the focus is lost, which makes entering and editing the search criteria difficult as the user has to reinsert the cursor every time. 问题 :每次按键后焦点都丢失了,这使得输入和编辑搜索条件变得困难,因为用户每次都必须重新插入光标。

What's not working : calling focus() on the TextField while it is already focused (isFocused = true) does nothing. 什么是行不通的 :在TextField已经被聚焦时(isFocused = true)在TextField上调用focus()不会执行任何操作。 Calling focus() only works when isFocused = false. 仅当isFocused = false时才调用focus()。 But this is only true when DetailsList.focusIndex() is called after a selection is restored in a filtered list. 但这仅在筛选列表中恢复选择后调用DetailsList.focusIndex()时才如此。

Pseudo-code: 伪代码:

componentDidUpdate(previousProps: any, previousState: AppProjectListState) {
  //Toggle off the current selection
  this._selection.toggleIndexSelected(currentIdx);
  //Set the new selection
  this._selection.toggleIndexSelected(newIdx);
  //Scroll the selection into view
  this._detailsListRef.current.focusIndex(newIdx, false);
}

Is this a bug of some sort in either the TextField or DetailsList component? 这是TextField或DetailsList组件中的某种错误吗? Or with the way I'm doing this in the React component life cycle? 还是用我在React组件生命周期中执行此操作的方式? Or is there a way to ensure that focus is not lost from the TextField while the user is typing and the list items are being recalculated and while the selected index is modified? 还是有一种方法可以确保在用户键入文本和重新计算列表项以及修改所选索引时不会从TextField中失去焦点?

I recently stumbled upon on a similar feature request and came up with the following solution which allows to preserve the selection in DetailsList while the data is getting filtered. 我最近偶然发现了类似的功能请求,并提出了以下解决方案,该解决方案允许在过滤数据时将选择保留DetailsList

First a separate component is introduced which implements the logic to preserve the selection: 首先,引入一个单独的组件,该组件实现保留选择的逻辑:

export interface IViewSelection {}

export interface IViewSelectionProps
  extends React.HTMLAttributes<HTMLDivElement> {
  componentRef?: IRefObject<IViewSelection>;

  /**
   * The selection object to interact with when updating selection changes.
   */
  selection: ISelection;

  items: any[];
}

export interface IViewSelectionState {}

export class ViewSelection extends BaseComponent<
  IViewSelectionProps,
  IViewSelectionState
> {
  private items: any[];
  private selectedIndices: any[];
  constructor(props: IViewSelectionProps) {
    super(props);
    this.state = {};
    this.items = this.props.items;
    this.selectedIndices = [];
  }

  public render() {
    const { children } = this.props;
    return <div>{children}</div>;
  }

  public componentWillUpdate(
    nextProps: IViewSelectionProps,
    nextState: IViewSelectionState
  ) {
    this.saveSelection();
  }

  public componentDidUpdate(
    prevProps: IViewSelectionProps,
    prevState: IViewSelectionState
  ) {
    this.restoreSelection();
  }

  private toListIndex(index: number) {
    const viewItems = this.props.selection.getItems();
    const viewItem = viewItems[index];
    return this.items.findIndex(listItem => listItem === viewItem);
  }

  private toViewIndex(index: number) {
    const listItem = this.items[index];
    const viewIndex = this.props.selection
      .getItems()
      .findIndex(viewItem => viewItem === listItem);
    return viewIndex;
  }

  private saveSelection(): void {
    const newIndices = this.props.selection
      .getSelectedIndices()
      .map(index => this.toListIndex(index))
      .filter(index => this.selectedIndices.indexOf(index) === -1);

    const unselectedIndices = this.props.selection
      .getItems()
      .map((item, index) => index)
      .filter(index => this.props.selection.isIndexSelected(index) === false)
      .map(index => this.toListIndex(index));

    this.selectedIndices = this.selectedIndices.filter(
      index => unselectedIndices.indexOf(index) === -1
    );
    this.selectedIndices = [...this.selectedIndices, ...newIndices];
  }

  private restoreSelection(): void {
    const indices = this.selectedIndices
      .map(index => this.toViewIndex(index))
      .filter(index => index !== -1);
    for (const index of indices) {
      this.props.selection.setIndexSelected(index, true, false);
    }
  }
}

Now DetailsList component needs to be wrapped with ViewSelection component to save and restore the selection while filtering is applied : 现在, DetailsList组件需要与ViewSelection组件一起包装, 以在应用过滤时保存和恢复选择

const items = generateItems(20);

export default class DetailsListBasicExample extends React.Component<
  {},
  {
    viewItems: any[];
  }
> {
  private selection: Selection;
  private detailsList = React.createRef<IDetailsList>();

  constructor(props: {}) {
    super(props);

    this.selection = new Selection({
    });
    this.state = {
      viewItems: items
    };
    this.handleChange = this.handleChange.bind(this);
  }

  public render(): JSX.Element {
    return (
      <div>
        <TextField label="Filter by name:" onChange={this.handleChange} />
        <ViewSelection selection={this.selection} items={this.state.viewItems} >
          <DetailsList
            componentRef={this.detailsList}
            items={this.state.viewItems}
            columns={columns}
            setKey="set"
            layoutMode={DetailsListLayoutMode.fixedColumns}
            selection={this.selection}
            selectionMode={SelectionMode.multiple}
            selectionPreservedOnEmptyClick={true}
          />
        </ViewSelection>
      </div>
    );
  }

  private handleChange = (
    ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
    text: string
  ): void => {
    const viewItems = text
      ? items.filter(item => item.name.toLowerCase().indexOf(text.toLocaleLowerCase()) > -1)
      : items;
    this.setState({ viewItems });
  };
}

Here is a demo 这是一个演示

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

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