@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
94 lines (87 loc) • 3.04 kB
text/typescript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {MenuOption, TriggerFn} from './shared/LexicalMenu';
import {getScrollParent as getScrollParent_} from '@lexical/utils';
import {createCommand, type LexicalCommand} from 'lexical';
import {useCallback} from 'react';
/**
* The default set of punctuation characters (as a character-class fragment)
* that terminate a typeahead query. Used as the default `punctuation` option of
* {@link useBasicTypeaheadTriggerMatch}.
*/
export const PUNCTUATION =
'\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
/**
* Command dispatched while the typeahead menu is open to scroll the option at
* the given `index` into view. The built-in menu hook registers a default
* handler; custom implementations can register their own handler at a higher
* priority to override the scrolling behavior.
*/
export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
index: number;
option: MenuOption;
}> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
/** @deprecated Moved to `@lexical/utils`. Import `getScrollParent` from there. */
export const getScrollParent = getScrollParent_;
/**
* Builds a {@link TriggerFn} for the common case of a single-character
* `trigger` (such as `@` or `#`) followed by a query. The returned function
* matches when the trigger is preceded by whitespace, an open parenthesis,
* or the start of the line
* and is followed by between `minLength` and `maxLength` non-`punctuation`
* characters (optionally allowing whitespace).
*
* @returns A memoized trigger function for {@link LexicalTypeaheadMenuPlugin}.
*/
export function useBasicTypeaheadTriggerMatch(
trigger: string,
{
minLength = 1,
maxLength = 75,
punctuation = PUNCTUATION,
allowWhitespace = false,
}: {
minLength?: number;
maxLength?: number;
punctuation?: string;
allowWhitespace?: boolean;
},
): TriggerFn {
return useCallback(
(text: string) => {
const validCharsSuffix = allowWhitespace ? '' : '\\s';
const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
const TypeaheadTriggerRegex = new RegExp(
'(^|\\s|\\()(' +
'[' +
trigger +
']' +
'((?:' +
validChars +
'){0,' +
maxLength +
'})' +
')$',
);
const match = TypeaheadTriggerRegex.exec(text);
if (match !== null) {
const maybeLeadingWhitespace = match[1];
const matchingString = match[3];
if (matchingString.length >= minLength) {
return {
leadOffset: match.index + maybeLeadingWhitespace.length,
matchingString,
replaceableString: match[2],
};
}
}
return null;
},
[allowWhitespace, trigger, punctuation, maxLength, minLength],
);
}