[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/dist/ -> interactivity-router.js (source)

   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    parseServerData,
  56    populateServerData,
  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 = parseServerData(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        populateServerData(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  let hasLoadedNavigationTextsData = false;
 192  const navigationTexts = {
 193    loading: 'Loading page, please wait.',
 194    loaded: 'Page Loaded.'
 195  };
 196  const {
 197    state,
 198    actions
 199  } = (0,interactivity_namespaceObject.store)('core/router', {
 200    state: {
 201      url: window.location.href,
 202      navigation: {
 203        hasStarted: false,
 204        hasFinished: false,
 205        message: ''
 206      }
 207    },
 208    actions: {
 209      /**
 210       * Navigates to the specified page.
 211       *
 212       * This function normalizes the passed href, fetchs the page HTML if
 213       * needed, and updates any interactive regions whose contents have
 214       * changed. It also creates a new entry in the browser session history.
 215       *
 216       * @param href                               The page href.
 217       * @param [options]                          Options object.
 218       * @param [options.force]                    If true, it forces re-fetching the URL.
 219       * @param [options.html]                     HTML string to be used instead of fetching the requested URL.
 220       * @param [options.replace]                  If true, it replaces the current entry in the browser session history.
 221       * @param [options.timeout]                  Time until the navigation is aborted, in milliseconds. Default is 10000.
 222       * @param [options.loadingAnimation]         Whether an animation should be shown while navigating. Default to `true`.
 223       * @param [options.screenReaderAnnouncement] Whether a message for screen readers should be announced while navigating. Default to `true`.
 224       *
 225       * @return  Promise that resolves once the navigation is completed or aborted.
 226       */
 227      *navigate(href, options = {}) {
 228        const {
 229          clientNavigationDisabled
 230        } = (0,interactivity_namespaceObject.getConfig)();
 231        if (clientNavigationDisabled) {
 232          yield forcePageReload(href);
 233        }
 234        const pagePath = getPagePath(href);
 235        const {
 236          navigation
 237        } = state;
 238        const {
 239          loadingAnimation = true,
 240          screenReaderAnnouncement = true,
 241          timeout = 10000
 242        } = options;
 243        navigatingTo = href;
 244        actions.prefetch(pagePath, options);
 245  
 246        // Create a promise that resolves when the specified timeout ends.
 247        // The timeout value is 10 seconds by default.
 248        const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout));
 249  
 250        // Don't update the navigation status immediately, wait 400 ms.
 251        const loadingTimeout = setTimeout(() => {
 252          if (navigatingTo !== href) {
 253            return;
 254          }
 255          if (loadingAnimation) {
 256            navigation.hasStarted = true;
 257            navigation.hasFinished = false;
 258          }
 259          if (screenReaderAnnouncement) {
 260            a11ySpeak('loading');
 261          }
 262        }, 400);
 263        const page = yield Promise.race([pages.get(pagePath), timeoutPromise]);
 264  
 265        // Dismiss loading message if it hasn't been added yet.
 266        clearTimeout(loadingTimeout);
 267  
 268        // Once the page is fetched, the destination URL could have changed
 269        // (e.g., by clicking another link in the meantime). If so, bail
 270        // out, and let the newer execution to update the HTML.
 271        if (navigatingTo !== href) {
 272          return;
 273        }
 274        if (page && !page.initialData?.config?.['core/router']?.clientNavigationDisabled) {
 275          yield renderRegions(page);
 276          window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
 277  
 278          // Update the URL in the state.
 279          state.url = href;
 280  
 281          // Update the navigation status once the the new page rendering
 282          // has been completed.
 283          if (loadingAnimation) {
 284            navigation.hasStarted = false;
 285            navigation.hasFinished = true;
 286          }
 287          if (screenReaderAnnouncement) {
 288            a11ySpeak('loaded');
 289          }
 290  
 291          // Scroll to the anchor if exits in the link.
 292          const {
 293            hash
 294          } = new URL(href, window.location.href);
 295          if (hash) {
 296            document.querySelector(hash)?.scrollIntoView();
 297          }
 298        } else {
 299          yield forcePageReload(href);
 300        }
 301      },
 302      /**
 303       * Prefetchs the page with the passed URL.
 304       *
 305       * The function normalizes the URL and stores internally the fetch
 306       * promise, to avoid triggering a second fetch for an ongoing request.
 307       *
 308       * @param url             The page URL.
 309       * @param [options]       Options object.
 310       * @param [options.force] Force fetching the URL again.
 311       * @param [options.html]  HTML string to be used instead of fetching the requested URL.
 312       */
 313      prefetch(url, options = {}) {
 314        const {
 315          clientNavigationDisabled
 316        } = (0,interactivity_namespaceObject.getConfig)();
 317        if (clientNavigationDisabled) {
 318          return;
 319        }
 320        const pagePath = getPagePath(url);
 321        if (options.force || !pages.has(pagePath)) {
 322          pages.set(pagePath, fetchPage(pagePath, {
 323            html: options.html
 324          }));
 325        }
 326      }
 327    }
 328  });
 329  
 330  /**
 331   * Announces a message to screen readers.
 332   *
 333   * This is a wrapper around the `@wordpress/a11y` package's `speak` function. It handles importing
 334   * the package on demand and should be used instead of calling `ally.speak` direacly.
 335   *
 336   * @param messageKey The message to be announced by assistive technologies.
 337   */
 338  function a11ySpeak(messageKey) {
 339    if (!hasLoadedNavigationTextsData) {
 340      hasLoadedNavigationTextsData = true;
 341      const content = document.getElementById('wp-script-module-data-@wordpress/interactivity-router')?.textContent;
 342      if (content) {
 343        try {
 344          const parsed = JSON.parse(content);
 345          if (typeof parsed?.i18n?.loading === 'string') {
 346            navigationTexts.loading = parsed.i18n.loading;
 347          }
 348          if (typeof parsed?.i18n?.loaded === 'string') {
 349            navigationTexts.loaded = parsed.i18n.loaded;
 350          }
 351        } catch {}
 352      } else {
 353        // Fallback to localized strings from Interactivity API state.
 354        if (state.navigation.texts?.loading) {
 355          navigationTexts.loading = state.navigation.texts.loading;
 356        }
 357        if (state.navigation.texts?.loaded) {
 358          navigationTexts.loaded = state.navigation.texts.loaded;
 359        }
 360      }
 361    }
 362    const message = navigationTexts[messageKey];
 363    if (false) {} else {
 364      state.navigation.message =
 365      // Announce that the page has been loaded. If the message is the
 366      // same, we use a no-break space similar to the @wordpress/a11y
 367      // package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
 368      message + (state.navigation.message === message ? '\u00A0' : '');
 369    }
 370  }
 371  
 372  // Add click and prefetch to all links.
 373  if (false) {}
 374  
 375  var __webpack_exports__actions = __webpack_exports__.o;
 376  var __webpack_exports__state = __webpack_exports__.w;
 377  export { __webpack_exports__actions as actions, __webpack_exports__state as state };


Generated : Tue Sep 24 08:20:01 2024 Cross-referenced by PHPXref