[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/dist/script-modules/block-library/tabs/ -> view.js (source)

   1  // packages/block-library/build-module/tabs/view.mjs
   2  import {
   3    store,
   4    getContext,
   5    getElement,
   6    withSyncEvent
   7  } from "@wordpress/interactivity";
   8  var { actions, state } = store(
   9    "core/tabs",
  10    {
  11      state: {
  12        /**
  13         * Gets a contextually aware list of tabs for the current tabs block.
  14         *
  15         * @type {Array}
  16         */
  17        get tabsList() {
  18          const context = getContext();
  19          const tabsId = context?.tabsId;
  20          const tabsList = state[tabsId];
  21          return tabsList;
  22        },
  23        /**
  24         * Gets the index of the active tab element whether it
  25         * is a tab label or tab panel.
  26         *
  27         * @type {number|null}
  28         */
  29        get tabIndex() {
  30          const { attributes } = getElement();
  31          const tabId = attributes?.id?.replace("tab__", "") || null;
  32          if (!tabId) {
  33            return null;
  34          }
  35          const { tabsList } = state;
  36          const tabIndex = tabsList.findIndex((t) => t === tabId);
  37          return tabIndex;
  38        },
  39        /**
  40         * Whether the tab panel or tab label is the active tab.
  41         *
  42         * @type {boolean}
  43         */
  44        get isActiveTab() {
  45          const { activeTabIndex } = getContext();
  46          const { tabIndex } = state;
  47          return activeTabIndex === tabIndex;
  48        },
  49        /**
  50         * The value of the hidden attribute for tab panels.
  51         *
  52         * Inactive panels use the `until-found` state rather than being
  53         * hidden outright, so their content stays reachable by the
  54         * browser's find-in-page and by fragment navigation. The browser
  55         * fires `beforematch` on the panel before revealing it, which is
  56         * where the matching tab gets activated.
  57         *
  58         * @type {string|null}
  59         */
  60        get isHidden() {
  61          return state.isActiveTab ? null : "until-found";
  62        },
  63        /**
  64         * The value of the tabindex attribute for tab buttons and tab
  65         * panels. Only the active tab should be in the tab sequence.
  66         *
  67         * An inactive panel is hidden with `until-found`, which leaves it
  68         * in the layout, so it stays focusable unless it is taken out of
  69         * the tab sequence here.
  70         *
  71         * @type {number}
  72         */
  73        get tabIndexAttribute() {
  74          return state.isActiveTab ? 0 : -1;
  75        }
  76      },
  77      actions: {
  78        /**
  79         * Handles the keydown events for the tab label and tabs controller.
  80         *
  81         * @param {KeyboardEvent} event The keydown event.
  82         */
  83        handleTabKeyDown: withSyncEvent((event) => {
  84          const { tabIndex, tabsList } = state;
  85          if (tabIndex === null) {
  86            return;
  87          }
  88          if (event.key === "ArrowRight") {
  89            event.preventDefault();
  90            actions.moveFocus(tabIndex + 1);
  91          } else if (event.key === "ArrowLeft") {
  92            event.preventDefault();
  93            actions.moveFocus(tabIndex - 1);
  94          } else if (event.key === "Home") {
  95            event.preventDefault();
  96            actions.moveFocus(0);
  97          } else if (event.key === "End") {
  98            event.preventDefault();
  99            actions.moveFocus(tabsList.length - 1);
 100          }
 101        }),
 102        /**
 103         * Handles the click event for the tab label.
 104         *
 105         * @param {MouseEvent} event The click event.
 106         */
 107        handleTabClick: withSyncEvent((event) => {
 108          event.preventDefault();
 109          const { tabIndex } = state;
 110          if (tabIndex !== null) {
 111            actions.setActiveTab(tabIndex);
 112          }
 113        }),
 114        /**
 115         * Activates the tab whose panel the browser is about to reveal.
 116         *
 117         * Fired on a panel hidden with `until-found` when find-in-page or
 118         * a fragment navigation matches content inside it.
 119         */
 120        handleBeforeMatch: () => {
 121          const { tabIndex } = state;
 122          if (tabIndex !== null) {
 123            actions.setActiveTab(tabIndex);
 124          }
 125        },
 126        /**
 127         * Moves focus to a specific tab without activating it.
 128         *
 129         * @param {number} tabIndex The index to move focus to.
 130         */
 131        moveFocus: (tabIndex) => {
 132          const { tabsList } = state;
 133          if (!tabsList || tabsList.length === 0) {
 134            return;
 135          }
 136          let newIndex = tabIndex;
 137          if (newIndex < 0) {
 138            newIndex = tabsList.length - 1;
 139          } else if (newIndex >= tabsList.length) {
 140            newIndex = 0;
 141          }
 142          const tabId = tabsList[newIndex];
 143          const tabElement = document.getElementById("tab__" + tabId);
 144          if (tabElement) {
 145            tabElement.focus();
 146          }
 147        },
 148        /**
 149         * Sets the active tab index.
 150         *
 151         * @param {number}  tabIndex    The index of the active tab.
 152         * @param {boolean} scrollToTab Whether to scroll to the tab element.
 153         */
 154        setActiveTab: (tabIndex, scrollToTab = false) => {
 155          const { tabsList } = state;
 156          if (!tabsList || tabsList.length === 0) {
 157            return;
 158          }
 159          let newIndex = tabIndex;
 160          if (newIndex < 0) {
 161            newIndex = 0;
 162          } else if (newIndex >= tabsList.length) {
 163            newIndex = tabsList.length - 1;
 164          }
 165          const context = getContext();
 166          context.activeTabIndex = newIndex;
 167          if (scrollToTab) {
 168            const tabId = tabsList[newIndex];
 169            const tabElement = document.getElementById(tabId);
 170            if (tabElement) {
 171              setTimeout(() => {
 172                tabElement.scrollIntoView({ behavior: "smooth" });
 173              }, 100);
 174            }
 175          }
 176        }
 177      },
 178      callbacks: {
 179        activateTabByHash: () => {
 180          const { tabsList } = state;
 181          if (!tabsList || tabsList.length === 0) {
 182            return;
 183          }
 184          const targetElement = document.querySelector(":target");
 185          if (!targetElement) {
 186            return;
 187          }
 188          const panelIndex = tabsList.findIndex(
 189            (t) => t === targetElement.id
 190          );
 191          if (panelIndex >= 0) {
 192            actions.setActiveTab(panelIndex, true);
 193            return;
 194          }
 195          let panel = targetElement.closest(".wp-block-tab-panel");
 196          let tabIndex = -1;
 197          while (panel && tabIndex < 0) {
 198            tabIndex = tabsList.findIndex((t) => t === panel.id);
 199            panel = panel.parentElement?.closest(".wp-block-tab-panel") ?? null;
 200          }
 201          if (tabIndex < 0) {
 202            return;
 203          }
 204          actions.setActiveTab(tabIndex);
 205          window.setTimeout(() => {
 206            targetElement.scrollIntoView();
 207          }, 0);
 208        }
 209      }
 210    },
 211    {
 212      lock: true
 213    }
 214  );


Generated : Thu Sep 24 08:20:34 2026 Cross-referenced by PHPXref