Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
changes selection to be completely controlled by parent component
Browse files Browse the repository at this point in the history
  • Loading branch information
GoodGuyMarco committed Nov 29, 2022
1 parent 6bb2128 commit c6d1071
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 138 deletions.
133 changes: 54 additions & 79 deletions src/components/structures/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { useState, ReactNode, ChangeEvent, KeyboardEvent, useEffect, useCallback, useRef } from 'react';
import React, { useState, ReactNode, ChangeEvent, KeyboardEvent, useRef } from 'react';
import classNames from 'classnames';

import Autocompleter from "../../autocomplete/AutocompleteProvider";
Expand All @@ -27,10 +27,10 @@ import { Icon as CheckmarkIcon } from '../../../res/img/element-icons/roomlist/c
interface AutocompleteInputProps {
provider: Autocompleter;
placeholder: string;
selection: ICompletion[];
onSelectionChange: (selection: ICompletion[]) => void;
maxSuggestions?: number;
renderSuggestion?: (s: ICompletion) => ReactNode;
selection?: ICompletion[];
onSelectionChange?: (selection: ICompletion[]) => void;
renderSelection?: (m: ICompletion) => ReactNode;
additionalFilter?: (suggestion: ICompletion) => boolean;
}
Expand All @@ -45,99 +45,74 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
selection,
additionalFilter,
}) => {
const [_selection, setSelection] = useState<ICompletion[]>([]);
const [suggestions, setSuggestions] = useState<ICompletion[]>([]);
const [query, setQuery] = useState<string>('');
const [focusedElement, setFocusedElement] = useState<HTMLElement>(null);

const [suggestions, setSuggestions] = useState<ICompletion[]>([]);
const [isFocused, setFocused] = useState<boolean>(false);
const editorContainerRef = useRef<HTMLDivElement>();
const editorRef = useRef<HTMLInputElement>();

const getSuggestions = useCallback(async () => {
if (query) {
let matches = await provider.getCompletions(
query,
{ start: query.length, end: query.length },
true,
maxSuggestions,
);
if (additionalFilter) {
matches = matches.filter(additionalFilter);
}
setSuggestions(matches);
}
}, [query, maxSuggestions, provider, additionalFilter]);

useEffect(() => {
getSuggestions();

if (selection) {
setSelection(selection);
}
}, [getSuggestions, selection]);

const focusEditor = () => {
editorRef?.current?.focus();
};

const removeSelection = (t: ICompletion) => {
const newSelection = _selection.map(s => s);
const idx = _selection.findIndex(s => s.completionId === t.completionId);
const onQueryChange = async (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.trim();

if (idx >= 0) {
newSelection.splice(idx, 1);
if (onSelectionChange) {
onSelectionChange(newSelection);
}
setSelection(newSelection);
}
setQuery(value);

setQuery('');
};
let matches = await provider.getCompletions(
query,
{ start: query.length, end: query.length },
true,
maxSuggestions,
);

const onFilterChange = async (e: ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
e.preventDefault();
if (additionalFilter) {
matches = matches.filter(additionalFilter);
}

setQuery(e.target.value.trim());
setSuggestions(matches);
};

const onClickInputArea = (e) => {
e.stopPropagation();
e.preventDefault();

const onClickInputArea = () => {
focusEditor();
};

const onKeyDown = (e: KeyboardEvent) => {
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;

// when the field is empty and the user hits backspace remove the right-most target
if (!query && _selection.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
e.preventDefault();
removeSelection(_selection[_selection.length - 1]);
if (!query && selection.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
removeSelection(selection[selection.length - 1]);
}
};

const toggleSelection = (e: ICompletion) => {
const newSelection = _selection.map(t => t);
const idx = _selection.findIndex(s => s.completionId === e.completionId);
if (idx >= 0) {
newSelection.splice(idx, 1);
const toggleSelection = (completion: ICompletion) => {
const newSelection = [...selection];
const index = selection.findIndex(selection => selection.completionId === completion.completionId);

if (index >= 0) {
newSelection.splice(index, 1);
} else {
newSelection.push(e);
newSelection.push(completion);
}

if (onSelectionChange) {
onSelectionChange(newSelection);
focusEditor();
};

const removeSelection = (completion: ICompletion) => {
const newSelection = [...selection];
const index = selection.findIndex(selection => selection.completionId === completion.completionId);

if (index >= 0) {
newSelection.splice(index, 1);
onSelectionChange(newSelection);
}
setSelection(newSelection);
setQuery('');

focusEditor();
};

const _renderSuggestion = (s: ICompletion): ReactNode => {
const isSelected = _selection.findIndex(selection => selection.completionId === s.completionId) >= 0;
const _renderSuggestion = (completion: ICompletion): ReactNode => {
const isSelected = selection.findIndex(selection => selection.completionId === completion.completionId) >= 0;
const classes = classNames({
'mx_AutocompleteInput_suggestion': true,
'mx_AutocompleteInput_suggestion--selected': isSelected,
Expand All @@ -149,10 +124,10 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
e.preventDefault();
e.stopPropagation();

toggleSelection(s);
toggleSelection(completion);
}}
key={s.completionId}
data-testid={`autocomplete-suggestion-item-${s.completionId}`}
key={completion.completionId}
data-testid={`autocomplete-suggestion-item-${completion.completionId}`}
>
<div>
{ children }
Expand All @@ -162,13 +137,13 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
);

if (renderSuggestion) {
return withContainer(renderSuggestion(s));
return withContainer(renderSuggestion(completion));
}

return withContainer(
<>
<span className='mx_AutocompleteInput_suggestion_title'>{ s.completion }</span>
<span className='mx_AutocompleteInput_suggestion_description'>{ s.completionId }</span>
<span className='mx_AutocompleteInput_suggestion_title'>{ completion.completion }</span>
<span className='mx_AutocompleteInput_suggestion_description'>{ completion.completionId }</span>
</>,
);
};
Expand Down Expand Up @@ -202,7 +177,7 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
);
};

const hasPlaceholder = (): boolean => _selection.length === 0 && query.length === 0;
const hasPlaceholder = (): boolean => selection.length === 0 && query.length === 0;

return (
<div className="mx_AutocompleteInput">
Expand All @@ -212,22 +187,22 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
onClick={onClickInputArea}
data-testid="autocomplete-editor"
>
{ _selection.map(s => _renderSelection(s)) }
{ selection.map(s => _renderSelection(s)) }
<input
ref={editorRef}
type="text"
onKeyDown={onKeyDown}
onChange={onFilterChange}
onChange={onQueryChange}
value={query}
autoComplete="off"
placeholder={hasPlaceholder() ? placeholder : null}
ref={editorRef}
onFocus={(e) => setFocusedElement(e.target)}
onBlur={() => setFocusedElement(null)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
data-testid="autocomplete-input"
/>
</div>
{
(focusedElement === editorRef.current && suggestions.length) ? (
(isFocused && suggestions.length) ? (
<div
className="mx_AutocompleteInput_matches"
style={{ top: editorContainerRef.current?.clientHeight }}
Expand Down
45 changes: 23 additions & 22 deletions src/components/views/settings/AddPrivilegedUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import React, { useCallback, useContext, useRef, useState } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import { EventType } from "matrix-js-sdk/src/@types/event";

import SettingsFieldSet from '../../../components/views/settings/SettingsFieldset';
import { _t } from "../../../languageHandler";
import { ICompletion } from '../../../autocomplete/Autocompleter';
import UserProvider from "../../../autocomplete/UserProvider";
Expand Down Expand Up @@ -67,27 +66,29 @@ export const AddPrivilegedUsers: React.FC<AddPrivilegedUsersProps> = ({ room, de
};

return (
<form className="mx_SettingsFieldset">
<legend className='mx_SettingsFieldset_legend'>{ _t('Add privileged users') }</legend>
<div className='mx_SettingsFieldset_description'>
{ _t('Give one or multiple users in this room more privileges') }
</div>
<AutocompleteInput
provider={userProvider.current}
placeholder={_t("Search users in this room …")}
onSelectionChange={setSelectedUsers}
selection={selectedUsers}
additionalFilter={filterSuggestions}
/>
<PowerSelector value={powerLevel} onChange={setPowerLevel} />
<AccessibleButton
type='submit'
kind='primary'
disabled={!selectedUsers.length || isLoading}
onClick={onSubmit}
>
{ _t('Apply') }
</AccessibleButton>
<form className="mx_SettingsFieldset" onSubmit={onSubmit}>
<fieldset style={{ width: '100%' }}>
<legend className='mx_SettingsFieldset_legend'>{ _t('Add privileged users') }</legend>
<div className='mx_SettingsFieldset_description'>
{ _t('Give one or multiple users in this room more privileges') }
</div>
<AutocompleteInput
provider={userProvider.current}
placeholder={_t("Search users in this room …")}
onSelectionChange={setSelectedUsers}
selection={selectedUsers}
additionalFilter={filterSuggestions}
/>
<PowerSelector value={powerLevel} onChange={setPowerLevel} />
<AccessibleButton
type='submit'
kind='primary'
disabled={!selectedUsers.length || isLoading}
onClick={onSubmit}
>
{ _t('Apply') }
</AccessibleButton>
</fieldset>
</form>
);
};
Loading

0 comments on commit c6d1071

Please sign in to comment.