PageSourceSearch

https://www.tokyodome-hotels.co.jp/assets/common/scripts/modules/dialog.js

js tokyodome-hotels.co.jp collected 2026-09-24 10:46:28 UTC 8,300 bytes, 215 lines download raw bytes

1import { Browser } from '../utility/browser.js';
2import YoutubePlayer from './youtubePlayer.js';
3(() => {
4    const config = {
5        // selector
6        dialogInnerSelector: '.fn-dialog__inner',
7        dialogContentSelector: '.fn-dialog__content',
8        dialogContentInnerSelector: '.fn-dialog__contentInner',
9        closeButtonSelector: '.fn-dialog__closeButton',
10        dialogVideoIframeSelector: '.fn-dialogVideo',
11    };
12    // 各 dialog の状態管理
13    const players = new WeakMap();
14    const initPromises = new WeakMap();
15    // イベントバインド管理(イベントの重複登録を防ぐ)
16    const boundDialogs = new WeakSet();
17    // videoId 抽出
18    const extractVideoId = (src) => {
19        try {
20            const url = new URL(src);
21            const match = url.pathname.match(/\/embed\/([^/?#]+)/);
22            return match?.[1] ?? '';
23        }
24        catch {
25            // URLでなければ空文字を返す
26            return '';
27        }
28    };
29    const ensureOriginParam = (iframe) => {
30        const src = iframe.getAttribute('src');
31        if (!src)
32            return;
33        try {
34            const url = new URL(src);
35            // enablejsapi がなければ何もしない(念のため)
36            if (url.searchParams.get('enablejsapi') !== '1')
37                return;
38            // すでに origin があれば何もしない
39            if (url.searchParams.has('origin'))
40                return;
41            url.searchParams.set('origin', window.location.origin);
42            // 再ロードを発生させるため一度消してから再セット
43            iframe.removeAttribute('src');
44            iframe.setAttribute('src', url.toString());
45        }
46        catch {
47            // 無効な URL の場合は何もしない
48        }
49    };
50    // ダイアログのiframeからプレイヤーを初期化(必要な時だけ)
51    const ensurePlayer = async (dialog, iframe) => {
52        // すでにインスタンス済みならそれを返す
53        const existing = players.get(dialog);
54        if (existing)
55            return existing;
56        ensureOriginParam(iframe);
57        const src = iframe.getAttribute('src') ?? '';
58        const videoId = extractVideoId(src);
59        if (!videoId) {
60            return null;
61        }
62        if (!iframe.id) {
63            const dialogId = dialog.id || 'dialog';
64            iframe.id = `yt-${dialogId}`;
65        }
66        const player = new YoutubePlayer(iframe.id, videoId);
67        players.set(dialog, player);
68        // 複数回の並行初期化を防ぐため Promise を共有
69        const init = player.init();
70        initPromises.set(dialog, init);
71        try {
72            await init;
73        }
74        catch {
75            players.delete(dialog);
76            initPromises.delete(dialog);
77            return null;
78        }
79        return player;
80    };
81    const setDialog = (element) => {
82        // 対応するdialogのidを取得
83        const dialogId = element.dataset.dialogTarget;
84        if (!dialogId || dialogId === '')
85            return;
86        const dialog = document.querySelector(`#${dialogId}`);
87        const dialogInner = dialog?.querySelector(config.dialogInnerSelector);
88        const dialogContent = dialog?.querySelector(config.dialogContentSelector);
89        const dialogContentInner = dialog?.querySelector(config.dialogContentInnerSelector);
90        const closeButtons = dialog?.querySelectorAll(config.closeButtonSelector);
91        const videoIframe = dialog?.querySelector(config.dialogVideoIframeSelector);
92        if (!dialog || !closeButtons?.length)
93            return;
94        /** スクロールが必要な場合にのみ、フォーカス移動用tabindexを付与 */
95        const setTabindex = () => {
96            if (!dialogContent || !dialogContentInner)
97                return;
98            if (dialogContentInner.scrollHeight > dialogContent.clientHeight) {
99                dialogContent.setAttribute('tabindex', '0');
100            }
101            else {
102                dialogContent.removeAttribute('tabindex');
103            }
104        };
105        /** ダイアログを開く処理 */
106        const showDialog = async () => {
107            if (dialog.open)
108                return;
109            dialog.setAttribute('tabindex', '-1');
110            if (dialog.classList.contains('js-tc-widget')) {
111                dialog.show();
112                document.documentElement.style.overflow = 'hidden';
113            }
114            else {
115                dialog.showModal();
116            }
117            dialog.focus({ preventScroll: true });
118            requestAnimationFrame(() => {
119                setTabindex();
120            });
121            // YouTube 再生制御
122            if (videoIframe) {
123                // 初期化 Promise 取得
124                let initP = initPromises.get(dialog);
125                if (!initP) {
126                    initP = (async () => {
127                        await ensurePlayer(dialog, videoIframe);
128                    })();
129                    initPromises.set(dialog, initP);
130                }
131                // 初期化完了を待つ
132                await initP;
133                // 初期化後にプレイヤーを取得して操作
134                const player = players.get(dialog);
135                if (player) {
136                    player.seekTo(0, true);
137                    // Safari SP(iOS)は完全に再生しない(YouTube 側仕様の既知バグ対応:2回目から勝手にautoplayを試行ã
137®ãŸã‚ï¼‰
138                    if (Browser.isIOS()) {
139                        player.stop();
140                        return;
141                    }
142                    // Safari PC は autoplay ブロック回避のため setTimeout
143                    if (Browser.isSafariPC()) {
144                        setTimeout(() => player.play(), 0);
145                        return;
146                    }
147                    // Chrome、他ブラウザ
148                    player.play();
149                }
150            }
151        };
152        /** ダイアログを閉じる処理 */
153        const hideDialog = () => {
154            if (!dialog.open)
155                return;
156            if (dialog.classList.contains('js-tc-widget')) {
157                dialog.close();
158                document.documentElement.style.overflow = '';
159            }
160            else {
161                dialog.close();
162            }
163            // YouTube 停止
164            const player = players.get(dialog);
165            if (player) {
166                player.stop();
167                player.seekTo(0, true);
168            }
169        };
170        /** イベント設定 */
171        const bindEvents = () => {
172            element.addEventListener('click', async (event) => {
173                event.preventDefault();
174                // 先に開く(UIをブロックしない)
175                await showDialog();
176            });
177            // ダイアログ自体のイベントは一度のみ
178            if (boundDialogs.has(dialog))
179                return;
180            // 背景クリックで閉じる
181            dialogInner?.addEventListener('click', (event) => {
182                if (event.target === dialogInner) {
183                    hideDialog();
184                }
185            });
186            // 閉じるボタンで閉じる
187            closeButtons.forEach((closeButton) => {
188                closeButton.addEventListener('click', hideDialog);
189            });
190            // キーダウンイベントを登録
191            dialog.addEventListener('keydown', (event) => {
192                if (event.key === 'Escape') {
193                    event.preventDefault();
194                    hideDialog();
195                }
196            });
197            // Esc など close 時も確実に停止
198            dialog.addEventListener('close', () => {
199                const player = players.get(dialog);
200                if (player) {
201                    player.stop();
202                    initPromises.delete(dialog);
203                }
204            });
205            boundDialogs.add(dialog);
206        };
207        bindEvents();
208    };
209    const elements = document.querySelectorAll('.js-dialogButton');
210    if (elements.length === 0)
211        return;
212    elements.forEach((element) => {
213        setDialog(element);
214    });
215})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.