PageSourceSearch

https://elantzen.eus/wp-content/plugins/h5p/h5p-php-library/js/h5p-confirmation-dialog.js?ver=1.17.9

js elantzen.eus collected 2026-10-02 18:58:35 UTC 13,317 bytes, 416 lines download raw bytes

1/* eslint-disable no-param-reassign */
2H5P.ConfirmationDialog = (function (EventDispatcher) {
3  /**
4   * Create a confirmation dialog
5   *
6   * @param [options] Options for confirmation dialog
7   * @param [options.instance] Instance that uses confirmation dialog
8   * @param [options.headerText] Header text
9   * @param [options.dialogText] Dialog text
10   * @param [options.cancelText] Cancel dialog button text
11   * @param [options.confirmText] Confirm dialog button text
12   * @param [options.hideCancel] Hide cancel button
13   * @param [options.hideExit] Hide exit button
14   * @param [options.skipRestoreFocus] Skip restoring focus when hiding the dialog
15   * @param [options.classes] Extra classes for popup
16   * @param [options.theme] Whether to use the new theme (true) or the old design (false)
17   * @constructor
18   */
19  function ConfirmationDialog(options) {
20    EventDispatcher.call(this);
21    const self = this;
22
23    // Make sure confirmation dialogs have unique id
24    H5P.ConfirmationDialog.uniqueId += 1;
25    const { uniqueId } = H5P.ConfirmationDialog;
26
27    // Default options
28    options = options || {};
29    options.headerText = options.headerText || H5P.t('confirmDialogHeader');
30    options.dialogText = options.dialogText || H5P.t('confirmDialogBody');
31    options.cancelText = options.cancelText || H5P.t('cancelLabel');
32    options.closeText = options.closeText || H5P.t('close');
33    options.confirmText = options.confirmText || H5P.t('confirmLabel');
34
35    /**
36     * Handle confirming event
37     * @param {Event} e
38     */
39    function dialogConfirmed(e) {
40      self.hide();
41      self.trigger('confirmed');
42      e.preventDefault();
43    }
44
45    /**
46     * Handle dialog canceled
47     * @param {Event} e Event.
48     * @param {object} [options] Options.
49     * @param {boolean} [options.wasExplicitChoice] 
50     * True if user chose cancel explicitly, otherwise (close, esc) false.
51     */
52    function dialogCanceled(e, options = {}) {
53      self.hide();
54      self.trigger('canceled', { wasExplicitChoice: options.wasExplicitChoice ?? false });
55      e.preventDefault();
56    }
57
58    /**
59     * Handle tabbing through buttons with focus trapping.
60     * @param {KeyboardEvent} event Keyboard event.
61     */
62    function handleTabbing(event) {
63      if (event.key !== 'Tab') {
64        return;
65      }
66
67      event.preventDefault();
68
69      const currentIndex = focusableButtons.indexOf(event.target);
70      const offset = event.shiftKey ? -1 : 1;
71      const nextIndex = (currentIndex + offset + focusableButtons.length) % focusableButtons.length;
72
73      focusableButtons[nextIndex].focus();
74    }
75
76    // Offset of exit button
77    const exitButtonOffset = 2 * 16;
78    const shadowOffset = 8;
79
80    // Determine if we are too large for our container and must resize
81    let resizeIFrame = false;
82
83    // Create background
84    const popupBackground = document.createElement('div');
85    popupBackground.classList
86      .add('h5p-confirmation-dialog-background', 'hidden', 'hiding');
87
88    if (options.theme) {
89      popupBackground.classList.add('h5p-theme');
90    }
91
92    if (window.H5PEditor) {
93      popupBackground.classList.add('h5peditor');
94
95      if (H5PIntegration.theme?.density) {
96        popupBackground.classList.add(`h5p-${H5PIntegration.theme.density}`);
97      }
98    }
99
100    // Create outer popup
101    const popup = document.createElement('div');
102    popup.classList.add('h5p-confirmation-dialog-popup', 'hidden');
103    if (options.classes) {
104      options.classes.forEach((popupClass) => {
105        popup.classList.add(popupClass);
106      });
107    }
108
109    popup.setAttribute('role', 'alertdialog');
110    popup.setAttribute('aria-modal', 'true');
111    popup.setAttribute('aria-labelledby', `h5p-confirmation-dialog-header-text-${uniqueId}`);
112    popup.setAttribute('aria-describedby', `h5p-confirmation-dialog-text-${uniqueId}`);
113    popupBackground.appendChild(popup);
114    popup.addEventListener('keydown', (e) => {
115      if (e.key === 'Escape') { // Esc key
116        // Exit dialog
117        dialogCanceled(e);
118      }
119    });
120    popup.addEventListener('keydown', (event) => {
121      handleTabbing(event);
122    });
123
124    // Popup header
125    const header = document.createElement('div');
126    header.classList.add('h5p-confirmation-dialog-header');
127    popup.appendChild(header);
128
129    // Header text
130    const headerText = document.createElement('div');
131    headerText.classList.add('h5p-confirmation-dialog-header-text');
132    headerText.id = `h5p-confirmation-dialog-dialog-header-text-${uniqueId}`;
133    headerText.innerHTML = options.headerText;
134    header.appendChild(headerText);
135
136    // Popup body
137    const body = document.createElement('div');
138    body.classList.add('h5p-confirmation-dialog-body');
139    popup.appendChild(body);
140
141    // Popup text
142    const text = document.createElement('div');
143    text.classList.add('h5p-confirmation-dialog-text');
144    text.innerHTML = options.dialogText;
145    text.id = `h5p-confirmation-dialog-dialog-text-${uniqueId}`;
146    body.appendChild(text);
147
148    // Popup buttons
149    const buttons = document.createElement('div');
150    buttons.classList.add('h5p-confirmation-dialog-buttons');
151    body.appendChild(buttons);
152
153    // Cancel button
154    if (!options.hideCancel) {
155      const cancelButton = document.createElement('button');
156      if (!options.theme) {
157        cancelButton.classList.add('h5p-core-cancel-button');
158      }
159      else {
160        cancelButton.classList.add('h5p-theme-button', 'h5p-theme-secondary-cta');
161        cancelButton.classList.add('h5p-theme-cancel');
162      }
163      const cancelText = document.createElement('span');
164      cancelText.textContent = options.cancelText;
165      cancelButton.appendChild(cancelText);
166      cancelButton.addEventListener('click', (event) => {
167        dialogCanceled(event, { wasExplicitChoice: true });
168      });
169      buttons.appendChild(cancelButton);
170    }
171    else {
172      // Center remaining buttons
173      buttons.classList.add('center');
174    }
175
176    // Confirm button
177    const confirmButton = document.createElement('button');
178    if (!options.theme) {
179      confirmButton.classList.add('h5p-core-button');
180    }
181    // confirmButton.classList.add('h5p-confirmation-dialog-confirm-button');
182    confirmButton.setAttribute('aria-label', options.confirmText);
183
184    if (options.theme) {
185      confirmButton.classList.add('h5p-theme-button', 'h5p-theme-primary-cta');
186      confirmButton.classList.add('h5p-theme-check');
187    }
188
189    confirmButton.addEventListener('click', dialogConfirmed);
190    const confirmText = document.createElement('span');
191    confirmText.textContent = options.confirmText;
192    confirmButton.appendChild(confirmText);
193    buttons.appendChild(confirmButton);
194
195    let focusableButtons = [...buttons.childNodes];
196
197    // Exit button
198    if (!options.hideExit) {
199      const exitButton = document.createElement('button');
200      exitButton.classList.add('h5p-confirmation-dialog-exit');
201      exitButton.setAttribute('aria-label', options.closeText);
202      exitButton.addEventListener('click', dialogCanceled);
203      if (options.theme) {
204        header.appendChild(exitButton);
205      }
206      else {
207        popup.appendChild(exitButton);
208      }
209      focusableButtons.push(exitButton);
210    }
211
212    // Wrapper element
213    let wrapperElement;
214
215    // Maintains hidden state of elements
216    let wrapperSiblingsHidden = [];
217    let popupSiblingsHidden = [];
218
219    // Element with focus before dialog
220    let previouslyFocused;
221
222    /**
223     * Set parent of confirmation dialog
224     * @param {HTMLElement} wrapper
225     * @returns {H5P.ConfirmationDialog}
226     */
227    this.appendTo = function (wrapper) {
228      wrapperElement = wrapper;
229      return this;
230    };
231
232    /**
233     * Hide siblings of element from assistive technology
234     *
235     * @param {HTMLElement} element
236     * @returns {Array} The previous hidden state of all siblings
237     */
238    const hideSiblings = function (element) {
239      const hiddenSiblings = [];
240      const siblings = element.parentNode.children;
241      let i;
242      for (i = 0; i < siblings.length; i += 1) {
243        // Preserve hidden state
244        hiddenSiblings[i] = !!siblings[i].getAttribute('aria-hidden');
245
246        if (siblings[i] !== element) {
247          if (siblings[i].getAttribute('aria-live')) {
248            siblings[i].setAttribute('aria-busy', true);
249          }
250          else {
251            siblings[i].setAttribute('aria-hidden', true);
252          }
253        }
254      }
255      return hiddenSiblings;
256    };
257
258    /**
259     * Restores assistive technology state of element's siblings
260     *
261     * @param {HTMLElement} element
262     * @param {Array} hiddenSiblings Hidden state of all siblings
263     */
264    const restoreSiblings = function (element, hiddenSiblings) {
265      const siblings = element.parentNode.children;
266      let i;
267      for (i = 0; i < siblings.length; i += 1) {
268        if (siblings[i] !== element && !hiddenSiblings[i]) {
269          if (siblings[i].getAttribute('aria-live')) {
270            siblings[i].setAttribute('aria-busy', false);
271          }
272          else {
273            siblings[i].removeAttribute('aria-hidden');
274          }
275        }
276      }
277    };
278
279    /**
280     * Hide siblings in underlay from assistive technologies
281     */
282    const disableUnderlay = function () {
283      wrapperSiblingsHidden = hideSiblings(wrapperElement);
284      popupSiblingsHidden = hideSiblings(popupBackground);
285    };
286
287    /**
288     * Restore state of underlay for assistive technologies
289     */
290    const restoreUnderlay = function () {
291      restoreSiblings(wrapperElement, wrapperSiblingsHidden);
292      restoreSiblings(popupBackground, popupSiblingsHidden);
293    };
294
295    /**
296     * Fit popup to container. Makes sure it doesn't overflow.
297     * @params {number} [offsetTop] Offset of popup
298     */
299    const fitToContainer = function (offsetTop) {
300      let popupOffsetTop = parseInt(popup.style.top, 10);
301      if (offsetTop !== undefined) {
302        popupOffsetTop = offsetTop;
303      }
304
305      if (!popupOffsetTop) {
306        popupOffsetTop = 0;
307      }
308
309      // Overflows height
310      if (popupOffsetTop + popup.offsetHeight > wrapperElement.offsetHeight) {
311        popupOffsetTop = wrapperElement.offsetHeight - popup.offsetHeight - shadowOffset;
312      }
313
314      if (popupOffsetTop - exitButtonOffset <= 0) {
315        popupOffsetTop = exitButtonOffset + shadowOffset;
316
317        // We are too big and must resize
318        resizeIFrame = true;
319      }
320      popup.style.top = `${popupOffsetTop}px`;
321    };
322
323    /**
324     * Show confirmation dialog
325     * @params {number} offsetTop Offset top
326     * @returns {H5P.ConfirmationDialog}
327     */
328    this.show = function (offsetTop) {
329      // Capture focused item
330      previouslyFocused = document.activeElement;
331      wrapperElement.appendChild(popupBackground);
332      popupBackground.classList.remove('hidden');
333      fitToContainer(offsetTop);
334      popup.classList.remove('hidden');
335      popupBackground.addEventListener('transitionend', () => {
336        buttons.firstChild.focus();
337      }, { once: true });
338      popupBackground.classList.remove('hiding');
339      disableUnderlay();
340
341      // Resize iFrame if necessary
342      if (resizeIFrame && options.instance) {
343        const minHeight = parseInt(popup.offsetHeight, 10)
344          + exitButtonOffset + (2 * shadowOffset);
345        self.setViewPortMinimumHeight(minHeight);
346        options.instance.trigger('resize');
347        resizeIFrame = false;
348      }
349
350      // Detect if the user prefers reduced motion, because in that case
351      // we cannot rely on transitionend triggering and we need to manually
352      // focus the buttons. It should also be checked for each show, since a
353      // user may change this setting at any time.
354      const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
355      if (prefersReducedMotion.matches) {
356        buttons.firstChild.focus();
357      }
358
359      return this;
360    };
361
362    /**
363     * Hide confirmation dialog
364     * @returns {H5P.ConfirmationDialog}
365     */
366    this.hide = function () {
367      restoreUnderlay();
368      popupBackground.classList.add('hiding');
369      popup.classList.add('hidden');
370
371      // Restore focus
372      if (!options.skipRestoreFocus) {
373        previouslyFocused.focus();
374      }
375      popupBackground.classList.add('hidden');
376      wrapperElement.removeChild(popupBackground);
377      self.setViewPortMinimumHeight(null);
378
379      return this;
380    };
381
382    /**
383     * Retrieve element
384     *
385     * @return {HTMLElement}
386     */
387    this.getElement = function () {
388      return popup;
389    };
390
391    /**
392     * Get previously focused element
393     * @return {HTMLElement}
394     */
395    this.getPreviouslyFocused = function () {
396      return previouslyFocused;
397    };
398
399    /**
400     * Sets the minimum height of the view port
401     *
402     * @param {number|null} minHeight
403     */
404    this.setViewPortMinimumHeight = function (minHeight) {
405      const container = document.querySelector('.h5p-container') || document.body;
406      container.style.minHeight = (typeof minHeight === 'number') ? (`${minHeight}px`) : minHeight;
407    };
408  }
409
410  ConfirmationDialog.prototype = Object.create(EventDispatcher.prototype);
411  ConfirmationDialog.prototype.constructor = ConfirmationDialog;
412
413  return ConfirmationDialog;
414}(H5P.EventDispatcher));
415
416H5P.ConfirmationDialog.uniqueId = -1;

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.