wp/wp-includes/js/dist/script-modules/interactivity-router/index.js
changeset 22 8c2e4d02f4ef
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
       
     1 import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity";
       
     2 /******/ var __webpack_modules__ = ({
       
     3 
       
     4 /***/ 317:
       
     5 /***/ ((module) => {
       
     6 
       
     7 module.exports = import("@wordpress/a11y");;
       
     8 
       
     9 /***/ })
       
    10 
       
    11 /******/ });
       
    12 /************************************************************************/
       
    13 /******/ // The module cache
       
    14 /******/ var __webpack_module_cache__ = {};
       
    15 /******/ 
       
    16 /******/ // The require function
       
    17 /******/ function __webpack_require__(moduleId) {
       
    18 /******/ 	// Check if module is in cache
       
    19 /******/ 	var cachedModule = __webpack_module_cache__[moduleId];
       
    20 /******/ 	if (cachedModule !== undefined) {
       
    21 /******/ 		return cachedModule.exports;
       
    22 /******/ 	}
       
    23 /******/ 	// Create a new module (and put it into the cache)
       
    24 /******/ 	var module = __webpack_module_cache__[moduleId] = {
       
    25 /******/ 		// no module.id needed
       
    26 /******/ 		// no module.loaded needed
       
    27 /******/ 		exports: {}
       
    28 /******/ 	};
       
    29 /******/ 
       
    30 /******/ 	// Execute the module function
       
    31 /******/ 	__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
       
    32 /******/ 
       
    33 /******/ 	// Return the exports of the module
       
    34 /******/ 	return module.exports;
       
    35 /******/ }
       
    36 /******/ 
       
    37 /************************************************************************/
       
    38 /******/ /* webpack/runtime/define property getters */
       
    39 /******/ (() => {
       
    40 /******/ 	// define getter functions for harmony exports
       
    41 /******/ 	__webpack_require__.d = (exports, definition) => {
       
    42 /******/ 		for(var key in definition) {
       
    43 /******/ 			if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
       
    44 /******/ 				Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
       
    45 /******/ 			}
       
    46 /******/ 		}
       
    47 /******/ 	};
       
    48 /******/ })();
       
    49 /******/ 
       
    50 /******/ /* webpack/runtime/hasOwnProperty shorthand */
       
    51 /******/ (() => {
       
    52 /******/ 	__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
       
    53 /******/ })();
       
    54 /******/ 
       
    55 /************************************************************************/
       
    56 var __webpack_exports__ = {};
       
    57 
       
    58 // EXPORTS
       
    59 __webpack_require__.d(__webpack_exports__, {
       
    60   o: () => (/* binding */ actions),
       
    61   w: () => (/* binding */ state)
       
    62 });
       
    63 
       
    64 ;// external "@wordpress/interactivity"
       
    65 var x = (y) => {
       
    66 	var x = {}; __webpack_require__.d(x, y); return x
       
    67 } 
       
    68 var y = (x) => (() => (x))
       
    69 const interactivity_namespaceObject = x({ ["getConfig"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getConfig), ["privateApis"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.privateApis), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store) });
       
    70 ;// ./node_modules/@wordpress/interactivity-router/build-module/head.js
       
    71 /**
       
    72  * The cache of prefetched stylesheets and scripts.
       
    73  */
       
    74 const headElements = new Map();
       
    75 
       
    76 /**
       
    77  * Helper to update only the necessary tags in the head.
       
    78  *
       
    79  * @async
       
    80  * @param newHead The head elements of the new page.
       
    81  */
       
    82 const updateHead = async newHead => {
       
    83   // Helper to get the tag id store in the cache.
       
    84   const getTagId = tag => tag.id || tag.outerHTML;
       
    85 
       
    86   // Map incoming head tags by their content.
       
    87   const newHeadMap = new Map();
       
    88   for (const child of newHead) {
       
    89     newHeadMap.set(getTagId(child), child);
       
    90   }
       
    91   const toRemove = [];
       
    92 
       
    93   // Detect nodes that should be added or removed.
       
    94   for (const child of document.head.children) {
       
    95     const id = getTagId(child);
       
    96     // Always remove styles and links as they might change.
       
    97     if (child.nodeName === 'LINK' || child.nodeName === 'STYLE') {
       
    98       toRemove.push(child);
       
    99     } else if (newHeadMap.has(id)) {
       
   100       newHeadMap.delete(id);
       
   101     } else if (child.nodeName !== 'SCRIPT' && child.nodeName !== 'META') {
       
   102       toRemove.push(child);
       
   103     }
       
   104   }
       
   105   await Promise.all([...headElements.entries()].filter(([, {
       
   106     tag
       
   107   }]) => tag.nodeName === 'SCRIPT').map(async ([url]) => {
       
   108     await import(/* webpackIgnore: true */url);
       
   109   }));
       
   110 
       
   111   // Prepare new assets.
       
   112   const toAppend = [...newHeadMap.values()];
       
   113 
       
   114   // Apply the changes.
       
   115   toRemove.forEach(n => n.remove());
       
   116   document.head.append(...toAppend);
       
   117 };
       
   118 
       
   119 /**
       
   120  * Fetches and processes head assets (stylesheets and scripts) from a specified document.
       
   121  *
       
   122  * @async
       
   123  * @param doc The document from which to fetch head assets. It should support standard DOM querying methods.
       
   124  *
       
   125  * @return Returns an array of HTML elements representing the head assets.
       
   126  */
       
   127 const fetchHeadAssets = async doc => {
       
   128   const headTags = [];
       
   129 
       
   130   // We only want to fetch module scripts because regular scripts (without
       
   131   // `async` or `defer` attributes) can depend on the execution of other scripts.
       
   132   // Scripts found in the head are blocking and must be executed in order.
       
   133   const scripts = doc.querySelectorAll('script[type="module"][src]');
       
   134   scripts.forEach(script => {
       
   135     const src = script.getAttribute('src');
       
   136     if (!headElements.has(src)) {
       
   137       // add the <link> elements to prefetch the module scripts
       
   138       const link = doc.createElement('link');
       
   139       link.rel = 'modulepreload';
       
   140       link.href = src;
       
   141       document.head.append(link);
       
   142       headElements.set(src, {
       
   143         tag: script
       
   144       });
       
   145     }
       
   146   });
       
   147   const stylesheets = doc.querySelectorAll('link[rel=stylesheet]');
       
   148   await Promise.all(Array.from(stylesheets).map(async tag => {
       
   149     const href = tag.getAttribute('href');
       
   150     if (!href) {
       
   151       return;
       
   152     }
       
   153     if (!headElements.has(href)) {
       
   154       try {
       
   155         const response = await fetch(href);
       
   156         const text = await response.text();
       
   157         headElements.set(href, {
       
   158           tag,
       
   159           text
       
   160         });
       
   161       } catch (e) {
       
   162         // eslint-disable-next-line no-console
       
   163         console.error(e);
       
   164       }
       
   165     }
       
   166     const headElement = headElements.get(href);
       
   167     const styleElement = doc.createElement('style');
       
   168     styleElement.textContent = headElement.text;
       
   169     headTags.push(styleElement);
       
   170   }));
       
   171   return [doc.querySelector('title'), ...doc.querySelectorAll('style'), ...headTags];
       
   172 };
       
   173 
       
   174 ;// ./node_modules/@wordpress/interactivity-router/build-module/index.js
       
   175 var _getConfig$navigation;
       
   176 /**
       
   177  * WordPress dependencies
       
   178  */
       
   179 
       
   180 
       
   181 /**
       
   182  * Internal dependencies
       
   183  */
       
   184 
       
   185 const {
       
   186   directivePrefix,
       
   187   getRegionRootFragment,
       
   188   initialVdom,
       
   189   toVdom,
       
   190   render,
       
   191   parseServerData,
       
   192   populateServerData,
       
   193   batch
       
   194 } = (0,interactivity_namespaceObject.privateApis)('I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WordPress.');
       
   195 // Check if the navigation mode is full page or region based.
       
   196 const navigationMode = (_getConfig$navigation = (0,interactivity_namespaceObject.getConfig)('core/router').navigationMode) !== null && _getConfig$navigation !== void 0 ? _getConfig$navigation : 'regionBased';
       
   197 
       
   198 // The cache of visited and prefetched pages, stylesheets and scripts.
       
   199 const pages = new Map();
       
   200 
       
   201 // Helper to remove domain and hash from the URL. We are only interesting in
       
   202 // caching the path and the query.
       
   203 const getPagePath = url => {
       
   204   const u = new URL(url, window.location.href);
       
   205   return u.pathname + u.search;
       
   206 };
       
   207 
       
   208 // Fetch a new page and convert it to a static virtual DOM.
       
   209 const fetchPage = async (url, {
       
   210   html
       
   211 }) => {
       
   212   try {
       
   213     if (!html) {
       
   214       const res = await window.fetch(url);
       
   215       if (res.status !== 200) {
       
   216         return false;
       
   217       }
       
   218       html = await res.text();
       
   219     }
       
   220     const dom = new window.DOMParser().parseFromString(html, 'text/html');
       
   221     return regionsToVdom(dom);
       
   222   } catch (e) {
       
   223     return false;
       
   224   }
       
   225 };
       
   226 
       
   227 // Return an object with VDOM trees of those HTML regions marked with a
       
   228 // `router-region` directive.
       
   229 const regionsToVdom = async (dom, {
       
   230   vdom
       
   231 } = {}) => {
       
   232   const regions = {
       
   233     body: undefined
       
   234   };
       
   235   let head;
       
   236   if (false) {}
       
   237   if (navigationMode === 'regionBased') {
       
   238     const attrName = `data-${directivePrefix}-router-region`;
       
   239     dom.querySelectorAll(`[${attrName}]`).forEach(region => {
       
   240       const id = region.getAttribute(attrName);
       
   241       regions[id] = vdom?.has(region) ? vdom.get(region) : toVdom(region);
       
   242     });
       
   243   }
       
   244   const title = dom.querySelector('title')?.innerText;
       
   245   const initialData = parseServerData(dom);
       
   246   return {
       
   247     regions,
       
   248     head,
       
   249     title,
       
   250     initialData
       
   251   };
       
   252 };
       
   253 
       
   254 // Render all interactive regions contained in the given page.
       
   255 const renderRegions = async page => {
       
   256   if (false) {}
       
   257   if (navigationMode === 'regionBased') {
       
   258     const attrName = `data-${directivePrefix}-router-region`;
       
   259     batch(() => {
       
   260       populateServerData(page.initialData);
       
   261       document.querySelectorAll(`[${attrName}]`).forEach(region => {
       
   262         const id = region.getAttribute(attrName);
       
   263         const fragment = getRegionRootFragment(region);
       
   264         render(page.regions[id], fragment);
       
   265       });
       
   266     });
       
   267   }
       
   268   if (page.title) {
       
   269     document.title = page.title;
       
   270   }
       
   271 };
       
   272 
       
   273 /**
       
   274  * Load the given page forcing a full page reload.
       
   275  *
       
   276  * The function returns a promise that won't resolve, useful to prevent any
       
   277  * potential feedback indicating that the navigation has finished while the new
       
   278  * page is being loaded.
       
   279  *
       
   280  * @param href The page href.
       
   281  * @return Promise that never resolves.
       
   282  */
       
   283 const forcePageReload = href => {
       
   284   window.location.assign(href);
       
   285   return new Promise(() => {});
       
   286 };
       
   287 
       
   288 // Listen to the back and forward buttons and restore the page if it's in the
       
   289 // cache.
       
   290 window.addEventListener('popstate', async () => {
       
   291   const pagePath = getPagePath(window.location.href); // Remove hash.
       
   292   const page = pages.has(pagePath) && (await pages.get(pagePath));
       
   293   if (page) {
       
   294     await renderRegions(page);
       
   295     // Update the URL in the state.
       
   296     state.url = window.location.href;
       
   297   } else {
       
   298     window.location.reload();
       
   299   }
       
   300 });
       
   301 
       
   302 // Initialize the router and cache the initial page using the initial vDOM.
       
   303 // Once this code is tested and more mature, the head should be updated for
       
   304 // region based navigation as well.
       
   305 if (false) {}
       
   306 pages.set(getPagePath(window.location.href), Promise.resolve(regionsToVdom(document, {
       
   307   vdom: initialVdom
       
   308 })));
       
   309 
       
   310 // Check if the link is valid for client-side navigation.
       
   311 const isValidLink = ref => ref && ref instanceof window.HTMLAnchorElement && ref.href && (!ref.target || ref.target === '_self') && ref.origin === window.location.origin && !ref.pathname.startsWith('/wp-admin') && !ref.pathname.startsWith('/wp-login.php') && !ref.getAttribute('href').startsWith('#') && !new URL(ref.href).searchParams.has('_wpnonce');
       
   312 
       
   313 // Check if the event is valid for client-side navigation.
       
   314 const isValidEvent = event => event && event.button === 0 &&
       
   315 // Left clicks only.
       
   316 !event.metaKey &&
       
   317 // Open in new tab (Mac).
       
   318 !event.ctrlKey &&
       
   319 // Open in new tab (Windows).
       
   320 !event.altKey &&
       
   321 // Download.
       
   322 !event.shiftKey && !event.defaultPrevented;
       
   323 
       
   324 // Variable to store the current navigation.
       
   325 let navigatingTo = '';
       
   326 let hasLoadedNavigationTextsData = false;
       
   327 const navigationTexts = {
       
   328   loading: 'Loading page, please wait.',
       
   329   loaded: 'Page Loaded.'
       
   330 };
       
   331 const {
       
   332   state,
       
   333   actions
       
   334 } = (0,interactivity_namespaceObject.store)('core/router', {
       
   335   state: {
       
   336     url: window.location.href,
       
   337     navigation: {
       
   338       hasStarted: false,
       
   339       hasFinished: false
       
   340     }
       
   341   },
       
   342   actions: {
       
   343     /**
       
   344      * Navigates to the specified page.
       
   345      *
       
   346      * This function normalizes the passed href, fetches the page HTML if
       
   347      * needed, and updates any interactive regions whose contents have
       
   348      * changed. It also creates a new entry in the browser session history.
       
   349      *
       
   350      * @param href                               The page href.
       
   351      * @param [options]                          Options object.
       
   352      * @param [options.force]                    If true, it forces re-fetching the URL.
       
   353      * @param [options.html]                     HTML string to be used instead of fetching the requested URL.
       
   354      * @param [options.replace]                  If true, it replaces the current entry in the browser session history.
       
   355      * @param [options.timeout]                  Time until the navigation is aborted, in milliseconds. Default is 10000.
       
   356      * @param [options.loadingAnimation]         Whether an animation should be shown while navigating. Default to `true`.
       
   357      * @param [options.screenReaderAnnouncement] Whether a message for screen readers should be announced while navigating. Default to `true`.
       
   358      *
       
   359      * @return  Promise that resolves once the navigation is completed or aborted.
       
   360      */
       
   361     *navigate(href, options = {}) {
       
   362       const {
       
   363         clientNavigationDisabled
       
   364       } = (0,interactivity_namespaceObject.getConfig)();
       
   365       if (clientNavigationDisabled) {
       
   366         yield forcePageReload(href);
       
   367       }
       
   368       const pagePath = getPagePath(href);
       
   369       const {
       
   370         navigation
       
   371       } = state;
       
   372       const {
       
   373         loadingAnimation = true,
       
   374         screenReaderAnnouncement = true,
       
   375         timeout = 10000
       
   376       } = options;
       
   377       navigatingTo = href;
       
   378       actions.prefetch(pagePath, options);
       
   379 
       
   380       // Create a promise that resolves when the specified timeout ends.
       
   381       // The timeout value is 10 seconds by default.
       
   382       const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout));
       
   383 
       
   384       // Don't update the navigation status immediately, wait 400 ms.
       
   385       const loadingTimeout = setTimeout(() => {
       
   386         if (navigatingTo !== href) {
       
   387           return;
       
   388         }
       
   389         if (loadingAnimation) {
       
   390           navigation.hasStarted = true;
       
   391           navigation.hasFinished = false;
       
   392         }
       
   393         if (screenReaderAnnouncement) {
       
   394           a11ySpeak('loading');
       
   395         }
       
   396       }, 400);
       
   397       const page = yield Promise.race([pages.get(pagePath), timeoutPromise]);
       
   398 
       
   399       // Dismiss loading message if it hasn't been added yet.
       
   400       clearTimeout(loadingTimeout);
       
   401 
       
   402       // Once the page is fetched, the destination URL could have changed
       
   403       // (e.g., by clicking another link in the meantime). If so, bail
       
   404       // out, and let the newer execution to update the HTML.
       
   405       if (navigatingTo !== href) {
       
   406         return;
       
   407       }
       
   408       if (page && !page.initialData?.config?.['core/router']?.clientNavigationDisabled) {
       
   409         yield renderRegions(page);
       
   410         window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
       
   411 
       
   412         // Update the URL in the state.
       
   413         state.url = href;
       
   414 
       
   415         // Update the navigation status once the the new page rendering
       
   416         // has been completed.
       
   417         if (loadingAnimation) {
       
   418           navigation.hasStarted = false;
       
   419           navigation.hasFinished = true;
       
   420         }
       
   421         if (screenReaderAnnouncement) {
       
   422           a11ySpeak('loaded');
       
   423         }
       
   424 
       
   425         // Scroll to the anchor if exits in the link.
       
   426         const {
       
   427           hash
       
   428         } = new URL(href, window.location.href);
       
   429         if (hash) {
       
   430           document.querySelector(hash)?.scrollIntoView();
       
   431         }
       
   432       } else {
       
   433         yield forcePageReload(href);
       
   434       }
       
   435     },
       
   436     /**
       
   437      * Prefetches the page with the passed URL.
       
   438      *
       
   439      * The function normalizes the URL and stores internally the fetch
       
   440      * promise, to avoid triggering a second fetch for an ongoing request.
       
   441      *
       
   442      * @param url             The page URL.
       
   443      * @param [options]       Options object.
       
   444      * @param [options.force] Force fetching the URL again.
       
   445      * @param [options.html]  HTML string to be used instead of fetching the requested URL.
       
   446      */
       
   447     prefetch(url, options = {}) {
       
   448       const {
       
   449         clientNavigationDisabled
       
   450       } = (0,interactivity_namespaceObject.getConfig)();
       
   451       if (clientNavigationDisabled) {
       
   452         return;
       
   453       }
       
   454       const pagePath = getPagePath(url);
       
   455       if (options.force || !pages.has(pagePath)) {
       
   456         pages.set(pagePath, fetchPage(pagePath, {
       
   457           html: options.html
       
   458         }));
       
   459       }
       
   460     }
       
   461   }
       
   462 });
       
   463 
       
   464 /**
       
   465  * Announces a message to screen readers.
       
   466  *
       
   467  * This is a wrapper around the `@wordpress/a11y` package's `speak` function. It handles importing
       
   468  * the package on demand and should be used instead of calling `ally.speak` direacly.
       
   469  *
       
   470  * @param messageKey The message to be announced by assistive technologies.
       
   471  */
       
   472 function a11ySpeak(messageKey) {
       
   473   if (!hasLoadedNavigationTextsData) {
       
   474     hasLoadedNavigationTextsData = true;
       
   475     const content = document.getElementById('wp-script-module-data-@wordpress/interactivity-router')?.textContent;
       
   476     if (content) {
       
   477       try {
       
   478         const parsed = JSON.parse(content);
       
   479         if (typeof parsed?.i18n?.loading === 'string') {
       
   480           navigationTexts.loading = parsed.i18n.loading;
       
   481         }
       
   482         if (typeof parsed?.i18n?.loaded === 'string') {
       
   483           navigationTexts.loaded = parsed.i18n.loaded;
       
   484         }
       
   485       } catch {}
       
   486     } else {
       
   487       // Fallback to localized strings from Interactivity API state.
       
   488       // @todo This block is for Core < 6.7.0. Remove when support is dropped.
       
   489 
       
   490       // @ts-expect-error
       
   491       if (state.navigation.texts?.loading) {
       
   492         // @ts-expect-error
       
   493         navigationTexts.loading = state.navigation.texts.loading;
       
   494       }
       
   495       // @ts-expect-error
       
   496       if (state.navigation.texts?.loaded) {
       
   497         // @ts-expect-error
       
   498         navigationTexts.loaded = state.navigation.texts.loaded;
       
   499       }
       
   500     }
       
   501   }
       
   502   const message = navigationTexts[messageKey];
       
   503   Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 317)).then(({
       
   504     speak
       
   505   }) => speak(message),
       
   506   // Ignore failures to load the a11y module.
       
   507   () => {});
       
   508 }
       
   509 
       
   510 // Add click and prefetch to all links.
       
   511 if (false) {}
       
   512 
       
   513 var __webpack_exports__actions = __webpack_exports__.o;
       
   514 var __webpack_exports__state = __webpack_exports__.w;
       
   515 export { __webpack_exports__actions as actions, __webpack_exports__state as state };