[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   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 };


Generated : Sun Mar 9 08:20:01 2025 Cross-referenced by PHPXref