Skip to content

Commit

Permalink
feat: Add open for mock (#127)
Browse files Browse the repository at this point in the history
* feat: support open

* chore: open mock
  • Loading branch information
zombieJ authored Jul 12, 2022
1 parent 97852d1 commit f2f43d2
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 25 deletions.
2 changes: 1 addition & 1 deletion examples/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import '../assets/index.less';
const { Option } = Mentions;

export default () => (
<Mentions rows={3} defaultValue="Hello World" open>
<Mentions rows={3} defaultValue="Hello @ World @" open>
<Option value="light">Light</Option>
<Option value="bamboo">Bamboo</Option>
<Option value="cat">Cat</Option>
Expand Down
90 changes: 73 additions & 17 deletions src/Mentions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import toArray from 'rc-util/lib/Children/toArray';
import KeyCode from 'rc-util/lib/KeyCode';
import warning from 'rc-util/lib/warning';
import * as React from 'react';
import { useState, useRef } from 'react';
import TextArea from 'rc-textarea';
Expand Down Expand Up @@ -75,6 +76,8 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
defaultValue,
children,

open,

// Events
validateSearch,
filterOption,
Expand All @@ -99,6 +102,12 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
...restProps
} = props;

const mergedPrefix = Array.isArray(prefix) ? prefix : [prefix];
const mergedProps = {
...props,
prefix: mergedPrefix,
};

// =============================== Refs ===============================
const textareaRef = useRef<TextArea>(null);
const measureRef = useRef<HTMLDivElement>(null);
Expand All @@ -122,6 +131,48 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
value: value,
});

// =============================== Open ===============================
const [
mergedMeasuring,
mergedMeasureText,
mergedMeasurePrefix,
mergedMeasureLocation,
] = React.useMemo<
[
typeof measuring,
typeof measureText,
typeof measurePrefix,
typeof measureLocation,
]
>(() => {
if (open) {
if (process.env.NODE_ENV !== 'production') {
warning(
false,
'`open` of Mentions is only used for debug usage. Do not use in you production.',
);
}

for (let i = 0; i < mergedPrefix.length; i += 1) {
const curPrefix = mergedPrefix[i];
const index = mergedValue.lastIndexOf(curPrefix);
if (index >= 0) {
return [true, '', curPrefix, index];
}
}
}

return [measuring, measureText, measurePrefix, measureLocation];
}, [
open,
measuring,
mergedPrefix,
mergedValue,
measureText,
measurePrefix,
measureLocation,
]);

// ============================== Option ==============================
const getOptions = React.useCallback(
(targetMeasureText: string) => {
Expand Down Expand Up @@ -151,8 +202,8 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
);

const options = React.useMemo(
() => getOptions(measureText),
[getOptions, measureText],
() => getOptions(mergedMeasureText),
[getOptions, mergedMeasureText],
);

// ============================= Measure ==============================
Expand Down Expand Up @@ -195,9 +246,9 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {

const { value: mentionValue = '' } = option;
const { text, selectionLocation } = replaceWithMeasure(mergedValue, {
measureLocation,
measureLocation: mergedMeasureLocation,
targetText: mentionValue,
prefix: measurePrefix,
prefix: mergedMeasurePrefix,
selectionStart: getTextArea()?.selectionStart,
split,
});
Expand All @@ -207,7 +258,7 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
setInputSelection(getTextArea(), selectionLocation);
});

onSelect?.(option, measurePrefix);
onSelect?.(option, mergedMeasurePrefix);
};

// ============================= KeyEvent =============================
Expand All @@ -220,7 +271,7 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
onKeyDown?.(event);

// Skip if not measuring
if (!measuring) {
if (!mergedMeasuring) {
return;
}

Expand Down Expand Up @@ -264,7 +315,7 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
const target = event.target as HTMLTextAreaElement;
const selectionStartText = getBeforeSelectionText(target);
const { location: measureIndex, prefix: nextMeasurePrefix } =
getLastMeasureIndex(selectionStartText, prefix);
getLastMeasureIndex(selectionStartText, mergedPrefix);

// If the client implements an onKeyUp handler, call it
onKeyUp?.(event);
Expand All @@ -281,19 +332,22 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
const nextMeasureText = selectionStartText.slice(
measureIndex + nextMeasurePrefix.length,
);
const validateMeasure: boolean = validateSearch(nextMeasureText, props);
const validateMeasure: boolean = validateSearch(
nextMeasureText,
mergedProps,
);
const matchOption = !!getOptions(nextMeasureText).length;

if (validateMeasure) {
if (
key === nextMeasurePrefix ||
key === 'Shift' ||
measuring ||
(nextMeasureText !== measureText && matchOption)
mergedMeasuring ||
(nextMeasureText !== mergedMeasureText && matchOption)
) {
startMeasure(nextMeasureText, nextMeasurePrefix, measureIndex);
}
} else if (measuring) {
} else if (mergedMeasuring) {
// Stop if measureText is invalidate
stopMeasure();
}
Expand All @@ -305,15 +359,15 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
if (onSearch && validateMeasure) {
onSearch(nextMeasureText, nextMeasurePrefix);
}
} else if (measuring) {
} else if (mergedMeasuring) {
stopMeasure();
}
};

const onInternalPressEnter: React.KeyboardEventHandler<
HTMLTextAreaElement
> = event => {
if (!measuring && onPressEnter) {
if (!mergedMeasuring && onPressEnter) {
onPressEnter(event);
}
};
Expand Down Expand Up @@ -359,9 +413,9 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
onFocus={onInternalFocus}
onBlur={onInternalBlur}
/>
{measuring && (
{mergedMeasuring && (
<div ref={measureRef} className={`${prefixCls}-measure`}>
{mergedValue.slice(0, measureLocation)}
{mergedValue.slice(0, mergedMeasureLocation)}
<MentionsContext.Provider
value={{
notFoundContent,
Expand All @@ -382,10 +436,12 @@ const Mentions = React.forwardRef<MentionsRef, MentionsProps>((props, ref) => {
getPopupContainer={getPopupContainer}
dropdownClassName={dropdownClassName}
>
<span>{measurePrefix}</span>
<span>{mergedMeasurePrefix}</span>
</KeywordTrigger>
</MentionsContext.Provider>
{mergedValue.slice(measureLocation + measurePrefix.length)}
{mergedValue.slice(
mergedMeasureLocation + mergedMeasurePrefix.length,
)}
</div>
)}
</div>
Expand Down
9 changes: 2 additions & 7 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ export function getBeforeSelectionText(input: HTMLTextAreaElement) {
return input.value.slice(0, selectionStart);
}

export function toArr<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}

interface MeasureIndex {
location: number;
prefix: string;
Expand All @@ -22,10 +18,9 @@ interface MeasureIndex {
*/
export function getLastMeasureIndex(
text: string,
prefix: string | string[] = '',
prefix: string[],
): MeasureIndex {
const prefixList: string[] = toArr(prefix);
return prefixList.reduce(
return prefix.reduce(
(lastMatch: MeasureIndex, prefixStr): MeasureIndex => {
const lastIndex = text.lastIndexOf(prefixStr);
if (lastIndex > lastMatch.location) {
Expand Down
27 changes: 27 additions & 0 deletions tests/Open.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import Mentions from '../src';
import { expectMeasuring } from './util';
import { render } from '@testing-library/react';

const { Option } = Mentions;

describe('Mentions.Open', () => {
it('force open', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

const { container } = render(
<Mentions open defaultValue="@cat @">
<Option value="bamboo">Bamboo</Option>
<Option value="light">Light</Option>
<Option value="cat">Cat</Option>
</Mentions>,
);

expectMeasuring(container);

expect(errorSpy).toHaveBeenCalledWith(
'Warning: `open` of Mentions is only used for debug usage. Do not use in you production.',
);
errorSpy.mockRestore();
});
});

0 comments on commit f2f43d2

Please sign in to comment.