@jupyterlab/ui-components
Version:
JupyterLab - UI components written in React
138 lines • 5.37 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ReactWidget } from './vdom';
import { StringExt } from '@lumino/algorithm';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Search } from '@jupyter/react-components';
import { searchIcon } from '../icon';
/**
* Perform a fuzzy search on a single item.
*/
export function fuzzySearch(source, query) {
// Set up the match score and indices array.
let score = Infinity;
let indices = null;
// Look for letters (including in Asian scripts), numbers, and diacritical marks.
const rgx = /[\p{L}\p{N}\p{M}]+/gu;
let continueSearch = true;
// Search the source by word boundary.
while (continueSearch) {
// Find the next word boundary in the source.
let rgxMatch = rgx.exec(source);
// Break if there is no more source context.
if (!rgxMatch) {
break;
}
// Run the string match on the relevant substring.
let match = StringExt.matchSumOfDeltas(source, query, rgxMatch.index);
// Break if there is no match.
if (!match) {
break;
}
// Update the match if the score is better.
if (match && match.score <= score) {
score = match.score;
indices = match.indices;
}
}
// Bail if there was no match.
if (!indices || score === Infinity) {
return null;
}
// Handle a split match.
return {
score,
indices
};
}
export const updateFilterFunction = (value, useFuzzyFilter, caseSensitive) => {
return (item) => {
if (useFuzzyFilter) {
// Run the fuzzy search for the item and query.
const query = value.toLowerCase();
// Ignore the item if it is not a match.
return fuzzySearch(item, query);
}
if (!caseSensitive) {
item = item.toLocaleLowerCase();
value = value.toLocaleLowerCase();
}
const i = item.indexOf(value);
if (i === -1) {
return null;
}
return {
indices: [...Array(value.length).keys()].map(x => x + i)
};
};
};
export const FilterBox = (props) => {
var _a, _b, _c;
const [filter, setFilter] = useState((_a = props.initialQuery) !== null && _a !== void 0 ? _a : '');
if (props.forceRefresh) {
useEffect(() => {
props.updateFilter((item) => {
return {};
});
}, []);
}
const firstRender = useRef(true);
const inputRef = (_b = props.inputRef) !== null && _b !== void 0 ? _b : useRef();
useEffect(() => {
// If there is an initial search value, pass the parent the initial filter function for that value.
if (firstRender.current) {
firstRender.current = false;
if (props.initialQuery !== undefined) {
props.updateFilter(updateFilterFunction(props.initialQuery, props.useFuzzyFilter, props.caseSensitive), props.initialQuery);
}
}
else {
if (inputRef.current) {
// If filter settings changed, update the filter
props.updateFilter(updateFilterFunction(inputRef.current.value, props.useFuzzyFilter, props.caseSensitive), inputRef.current.value);
}
}
}, [props.updateFilter, props.useFuzzyFilter, props.caseSensitive]);
/**
* Handler for search input changes.
*/
const handleChange = useCallback((e) => {
const target = e.target;
setFilter(target.value);
props.updateFilter(updateFilterFunction(target.value, props.useFuzzyFilter, props.caseSensitive), target.value);
}, [props.updateFilter, props.useFuzzyFilter, props.caseSensitive]);
// Show the icon by default, or if the caller specifically requests it
const showSearchIcon = (_c = props.showIcon) !== null && _c !== void 0 ? _c : true;
// Cast typing is required because HTMLInputElement and JupyterSearch element types don't exactly match
return (React.createElement(Search, { role: "search", className: "jp-FilterBox", ref: props.inputRef, value: filter, onChange: handleChange, onInput: handleChange, placeholder: props.placeholder, disabled: props.disabled }, showSearchIcon && React.createElement(searchIcon.react, { slot: "end", tag: null })));
};
/**
* A widget which hosts a input textbox to filter on file names.
*/
class FilenameSearcherWidget extends ReactWidget {
constructor(props) {
var _a;
super();
this._filterBoxProps = { ...props };
(_a = props === null || props === void 0 ? void 0 : props.filterSettingsChanged) === null || _a === void 0 ? void 0 : _a.connect((_, args) => {
this._updateProps(args);
}, this);
}
render() {
return React.createElement(FilterBox, { ...this._filterBoxProps });
}
/**
* Update a specific prop.
*/
_updateProps(props) {
Object.assign(this._filterBoxProps, props);
this.update();
}
}
/**
* Function which returns a widget that hosts an input textbox to filter on file names.
*/
export const FilenameSearcher = (props) => {
return new FilenameSearcherWidget(props);
};
//# sourceMappingURL=search.js.map