-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add contenteditable people picker prototypes #31491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
adamsamec
wants to merge
35
commits into
microsoft:master
Choose a base branch
from
adamsamec:adamsamec/content-editable-tags
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
dc26fe0
Base for content editable tags
f29e012
Fix contenteditable editting and errors and warnings
5a6d8a7
Fix selection from dropdown
04273de
Insert dropdown value into caret position
233c94c
Add field aria-label
e256010
Remove unused dependancy
71a4fdb
Work on picker item selection not yet done
9bab1c6
Proper selection when navigating in input
877c3fd
Refactor useCaretManipulation
f809361
Caret override and refactor
a3cf477
Still buggy
aaf8896
Merge branch 'master' into adamsamec/content-editable-tags
933164c
Some fixes
48d1e8c
wip
george-cz bc1980d
lexical
george-cz 6cfc97e
fix
george-cz 0cde4c1
split to 2 functions
george-cz 210dd61
split to 2 files
george-cz fb6ed8f
improve a11y
george-cz 39583da
move to activedescendant for autocomplete
george-cz 4fe5ceb
allow activelement on rendered pills
george-cz 5f9b8a7
fix onClick
george-cz 57ee762
fix autoswitch mode
george-cz 8c06377
repo cleanup
george-cz 10d3f7d
delete/backspace
george-cz 68ce95e
Merge pull request #1 from george-cz/jirivyhnalek/content-editable-tags
adamsamec e74faad
Merge remote-tracking branch 'upstream/master' into adamsamec/content…
1fe7add
Rename prototypes
9c2f442
Update yarn.lock
37b9a1c
Update yarn.lock
fdddee2
yarn.lock¨
128a4b6
Merge remote-tracking branch 'upstream/master' into adamsamec/content…
444d27d
Rename files and components
8e1f90c
Merge remote-tracking branch 'upstream/master' into adamsamec/content…
669a2cf
fix narration when going left/right
george-cz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-components-7470075f-b93d-4527-8daa-b2bca22b7cd1.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "patch", | ||
| "comment": "Picker prototype", | ||
| "packageName": "@fluentui/react-components", | ||
| "email": "asamec@microsoft.com", | ||
| "dependentChangeType": "patch" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -86,7 +86,9 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@fluentui/react-rating": "^9.0.10", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🕵 fluentui-web-components-v3 Open the Visual Regressions report to inspect the affected screenshotsAccordion 3 screenshots
Avatar 5 screenshots
Badge 6 screenshots
Button 15 screenshots
Divider 3 screenshots
MenuList 16 screenshots
Slider 2 screenshots
Switch 2 screenshots
Text 1 screenshots
TextInput 1 screenshots
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@fluentui/react-search": "^9.0.6", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@fluentui/react-teaching-popover": "^9.1.6", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@fluentui/react-tag-picker": "^9.0.4" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@fluentui/react-tag-picker": "^9.0.4", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "lexical": "^0.15.0", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@lexical/react": "^0.15.0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "peerDependencies": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "@types/react": ">=16.14.0 <19.0.0", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
126 changes: 126 additions & 0 deletions
126
...ponents/react-components/stories/ContentEditableTags/LexicalEditor/AutocompletePlugin.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import { makeStyles, mergeClasses, shorthands } from '@fluentui/react-components'; | ||
| import * as React from 'react'; | ||
| import { people } from '../data'; | ||
| import { AutocompletePluginCore } from './AutocompletePluginCore'; | ||
|
|
||
| const useStyles = makeStyles({ | ||
| root: { | ||
| display: 'grid', | ||
| gridTemplateRows: 'repeat(1fr)', | ||
| justifyItems: 'start', | ||
| ...shorthands.gap('2px'), | ||
| maxWidth: '400px', | ||
| }, | ||
| hidden: { | ||
| display: 'none', | ||
| }, | ||
| item: { | ||
| cursor: 'pointer', | ||
| }, | ||
| selected: { | ||
| fontWeight: 'bold', | ||
| }, | ||
| }); | ||
|
|
||
| const options = people; | ||
|
|
||
| const useFilteredList = (filter: string) => { | ||
| return React.useMemo( | ||
| () => (filter.length ? options.filter(option => option.toLowerCase().includes(filter.toLowerCase())) : []), | ||
| [filter], | ||
| ); | ||
| }; | ||
|
|
||
| export const AutocompletePlugin = ({ id }: { id: string }) => { | ||
| const [isOpen, setIsOpen] = React.useState(true); | ||
| const [query, setQuery] = React.useState(''); | ||
| const [selectedIndex, setSelectedIndex] = React.useState(0); | ||
|
|
||
| const styles = useStyles(); | ||
| const filtered = useFilteredList(query); | ||
|
|
||
| React.useEffect(() => { | ||
| setSelectedIndex(0); | ||
| setIsOpen(!!filtered.length); | ||
| }, [filtered]); | ||
|
|
||
| const selectedItem = React.useMemo(() => filtered[selectedIndex], [selectedIndex, filtered]); | ||
|
|
||
| React.useEffect(() => {}, [selectedItem]); | ||
|
|
||
| const onArrowKeyUp = React.useCallback( | ||
| event => { | ||
| if (isOpen) { | ||
| setSelectedIndex(currentIndex => { | ||
| return Math.max(0, currentIndex - 1); | ||
| }); | ||
| event.preventDefault(); | ||
| event.stopImmediatePropagation(); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, | ||
| [isOpen, selectedIndex], | ||
| ); | ||
|
|
||
| const onArrowKeyDown = React.useCallback( | ||
| event => { | ||
| if (isOpen) { | ||
| setSelectedIndex(currentIndex => { | ||
| return Math.min(currentIndex + 1, filtered.length - 1); | ||
| }); | ||
| event.preventDefault(); | ||
| event.stopImmediatePropagation(); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, | ||
| [isOpen, selectedIndex], | ||
| ); | ||
|
|
||
| return ( | ||
| <AutocompletePluginCore | ||
| id={id} | ||
| isOpen={isOpen} | ||
| query={query} | ||
| onQueryChange={newQuery => setQuery(newQuery)} | ||
| autocompleteItem={isOpen ? selectedItem : undefined} | ||
| onArrowKeyUp={onArrowKeyUp} | ||
| onArrowKeyDown={onArrowKeyDown} | ||
| onEscape={() => { | ||
| if (isOpen) { | ||
| setIsOpen(false); | ||
| return true; | ||
| } | ||
| return false; | ||
| }} | ||
| > | ||
| {({ onClick, getItemId }) => { | ||
| return ( | ||
| <div | ||
| className={mergeClasses(styles.root, !isOpen && styles.hidden)} | ||
| id={id} | ||
| role="listbox" | ||
| aria-expanded={isOpen} | ||
| > | ||
| {filtered.map((option, index) => ( | ||
| <div | ||
| id={getItemId(id, option)} | ||
| aria-label={option} | ||
| role="option" | ||
| key={option} | ||
| className={mergeClasses(styles.item, index === selectedIndex && styles.selected)} | ||
| onMouseOver={() => { | ||
| setSelectedIndex(index); | ||
| }} | ||
| onClick={onClick} | ||
| > | ||
| {option} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ); | ||
| }} | ||
| </AutocompletePluginCore> | ||
| ); | ||
| }; |
215 changes: 215 additions & 0 deletions
215
...nts/react-components/stories/ContentEditableTags/LexicalEditor/AutocompletePluginCore.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| import * as React from 'react'; | ||
| import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; | ||
| import { | ||
| $getSelection, | ||
| $isDecoratorNode, | ||
| $isNodeSelection, | ||
| $isRangeSelection, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| COMMAND_PRIORITY_EDITOR, | ||
| INSERT_PARAGRAPH_COMMAND, | ||
| KEY_ARROW_DOWN_COMMAND, | ||
| KEY_ARROW_LEFT_COMMAND, | ||
| KEY_ARROW_RIGHT_COMMAND, | ||
| KEY_ARROW_UP_COMMAND, | ||
| KEY_BACKSPACE_COMMAND, | ||
| KEY_DELETE_COMMAND, | ||
| KEY_ENTER_COMMAND, | ||
| KEY_ESCAPE_COMMAND, | ||
| SELECTION_CHANGE_COMMAND, | ||
| } from 'lexical'; | ||
| import { $createNamePillNode } from './NamePillNode'; | ||
|
|
||
| const getAutocompleteItemId = (autocompleteId: string, value: string) => { | ||
| return `${autocompleteId}_item-${value}`; | ||
| }; | ||
|
|
||
| type AutocompletePluginCoreProps = { | ||
| id: string; | ||
| isOpen: boolean; | ||
| onQueryChange: (newQuery: string) => void; | ||
| children: (renderProps: { onClick: () => void; getItemId: typeof getAutocompleteItemId }) => React.ReactElement; | ||
| autocompleteItem?: string; | ||
| onArrowKeyUp?: (event: KeyboardEvent) => boolean; | ||
| onArrowKeyDown?: (event: KeyboardEvent) => boolean; | ||
| onEscape?: (event: KeyboardEvent) => boolean; | ||
| query?: string; | ||
| }; | ||
|
|
||
| export const AutocompletePluginCore: React.FC<AutocompletePluginCoreProps> = ({ | ||
| id, | ||
| isOpen, | ||
| onQueryChange, | ||
| autocompleteItem, | ||
| children, | ||
| onArrowKeyUp, | ||
| onArrowKeyDown, | ||
| onEscape, | ||
| query, | ||
| }) => { | ||
| const [editor] = useLexicalComposerContext(); | ||
|
|
||
| const appendSelectedItem = React.useCallback(() => { | ||
| const sel = $getSelection(); | ||
|
|
||
| // handle deletion of single selected node | ||
| if ($isNodeSelection(sel) && sel.getNodes().length === 1) { | ||
| const selectedNode = sel.getNodes()[0]; | ||
| selectedNode.remove(); | ||
| return true; | ||
| } | ||
|
|
||
| // handle adding of new node from selection | ||
| if ($isRangeSelection(sel) && sel.getNodes().length === 1) { | ||
| if (autocompleteItem) { | ||
| const node = sel.getNodes()[0]; | ||
| const newNode = $createNamePillNode(autocompleteItem); | ||
| node.replace(newNode); | ||
| newNode.selectEnd(); | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }, [autocompleteItem]); | ||
|
|
||
| const onBackspace = React.useCallback(() => { | ||
| const sel = $getSelection(); | ||
|
|
||
| if ($isNodeSelection(sel) && sel.getNodes().length === 1) { | ||
| const selectedNode = sel.getNodes()[0]; | ||
| if ($isDecoratorNode(selectedNode)) { | ||
| // Have to move the selection at the end and let the native capslock handle the deletion | ||
| // otherwise 2 of the nodes will be deleted for some reason. This feels like a bug in lexical. | ||
| selectedNode.selectEnd(); | ||
| } | ||
| } | ||
| return false; | ||
| }, []); | ||
|
|
||
| const onDelete = React.useCallback(() => { | ||
| const sel = $getSelection(); | ||
|
|
||
| if ($isNodeSelection(sel) && sel.getNodes().length === 1) { | ||
| const selectedNode = sel.getNodes()[0]; | ||
| if ($isDecoratorNode(selectedNode)) { | ||
| // Have to move the selection at the start and let the native delete handle the deletion | ||
| // otherwise 2 of the nodes will be deleted for some reason. This feels like a bug in lexical. | ||
| selectedNode.selectStart(); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| }, []); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand(KEY_ENTER_COMMAND, appendSelectedItem, COMMAND_PRIORITY_CRITICAL); | ||
| }); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand(KEY_ESCAPE_COMMAND, onEscape, COMMAND_PRIORITY_CRITICAL); | ||
| }); | ||
| React.useEffect(() => { | ||
| return editor.registerCommand(KEY_BACKSPACE_COMMAND, onBackspace, COMMAND_PRIORITY_CRITICAL); | ||
| }); | ||
| React.useEffect(() => { | ||
| return editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_CRITICAL); | ||
| }); | ||
|
|
||
| // Update the activedescendant on the parent element to the currently selected item | ||
| React.useEffect(() => { | ||
| const editorElement = editor.getRootElement(); | ||
| if (editorElement && autocompleteItem) { | ||
| editorElement.setAttribute('aria-activedescendant', getAutocompleteItemId(id, autocompleteItem)); | ||
| } | ||
| }, [autocompleteItem, editor, id, query]); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| SELECTION_CHANGE_COMMAND, | ||
| () => { | ||
| const sel = $getSelection(); | ||
| if (!sel) { | ||
| return; | ||
| } | ||
|
|
||
| const editorElement = editor.getRootElement(); | ||
|
|
||
| const nodes = sel.getNodes(); | ||
| if ($isRangeSelection(sel) && nodes.length === 1) { | ||
| onQueryChange(nodes[0].getTextContent()); | ||
| if (!isOpen) { | ||
| editorElement?.setAttribute('aria-activedescendant', ''); | ||
| } | ||
| } | ||
|
|
||
| if ($isNodeSelection(sel)) { | ||
| const htmlElement = editor.getElementByKey(nodes[0].__key); | ||
|
|
||
| if (editorElement && htmlElement) { | ||
| editorElement.setAttribute('aria-activedescendant', htmlElement.id); | ||
| } | ||
| // when node is selected, clear query so that the autocomplete is not shown | ||
| onQueryChange(''); | ||
| } | ||
| }, | ||
| COMMAND_PRIORITY_EDITOR, | ||
| ); | ||
| }, [editor, isOpen, autocompleteItem, query, onQueryChange]); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| INSERT_PARAGRAPH_COMMAND, | ||
| () => { | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| ); | ||
| }, [editor]); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| KEY_ARROW_LEFT_COMMAND, | ||
| () => { | ||
| editor.getRootElement()?.setAttribute('aria-activedescendant', ''); | ||
| return false; | ||
| }, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| ); | ||
| }, [editor]); | ||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| KEY_ARROW_RIGHT_COMMAND, | ||
| () => { | ||
| editor.getRootElement()?.setAttribute('aria-activedescendant', ''); | ||
| return false; | ||
| }, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| ); | ||
| }, [editor]); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| KEY_ARROW_UP_COMMAND, | ||
| payload => { | ||
| return onArrowKeyUp?.(payload) ?? false; | ||
| }, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| ); | ||
| }, [editor, onArrowKeyUp]); | ||
|
|
||
| React.useEffect(() => { | ||
| return editor.registerCommand( | ||
| KEY_ARROW_DOWN_COMMAND, | ||
| payload => { | ||
| return onArrowKeyDown?.(payload) ?? false; | ||
| }, | ||
| COMMAND_PRIORITY_CRITICAL, | ||
| ); | ||
| }, [editor, onArrowKeyDown]); | ||
|
|
||
| const onClick = React.useCallback(() => { | ||
| editor.update(appendSelectedItem); | ||
| }, [appendSelectedItem, editor]); | ||
|
|
||
| return children({ onClick, getItemId: getAutocompleteItemId }); | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🕵 fluentuiv9 Open the Visual Regressions report to inspect the affected screenshots
Avatar Converged 1 screenshots
Card Converged - Selectable 4 screenshots
SwatchPicker Converged 2 screenshots