PluginProbe ʕ •ᴥ•ʔ
VK Blocks / 1.126.3
VK Blocks v1.126.3
1.126.4 1.126.3 1.126.1 1.126.2 1.126.0 1.125.0 1.124.0 1.123.0 1.122.0 1.121.0 1.120.0 1.119.2 1.119.1 1.119.0 1.118.7 1.82.0.0 1.82.0.1 1.83.0.0 1.83.0.1 1.84.0.0 1.84.0.1 1.85.0.2 1.85.0.3 1.85.1.0 1.85.1.1 1.86.0.0 1.86.0.1 1.86.1.0 1.87.0.0 1.87.0.1 1.88.0.1 1.88.0.2 1.89.0.0 1.9.2 1.90.0.0 1.90.0.1 1.90.1.1 1.91.0.0 1.91.1.1 1.92.0.0 1.92.0.1 1.92.1.0 1.92.1.1 1.93.0.0 trunk 1.93.0.1 0.1.1 1.93.1.0 0.10.3 1.93.1.1 0.11.0 1.94.0.0 0.12.6 1.94.0.1 0.15.1 1.94.1.0 0.16.2 1.94.2.1 0.17.6 1.94.2.2 0.2.2 1.95.0.3 0.22.4 1.95.0.4 0.26.7 1.96.2.0 0.3.2 1.96.2.1 0.31.0 1.97.0.1 0.35.5 1.97.0.2 0.37.5 1.99.0.0 0.38.8 1.99.0.1 0.39.4 0.4.7 0.41.0 0.42.0 0.44.13 0.45.2 0.46.1 0.47.0 0.48.0 0.49.8 0.5.2 0.50.3 0.51.0 0.52.2 0.53.2 0.54.0 0.55.0 0.56.2 0.56.3 0.57.6 0.58.10 0.59.0 0.6.0 0.60.1 0.61.2 0.7.1 0.8.2 1.0.16 1.10.0 1.100.0.0 1.100.0.1 1.102.0.0 1.102.0.1 1.103.0.0 1.104.0.0 1.104.0.1 1.105.1.0 1.105.1.1 1.106.0.0 1.106.0.1 1.107.0.0 1.107.0.1 1.107.0.2 1.108.0.0 1.108.0.1 1.109.0.0 1.109.0.1 1.11.4 1.110.0.0 1.110.0.1 1.111.0.0 1.111.0.1 1.111.0.2 1.112.0.0 1.112.0.1 1.113.0.0 1.113.0.1 1.114.0.0 1.114.0.1 1.114.2.0 1.114.2.1 1.115.0.0 1.115.0.1 1.115.1.0 1.115.1.1 1.115.2.0 1.115.2.1 1.116.0.0 1.116.0.1 1.116.1.0 1.116.1.1 1.116.2.0 1.117.0.0 1.117.0.1 1.117.1.0 1.118.2 1.118.5 1.12.0 1.13.2 1.14.1 1.15.1 1.16.11 1.17.0 1.18.6 1.19.0 1.19.1 1.2.3 1.20.7 1.21.0 1.22.4 1.23.0 1.24.5 1.25.1 1.26.2 1.27.7.2 1.28.0.1 1.29.2.0 1.3.9 1.30.0.1 1.31.0.1 1.32.0.2 1.33.2.1 1.36.1.5 1.37.0.0 1.39.2.1 1.4.6 1.40.0.1 1.40.1.0 1.40.1.1 1.41.0.0 1.41.0.1 1.41.2.2 1.41.2.3 1.43.0.0 1.43.0.1 1.43.0.2 1.44.0.0 1.44.0.1 1.45.0.0 1.45.0.1 1.46.0.0 1.46.0.1 1.47.0.0 1.47.0.1 1.47.1.0 1.48.0.0 1.48.0.1 1.48.0.2 1.48.1.0 1.48.1.1 1.5.0 1.50.0.0 1.50.0.1 1.51.0.0 1.51.0.1 1.52.0.0 1.52.0.1 1.53.0.0 1.53.0.1 1.54.0.0 1.54.0.1 1.55.0.0 1.55.0.1 1.56.0.0 1.56.0.1 1.57.0.0 1.57.0.2 1.57.0.3 1.57.0.4 1.57.0.5 1.57.1.0 1.57.1.1 1.57.1.2 1.58.0.0 1.58.0.1 1.59.0.0 1.59.0.1 1.6.0 1.60.0.0 1.60.0.1 1.63.0.0 1.63.0.1 1.64.0.0 1.64.0.1 1.64.1.0 1.64.1.2 1.67.0.0 1.67.0.1 1.68.0.0 1.68.0.1 1.69.0.0 1.69.0.1 1.69.1.1 1.69.1.2 1.7.1 1.70.0.0 1.70.0.1 1.71.0.0 1.71.0.1 1.72.0.0 1.72.1.0 1.72.1.1 1.73.0.0 1.73.0.1 1.74.0.0 1.74.0.1 1.75.0.0 1.75.1.0 1.75.1.1 1.76.0.0 1.76.0.1 1.76.1.0 1.76.1.1 1.76.2.0 1.76.2.1 1.77.0.0 1.77.0.1 1.78.0.0 1.78.0.1 1.79.0.0 1.79.0.1 1.79.0.2 1.79.0.3 1.79.1.0 1.79.1.1 1.8.2 1.80.1.0 1.80.1.1 1.80.1.2 1.81.0.1 1.81.0.2
vk-blocks / src / extensions / core / group / view.js
vk-blocks / src / extensions / core / group Last commit date
deprecated 2 months ago style.js 3 months ago style.scss 3 months ago view.js 1 week ago
view.js
771 lines
1 /**
2 * Group Block Scrollable Extension - Frontend View
3 *
4 * @package
5 */
6
7 (function () {
8 'use strict';
9
10 // 重複読み込み防止フラグ(より�
11 牢な初期化ガード)
12 if (window.__vkGroupScrollableInit) {
13 return;
14 }
15 window.__vkGroupScrollableInit = true;
16
17 // 更新中フラグ(無限ループを防ぐ)
18 let isUpdating = false;
19
20 // Breakpoint constants matching $xs (576px) / $md (992px) in
21 // src/utils/_breakpoints.scss. Unified to integers + `<` comparison,
22 // equivalent to the old `.98` notation (`<= 575.98` / `<= 991.98`),
23 // since window.innerWidth is always an integer. Previously duplicated
24 // in shouldApplyTableMode() and shouldApplyGridScrollableByBreakpoint();
25 // consolidated here into a single module-scope definition.
26 // _breakpoints.scss の $xs (576px) / $md (992px) と一致させるブレークポイント
27 // 定数。window.innerWidth は常に整数のため、旧 `.98` 記法
28 // (`<= 575.98` / `<= 991.98`)と等価な整数+`<` 比較に統一した。
29 // 以前は shouldApplyTableMode() と shouldApplyGridScrollableByBreakpoint()
30 // の2箇所に重複定義されていたため、ここに1箇所へ統合した。
31 // See: https://github.com/vektor-inc/vk-blocks-pro/issues/3091
32 const MOBILE_BREAKPOINT = 576;
33 const TABLET_BREAKPOINT = 992;
34
35 // 測定用コンテナ(1回だけ作成して再利用、パフォーマンス向上)
36 let measurementContainer = null;
37
38 /**
39 * 測定用のコンテナを取得(再利用)
40 * @return {HTMLElement} 測定用コンテナ要素
41 */
42 function getMeasurementContainer() {
43 if (!measurementContainer) {
44 measurementContainer = document.createElement('div');
45 // 測定用コンテナを非表示にする(レイアウトシフトを防ぐ)
46 measurementContainer.style.cssText =
47 'position: absolute; visibility: hidden; top: -9999px; left: -9999px;';
48 document.body.appendChild(measurementContainer);
49 }
50 return measurementContainer;
51 }
52
53 /**
54 * カラムの�
55 を測定(バッチ処理でパフォーマンス向上)
56 * @param {HTMLElement[]} columns - 測定対象のカラム要素の�
57 �列
58 * @param {HTMLElement} tempContainer - 測定用コンテナ
59 * @return {number} 最大�
60
61 */
62 function measureColumnsWidth(columns, tempContainer) {
63 const clones = [];
64 let maxWidth = 0;
65
66 // バッチ処理:すべてのクローンを一度に作成
67 columns.forEach((column) => {
68 if (!column) {
69 return;
70 }
71
72 try {
73 const columnClone = column.cloneNode(true);
74 const computedStyle = window.getComputedStyle(column);
75
76 // クローンに�
77 要なスタイルを適用
78 columnClone.style.cssText = `
79 position: absolute;
80 visibility: hidden;
81 top: -9999px;
82 left: -9999px;
83 width: auto;
84 max-width: none;
85 min-width: auto;
86 padding: ${computedStyle.padding};
87 margin: ${computedStyle.margin};
88 border: ${computedStyle.border};
89 box-sizing: ${computedStyle.boxSizing};
90 font-size: ${computedStyle.fontSize};
91 font-family: ${computedStyle.fontFamily};
92 `;
93
94 tempContainer.appendChild(columnClone);
95 clones.push(columnClone);
96 } catch (error) {
97 // エラーが発生した場合はスキップ
98 }
99 });
100
101 // バッチ処理:すべてのクローンの�
102 を一度に測定
103 clones.forEach((clone) => {
104 const contentWidth = clone.scrollWidth || clone.offsetWidth;
105 if (contentWidth > maxWidth) {
106 maxWidth = contentWidth;
107 }
108 });
109
110 // バッチ処理:すべてのクローンを一度に削除
111 clones.forEach((clone) => {
112 try {
113 tempContainer.removeChild(clone);
114 } catch (error) {
115 // エラーが発生した場合はスキップ
116 }
117 });
118
119 return maxWidth;
120 }
121
122 /**
123 * ブレークポイントに応じてテーブルモードを適用するかどうかを判定
124 * @param {HTMLElement} group - グループブロック要素
125 * @return {boolean} テーブルモードを適用するかどうか
126 */
127 function shouldApplyTableMode(group) {
128 const breakpoint = group.getAttribute('data-scroll-breakpoint');
129 if (!breakpoint) {
130 return false;
131 }
132
133 const windowWidth = window.innerWidth;
134
135 // PCブレークポイントの場合:常に適用
136 if (breakpoint === 'group-scrollable-pc') {
137 return true;
138 }
139
140 // Tabletブレークポイントの場合:992px未満の場合のみ適用
141 if (breakpoint === 'group-scrollable-tablet') {
142 return windowWidth < TABLET_BREAKPOINT;
143 }
144
145 // Mobileブレークポイントの場合:576px未満の場合のみ適用
146 if (breakpoint === 'group-scrollable-mobile') {
147 return windowWidth < MOBILE_BREAKPOINT;
148 }
149
150 return false;
151 }
152
153 /**
154 * スクロール可能なグループブロック�
155 のカラムブロックの�
156 を統一する
157 * テーブルのように複数の行(.wp-block-columns)がある場合、同じ列位置のカラムを統一する
158 * リサイズ時やカスタムイベント時に使用(data-table-mode="true"のもののみ処理)
159 */
160 function equalizeColumnWidths() {
161 // テーブルモードが有効なスクロール可能なグループブロックを取得
162 const scrollableGroups = document.querySelectorAll(
163 '.wp-block-group.is-style-vk-group-scrollable[data-table-mode="true"]'
164 );
165
166 // 新しく追加されたグループブロックを監視対象に追加
167 scrollableGroups.forEach((group) => {
168 observeGroup(group);
169 });
170
171 // 各グループブロックを個別に処理(パフォーマンス向上)
172 scrollableGroups.forEach((group) => {
173 // ブレークポイントに応じてテーブルモードを適用するかどうかを判定
174 if (shouldApplyTableMode(group)) {
175 processGroupBlock(group);
176 } else {
177 // テーブルモードを適用しない場合は、min-widthを削除
178 removeMinWidthFromColumns(group);
179 }
180 });
181 }
182
183 // 監視中のグループブロックを記録(重複監視を防ぐ)
184 const observedGroups = new WeakSet();
185 // Intersection Observerで監視中のグループブロックを記録
186 const intersectionObservedGroups = new WeakSet();
187
188 // MutationObserverで動的に追加された要素にも対応
189 // attributesの変更(minWidthの設定)は無視する
190 const observer = new MutationObserver((mutations) => {
191 // 更新中は無視
192 if (isUpdating) {
193 return;
194 }
195
196 // attributesの変更は無視(minWidthの設定による再実行を防ぐ)
197 const hasRelevantChanges = mutations.some((mutation) => {
198 return (
199 mutation.type === 'childList' &&
200 (mutation.addedNodes.length > 0 ||
201 mutation.removedNodes.length > 0)
202 );
203 });
204
205 if (hasRelevantChanges) {
206 // 変更があったグループブロックのみを処理(パフォーマンス向上)
207 const groupsToProcess = new Set();
208 const groupsToClean = new Set();
209 mutations.forEach((mutation) => {
210 let group = mutation.target;
211 // 親要素を遡ってグループブロックを探す
212 while (
213 group &&
214 (!group.classList ||
215 !group.classList.contains('wp-block-group') ||
216 !group.classList.contains(
217 'is-style-vk-group-scrollable'
218 ))
219 ) {
220 group = group.parentElement;
221 }
222 if (group) {
223 // data-table-modeがtrueの場合は処理対象に追加
224 if (group.getAttribute('data-table-mode') === 'true') {
225 // ブレークポイントに応じてテーブルモードを適用するかどうかを判定
226 if (shouldApplyTableMode(group)) {
227 groupsToProcess.add(group);
228 } else {
229 // テーブルモードを適用しない場合はmin-width削除対象に追加
230 groupsToClean.add(group);
231 }
232 } else {
233 // data-table-modeがtrueでない場合はmin-width削除対象に追加
234 groupsToClean.add(group);
235 }
236 }
237 });
238 // 変更があったグループブロックを処理(コンテンツ変更時は再処理が�
239 要)
240 groupsToProcess.forEach((group) => {
241 // 非同期処理でブラウザをブロックしない
242 requestAnimationFrame(() => {
243 processGroupBlock(group);
244 });
245 });
246 // data-table-modeがtrueでないグループブロックからmin-widthを削除
247 groupsToClean.forEach((group) => {
248 requestAnimationFrame(() => {
249 removeMinWidthFromColumns(group);
250 });
251 });
252 }
253 });
254
255 /**
256 * グループブロックを監視対象に追加
257 * @param {HTMLElement} group - 監視対象のグループブロック要素
258 */
259 function observeGroup(group) {
260 if (!observedGroups.has(group)) {
261 observer.observe(group, {
262 childList: true,
263 subtree: true,
264 attributes: false, // attributesの変更は無視
265 });
266 observedGroups.add(group);
267 }
268 }
269 // 処理済みのグループブロックを記録(Intersection Observerで初回表示時のみ処理)
270 const processedGroups = new WeakSet();
271
272 // Intersection Observerで動的に追加されたグループブロックを自動検出
273 // 表示領域に�
274 �った時に自動的に処理される
275 const intersectionObserver = new IntersectionObserver(
276 (entries) => {
277 entries.forEach((entry) => {
278 if (
279 entry.isIntersecting &&
280 !processedGroups.has(entry.target)
281 ) {
282 // 表示領域に�
283 �ったグループブロックを処理(同期的に処理)
284 // ブレークポイントに応じてテーブルモードを適用するかどうかを判定
285 if (shouldApplyTableMode(entry.target)) {
286 processGroupBlock(entry.target);
287 } else {
288 // テーブルモードを適用しない場合は、min-widthを削除
289 removeMinWidthFromColumns(entry.target);
290 }
291 processedGroups.add(entry.target);
292 // 一度処理したら監視を解除(パフォーマンス向上)
293 intersectionObserver.unobserve(entry.target);
294 }
295 });
296 },
297 {
298 // 少し早めに検出(表示領域の10%が見えた時点)
299 rootMargin: '10%',
300 }
301 );
302
303 /**
304 * テーブルモードが無効なグループブロックからmin-widthを削除
305 * @param {HTMLElement} group - 処理対象のグループブロック要素
306 */
307 function removeMinWidthFromColumns(group) {
308 // 一度にすべてのカラムを取得(min-widthが設定されているもののみ)
309 const columns = group.querySelectorAll(
310 '.wp-block-columns .wp-block-column[style*="min-width"]'
311 );
312
313 // requestAnimationFrameでまとめて処理
314 if (columns.length > 0) {
315 requestAnimationFrame(() => {
316 columns.forEach((column) => {
317 column.style.minWidth = '';
318 });
319 });
320 }
321 }
322
323 /**
324 * 単一のグループブロックを処理(パフォーマンス向上のため個別処理)
325 * @param {HTMLElement} group - 処理対象のグループブロック要素(data-table-mode="true"のもののみ)
326 */
327 function processGroupBlock(group) {
328 // 更新中フラグを設定
329 isUpdating = true;
330
331 // グループ�
332 のすべてのカラムブロック(行)を取得
333 const columnsContainers = Array.from(
334 group.querySelectorAll('.wp-block-columns')
335 );
336 if (columnsContainers.length === 0) {
337 isUpdating = false;
338 return;
339 }
340
341 // 測定用コンテナを取得(再利用)
342 const tempContainer = getMeasurementContainer();
343
344 // 最大列数を取得
345 let maxColumnCount = 0;
346 columnsContainers.forEach((container) => {
347 const columnCount =
348 container.querySelectorAll('.wp-block-column').length;
349 if (columnCount > maxColumnCount) {
350 maxColumnCount = columnCount;
351 }
352 });
353
354 // 各列位置ごとに最大�
355 を計算(バッチ処理でパフォーマンス向上)
356 const columnMaxWidths = [];
357
358 for (let colIndex = 0; colIndex < maxColumnCount; colIndex++) {
359 // 同じ列位置にあるすべてのカラムを収集
360 const columns = [];
361 columnsContainers.forEach((container) => {
362 const containerColumns = Array.from(
363 container.querySelectorAll('.wp-block-column')
364 );
365 if (containerColumns[colIndex]) {
366 columns.push(containerColumns[colIndex]);
367 }
368 });
369
370 // バッチ処理で一度に測定
371 columnMaxWidths[colIndex] = measureColumnsWidth(
372 columns,
373 tempContainer
374 );
375 }
376
377 // 各列位置のカラムに同じ最小�
378 を設定
379 columnsContainers.forEach((container) => {
380 const columns = Array.from(
381 container.querySelectorAll('.wp-block-column')
382 );
383 columns.forEach((column, colIndex) => {
384 if (columnMaxWidths[colIndex] > 0) {
385 column.style.minWidth = `${columnMaxWidths[colIndex]}px`;
386 }
387 });
388 });
389
390 // 更新中フラグを解除
391 isUpdating = false;
392 }
393
394 /**
395 * 新しく追加されたグループブロックをIntersection Observerで監視
396 * @param {Node} node - チェック対象のノード
397 */
398 function observeNewGroup(node) {
399 // 追加されたノード自体がグループブロックか確認(data-table-mode="true"のもののみ)
400 if (
401 node.nodeType === Node.ELEMENT_NODE &&
402 node.classList &&
403 node.classList.contains('wp-block-group') &&
404 node.classList.contains('is-style-vk-group-scrollable') &&
405 node.getAttribute('data-table-mode') === 'true'
406 ) {
407 if (!intersectionObservedGroups.has(node)) {
408 intersectionObserver.observe(node);
409 intersectionObservedGroups.add(node);
410 }
411 }
412
413 if (
414 node.nodeType === Node.ELEMENT_NODE &&
415 node.classList &&
416 node.classList.contains('wp-block-group') &&
417 node.classList.contains('is-style-vk-group-scrollable')
418 ) {
419 applyNestedGridScrollableStyle(node);
420 }
421
422 // 追加されたノードの子要素にグループブロックが含まれているか確認
423 if (node.nodeType === Node.ELEMENT_NODE) {
424 const nestedScrollableGroups = node.querySelectorAll(
425 '.wp-block-group.is-style-vk-group-scrollable'
426 );
427 nestedScrollableGroups.forEach((group) => {
428 applyNestedGridScrollableStyle(group);
429 });
430
431 // data-table-mode="true"のもののみIntersection Observerに追加
432 const nestedGroups = node.querySelectorAll(
433 '.wp-block-group.is-style-vk-group-scrollable[data-table-mode="true"]'
434 );
435 nestedGroups.forEach((group) => {
436 if (!intersectionObservedGroups.has(group)) {
437 intersectionObserver.observe(group);
438 intersectionObservedGroups.add(group);
439 }
440 });
441 }
442 }
443
444 // DOM変更を監視して、新しく追加されたグループブロックを検出(軽量版)
445 // 追加されたノードのみをチェックして、パフォーマンスを最適化
446 const checkNewGroupsObserver = new MutationObserver((mutations) => {
447 mutations.forEach((mutation) => {
448 if (
449 mutation.type === 'childList' &&
450 mutation.addedNodes.length > 0
451 ) {
452 mutation.addedNodes.forEach((node) => {
453 observeNewGroup(node);
454 });
455 }
456 });
457 });
458
459 /**
460 * 親要素のgrid-template-columnsからminimumColumnWidthを取得
461 * @param {HTMLElement} group - グループブロック要素
462 * @return {{kind: string, value: string}|null} minimumColumnWidthの値
463 */
464 function getMinimumColumnWidth(group) {
465 const extractMinimumColumnWidth = (gridTemplateColumns) => {
466 if (!gridTemplateColumns) {
467 return null;
468 }
469 // grid-template-columns: repeat(3, minmax(0, 1fr))
470 const repeatMinmaxMatch = gridTemplateColumns.match(
471 /repeat\(\s*(\d+)\s*,\s*minmax\(\s*0(?:px)?\s*,\s*1fr\s*\)\s*\)/i
472 );
473 if (repeatMinmaxMatch && repeatMinmaxMatch[1]) {
474 return {
475 kind: 'repeat-fr',
476 value: repeatMinmaxMatch[1].trim(),
477 };
478 }
479 // grid-template-columns: repeat(3, 1fr)
480 const repeatFrMatch = gridTemplateColumns.match(
481 /repeat\(\s*(\d+)\s*,\s*1fr\s*\)/i
482 );
483 if (repeatFrMatch && repeatFrMatch[1]) {
484 return { kind: 'repeat-fr', value: repeatFrMatch[1].trim() };
485 }
486 // grid-template-columns: repeat(auto-fill, minmax(min(XXXpx, 100%), 1fr))
487 const minMatch = gridTemplateColumns.match(
488 /min\(([^,]+),\s*100%\)/i
489 );
490 if (minMatch && minMatch[1]) {
491 return { kind: 'min', value: minMatch[1].trim() };
492 }
493 // grid-template-columns: repeat(3, minmax(0, 1fr))
494 const minmaxMatch = gridTemplateColumns.match(
495 /minmax\(\s*([^,]+)\s*,\s*([^)]+)\)/i
496 );
497 if (minmaxMatch && minmaxMatch[1] && minmaxMatch[2]) {
498 const minValue = minmaxMatch[1].trim();
499 const maxValue = minmaxMatch[2].trim();
500 return {
501 kind: 'minmax',
502 value: `minmax(${minValue}, ${maxValue})`,
503 };
504 }
505 return null;
506 };
507 // wp-container-core-group-is-layout-* クラスを持つ要素を検索
508 // まずグループブロック自体をチェック
509 let containerElement = null;
510 let containerClassName = null;
511 const groupClasses = Array.from(group.classList);
512 const containerClass = groupClasses.find((cls) =>
513 cls.includes('wp-container-core-group-is-layout-')
514 );
515
516 if (containerClass) {
517 containerElement = group;
518 containerClassName = containerClass;
519 } else {
520 // グループブロックの子要素を検索
521 containerElement = group.querySelector(
522 '[class*="wp-container-core-group-is-layout-"]'
523 );
524 if (containerElement) {
525 const containerClasses = Array.from(containerElement.classList);
526 containerClassName = containerClasses.find((cls) =>
527 cls.includes('wp-container-core-group-is-layout-')
528 );
529 }
530 }
531
532 // スタイルシートから直接値を取得
533 if (containerClassName) {
534 // すべてのスタイルシートを検索
535 for (let i = 0; i < document.styleSheets.length; i++) {
536 try {
537 const styleSheet = document.styleSheets[i];
538 const rules = styleSheet.cssRules || styleSheet.rules;
539 if (!rules) {
540 continue;
541 }
542
543 for (let j = 0; j < rules.length; j++) {
544 const rule = rules[j];
545 if (
546 rule.selectorText &&
547 rule.selectorText.includes(containerClassName)
548 ) {
549 const gridTemplateColumns =
550 rule.style.gridTemplateColumns;
551
552 const extracted =
553 extractMinimumColumnWidth(gridTemplateColumns);
554 if (extracted) {
555 return extracted;
556 }
557 }
558 }
559 } catch (e) {
560 // クロスオリジンのスタイルシートなどでエラーが発生する可能性がある
561 // エラーは無視して次のスタイルシートをチェック
562 }
563 }
564 }
565
566 // デフォルト値(WordPressのデフォルトは12rem)
567 return { kind: 'min', value: '12rem' };
568 }
569
570 /**
571 * ブレークポイントに応じてグリッドレイアウトの横スクロールを適用するかどうかを判定
572 * @param {string} breakpoint ブレークポイント
573 * @return {boolean} グリッドレイアウトの横スクロールを適用するかどうか
574 */
575 function shouldApplyGridScrollableByBreakpoint(breakpoint) {
576 if (!breakpoint) {
577 return false;
578 }
579
580 const windowWidth = window.innerWidth;
581
582 // PCブレークポイントの場合:常に適用
583 if (breakpoint === 'group-scrollable-pc') {
584 return true;
585 }
586
587 // Tabletブレークポイントの場合:992px未満の場合のみ適用
588 if (breakpoint === 'group-scrollable-tablet') {
589 return windowWidth < TABLET_BREAKPOINT;
590 }
591
592 // Mobileブレークポイントの場合:576px未満の場合のみ適用
593 if (breakpoint === 'group-scrollable-mobile') {
594 return windowWidth < MOBILE_BREAKPOINT;
595 }
596
597 return false;
598 }
599
600 /**
601 * グリッドレイアウトの横スクロール設定を適用
602 * @param {HTMLElement} group グループブロック要素
603 * @param {string} breakpoint ブレークポイント
604 */
605 function applyGridScrollableStyleWithBreakpoint(group, breakpoint) {
606 if (!group.classList.contains('is-layout-grid')) {
607 return;
608 }
609
610 // ブレークポイントに応じて適用するかどうかを判定
611 if (!shouldApplyGridScrollableByBreakpoint(breakpoint)) {
612 // 適用しない場合は、設定を削除
613 group.style.gridTemplateColumns = '';
614 group.style.gridAutoColumns = '';
615 return;
616 }
617
618 const minimumColumnWidth = getMinimumColumnWidth(group);
619 if (minimumColumnWidth) {
620 // grid-template-columnsをnoneにして、grid-auto-columnsを設定
621 group.style.gridTemplateColumns = 'none';
622 if (minimumColumnWidth.kind === 'repeat-fr') {
623 group.style.gridAutoColumns = `calc(100% / ${minimumColumnWidth.value})`;
624 } else if (minimumColumnWidth.kind === 'minmax') {
625 group.style.gridAutoColumns = minimumColumnWidth.value;
626 } else {
627 group.style.gridAutoColumns = `minmax(min(${minimumColumnWidth.value}, 100%), max-content)`;
628 }
629 } else {
630 group.style.gridTemplateColumns = '';
631 group.style.gridAutoColumns = '';
632 }
633 }
634
635 function applyNestedGridScrollableStyle(group) {
636 if (
637 !group.classList.contains('wp-block-group') ||
638 !group.classList.contains('is-style-vk-group-scrollable')
639 ) {
640 return;
641 }
642 const breakpoint = group.getAttribute('data-scroll-breakpoint');
643 if (!breakpoint) {
644 return;
645 }
646 Array.from(group.children).forEach((child) => {
647 if (
648 child.classList &&
649 child.classList.contains('wp-block-group') &&
650 child.classList.contains('is-layout-grid')
651 ) {
652 applyGridScrollableStyleWithBreakpoint(child, breakpoint);
653 }
654 });
655 }
656
657 /**
658 * 初期化処理
659 */
660 function init() {
661 const scrollableGroups = document.querySelectorAll(
662 '.wp-block-group.is-style-vk-group-scrollable'
663 );
664 scrollableGroups.forEach((group) => {
665 applyNestedGridScrollableStyle(group);
666 });
667
668 // 既存のグループブロックをIntersection Observerで監視(表示領域に�
669 �った時だけ処理)
670 // data-table-mode="true"のもののみ処理することでパフォーマンスを向上
671 const existingGroups = document.querySelectorAll(
672 '.wp-block-group.is-style-vk-group-scrollable[data-table-mode="true"]'
673 );
674 existingGroups.forEach((group) => {
675 if (!intersectionObservedGroups.has(group)) {
676 intersectionObserver.observe(group);
677 intersectionObservedGroups.add(group);
678 }
679 });
680
681 // data-table-modeがtrueでないスクロール可能なグループブロックからmin-widthを削除
682 const nonTableModeGroups = document.querySelectorAll(
683 '.wp-block-group.is-style-vk-group-scrollable:not([data-table-mode="true"])'
684 );
685 nonTableModeGroups.forEach((group) => {
686 removeMinWidthFromColumns(group);
687 });
688
689 // document.bodyを監視して、新しく追加されたグループブロックを検出
690 checkNewGroupsObserver.observe(document.body, {
691 childList: true,
692 subtree: true,
693 });
694 }
695
696 // DOMContentLoaded時に初期化
697 if (document.readyState === 'loading') {
698 document.addEventListener('DOMContentLoaded', init);
699 } else {
700 init();
701 }
702
703 /**
704 * 要素が可視領域�
705 にあるかチェック
706 * @param {HTMLElement} element - チェック対象の要素
707 * @return {boolean} 可視領域�
708 にある場合true
709 */
710 function isElementVisible(element) {
711 const rect = element.getBoundingClientRect();
712 return (
713 rect.top < window.innerHeight &&
714 rect.bottom > 0 &&
715 rect.left < window.innerWidth &&
716 rect.right > 0
717 );
718 }
719
720 // リサイズ時にも再計算(可視領域�
721 のみ、パフォーマンス向上)
722 let resizeTimer;
723 window.addEventListener('resize', () => {
724 clearTimeout(resizeTimer);
725 resizeTimer = setTimeout(() => {
726 const scrollableGroups = document.querySelectorAll(
727 '.wp-block-group.is-style-vk-group-scrollable'
728 );
729 scrollableGroups.forEach((group) => {
730 if (isElementVisible(group)) {
731 applyNestedGridScrollableStyle(group);
732 }
733 });
734
735 // 可視領域�
736 のテーブルモードが有効なグループブロックのみを処理
737 const scrollableTableGroups = document.querySelectorAll(
738 '.wp-block-group.is-style-vk-group-scrollable[data-table-mode="true"]'
739 );
740 scrollableTableGroups.forEach((group) => {
741 if (isElementVisible(group)) {
742 requestAnimationFrame(() => {
743 // ブレークポイントに応じてテーブルモードを適用するかどうかを判定
744 if (shouldApplyTableMode(group)) {
745 processGroupBlock(group);
746 } else {
747 // テーブルモードを適用しない場合は、min-widthを削除
748 removeMinWidthFromColumns(group);
749 }
750 });
751 }
752 });
753
754 // data-table-modeがtrueでないスクロール可能なグループブロックからmin-widthを削除
755 const nonTableModeGroups = document.querySelectorAll(
756 '.wp-block-group.is-style-vk-group-scrollable:not([data-table-mode="true"])'
757 );
758 nonTableModeGroups.forEach((group) => {
759 if (isElementVisible(group)) {
760 removeMinWidthFromColumns(group);
761 }
762 });
763 }, 250);
764 });
765
766 // カスタムイベントもサポート(明示的に呼び出したい場合、同期的に処理)
767 window.addEventListener('vk-blocks-group-scrollable-update', () => {
768 equalizeColumnWidths();
769 });
770 })();
771