diff --git a/assets/js/autosuggest.js b/assets/js/autosuggest.js index 7c8b354078..f0724f787f 100644 --- a/assets/js/autosuggest.js +++ b/assets/js/autosuggest.js @@ -4,6 +4,7 @@ import { escapeDoubleQuotes, replaceGlobally, debounce, + domReady, } from './utils/helpers'; import 'element-closest'; import 'promise-polyfill/src/polyfill'; @@ -391,82 +392,8 @@ function init() { return; } - const epInputNodes = document.querySelectorAll(selectors); - - // build the container into which we place the search results. - // These will be cloned later for each instance - // of autosuggest inputs - const epAutosuggest = document.createElement('div'); - epAutosuggest.classList.add('ep-autosuggest'); - const autosuggestList = document.createElement('ul'); - autosuggestList.classList.add('autosuggest-list'); - autosuggestList.setAttribute('role', 'listbox'); - epAutosuggest.appendChild(autosuggestList); - - // Build the auto-suggest containers - // excluding the facet search field and search block field - const epInputs = Array.from(epInputNodes).filter( - (node) => - !node.classList.contains('facet-search') && - !node.classList.contains('wp-block-search__input'), - ); - - // Handle search blocks separately - const epBlockInputs = Array.from(epInputNodes).filter((node) => - node.classList.contains('wp-block-search__input'), - ); - - epInputs.forEach((input) => { - const epContainer = document.createElement('div'); - epContainer.classList.add('ep-autosuggest-container'); - - // Disable autocomplete - input.setAttribute('autocomplete', 'off'); - - // insert the container - later we will place - // the input inside this container - input.insertAdjacentElement('afterend', epContainer); - - // move the input inside the container - const form = input.closest('form'); - const container = form.querySelector('.ep-autosuggest-container'); - container.appendChild(input); - - const clonedContainer = epAutosuggest.cloneNode(true); - input.insertAdjacentElement('afterend', clonedContainer); - - // announce that this is has been done - const event = new CustomEvent('elasticpress.input.moved'); - input.dispatchEvent(event); - }); - - /** - * For search blocks, because we know the output mark up, we reuse it - * for autosuggest. - */ - epBlockInputs.forEach((input) => { - // Disable autocomplete - input.setAttribute('autocomplete', 'off'); - - input.form.classList.add('ep-autosuggest-container'); - - const clonedContainer = epAutosuggest.cloneNode(true); - input.parentElement.insertAdjacentElement('afterend', clonedContainer); - - // announce that this is has been done - const event = new CustomEvent('elasticpress.input.moved'); - input.dispatchEvent(event); - }); - - if (epInputs.length > 0) { - epAutosuggest.setAttribute( - 'style', - ` - top: ${epInputs[0].offsetHeight - 1}; - background-color: ${getComputedStyle(epInputs[0], 'background-color')} - `, - ); - } + // For the Autosuggest element that will be cloned. + let autosuggestElement; // to be used by the handleUpDown function // to keep track of the currently selected result @@ -509,7 +436,7 @@ function init() { * helper function to deselect results */ const deSelectResults = () => { - results.forEach((result) => { + Array.from(results).forEach((result) => { result.classList.remove('selected'); result.setAttribute('aria-selected', 'false'); }); @@ -685,19 +612,171 @@ function init() { }; /** - * Listen for any events: + * Wrap an element with an autosuggest container. + * + * @param {Element} element Element to wrap. + * @return {void} + */ + const wrapInAutosuggestContainer = (element) => { + const epContainer = document.createElement('div'); + + epContainer.classList.add('ep-autosuggest-container'); + + element.insertAdjacentElement('afterend', epContainer); + + epContainer.appendChild(element); + }; + + /** + * Insert an autosuggest list after an element. * - * keyup - * send them for a query to the Elasticsearch server - * handle up and down keys to move between results + * @param {Element} element Element to add the autosuggest list after. + * @return {void} + */ + const insertAutosuggestElement = (element) => { + if (!autosuggestElement) { + autosuggestElement = document.createElement('div'); + autosuggestElement.classList.add('ep-autosuggest'); + + const autosuggestList = document.createElement('ul'); + + autosuggestList.classList.add('autosuggest-list'); + autosuggestList.setAttribute('role', 'listbox'); + + autosuggestElement.appendChild(autosuggestList); + } + + const clonedElement = autosuggestElement.cloneNode(true); + + element.insertAdjacentElement('afterend', clonedElement); + }; + + /** + * Prepare an input for Autosuggest. * - * blur - * hide the autosuggest box + * @param {Element} input Input to prepare. + * @return {void} */ - [...epInputs, ...epBlockInputs].forEach((input) => { + const prepareInputForAutosuggest = (input) => { + /** + * Skip facet widget search fields. + */ + if (input.classList.contains('facet-search')) { + return; + } + + /** + * Disable autocomplete. + */ + input.setAttribute('autocomplete', 'off'); + + /** + * We know the markup of the Search block, so we don't need to add a + * wrapper. + */ + if (input.classList.contains('wp-block-search__input')) { + input.form.classList.add('ep-autosuggest-container'); + insertAutosuggestElement(input.parentElement); + } else { + wrapInAutosuggestContainer(input); + insertAutosuggestElement(input); + } + + /** + * Dispatch an event announcing the input has moved. + */ + const event = new CustomEvent('elasticpress.input.moved'); + + input.dispatchEvent(event); + + /** + * Listen for any events: + * + * keyup + * send them for a query to the Elasticsearch server + * handle up and down keys to move between results + * + * blur + * hide the autosuggest box + */ input.addEventListener('keyup', handleKeyup); input.addEventListener('blur', function () { window.setTimeout(hideAutosuggestBox, 200); }); - }); + }; + + /** + * Find inputs within an element and prepare them for Autosuggest. + * + * @param {Element} element Element to find inputs within. + * @return {void} + */ + const findAndPrepareInputsForAutosuggest = (element) => { + const inputs = element.querySelectorAll(selectors); + + if (inputs) { + Array.from(inputs).forEach(prepareInputForAutosuggest); + } + }; + + /** + * Observe the document for new potential Autosuggest inputs, and add + * Autosuggest to any found inputs. + * + * @return {void} + */ + const observeDocumentForInputs = () => { + const target = document.body; + const config = { + subtree: true, + childList: true, + }; + + const observer = new MutationObserver((mutations, observer) => { + mutations.forEach((mutation) => { + Array.from(mutation.addedNodes).forEach((node) => { + if (node.nodeType !== Node.ELEMENT_NODE) { + return; + } + + /** + * Adding autosuggest to an input moves it in the DOM, + * which would trigger our observer, so we need to + * stop observing until it's been prepared. + */ + observer.disconnect(); + + /** + * If the node is an input, prepare it for Autosuggest if + * it matches the selectors, otherwise search the node for + * inputs. + */ + if (node.tagName === 'INPUT') { + if (node.matches(selectors)) { + prepareInputForAutosuggest(node); + } + } else { + findAndPrepareInputsForAutosuggest(node); + } + + /** + * Resume observing. + */ + observer.observe(target, config); + }); + }); + }); + + observer.observe(target, config); + }; + + /** + * Add autosuggest to any inputs in the document. + */ + findAndPrepareInputsForAutosuggest(document.body); + + /** + * When the DOM is ready start observing for new inputs. + */ + domReady(observeDocumentForInputs); } diff --git a/assets/js/utils/helpers.js b/assets/js/utils/helpers.js index 12934b636c..2d9f9453d3 100644 --- a/assets/js/utils/helpers.js +++ b/assets/js/utils/helpers.js @@ -132,3 +132,27 @@ export const showElements = (els) => showOrHideNodes(els, 'inline-block'); * @return {Function} - showOrHideNodes */ export const hideElements = (els) => showOrHideNodes(els, 'none'); + +/** + * Specify a function to execute when the DOM is fully loaded. + * + * @param {Function} callback A function to execute after the DOM is ready. + * @return {void} + */ +export const domReady = (callback) => { + if (typeof document === 'undefined') { + return; + } + + if ( + document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly. + document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly. + ) { + callback(); + return; + } + + // DOMContentLoaded has not fired yet, delay callback until then. + // eslint-disable-next-line @wordpress/no-global-event-listener + document.addEventListener('DOMContentLoaded', callback); +}; diff --git a/dist/js/autosuggest-script.min.js b/dist/js/autosuggest-script.min.js index d3acb168e4..f58d558793 100644 --- a/dist/js/autosuggest-script.min.js +++ b/dist/js/autosuggest-script.min.js @@ -1 +1 @@ -!function(){var t={3099:function(t){t.exports=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t}},6077:function(t,e,r){var n=r(111);t.exports=function(t){if(!n(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},1223:function(t,e,r){var n=r(5112),o=r(30),i=r(3070),a=n("unscopables"),c=Array.prototype;void 0==c[a]&&i.f(c,a,{configurable:!0,value:o(null)}),t.exports=function(t){c[a][t]=!0}},1530:function(t,e,r){"use strict";var n=r(8710).charAt;t.exports=function(t,e,r){return e+(r?n(t,e).length:1)}},5787:function(t){t.exports=function(t,e,r){if(!(t instanceof e))throw TypeError("Incorrect "+(r?r+" ":"")+"invocation");return t}},9670:function(t,e,r){var n=r(111);t.exports=function(t){if(!n(t))throw TypeError(String(t)+" is not an object");return t}},8533:function(t,e,r){"use strict";var n=r(2092).forEach,o=r(9341)("forEach");t.exports=o?[].forEach:function(t){return n(this,t,arguments.length>1?arguments[1]:void 0)}},8457:function(t,e,r){"use strict";var n=r(9974),o=r(7908),i=r(3411),a=r(7659),c=r(7466),u=r(6135),s=r(1246);t.exports=function(t){var e,r,f,l,p,h,d=o(t),v="function"==typeof this?this:Array,y=arguments.length,g=y>1?arguments[1]:void 0,m=void 0!==g,b=s(d),x=0;if(m&&(g=n(g,y>2?arguments[2]:void 0,2)),void 0==b||v==Array&&a(b))for(r=new v(e=c(d.length));e>x;x++)h=m?g(d[x],x):d[x],u(r,x,h);else for(p=(l=b.call(d)).next,r=new v;!(f=p.call(l)).done;x++)h=m?i(l,g,[f.value,x],!0):f.value,u(r,x,h);return r.length=x,r}},1318:function(t,e,r){var n=r(5656),o=r(7466),i=r(1400),a=function(t){return function(e,r,a){var c,u=n(e),s=o(u.length),f=i(a,s);if(t&&r!=r){for(;s>f;)if((c=u[f++])!=c)return!0}else for(;s>f;f++)if((t||f in u)&&u[f]===r)return t||f||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},2092:function(t,e,r){var n=r(9974),o=r(8361),i=r(7908),a=r(7466),c=r(5417),u=[].push,s=function(t){var e=1==t,r=2==t,s=3==t,f=4==t,l=6==t,p=7==t,h=5==t||l;return function(d,v,y,g){for(var m,b,x=i(d),w=o(x),E=n(v,y,3),S=a(w.length),A=0,_=g||c,j=e?_(d,S):r||p?_(d,0):void 0;S>A;A++)if((h||A in w)&&(b=E(m=w[A],A,x),t))if(e)j[A]=b;else if(b)switch(t){case 3:return!0;case 5:return m;case 6:return A;case 2:u.call(j,m)}else switch(t){case 4:return!1;case 7:u.call(j,m)}return l?-1:s||f?f:j}};t.exports={forEach:s(0),map:s(1),filter:s(2),some:s(3),every:s(4),find:s(5),findIndex:s(6),filterReject:s(7)}},1194:function(t,e,r){var n=r(7293),o=r(5112),i=r(7392),a=o("species");t.exports=function(t){return i>=51||!n((function(){var e=[];return(e.constructor={})[a]=function(){return{foo:1}},1!==e[t](Boolean).foo}))}},9341:function(t,e,r){"use strict";var n=r(7293);t.exports=function(t,e){var r=[][t];return!!r&&n((function(){r.call(null,e||function(){throw 1},1)}))}},4362:function(t){var e=Math.floor,r=function(t,i){var a=t.length,c=e(a/2);return a<8?n(t,i):o(r(t.slice(0,c),i),r(t.slice(c),i),i)},n=function(t,e){for(var r,n,o=t.length,i=1;i0;)t[n]=t[--n];n!==i++&&(t[n]=r)}return t},o=function(t,e,r){for(var n=t.length,o=e.length,i=0,a=0,c=[];i=74)&&(n=a.match(/Chrome\/(\d+)/))&&(o=n[1]),t.exports=o&&+o},8008:function(t,e,r){var n=r(8113).match(/AppleWebKit\/(\d+)\./);t.exports=!!n&&+n[1]},748:function(t){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:function(t,e,r){var n=r(7854),o=r(1236).f,i=r(8880),a=r(1320),c=r(3505),u=r(9920),s=r(4705);t.exports=function(t,e){var r,f,l,p,h,d=t.target,v=t.global,y=t.stat;if(r=v?n:y?n[d]||c(d,{}):(n[d]||{}).prototype)for(f in e){if(p=e[f],l=t.noTargetGet?(h=o(r,f))&&h.value:r[f],!s(v?f:d+(y?".":"#")+f,t.forced)&&void 0!==l){if(typeof p===typeof l)continue;u(p,l)}(t.sham||l&&l.sham)&&i(p,"sham",!0),a(r,f,p,t)}}},7293:function(t){t.exports=function(t){try{return!!t()}catch(t){return!0}}},7007:function(t,e,r){"use strict";r(4916);var n=r(1320),o=r(2261),i=r(7293),a=r(5112),c=r(8880),u=a("species"),s=RegExp.prototype;t.exports=function(t,e,r,f){var l=a(t),p=!i((function(){var e={};return e[l]=function(){return 7},7!=""[t](e)})),h=p&&!i((function(){var e=!1,r=/a/;return"split"===t&&((r={}).constructor={},r.constructor[u]=function(){return r},r.flags="",r[l]=/./[l]),r.exec=function(){return e=!0,null},r[l](""),!e}));if(!p||!h||r){var d=/./[l],v=e(l,""[t],(function(t,e,r,n,i){var a=e.exec;return a===o||a===s.exec?p&&!i?{done:!0,value:d.call(e,r,n)}:{done:!0,value:t.call(r,e,n)}:{done:!1}}));n(String.prototype,t,v[0]),n(s,l,v[1])}f&&c(s[l],"sham",!0)}},9974:function(t,e,r){var n=r(3099);t.exports=function(t,e,r){if(n(t),void 0===e)return t;switch(r){case 0:return function(){return t.call(e)};case 1:return function(r){return t.call(e,r)};case 2:return function(r,n){return t.call(e,r,n)};case 3:return function(r,n,o){return t.call(e,r,n,o)}}return function(){return t.apply(e,arguments)}}},5005:function(t,e,r){var n=r(7854),o=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?o(n[t]):n[t]&&n[t][e]}},1246:function(t,e,r){var n=r(648),o=r(7497),i=r(5112)("iterator");t.exports=function(t){if(void 0!=t)return t[i]||t["@@iterator"]||o[n(t)]}},647:function(t,e,r){var n=r(7908),o=Math.floor,i="".replace,a=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,c=/\$([$&'`]|\d{1,2})/g;t.exports=function(t,e,r,u,s,f){var l=r+t.length,p=u.length,h=c;return void 0!==s&&(s=n(s),h=a),i.call(f,h,(function(n,i){var a;switch(i.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,r);case"'":return e.slice(l);case"<":a=s[i.slice(1,-1)];break;default:var c=+i;if(0===c)return n;if(c>p){var f=o(c/10);return 0===f?n:f<=p?void 0===u[f-1]?i.charAt(1):u[f-1]+i.charAt(1):n}a=u[c-1]}return void 0===a?"":a}))}},7854:function(t,e,r){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof r.g&&r.g)||function(){return this}()||Function("return this")()},6656:function(t,e,r){var n=r(7908),o={}.hasOwnProperty;t.exports=Object.hasOwn||function(t,e){return o.call(n(t),e)}},3501:function(t){t.exports={}},842:function(t,e,r){var n=r(7854);t.exports=function(t,e){var r=n.console;r&&r.error&&(1===arguments.length?r.error(t):r.error(t,e))}},490:function(t,e,r){var n=r(5005);t.exports=n("document","documentElement")},4664:function(t,e,r){var n=r(9781),o=r(7293),i=r(317);t.exports=!n&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:function(t,e,r){var n=r(7293),o=r(4326),i="".split;t.exports=n((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},9587:function(t,e,r){var n=r(111),o=r(7674);t.exports=function(t,e,r){var i,a;return o&&"function"==typeof(i=e.constructor)&&i!==r&&n(a=i.prototype)&&a!==r.prototype&&o(t,a),t}},2788:function(t,e,r){var n=r(5465),o=Function.toString;"function"!=typeof n.inspectSource&&(n.inspectSource=function(t){return o.call(t)}),t.exports=n.inspectSource},9909:function(t,e,r){var n,o,i,a=r(8536),c=r(7854),u=r(111),s=r(8880),f=r(6656),l=r(5465),p=r(6200),h=r(3501),d="Object already initialized",v=c.WeakMap;if(a||l.state){var y=l.state||(l.state=new v),g=y.get,m=y.has,b=y.set;n=function(t,e){if(m.call(y,t))throw new TypeError(d);return e.facade=t,b.call(y,t,e),e},o=function(t){return g.call(y,t)||{}},i=function(t){return m.call(y,t)}}else{var x=p("state");h[x]=!0,n=function(t,e){if(f(t,x))throw new TypeError(d);return e.facade=t,s(t,x,e),e},o=function(t){return f(t,x)?t[x]:{}},i=function(t){return f(t,x)}}t.exports={set:n,get:o,has:i,enforce:function(t){return i(t)?o(t):n(t,{})},getterFor:function(t){return function(e){var r;if(!u(e)||(r=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return r}}}},7659:function(t,e,r){var n=r(5112),o=r(7497),i=n("iterator"),a=Array.prototype;t.exports=function(t){return void 0!==t&&(o.Array===t||a[i]===t)}},3157:function(t,e,r){var n=r(4326);t.exports=Array.isArray||function(t){return"Array"==n(t)}},4705:function(t,e,r){var n=r(7293),o=/#|\.prototype\./,i=function(t,e){var r=c[a(t)];return r==s||r!=u&&("function"==typeof e?n(e):!!e)},a=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},c=i.data={},u=i.NATIVE="N",s=i.POLYFILL="P";t.exports=i},111:function(t){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},1913:function(t){t.exports=!1},7850:function(t,e,r){var n=r(111),o=r(4326),i=r(5112)("match");t.exports=function(t){var e;return n(t)&&(void 0!==(e=t[i])?!!e:"RegExp"==o(t))}},2190:function(t,e,r){var n=r(5005),o=r(3307);t.exports=o?function(t){return"symbol"==typeof t}:function(t){var e=n("Symbol");return"function"==typeof e&&Object(t)instanceof e}},408:function(t,e,r){var n=r(9670),o=r(7659),i=r(7466),a=r(9974),c=r(1246),u=r(9212),s=function(t,e){this.stopped=t,this.result=e};t.exports=function(t,e,r){var f,l,p,h,d,v,y,g=r&&r.that,m=!(!r||!r.AS_ENTRIES),b=!(!r||!r.IS_ITERATOR),x=!(!r||!r.INTERRUPTED),w=a(e,g,1+m+x),E=function(t){return f&&u(f),new s(!0,t)},S=function(t){return m?(n(t),x?w(t[0],t[1],E):w(t[0],t[1])):x?w(t,E):w(t)};if(b)f=t;else{if("function"!=typeof(l=c(t)))throw TypeError("Target is not iterable");if(o(l)){for(p=0,h=i(t.length);h>p;p++)if((d=S(t[p]))&&d instanceof s)return d;return new s(!1)}f=l.call(t)}for(v=f.next;!(y=v.call(f)).done;){try{d=S(y.value)}catch(t){throw u(f),t}if("object"==typeof d&&d&&d instanceof s)return d}return new s(!1)}},9212:function(t,e,r){var n=r(9670);t.exports=function(t){var e=t.return;if(void 0!==e)return n(e.call(t)).value}},3383:function(t,e,r){"use strict";var n,o,i,a=r(7293),c=r(9518),u=r(8880),s=r(6656),f=r(5112),l=r(1913),p=f("iterator"),h=!1;[].keys&&("next"in(i=[].keys())?(o=c(c(i)))!==Object.prototype&&(n=o):h=!0);var d=void 0==n||a((function(){var t={};return n[p].call(t)!==t}));d&&(n={}),l&&!d||s(n,p)||u(n,p,(function(){return this})),t.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:h}},7497:function(t){t.exports={}},5948:function(t,e,r){var n,o,i,a,c,u,s,f,l=r(7854),p=r(1236).f,h=r(261).set,d=r(6833),v=r(1528),y=r(1036),g=r(5268),m=l.MutationObserver||l.WebKitMutationObserver,b=l.document,x=l.process,w=l.Promise,E=p(l,"queueMicrotask"),S=E&&E.value;S||(n=function(){var t,e;for(g&&(t=x.domain)&&t.exit();o;){e=o.fn,o=o.next;try{e()}catch(t){throw o?a():i=void 0,t}}i=void 0,t&&t.enter()},d||g||y||!m||!b?!v&&w&&w.resolve?((s=w.resolve(void 0)).constructor=w,f=s.then,a=function(){f.call(s,n)}):a=g?function(){x.nextTick(n)}:function(){h.call(l,n)}:(c=!0,u=b.createTextNode(""),new m(n).observe(u,{characterData:!0}),a=function(){u.data=c=!c})),t.exports=S||function(t){var e={fn:t,next:void 0};i&&(i.next=e),o||(o=e,a()),i=e}},3366:function(t,e,r){var n=r(7854);t.exports=n.Promise},133:function(t,e,r){var n=r(7392),o=r(7293);t.exports=!!Object.getOwnPropertySymbols&&!o((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&n&&n<41}))},8536:function(t,e,r){var n=r(7854),o=r(2788),i=n.WeakMap;t.exports="function"===typeof i&&/native code/.test(o(i))},8523:function(t,e,r){"use strict";var n=r(3099),o=function(t){var e,r;this.promise=new t((function(t,n){if(void 0!==e||void 0!==r)throw TypeError("Bad Promise constructor");e=t,r=n})),this.resolve=n(e),this.reject=n(r)};t.exports.f=function(t){return new o(t)}},3929:function(t,e,r){var n=r(7850);t.exports=function(t){if(n(t))throw TypeError("The method doesn't accept regular expressions");return t}},30:function(t,e,r){var n,o=r(9670),i=r(6048),a=r(748),c=r(3501),u=r(490),s=r(317),f=r(6200),l=f("IE_PROTO"),p=function(){},h=function(t){return"