wp/wp-includes/js/dist/interactivity-router.js
changeset 21 48c4eec2b7e6
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity";
       
     2 /******/ // The require scope
       
     3 /******/ var __webpack_require__ = {};
       
     4 /******/ 
       
     5 /************************************************************************/
       
     6 /******/ /* webpack/runtime/define property getters */
       
     7 /******/ (() => {
       
     8 /******/ 	// define getter functions for harmony exports
       
     9 /******/ 	__webpack_require__.d = (exports, definition) => {
       
    10 /******/ 		for(var key in definition) {
       
    11 /******/ 			if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
       
    12 /******/ 				Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
       
    13 /******/ 			}
       
    14 /******/ 		}
       
    15 /******/ 	};
       
    16 /******/ })();
       
    17 /******/ 
       
    18 /******/ /* webpack/runtime/hasOwnProperty shorthand */
       
    19 /******/ (() => {
       
    20 /******/ 	__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
       
    21 /******/ })();
       
    22 /******/ 
       
    23 /************************************************************************/
       
    24 var __webpack_exports__ = {};
       
    25 
       
    26 // EXPORTS
       
    27 __webpack_require__.d(__webpack_exports__, {
       
    28   o: () => (/* binding */ actions),
       
    29   w: () => (/* binding */ state)
       
    30 });
       
    31 
       
    32 ;// CONCATENATED MODULE: external "@wordpress/interactivity"
       
    33 var x = (y) => {
       
    34 	var x = {}; __webpack_require__.d(x, y); return x
       
    35 } 
       
    36 var y = (x) => (() => (x))
       
    37 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) });
       
    38 ;// CONCATENATED MODULE: ./node_modules/@wordpress/interactivity-router/build-module/index.js
       
    39 var _getConfig$navigation;
       
    40 /**
       
    41  * WordPress dependencies
       
    42  */
       
    43 
       
    44 
       
    45 /**
       
    46  * Internal dependencies
       
    47  */
       
    48 
       
    49 const {
       
    50   directivePrefix,
       
    51   getRegionRootFragment,
       
    52   initialVdom,
       
    53   toVdom,
       
    54   render,
       
    55   parseInitialData,
       
    56   populateInitialData,
       
    57   batch
       
    58 } = (0,interactivity_namespaceObject.privateApis)('I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WordPress.');
       
    59 // Check if the navigation mode is full page or region based.
       
    60 const navigationMode = (_getConfig$navigation = (0,interactivity_namespaceObject.getConfig)('core/router').navigationMode) !== null && _getConfig$navigation !== void 0 ? _getConfig$navigation : 'regionBased';
       
    61 
       
    62 // The cache of visited and prefetched pages, stylesheets and scripts.
       
    63 const pages = new Map();
       
    64 const headElements = new Map();
       
    65 
       
    66 // Helper to remove domain and hash from the URL. We are only interesting in
       
    67 // caching the path and the query.
       
    68 const getPagePath = url => {
       
    69   const u = new URL(url, window.location.href);
       
    70   return u.pathname + u.search;
       
    71 };
       
    72 
       
    73 // Fetch a new page and convert it to a static virtual DOM.
       
    74 const fetchPage = async (url, {
       
    75   html
       
    76 }) => {
       
    77   try {
       
    78     if (!html) {
       
    79       const res = await window.fetch(url);
       
    80       if (res.status !== 200) {
       
    81         return false;
       
    82       }
       
    83       html = await res.text();
       
    84     }
       
    85     const dom = new window.DOMParser().parseFromString(html, 'text/html');
       
    86     return regionsToVdom(dom);
       
    87   } catch (e) {
       
    88     return false;
       
    89   }
       
    90 };
       
    91 
       
    92 // Return an object with VDOM trees of those HTML regions marked with a
       
    93 // `router-region` directive.
       
    94 const regionsToVdom = async (dom, {
       
    95   vdom
       
    96 } = {}) => {
       
    97   const regions = {
       
    98     body: undefined
       
    99   };
       
   100   let head;
       
   101   if (false) {}
       
   102   if (navigationMode === 'regionBased') {
       
   103     const attrName = `data-${directivePrefix}-router-region`;
       
   104     dom.querySelectorAll(`[${attrName}]`).forEach(region => {
       
   105       const id = region.getAttribute(attrName);
       
   106       regions[id] = vdom?.has(region) ? vdom.get(region) : toVdom(region);
       
   107     });
       
   108   }
       
   109   const title = dom.querySelector('title')?.innerText;
       
   110   const initialData = parseInitialData(dom);
       
   111   return {
       
   112     regions,
       
   113     head,
       
   114     title,
       
   115     initialData
       
   116   };
       
   117 };
       
   118 
       
   119 // Render all interactive regions contained in the given page.
       
   120 const renderRegions = page => {
       
   121   batch(() => {
       
   122     if (false) {}
       
   123     if (navigationMode === 'regionBased') {
       
   124       populateInitialData(page.initialData);
       
   125       const attrName = `data-${directivePrefix}-router-region`;
       
   126       document.querySelectorAll(`[${attrName}]`).forEach(region => {
       
   127         const id = region.getAttribute(attrName);
       
   128         const fragment = getRegionRootFragment(region);
       
   129         render(page.regions[id], fragment);
       
   130       });
       
   131     }
       
   132     if (page.title) {
       
   133       document.title = page.title;
       
   134     }
       
   135   });
       
   136 };
       
   137 
       
   138 /**
       
   139  * Load the given page forcing a full page reload.
       
   140  *
       
   141  * The function returns a promise that won't resolve, useful to prevent any
       
   142  * potential feedback indicating that the navigation has finished while the new
       
   143  * page is being loaded.
       
   144  *
       
   145  * @param href The page href.
       
   146  * @return Promise that never resolves.
       
   147  */
       
   148 const forcePageReload = href => {
       
   149   window.location.assign(href);
       
   150   return new Promise(() => {});
       
   151 };
       
   152 
       
   153 // Listen to the back and forward buttons and restore the page if it's in the
       
   154 // cache.
       
   155 window.addEventListener('popstate', async () => {
       
   156   const pagePath = getPagePath(window.location.href); // Remove hash.
       
   157   const page = pages.has(pagePath) && (await pages.get(pagePath));
       
   158   if (page) {
       
   159     renderRegions(page);
       
   160     // Update the URL in the state.
       
   161     state.url = window.location.href;
       
   162   } else {
       
   163     window.location.reload();
       
   164   }
       
   165 });
       
   166 
       
   167 // Initialize the router and cache the initial page using the initial vDOM.
       
   168 // Once this code is tested and more mature, the head should be updated for
       
   169 // region based navigation as well.
       
   170 if (false) {}
       
   171 pages.set(getPagePath(window.location.href), Promise.resolve(regionsToVdom(document, {
       
   172   vdom: initialVdom
       
   173 })));
       
   174 
       
   175 // Check if the link is valid for client-side navigation.
       
   176 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');
       
   177 
       
   178 // Check if the event is valid for client-side navigation.
       
   179 const isValidEvent = event => event && event.button === 0 &&
       
   180 // Left clicks only.
       
   181 !event.metaKey &&
       
   182 // Open in new tab (Mac).
       
   183 !event.ctrlKey &&
       
   184 // Open in new tab (Windows).
       
   185 !event.altKey &&
       
   186 // Download.
       
   187 !event.shiftKey && !event.defaultPrevented;
       
   188 
       
   189 // Variable to store the current navigation.
       
   190 let navigatingTo = '';
       
   191 const {
       
   192   state,
       
   193   actions
       
   194 } = (0,interactivity_namespaceObject.store)('core/router', {
       
   195   state: {
       
   196     url: window.location.href,
       
   197     navigation: {
       
   198       hasStarted: false,
       
   199       hasFinished: false,
       
   200       texts: {
       
   201         loading: '',
       
   202         loaded: ''
       
   203       },
       
   204       message: ''
       
   205     }
       
   206   },
       
   207   actions: {
       
   208     /**
       
   209      * Navigates to the specified page.
       
   210      *
       
   211      * This function normalizes the passed href, fetchs the page HTML if
       
   212      * needed, and updates any interactive regions whose contents have
       
   213      * changed. It also creates a new entry in the browser session history.
       
   214      *
       
   215      * @param href                               The page href.
       
   216      * @param [options]                          Options object.
       
   217      * @param [options.force]                    If true, it forces re-fetching the URL.
       
   218      * @param [options.html]                     HTML string to be used instead of fetching the requested URL.
       
   219      * @param [options.replace]                  If true, it replaces the current entry in the browser session history.
       
   220      * @param [options.timeout]                  Time until the navigation is aborted, in milliseconds. Default is 10000.
       
   221      * @param [options.loadingAnimation]         Whether an animation should be shown while navigating. Default to `true`.
       
   222      * @param [options.screenReaderAnnouncement] Whether a message for screen readers should be announced while navigating. Default to `true`.
       
   223      *
       
   224      * @return  Promise that resolves once the navigation is completed or aborted.
       
   225      */
       
   226     *navigate(href, options = {}) {
       
   227       const {
       
   228         clientNavigationDisabled
       
   229       } = (0,interactivity_namespaceObject.getConfig)();
       
   230       if (clientNavigationDisabled) {
       
   231         yield forcePageReload(href);
       
   232       }
       
   233       const pagePath = getPagePath(href);
       
   234       const {
       
   235         navigation
       
   236       } = state;
       
   237       const {
       
   238         loadingAnimation = true,
       
   239         screenReaderAnnouncement = true,
       
   240         timeout = 10000
       
   241       } = options;
       
   242       navigatingTo = href;
       
   243       actions.prefetch(pagePath, options);
       
   244 
       
   245       // Create a promise that resolves when the specified timeout ends.
       
   246       // The timeout value is 10 seconds by default.
       
   247       const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout));
       
   248 
       
   249       // Don't update the navigation status immediately, wait 400 ms.
       
   250       const loadingTimeout = setTimeout(() => {
       
   251         if (navigatingTo !== href) {
       
   252           return;
       
   253         }
       
   254         if (loadingAnimation) {
       
   255           navigation.hasStarted = true;
       
   256           navigation.hasFinished = false;
       
   257         }
       
   258         if (screenReaderAnnouncement) {
       
   259           navigation.message = navigation.texts.loading;
       
   260         }
       
   261       }, 400);
       
   262       const page = yield Promise.race([pages.get(pagePath), timeoutPromise]);
       
   263 
       
   264       // Dismiss loading message if it hasn't been added yet.
       
   265       clearTimeout(loadingTimeout);
       
   266 
       
   267       // Once the page is fetched, the destination URL could have changed
       
   268       // (e.g., by clicking another link in the meantime). If so, bail
       
   269       // out, and let the newer execution to update the HTML.
       
   270       if (navigatingTo !== href) {
       
   271         return;
       
   272       }
       
   273       if (page && !page.initialData?.config?.['core/router']?.clientNavigationDisabled) {
       
   274         yield renderRegions(page);
       
   275         window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
       
   276 
       
   277         // Update the URL in the state.
       
   278         state.url = href;
       
   279 
       
   280         // Update the navigation status once the the new page rendering
       
   281         // has been completed.
       
   282         if (loadingAnimation) {
       
   283           navigation.hasStarted = false;
       
   284           navigation.hasFinished = true;
       
   285         }
       
   286         if (screenReaderAnnouncement) {
       
   287           // Announce that the page has been loaded. If the message is the
       
   288           // same, we use a no-break space similar to the @wordpress/a11y
       
   289           // package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
       
   290           navigation.message = navigation.texts.loaded + (navigation.message === navigation.texts.loaded ? '\u00A0' : '');
       
   291         }
       
   292 
       
   293         // Scroll to the anchor if exits in the link.
       
   294         const {
       
   295           hash
       
   296         } = new URL(href, window.location.href);
       
   297         if (hash) {
       
   298           document.querySelector(hash)?.scrollIntoView();
       
   299         }
       
   300       } else {
       
   301         yield forcePageReload(href);
       
   302       }
       
   303     },
       
   304     /**
       
   305      * Prefetchs the page with the passed URL.
       
   306      *
       
   307      * The function normalizes the URL and stores internally the fetch
       
   308      * promise, to avoid triggering a second fetch for an ongoing request.
       
   309      *
       
   310      * @param url             The page URL.
       
   311      * @param [options]       Options object.
       
   312      * @param [options.force] Force fetching the URL again.
       
   313      * @param [options.html]  HTML string to be used instead of fetching the requested URL.
       
   314      */
       
   315     prefetch(url, options = {}) {
       
   316       const {
       
   317         clientNavigationDisabled
       
   318       } = (0,interactivity_namespaceObject.getConfig)();
       
   319       if (clientNavigationDisabled) {
       
   320         return;
       
   321       }
       
   322       const pagePath = getPagePath(url);
       
   323       if (options.force || !pages.has(pagePath)) {
       
   324         pages.set(pagePath, fetchPage(pagePath, {
       
   325           html: options.html
       
   326         }));
       
   327       }
       
   328     }
       
   329   }
       
   330 });
       
   331 
       
   332 // Add click and prefetch to all links.
       
   333 if (false) {}
       
   334 
       
   335 var __webpack_exports__actions = __webpack_exports__.o;
       
   336 var __webpack_exports__state = __webpack_exports__.w;
       
   337 export { __webpack_exports__actions as actions, __webpack_exports__state as state };