Office UI Fabric TextField focus/cursor 问题

Office UI Fabric TextField focus/cursor issues

我有一个 CodePen 可以说明这里的问题:https://codepen.io/elegault/pen/QzZwLO

场景:一个DetailsList组件和一个搜索框(TextField组件)。当用户在搜索框中键入内容时,可以过滤列表项。任何选定的项目如果在搜索结果中,仍会在搜索结果中被选中。如果它不在搜索结果中,并且后续搜索确实包含该选择,则会重新选择它。 (注意:Office UI Fabric 团队似乎意识到这应该在本机处理,但我不确定添加此功能的计划,根据此 GitHub issue)。

问题:每次按键后焦点都会丢失,这使得输入和编辑搜索条件变得困难,因为用户每次都必须重新插入光标。

什么不起作用:在 TextField 已经聚焦 (isFocused = true) 时调用 focus() 没有任何作用。调用 focus() 仅在 isFocused = false 时有效。但这仅在筛选列表中恢复选择后调用 DetailsList.focusIndex() 时才成立。

伪代码:

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);
}

这是 TextField 或 DetailsList 组件中的某种错误吗?或者我在 React 组件生命周期中这样做的方式?或者有没有一种方法可以确保在用户键入和重新计算列表项以及修改所选索引时焦点不会从 TextField 丢失?

我最近偶然发现了一个类似的功能请求,并想出了以下解决方案,允许在过滤数据时在 DetailsList 中保留选择

首先引入一个单独的组件来实现保留选择的逻辑:

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);
    }
  }
}

现在 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 });
  };
}

这里是a demo