PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | build/modules/block-library/tabs/view.js +81 -113 23.2.2 → 24.1.0 View file →
@@ -4,42 +4,10 @@
4 4 getContext,
5 5 getElement,
6 6 withSyncEvent
7 7 } from "@wordpress/interactivity";
8 -function createReadOnlyProxy(obj) {
9 - const arrayMutationMethods = /* @__PURE__ */ new Set([
10 - "push",
11 - "pop",
12 - "shift",
13 - "unshift",
14 - "splice",
15 - "sort",
16 - "reverse",
17 - "copyWithin",
18 - "fill"
19 - ]);
20 - return new Proxy(obj, {
21 - get(target, prop) {
22 - if (Array.isArray(target) && arrayMutationMethods.has(prop)) {
23 - return () => {
24 - };
25 - }
26 - const value = target[prop];
27 - if (typeof value === "object" && value !== null) {
28 - return createReadOnlyProxy(value);
29 - }
30 - return value;
31 - },
32 - set() {
33 - return false;
34 - },
35 - deleteProperty() {
36 - return false;
37 - }
38 - });
39 -}
40 -var { actions: privateActions, state: privateState } = store(
41 - "core/tabs/private",
8 +var { actions, state } = store(
9 + "core/tabs",
42 10 {
43 11 state: {
44 12 /**
45 13 * Gets a contextually aware list of tabs for the current tabs block.
@@ -48,9 +16,9 @@
48 16 */
49 17 get tabsList() {
50 18 const context = getContext();
51 19 const tabsId = context?.tabsId;
52 - const tabsList = privateState[tabsId];
20 + const tabsList = state[tabsId];
53 21 return tabsList;
54 22 },
55 23 /**
56 24 * Gets the index of the active tab element whether it
@@ -63,10 +31,10 @@
63 31 const tabId = attributes?.id?.replace("tab__", "") || null;
64 32 if (!tabId) {
65 33 return null;
66 34 }
67 - const { tabsList } = privateState;
68 - const tabIndex = tabsList.findIndex((t) => t.id === tabId);
35 + const { tabsList } = state;
36 + const tabIndex = tabsList.findIndex((t) => t === tabId);
69 37 return tabIndex;
70 38 },
71 39 /**
72 40 * Whether the tab panel or tab label is the active tab.
@@ -74,19 +42,37 @@
74 42 * @type {boolean}
75 43 */
76 44 get isActiveTab() {
77 45 const { activeTabIndex } = getContext();
78 - const { tabIndex } = privateState;
46 + const { tabIndex } = state;
79 47 return activeTabIndex === tabIndex;
80 48 },
81 49 /**
82 - * The value of the tabindex attribute for tab buttons.
83 - * Only the active tab should be in the tab sequence.
50 + * The value of the hidden attribute for tab panels.
84 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 + *
85 71 * @type {number}
86 72 */
87 73 get tabIndexAttribute() {
88 - return privateState.isActiveTab ? 0 : -1;
74 + return state.isActiveTab ? 0 : -1;
89 75 }
90 76 },
91 77 actions: {
92 78 /**
@@ -94,26 +80,24 @@
94 80 *
95 81 * @param {KeyboardEvent} event The keydown event.
96 82 */
97 83 handleTabKeyDown: withSyncEvent((event) => {
98 - const context = getContext();
99 - const { isVertical } = context;
100 - const { tabIndex } = privateState;
84 + const { tabIndex, tabsList } = state;
101 85 if (tabIndex === null) {
102 86 return;
103 87 }
104 - if (event.key === "ArrowRight" && !isVertical) {
88 + if (event.key === "ArrowRight") {
105 89 event.preventDefault();
106 - privateActions.moveFocus(tabIndex + 1);
107 - } else if (event.key === "ArrowLeft" && !isVertical) {
90 + actions.moveFocus(tabIndex + 1);
91 + } else if (event.key === "ArrowLeft") {
108 92 event.preventDefault();
109 - privateActions.moveFocus(tabIndex - 1);
110 - } else if (event.key === "ArrowDown" && isVertical) {
93 + actions.moveFocus(tabIndex - 1);
94 + } else if (event.key === "Home") {
111 95 event.preventDefault();
112 - privateActions.moveFocus(tabIndex + 1);
113 - } else if (event.key === "ArrowUp" && isVertical) {
96 + actions.moveFocus(0);
97 + } else if (event.key === "End") {
114 98 event.preventDefault();
115 - privateActions.moveFocus(tabIndex - 1);
99 + actions.moveFocus(tabsList.length - 1);
116 100 }
117 101 }),
118 102 /**
119 103 * Handles the click event for the tab label.
@@ -121,20 +105,32 @@
121 105 * @param {MouseEvent} event The click event.
122 106 */
123 107 handleTabClick: withSyncEvent((event) => {
124 108 event.preventDefault();
125 - const { tabIndex } = privateState;
109 + const { tabIndex } = state;
126 110 if (tabIndex !== null) {
127 - privateActions.setActiveTab(tabIndex);
111 + actions.setActiveTab(tabIndex);
128 112 }
129 113 }),
130 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 + /**
131 127 * Moves focus to a specific tab without activating it.
132 128 *
133 129 * @param {number} tabIndex The index to move focus to.
134 130 */
135 131 moveFocus: (tabIndex) => {
136 - const { tabsList } = privateState;
132 + const { tabsList } = state;
137 133 if (!tabsList || tabsList.length === 0) {
138 134 return;
139 135 }
140 136 let newIndex = tabIndex;
@@ -142,9 +138,9 @@
142 138 newIndex = tabsList.length - 1;
143 139 } else if (newIndex >= tabsList.length) {
144 140 newIndex = 0;
145 141 }
146 - const tabId = tabsList[newIndex].id;
142 + const tabId = tabsList[newIndex];
147 143 const tabElement = document.getElementById("tab__" + tabId);
148 144 if (tabElement) {
149 145 tabElement.focus();
150 146 }
@@ -149,15 +145,15 @@
149 145 tabElement.focus();
150 146 }
151 147 },
152 148 /**
153 - * Sets the active tab index (internal implementation).
149 + * Sets the active tab index.
154 150 *
155 151 * @param {number} tabIndex The index of the active tab.
156 152 * @param {boolean} scrollToTab Whether to scroll to the tab element.
157 153 */
158 154 setActiveTab: (tabIndex, scrollToTab = false) => {
159 - const { tabsList } = privateState;
155 + const { tabsList } = state;
160 156 if (!tabsList || tabsList.length === 0) {
161 157 return;
162 158 }
163 159 let newIndex = tabIndex;
@@ -168,9 +164,9 @@
168 164 }
169 165 const context = getContext();
170 166 context.activeTabIndex = newIndex;
171 167 if (scrollToTab) {
172 - const tabId = tabsList[newIndex].id;
168 + const tabId = tabsList[newIndex];
173 169 const tabElement = document.getElementById(tabId);
174 170 if (tabElement) {
175 171 setTimeout(() => {
176 172 tabElement.scrollIntoView({ behavior: "smooth" });
@@ -179,23 +175,37 @@
179 175 }
180 176 }
181 177 },
182 178 callbacks: {
183 - /**
184 - * When the tabs are initialized, we need to check if there is a hash in the url and if so if it exists in the current tabsList, set the active tab to that index.
185 - *
186 - */
187 - onTabsInit: () => {
188 - const { tabsList } = privateState;
189 - if (tabsList.length === 0) {
179 + activateTabByHash: () => {
180 + const { tabsList } = state;
181 + if (!tabsList || tabsList.length === 0) {
190 182 return;
191 183 }
192 - const { hash } = window.location;
193 - const tabId = hash.replace("#", "");
194 - const tabIndex = tabsList.findIndex((t) => t.id === tabId);
195 - if (tabIndex >= 0) {
196 - privateActions.setActiveTab(tabIndex, true);
184 + const targetElement = document.querySelector(":target");
185 + if (!targetElement) {
186 + return;
197 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);
198 208 }
199 209 }
200 210 },
201 211 {
@@ -201,47 +211,5 @@
201 211 {
202 212 lock: true
203 213 }
204 214 );
205 -store("core/tabs", {
206 - state: {
207 - /**
208 - * Gets a contextually aware list of tabs for the current tabs block.
209 - * Public API for third-party access.
210 - *
211 - * @type {Array}
212 - */
213 - get tabsList() {
214 - return createReadOnlyProxy(privateState.tabsList);
215 - },
216 - /**
217 - * Gets the index of the active tab element whether it
218 - * is a tab label or tab panel.
219 - *
220 - * @type {number|null}
221 - */
222 - get tabIndex() {
223 - return privateState.tabIndex;
224 - },
225 - /**
226 - * Whether the tab panel or tab label is the active tab.
227 - *
228 - * @type {boolean}
229 - */
230 - get isActiveTab() {
231 - return privateState.isActiveTab;
232 - }
233 - },
234 - actions: {
235 - /**
236 - * Sets the active tab index.
237 - * Public API for third-party programmatic tab activation.
238 - *
239 - * @param {number} tabIndex The index of the active tab.
240 - * @param {boolean} scrollToTab Whether to scroll to the tab element.
241 - */
242 - setActiveTab: (tabIndex, scrollToTab = false) => {
243 - privateActions.setActiveTab(tabIndex, scrollToTab);
244 - }
245 - }
246 -});
247 215 //# sourceMappingURL=view.js.map