PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / js / wp2fa-dialog.js
wp-2fa / js Last commit date
passkeys 1 day ago profile 1 day ago wp2fa-dialog.js 1 day ago wp2fa-premium-badge-dialog.js 1 day ago wp2fa-wizard-backup-codes.js 1 day ago wp2fa-wizard-email.js 1 day ago wp2fa-wizard-totp.js 1 day ago wp2fa-wizard.js 1 day ago
wp2fa-dialog.js
289 lines
1 /**
2 * WP 2FA – Unified Dialog Utility
3 *
4 * Provides a simple, vanilla JS modal dialog system used across the plugin
5 * (admin and frontend). Replaces legacy jQuery UI .dialog() calls with
6 * accessible, keyboard-friendly, consistently styled modals.
7 *
8 * Usage:
9 * wp2faDialog.alert({ title: '...', message: '...' });
10 * wp2faDialog.confirm({ title: '...', message: '...', onConfirm: fn });
11 * wp2faDialog.open({ title: '...', content: '...', buttons: [...] });
12 * wp2faDialog.close();
13 *
14 * @package wp-2fa
15 * @since 4.0.0
16 */
17
18 ( function () {
19 'use strict';
20
21 var OVERLAY_ID = 'wp2fa-dialog-overlay';
22 var activeOverlay = null;
23 var escHandler = null;
24
25 /* ──────────────────────────────────────────────
26 * Internal: build and show a dialog
27 * ────────────────────────────────────────────── */
28
29 /**
30 * @param {Object} options
31 * @param {string} options.title - Dialog title text (plain text, escaped via textContent).
32 * @param {string} [options.htmlTitle] - Dialog title with HTML markup (uses innerHTML; caller must sanitize).
33 * @param {string} [options.content] - HTML content for the body.
34 * @param {string} [options.message] - Plain-text or HTML message (alias for content).
35 * @param {Array} [options.buttons] - Array of button descriptors.
36 * @param {string} [options.note] - Optional footer note text.
37 * @param {Function} [options.onClose] - Called when the dialog is closed.
38 * @param {string} [options.size] - 'small' (400px) | 'medium' (default, 520px) | 'large' (640px).
39 * @param {string} [options.overlayClass] - Extra class name for overlay element.
40 * @param {string} [options.modalClass] - Extra class name for modal element.
41 */
42 function openDialog( options ) {
43 // Close any existing dialog first.
44 closeDialog();
45
46 var opts = options || {};
47 var bodyHtml = opts.content || opts.message || '';
48 var buttons = opts.buttons || [];
49 var size = opts.size || 'medium';
50
51 // Overlay.
52 var overlay = document.createElement( 'div' );
53 overlay.id = OVERLAY_ID;
54 overlay.className = 'wp2fa-dialog-overlay wp2fa-dialog-overlay--' + size;
55 if ( opts.overlayClass ) {
56 overlay.className += ' ' + opts.overlayClass;
57 }
58 overlay.setAttribute( 'role', 'dialog' );
59 overlay.setAttribute( 'aria-modal', 'true' );
60 var titleText = opts.htmlTitle || opts.title;
61 if ( titleText ) {
62 overlay.setAttribute( 'aria-labelledby', 'wp2fa-dialog-title' );
63 }
64
65 // Modal box.
66 var modal = document.createElement( 'div' );
67 modal.className = 'wp2fa-dialog-modal';
68 if ( opts.modalClass ) {
69 modal.className += ' ' + opts.modalClass;
70 }
71
72 // Header (title + close button).
73 if ( titleText ) {
74 var header = document.createElement( 'div' );
75 header.className = 'wp2fa-dialog-header';
76
77 var title = document.createElement( 'h2' );
78 title.id = 'wp2fa-dialog-title';
79 title.className = 'wp2fa-dialog-title';
80 if ( opts.htmlTitle ) {
81 title.innerHTML = opts.htmlTitle;
82 } else {
83 title.textContent = opts.title;
84 }
85 header.appendChild( title );
86
87 var closeBtn = document.createElement( 'button' );
88 closeBtn.type = 'button';
89 closeBtn.className = 'wp2fa-dialog-close';
90 closeBtn.setAttribute( 'aria-label', 'Close' );
91 closeBtn.innerHTML = '×';
92 closeBtn.addEventListener( 'click', function () {
93 closeDialog();
94 if ( typeof opts.onClose === 'function' ) {
95 opts.onClose();
96 }
97 } );
98 header.appendChild( closeBtn );
99
100 modal.appendChild( header );
101 }
102
103 // Body.
104 var body = document.createElement( 'div' );
105 body.className = 'wp2fa-dialog-body';
106 body.innerHTML = bodyHtml;
107 modal.appendChild( body );
108
109 // Buttons.
110 if ( buttons.length > 0 ) {
111 var btnWrap = document.createElement( 'div' );
112 btnWrap.className = 'wp2fa-dialog-buttons';
113
114 buttons.forEach( function ( btnDef ) {
115 var btn = null;
116
117 if ( 'link' === btnDef.element ) {
118 btn = document.createElement( 'a' );
119 btn.href = '#';
120 btn.className = btnDef.className || '';
121 btn.textContent = btnDef.text || 'OK';
122 } else {
123 btn = document.createElement( 'button' );
124 btn.type = 'button';
125 btn.className = 'button ' + ( btnDef.className || 'button-secondary' );
126 btn.textContent = btnDef.text || 'OK';
127 }
128
129 btn.addEventListener( 'click', function ( e ) {
130 if ( 'link' === btnDef.element ) {
131 e.preventDefault();
132 }
133
134 if ( typeof btnDef.onClick === 'function' ) {
135 btnDef.onClick();
136 }
137 closeDialog();
138 } );
139 btnWrap.appendChild( btn );
140 } );
141
142 modal.appendChild( btnWrap );
143 }
144
145 // Footer note.
146 if ( opts.note ) {
147 var note = document.createElement( 'p' );
148 note.className = 'wp2fa-dialog-note';
149 note.textContent = opts.note;
150 modal.appendChild( note );
151 }
152
153 // Close on overlay click.
154 overlay.addEventListener( 'click', function ( e ) {
155 if ( e.target === overlay ) {
156 closeDialog();
157 if ( typeof opts.onClose === 'function' ) {
158 opts.onClose();
159 }
160 }
161 } );
162
163 // Close on Escape.
164 escHandler = function ( e ) {
165 if ( e.key === 'Escape' || e.keyCode === 27 ) {
166 closeDialog();
167 if ( typeof opts.onClose === 'function' ) {
168 opts.onClose();
169 }
170 }
171 };
172 document.addEventListener( 'keydown', escHandler );
173
174 overlay.appendChild( modal );
175 document.body.appendChild( overlay );
176 activeOverlay = overlay;
177
178 // Focus the first button or the close button.
179 var focusTarget = modal.querySelector( '.wp2fa-dialog-buttons button, .wp2fa-dialog-buttons a' ) || modal.querySelector( '.wp2fa-dialog-close' );
180 if ( focusTarget ) {
181 focusTarget.focus();
182 }
183 }
184
185 /**
186 * Close the currently open dialog.
187 */
188 function closeDialog() {
189 if ( activeOverlay ) {
190 activeOverlay.remove();
191 activeOverlay = null;
192 }
193 if ( escHandler ) {
194 document.removeEventListener( 'keydown', escHandler );
195 escHandler = null;
196 }
197 }
198
199 /* ──────────────────────────────────────────────
200 * Public API
201 * ────────────────────────────────────────────── */
202
203 window.wp2faDialog = {
204
205 /**
206 * Show a simple alert dialog (title, message, OK button).
207 *
208 * @param {Object} options
209 * @param {string} options.title - Dialog title.
210 * @param {string} options.message - Body message (HTML allowed).
211 * @param {string} [options.buttonText] - OK button label (default: 'OK').
212 * @param {Function} [options.onClose] - Callback when closed.
213 * @param {string} [options.size] - 'small' | 'medium' | 'large'.
214 */
215 alert: function ( options ) {
216 var opts = options || {};
217 openDialog( {
218 title: opts.title || '',
219 content: opts.message || '',
220 size: opts.size || 'small',
221 onClose: opts.onClose || null,
222 buttons: [
223 {
224 text: opts.buttonText || 'OK',
225 className: 'button-primary',
226 onClick: opts.onClose || null
227 }
228 ]
229 } );
230 },
231
232 /**
233 * Show a confirmation dialog with confirm and cancel buttons.
234 *
235 * @param {Object} options
236 * @param {string} options.title - Dialog title.
237 * @param {string} options.message - Body message (HTML allowed).
238 * @param {string} [options.confirmText] - Confirm button label (default: 'Confirm').
239 * @param {string} [options.cancelText] - Cancel button label (default: 'Cancel').
240 * @param {string} [options.confirmClass]- CSS class for confirm button (default: 'button-primary').
241 * @param {Function} [options.onConfirm] - Called on confirm.
242 * @param {Function} [options.onCancel] - Called on cancel/close.
243 * @param {string} [options.note] - Optional footer note.
244 * @param {string} [options.size] - 'small' | 'medium' | 'large'.
245 */
246 confirm: function ( options ) {
247 var opts = options || {};
248 openDialog( {
249 title: opts.title || '',
250 content: opts.message || '',
251 size: opts.size || 'medium',
252 note: opts.note || '',
253 onClose: opts.onCancel || null,
254 buttons: [
255 {
256 text: opts.confirmText || 'Confirm',
257 className: opts.confirmClass || 'button-primary',
258 onClick: opts.onConfirm || null
259 },
260 {
261 text: opts.cancelText || 'Cancel',
262 className: 'button-secondary',
263 onClick: opts.onCancel || null
264 }
265 ]
266 } );
267 },
268
269 /**
270 * Show a fully custom dialog.
271 *
272 * @param {Object} options
273 * @param {string} options.title - Dialog title.
274 * @param {string} options.content - Body HTML.
275 * @param {Array} options.buttons - Array of { text, className, onClick }.
276 * @param {string} [options.note] - Optional footer note.
277 * @param {Function} [options.onClose]- Called when dialog is closed.
278 * @param {string} [options.size] - 'small' | 'medium' | 'large'.
279 */
280 open: openDialog,
281
282 /**
283 * Programmatically close the active dialog.
284 */
285 close: closeDialog
286 };
287
288 } )();
289