From e24ae0f5c2a346a847ae305dae93db63156b1d68 Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Thu, 8 Apr 2021 20:21:57 -0500 Subject: [PATCH 1/4] Stop matching upon mismatch, reset when trigger char or backspace is pressed --- packages/components/src/autocomplete/index.js | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index af1b9a7c611b7f..19c7016a338974 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -13,7 +13,15 @@ import { useLayoutEffect, useState, } from '@wordpress/element'; -import { ENTER, ESCAPE, UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; +import { + ENTER, + ESCAPE, + UP, + DOWN, + LEFT, + RIGHT, + BACKSPACE, +} from '@wordpress/keycodes'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useInstanceId } from '@wordpress/compose'; import { @@ -293,6 +301,7 @@ function Autocomplete( { const [ filterValue, setFilterValue ] = useState( '' ); const [ autocompleter, setAutocompleter ] = useState( null ); const [ AutocompleterUI, setAutocompleterUI ] = useState( null ); + const [ mismatch, setMismatch ] = useState( false ); function insertCompletion( replacement ) { const end = record.start; @@ -375,12 +384,14 @@ function Autocomplete( { } function handleKeyDown( event ) { - if ( ! autocompleter ) { + if ( ! autocompleter && event.keyCode !== BACKSPACE ) { return; } - if ( filteredOptions.length === 0 ) { + if ( filteredOptions.length === 0 && event.keyCode !== BACKSPACE ) { + setMismatch( true ); return; } + switch ( event.keyCode ) { case UP: setSelectedIndex( @@ -404,12 +415,13 @@ function Autocomplete( { case ENTER: select( filteredOptions[ selectedIndex ] ); break; - + case BACKSPACE: + setMismatch( false ); + return; case LEFT: case RIGHT: reset(); return; - default: return; } @@ -438,6 +450,12 @@ function Autocomplete( { const completer = find( completers, ( { triggerPrefix, allowContext } ) => { + // If we don't have any matching filteredOptions from the last render iteration + + // we didn't have a new trigger typed, then we should not continue with this effect. + if ( mismatch && text.slice( -1 ) !== triggerPrefix ) { + return false; + } + const index = text.lastIndexOf( triggerPrefix ); if ( index === -1 ) { @@ -477,6 +495,8 @@ function Autocomplete( { .match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) ); const query = match && match[ 1 ]; + // console.log( match ); // uncomment this to make this easier to test + setAutocompleter( completer ); setAutocompleterUI( () => completer !== autocompleter From 97bf419d4e601a3852417082dddc2ef0161c2e5f Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Fri, 9 Apr 2021 11:30:07 -0500 Subject: [PATCH 2/4] Simplify --- packages/components/src/autocomplete/index.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 19c7016a338974..5a695db909f6d5 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -20,7 +20,6 @@ import { DOWN, LEFT, RIGHT, - BACKSPACE, } from '@wordpress/keycodes'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useInstanceId } from '@wordpress/compose'; @@ -301,7 +300,6 @@ function Autocomplete( { const [ filterValue, setFilterValue ] = useState( '' ); const [ autocompleter, setAutocompleter ] = useState( null ); const [ AutocompleterUI, setAutocompleterUI ] = useState( null ); - const [ mismatch, setMismatch ] = useState( false ); function insertCompletion( replacement ) { const end = record.start; @@ -384,11 +382,10 @@ function Autocomplete( { } function handleKeyDown( event ) { - if ( ! autocompleter && event.keyCode !== BACKSPACE ) { + if ( ! autocompleter ) { return; } - if ( filteredOptions.length === 0 && event.keyCode !== BACKSPACE ) { - setMismatch( true ); + if ( filteredOptions.length === 0 ) { return; } @@ -415,9 +412,6 @@ function Autocomplete( { case ENTER: select( filteredOptions[ selectedIndex ] ); break; - case BACKSPACE: - setMismatch( false ); - return; case LEFT: case RIGHT: reset(); @@ -452,6 +446,7 @@ function Autocomplete( { ( { triggerPrefix, allowContext } ) => { // If we don't have any matching filteredOptions from the last render iteration + // we didn't have a new trigger typed, then we should not continue with this effect. + const mismatch = filteredOptions.length === 0; if ( mismatch && text.slice( -1 ) !== triggerPrefix ) { return false; } @@ -495,7 +490,7 @@ function Autocomplete( { .match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) ); const query = match && match[ 1 ]; - // console.log( match ); // uncomment this to make this easier to test + //console.log( match ); // uncomment this to make this easier to test setAutocompleter( completer ); setAutocompleterUI( () => From 09b31bf750af7c29ab98559d9fb616cc71c8d14a Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Fri, 9 Apr 2021 13:36:10 -0500 Subject: [PATCH 3/4] wip --- packages/components/src/autocomplete/index.js | 142 ++++++++++-------- 1 file changed, 81 insertions(+), 61 deletions(-) diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 5a695db909f6d5..e20060deabd637 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -20,6 +20,7 @@ import { DOWN, LEFT, RIGHT, + BACKSPACE, } from '@wordpress/keycodes'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useInstanceId } from '@wordpress/compose'; @@ -301,6 +302,66 @@ function Autocomplete( { const [ autocompleter, setAutocompleter ] = useState( null ); const [ AutocompleterUI, setAutocompleterUI ] = useState( null ); + let textContent, text; + if ( isCollapsed( record ) ) { + textContent = getTextContent( slice( record, 0 ) ); + text = deburr( textContent ); + } + + function getQuery( triggerPrefix ) { + const safeTrigger = escapeRegExp( triggerPrefix ); + const match = text + .slice( text.lastIndexOf( triggerPrefix ) ) + .match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) ); + const query = match && match[ 1 ]; + console.log( 'query: ', query ); + return query; + } + + function getCompleterFromTextContext() { + const textAfterSelection = getTextContent( + slice( record, undefined, getTextContent( record ).length ) + ); + + return find( completers, ( { triggerPrefix, allowContext } ) => { + const index = text.lastIndexOf( triggerPrefix ); + + if ( index === -1 ) { + return false; + } + + if ( + allowContext && + ! allowContext( text.slice( 0, index ), textAfterSelection ) + ) { + return false; + } + + const textWithoutTrigger = text.slice( + index + triggerPrefix.length + ); + + if ( + /^\s/.test( textWithoutTrigger ) || + /\s\s+$/.test( textWithoutTrigger ) + ) { + return false; + } + + return /[\u0000-\uFFFF]*$/.test( textWithoutTrigger ); + } ); + } + + function setupAutocompleter( completer ) { + setFilterValue( getQuery( completer.triggerPrefix ) ); + setAutocompleter( completer ); + setAutocompleterUI( () => + completer !== autocompleter + ? getAutoCompleterUI( completer ) + : AutocompleterUI + ); + } + function insertCompletion( replacement ) { const end = record.start; const start = @@ -382,6 +443,15 @@ function Autocomplete( { } function handleKeyDown( event ) { + console.log( 'keypress!!' ); + if ( event.keyCode === BACKSPACE ) { + const completer = getCompleterFromTextContext(); + console.log( 'completer: ', completer ); + if ( completer ) { + setupAutocompleter( completer ); + } + console.log( 'backspacze!' ); + } if ( ! autocompleter ) { return; } @@ -426,79 +496,29 @@ function Autocomplete( { event.stopPropagation(); } - let textContent; - - if ( isCollapsed( record ) ) { - textContent = getTextContent( slice( record, 0 ) ); - } - useEffect( () => { if ( ! textContent ) { return; } - const text = deburr( textContent ); - const textAfterSelection = getTextContent( - slice( record, undefined, getTextContent( record ).length ) - ); - const completer = find( - completers, - ( { triggerPrefix, allowContext } ) => { - // If we don't have any matching filteredOptions from the last render iteration + - // we didn't have a new trigger typed, then we should not continue with this effect. - const mismatch = filteredOptions.length === 0; - if ( mismatch && text.slice( -1 ) !== triggerPrefix ) { - return false; - } - - const index = text.lastIndexOf( triggerPrefix ); - - if ( index === -1 ) { - return false; - } - - if ( - allowContext && - ! allowContext( text.slice( 0, index ), textAfterSelection ) - ) { - return false; - } - - const textWithoutTrigger = text.slice( - index + triggerPrefix.length - ); - - if ( - /^\s/.test( textWithoutTrigger ) || - /\s\s+$/.test( textWithoutTrigger ) - ) { - return false; - } - - return /[\u0000-\uFFFF]*$/.test( textWithoutTrigger ); - } - ); - + const completer = getCompleterFromTextContext(); if ( ! completer ) { reset(); return; } - const safeTrigger = escapeRegExp( completer.triggerPrefix ); - const match = text - .slice( text.lastIndexOf( completer.triggerPrefix ) ) - .match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) ); - const query = match && match[ 1 ]; + console.log( 'filteredOptions: ', filteredOptions ); + // If we don't have any matching filteredOptions + // we didn't have a new trigger typed, then we should not continue with this effect. + const mismatch = filteredOptions.length === 0; + if ( mismatch && text.slice( -1 ) !== completer.triggerPrefix ) { + console.log( 'mismatch' ); + return; + } - //console.log( match ); // uncomment this to make this easier to test + console.log( getQuery( completer.triggerPrefix ) ); // uncomment this to make this easier to test - setAutocompleter( completer ); - setAutocompleterUI( () => - completer !== autocompleter - ? getAutoCompleterUI( completer ) - : AutocompleterUI - ); - setFilterValue( query ); + setupAutocompleter( completer ); }, [ textContent ] ); const { key: selectedKey = '' } = filteredOptions[ selectedIndex ] || {}; From fff45fb76fbc712fec901c9fe987af5c4978936b Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Fri, 9 Apr 2021 16:59:55 -0500 Subject: [PATCH 4/4] Compromise --- packages/components/src/autocomplete/index.js | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index e20060deabd637..e6abd7473eaaa4 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -301,6 +301,7 @@ function Autocomplete( { const [ filterValue, setFilterValue ] = useState( '' ); const [ autocompleter, setAutocompleter ] = useState( null ); const [ AutocompleterUI, setAutocompleterUI ] = useState( null ); + const [ backspacing, setBackspacing ] = useState( false ); let textContent, text; if ( isCollapsed( record ) ) { @@ -330,6 +331,27 @@ function Autocomplete( { return false; } + const textWithoutTrigger = text.slice( + index + triggerPrefix.length + ); + + console.log( 'filteredOptions: ', filteredOptions ); + // If we don't have any matching filteredOptions + // we didn't have a new trigger typed, then we should not continue with this effect. + const mismatch = filteredOptions.length === 0; + const atTrigger = text.slice( -1 ) === triggerPrefix; + const tooDistantFromTrigger = textWithoutTrigger.length > 60; // 60 chars seem to be a good limit + + console.log( `atTrigger: `, atTrigger ); + console.log( 'mismatch: ', mismatch ); + console.log( 'tooDistant: ', tooDistantFromTrigger ); + + if ( tooDistantFromTrigger ) return false; + if ( mismatch && ! atTrigger && ! backspacing ) { + console.log( 'here lies a mismatch!' ); + return false; + } + if ( allowContext && ! allowContext( text.slice( 0, index ), textAfterSelection ) @@ -337,10 +359,6 @@ function Autocomplete( { return false; } - const textWithoutTrigger = text.slice( - index + triggerPrefix.length - ); - if ( /^\s/.test( textWithoutTrigger ) || /\s\s+$/.test( textWithoutTrigger ) @@ -360,6 +378,7 @@ function Autocomplete( { ? getAutoCompleterUI( completer ) : AutocompleterUI ); + setBackspacing( false ); } function insertCompletion( replacement ) { @@ -443,14 +462,8 @@ function Autocomplete( { } function handleKeyDown( event ) { - console.log( 'keypress!!' ); if ( event.keyCode === BACKSPACE ) { - const completer = getCompleterFromTextContext(); - console.log( 'completer: ', completer ); - if ( completer ) { - setupAutocompleter( completer ); - } - console.log( 'backspacze!' ); + setBackspacing( true ); } if ( ! autocompleter ) { return; @@ -507,15 +520,6 @@ function Autocomplete( { return; } - console.log( 'filteredOptions: ', filteredOptions ); - // If we don't have any matching filteredOptions - // we didn't have a new trigger typed, then we should not continue with this effect. - const mismatch = filteredOptions.length === 0; - if ( mismatch && text.slice( -1 ) !== completer.triggerPrefix ) { - console.log( 'mismatch' ); - return; - } - console.log( getQuery( completer.triggerPrefix ) ); // uncomment this to make this easier to test setupAutocompleter( completer );