PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / page-setup-wizard / step-start-end-times / _src / step-start-end-times.js

step-start-end-times.js in Booking Calendar 11.9, at includes/page-setup-wizard/step-start-end-times/_src/step-start-end-times.js

702 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Reusable sortable time-choice Setup Wizard editor.
3 *
4 * @package Booking Calendar
5 */
6
7 ( function ( window ) {
8 'use strict';
9
10 var module_config = window.wpbc_setup_wizard_start_end_times || { i18n: {} };
11
12 /**
13 * Create an isolated time-choice editor adapter.
14 *
15 * The server owns field IDs, list IDs, option allow-lists, and validation
16 * semantics. This browser adapter owns only interaction, ordering, focus,
17 * and serialization mechanics shared by Start/End and Start/Duration.
18 *
19 * @param {Object} config Module limits, identifiers, and translations.
20 * @return {Object} Step adapter accepted by the shared wizard shell.
21 */
22 function create_time_choices_adapter( config ) {
23 var shell_api = null;
24 var editor = null;
25
26 /**
27 * Return one translated string with a safe fallback.
28 *
29 * @param {string} key Translation key.
30 * @param {string} fallback Fallback text.
31 * @return {string} Translation or fallback.
32 */
33 function get_message( key, fallback ) {
34 return config.i18n && config.i18n[ key ] ? config.i18n[ key ] : fallback;
35 }
36
37 /**
38 * Convert a clock or duration `HH:MM` value to minutes.
39 *
40 * `24:00` is accepted for a full-day duration but cannot appear in the
41 * clock-time option list supplied by the server.
42 *
43 * @param {string} time_value Time or duration candidate.
44 * @return {number|null} Minute value, or null when invalid.
45 */
46 function time_to_minutes( time_value ) {
47 var normalized_value = String( time_value || '' );
48 var match = /^(?:(?:[01]\d|2[0-3]):[0-5]\d|24:00)$/.exec( normalized_value );
49
50 if ( ! match ) {
51 return null;
52 }
53
54 return parseInt( normalized_value.substring( 0, 2 ), 10 ) * 60 + parseInt( normalized_value.substring( 3, 5 ), 10 );
55 }
56
57 /**
58 * Convert a bounded minute value into `HH:MM`.
59 *
60 * @param {number} minute_value Minute value between zero and 1440.
61 * @return {string} Normalized value.
62 */
63 function minutes_to_time( minute_value ) {
64 var hours = Math.floor( minute_value / 60 );
65 var minutes = minute_value % 60;
66
67 return String( hours ).padStart( 2, '0' ) + ':' + String( minutes ).padStart( 2, '0' );
68 }
69
70 /**
71 * Return one registered list.
72 *
73 * @param {string} list_id Stable server-owned list identifier.
74 * @return {Object|null} List state or null.
75 */
76 function get_list( list_id ) {
77 return editor && editor.lists && editor.lists[ list_id ] ? editor.lists[ list_id ] : null;
78 }
79
80 /**
81 * Return the visible label for one list option.
82 *
83 * @param {Object} list List state.
84 * @param {string} option_value Stored option value.
85 * @return {string} Visible server-owned label.
86 */
87 function get_choice_label( list, option_value ) {
88 var select = list.template.content.querySelector( '[data-wpbc-time-choice]' );
89 var option = null;
90 var option_index;
91
92 if ( select ) {
93 for ( option_index = 0; option_index < select.options.length; option_index++ ) {
94 if ( select.options[ option_index ].value === option_value ) {
95 option = select.options[ option_index ];
96 break;
97 }
98 }
99 }
100
101 return option ? option.textContent : option_value;
102 }
103
104 /**
105 * Serialize every ordered list into the hidden wizard field.
106 *
107 * @return {void}
108 */
109 function sync_time_choices_draft() {
110 var serialized_lists = {};
111
112 if ( ! editor ) {
113 return;
114 }
115
116 editor.list_order.forEach( function ( list_id ) {
117 serialized_lists[ list_id ] = editor.lists[ list_id ].values.slice();
118 } );
119 editor.transport.value = JSON.stringify( serialized_lists );
120 }
121
122 /**
123 * Return the preferred validation focus target for a list item.
124 *
125 * @param {string} list_id Stable list identifier.
126 * @param {number} item_index Preferred item index.
127 * @return {HTMLElement|null} Select, Add button, or null.
128 */
129 function get_choice_control( list_id, item_index ) {
130 var list = get_list( list_id );
131 var item = list ? list.node.querySelector( '[data-time-index="' + item_index + '"]' ) : null;
132
133 return item ? item.querySelector( '[data-wpbc-time-choice]' ) : ( list ? list.add_button : null );
134 }
135
136 /**
137 * Focus one choice after rendering.
138 *
139 * @param {string} list_id Stable list identifier.
140 * @param {number} item_index Preferred item index.
141 * @return {void}
142 */
143 function focus_choice( list_id, item_index ) {
144 var target = get_choice_control( list_id, item_index );
145
146 if ( target ) {
147 target.focus();
148 }
149 }
150
151 /**
152 * Focus a move handle after reordering.
153 *
154 * @param {string} list_id Stable list identifier.
155 * @param {number} item_index New item index.
156 * @return {void}
157 */
158 function focus_move_control( list_id, item_index ) {
159 var list = get_list( list_id );
160 var item = list ? list.node.querySelector( '[data-time-index="' + item_index + '"]' ) : null;
161 var target = item ? item.querySelector( '[data-wpbc-move-time]' ) : null;
162
163 if ( target ) {
164 target.focus();
165 }
166 }
167
168 /**
169 * Show one field error and identify its first invalid control.
170 *
171 * @param {string} message Validation message.
172 * @param {string} list_id Stable list identifier.
173 * @param {number} item_index Preferred item index.
174 * @return {HTMLElement|null} Invalid control or null.
175 */
176 function set_choice_error( message, list_id, item_index ) {
177 var target;
178
179 shell_api.set_field_error( editor.field_id, message );
180 target = get_choice_control( list_id, item_index );
181 if ( target ) {
182 target.setAttribute( 'aria-invalid', 'true' );
183 }
184
185 return target;
186 }
187
188 /**
189 * Find the second occurrence of the first duplicate.
190 *
191 * @param {string[]} choices Ordered choices.
192 * @return {number} Duplicate index or -1.
193 */
194 function find_duplicate_index( choices ) {
195 var seen_choices = {};
196 var duplicate_index = -1;
197
198 choices.some( function ( choice, item_index ) {
199 if ( Object.prototype.hasOwnProperty.call( seen_choices, choice ) ) {
200 duplicate_index = item_index;
201 return true;
202 }
203 seen_choices[ choice ] = true;
204 return false;
205 } );
206
207 return duplicate_index;
208 }
209
210 /**
211 * Render one list from data-only state.
212 *
213 * @param {string} list_id Stable list identifier.
214 * @return {void}
215 */
216 function render_list( list_id ) {
217 var list = get_list( list_id );
218
219 if ( ! list || ! list.template ) {
220 return;
221 }
222
223 list.node.textContent = '';
224 list.add_button.removeAttribute( 'data-wpbc-setup-wizard-error-control-for' );
225 if ( ! list.values.length ) {
226 list.add_button.setAttribute( 'data-wpbc-setup-wizard-error-control-for', editor.field_id );
227 }
228
229 list.values.forEach( function ( choice_value, item_index ) {
230 var choice = list.template.content.firstElementChild.cloneNode( true );
231 var select = choice.querySelector( '[data-wpbc-time-choice]' );
232 var select_label = choice.querySelector( '[data-wpbc-time-choice-label]' );
233 var move_button = choice.querySelector( '[data-wpbc-move-time]' );
234 var remove_button = choice.querySelector( '[data-wpbc-remove-time]' );
235 var visible_choice = get_choice_label( list, choice_value );
236 var select_id = 'wpbc-' + editor.field_id.replace( /_/g, '-' ) + '-' + list_id.replace( /_/g, '-' ) + '-' + item_index;
237
238 choice.dataset.timeList = list_id;
239 choice.dataset.timeIndex = String( item_index );
240 select.id = select_id;
241 select.value = choice_value;
242 if ( 0 === item_index ) {
243 select.setAttribute( 'data-wpbc-setup-wizard-error-control-for', editor.field_id );
244 }
245 select_label.htmlFor = select_id;
246 select_label.textContent = list.label + ': ' + visible_choice;
247 move_button.setAttribute( 'aria-label', get_message( 'move_time_choice', 'Move %1$s in the %2$s list' ).replace( '%1$s', visible_choice ).replace( '%2$s', list.label ) );
248 remove_button.setAttribute( 'aria-label', get_message( 'remove_time_choice', 'Remove %1$s from the %2$s list' ).replace( '%1$s', visible_choice ).replace( '%2$s', list.label ) );
249 list.node.appendChild( choice );
250 } );
251
252 list.add_button.disabled = list.values.length >= editor.max_times;
253 list.clear_button.disabled = ! list.values.length;
254 sync_time_choices_draft();
255 }
256
257 /**
258 * Render every registered list.
259 *
260 * @return {void}
261 */
262 function render_editor() {
263 editor.list_order.forEach( render_list );
264 }
265
266 /**
267 * Generate an inclusive ordered range from one list's toolbar.
268 *
269 * @param {string} list_id Stable list identifier.
270 * @return {void}
271 */
272 function generate_choices( list_id ) {
273 var list = get_list( list_id );
274 var start_minutes;
275 var end_minutes;
276 var interval_minutes;
277 var generated_values = [];
278 var minute;
279
280 if ( ! list ) {
281 return;
282 }
283
284 start_minutes = time_to_minutes( list.generator_from.value );
285 end_minutes = time_to_minutes( list.generator_to.value );
286 interval_minutes = parseInt( list.generator_interval.value, 10 );
287 if ( null === start_minutes || null === end_minutes || end_minutes < start_minutes || ! isFinite( interval_minutes ) || interval_minutes < editor.time_increment || 0 !== interval_minutes % editor.time_increment ) {
288 list.generator_to.setAttribute( 'aria-invalid', 'true' );
289 shell_api.set_status( get_message( 'generator_invalid', 'The last generated choice must not be earlier than the first choice.' ) );
290 list.generator_to.focus();
291 return;
292 }
293
294 list.generator_to.removeAttribute( 'aria-invalid' );
295 for ( minute = start_minutes; minute <= end_minutes && generated_values.length < editor.max_times; minute += interval_minutes ) {
296 if ( -1 !== list.option_values.indexOf( minutes_to_time( minute ) ) ) {
297 generated_values.push( minutes_to_time( minute ) );
298 }
299 }
300 list.values = generated_values;
301 render_list( list_id );
302 focus_choice( list_id, 0 );
303 shell_api.clear_field_error( editor.field_id );
304 }
305
306 /**
307 * Find the next unused server-owned option.
308 *
309 * @param {Object} list List state.
310 * @return {string|null} Proposed value or null.
311 */
312 function find_choice_proposal( list ) {
313 var start_index = list.values.length ? list.option_values.indexOf( list.values[ list.values.length - 1 ] ) + 1 : 0;
314 var offset;
315 var candidate_value;
316
317 for ( offset = 0; offset < list.option_values.length; offset += 1 ) {
318 candidate_value = list.option_values[ ( start_index + offset ) % list.option_values.length ];
319 if ( -1 === list.values.indexOf( candidate_value ) ) {
320 return candidate_value;
321 }
322 }
323
324 return null;
325 }
326
327 /**
328 * Add one unused server-owned choice.
329 *
330 * @param {string} list_id Stable list identifier.
331 * @return {void}
332 */
333 function add_choice( list_id ) {
334 var list = get_list( list_id );
335 var proposal;
336
337 if ( ! list || list.values.length >= editor.max_times ) {
338 shell_api.set_status( get_message( 'time_limit', 'No additional choice can be added to this list.' ) );
339 return;
340 }
341
342 proposal = find_choice_proposal( list );
343 if ( ! proposal ) {
344 shell_api.set_status( get_message( 'time_limit', 'No additional choice can be added to this list.' ) );
345 return;
346 }
347
348 list.values.push( proposal );
349 render_list( list_id );
350 focus_choice( list_id, list.values.length - 1 );
351 shell_api.clear_field_error( editor.field_id );
352 }
353
354 /**
355 * Clear one list and keep focus within its action area.
356 *
357 * @param {string} list_id Stable list identifier.
358 * @return {void}
359 */
360 function clear_choices( list_id ) {
361 var list = get_list( list_id );
362
363 if ( ! list || ! list.values.length ) {
364 return;
365 }
366
367 list.values = [];
368 render_list( list_id );
369 list.add_button.focus();
370 shell_api.clear_field_error( editor.field_id );
371 shell_api.set_status( list.clear_status || get_message( list_id + '_cleared', 'Choices cleared.' ) );
372 }
373
374 /**
375 * Move one choice inside its list.
376 *
377 * @param {string} list_id Stable list identifier.
378 * @param {number} source_index Current index.
379 * @param {number} target_index Target index.
380 * @return {void}
381 */
382 function move_choice( list_id, source_index, target_index ) {
383 var list = get_list( list_id );
384 var moved_value;
385
386 if ( ! list || source_index === target_index || source_index < 0 || target_index < 0 || source_index >= list.values.length || target_index >= list.values.length ) {
387 return;
388 }
389
390 moved_value = list.values.splice( source_index, 1 )[0];
391 list.values.splice( target_index, 0, moved_value );
392 render_list( list_id );
393 focus_move_control( list_id, target_index );
394 shell_api.clear_field_error( editor.field_id );
395 }
396
397 /**
398 * Initialize SortableJS for one list without cross-list moves.
399 *
400 * @param {string} list_id Stable list identifier.
401 * @return {void}
402 */
403 function initialize_sortable_list( list_id ) {
404 var list = get_list( list_id );
405
406 if ( ! list || 'function' !== typeof window.Sortable ) {
407 return;
408 }
409
410 list.sortable = new window.Sortable( list.node, {
411 animation: 120,
412 draggable: '[data-time-index]',
413 handle: '[data-wpbc-move-time]',
414 ghostClass: 'is-drag-placeholder',
415 chosenClass: 'is-dragging',
416 dragClass: 'is-dragging',
417 onEnd: function ( event ) {
418 var source_index = Number( event.oldIndex );
419 var target_index = Number( event.newIndex );
420
421 if ( Number.isInteger( source_index ) && Number.isInteger( target_index ) ) {
422 move_choice( list_id, source_index, target_index );
423 } else {
424 render_list( list_id );
425 }
426 }
427 } );
428 }
429
430 /**
431 * Handle generator, add, clear, copy, and remove actions.
432 *
433 * @param {MouseEvent} event Delegated click event.
434 * @return {void}
435 */
436 function handle_click( event ) {
437 var copy_button = event.target.closest( '[data-wpbc-copy-times]' );
438 var generator = event.target.closest( '[data-wpbc-time-generator]' );
439 var list_panel = event.target.closest( '[data-wpbc-time-list-panel]' );
440 var choice = event.target.closest( '[data-time-index]' );
441
442 if ( copy_button ) {
443 var source_list = get_list( copy_button.dataset.sourceList );
444 var target_list = get_list( copy_button.dataset.targetList );
445
446 event.preventDefault();
447 if ( source_list && target_list ) {
448 target_list.values = source_list.values.filter( function ( choice_value ) {
449 return -1 !== target_list.option_values.indexOf( choice_value );
450 } );
451 render_list( target_list.id );
452 focus_choice( target_list.id, 0 );
453 shell_api.clear_field_error( editor.field_id );
454 shell_api.set_status( copy_button.dataset.status || '' );
455 }
456 return;
457 }
458
459 if ( generator && event.target.closest( '[data-wpbc-generate-times]' ) ) {
460 event.preventDefault();
461 generate_choices( generator.dataset.wpbcTimeGenerator );
462 return;
463 }
464
465 if ( list_panel && event.target.closest( '[data-wpbc-add-time]' ) ) {
466 event.preventDefault();
467 add_choice( list_panel.dataset.wpbcTimeListPanel );
468 return;
469 }
470
471 if ( list_panel && event.target.closest( '[data-wpbc-clear-times]' ) ) {
472 event.preventDefault();
473 clear_choices( list_panel.dataset.wpbcTimeListPanel );
474 return;
475 }
476
477 if ( choice && event.target.closest( '[data-wpbc-remove-time]' ) ) {
478 var list = get_list( choice.dataset.timeList );
479 var item_index = parseInt( choice.dataset.timeIndex, 10 );
480
481 event.preventDefault();
482 if ( list ) {
483 list.values.splice( item_index, 1 );
484 render_list( list.id );
485 focus_choice( list.id, Math.min( item_index, list.values.length - 1 ) );
486 shell_api.clear_field_error( editor.field_id );
487 }
488 }
489 }
490
491 /**
492 * Synchronize one changed choice.
493 *
494 * @param {Event} event Delegated change event.
495 * @return {void}
496 */
497 function handle_change( event ) {
498 var choice;
499 var list;
500 var item_index;
501
502 if ( ! event.target.matches( '[data-wpbc-time-choice]' ) ) {
503 return;
504 }
505
506 choice = event.target.closest( '[data-time-index]' );
507 list = choice ? get_list( choice.dataset.timeList ) : null;
508 item_index = choice ? parseInt( choice.dataset.timeIndex, 10 ) : -1;
509 if ( ! list || item_index < 0 ) {
510 return;
511 }
512
513 list.values[ item_index ] = event.target.value;
514 render_list( list.id );
515 focus_choice( list.id, item_index );
516 shell_api.clear_field_error( editor.field_id );
517 }
518
519 /**
520 * Provide keyboard reordering equivalent to drag and drop.
521 *
522 * @param {KeyboardEvent} event Delegated keydown event.
523 * @return {void}
524 */
525 function handle_keydown( event ) {
526 var move_button = event.target.closest( '[data-wpbc-move-time]' );
527 var choice;
528 var item_index;
529 var target_index;
530
531 if ( ! move_button || ! [ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown' ].includes( event.key ) ) {
532 return;
533 }
534
535 choice = move_button.closest( '[data-time-index]' );
536 item_index = parseInt( choice.dataset.timeIndex, 10 );
537 target_index = [ 'ArrowLeft', 'ArrowUp' ].includes( event.key ) ? item_index - 1 : item_index + 1;
538 event.preventDefault();
539 move_choice( choice.dataset.timeList, item_index, target_index );
540 }
541
542 /**
543 * Initialize one server-defined list from DOM and checkpoint values.
544 *
545 * @param {HTMLElement} list_node List container.
546 * @param {string[]} values Stored list values.
547 * @return {Object|null} List state or null.
548 */
549 function initialize_list( list_node, values ) {
550 var list_id = list_node.dataset.wpbcTimeChoiceList;
551 var panel = editor.node.querySelector( '[data-wpbc-time-list-panel="' + list_id + '"]' );
552 var generator = panel ? panel.querySelector( '[data-wpbc-time-generator]' ) : null;
553 var template = shell_api.root.querySelector( '[data-wpbc-time-choice-template="' + list_id + '"]' );
554 var template_select = template ? template.content.querySelector( '[data-wpbc-time-choice]' ) : null;
555
556 if ( ! panel || ! generator || ! template || ! template_select ) {
557 return null;
558 }
559
560 return {
561 id: list_id,
562 node: list_node,
563 template: template,
564 label: list_node.dataset.listLabel || list_id,
565 clear_status: list_node.dataset.clearStatus || '',
566 values: Array.isArray( values ) ? values.slice() : [],
567 option_values: Array.prototype.map.call( template_select.options, function ( option ) { return option.value; } ),
568 add_button: panel.querySelector( '[data-wpbc-add-time]' ),
569 clear_button: panel.querySelector( '[data-wpbc-clear-times]' ),
570 generator_from: generator.querySelector( '[data-wpbc-generator-from]' ),
571 generator_to: generator.querySelector( '[data-wpbc-generator-to]' ),
572 generator_interval: generator.querySelector( '[data-wpbc-generator-interval]' ),
573 sortable: null
574 };
575 }
576
577 /**
578 * Initialize this module against shell-provided services.
579 *
580 * @param {Object} registered_shell_api Shared wizard adapter services.
581 * @return {void}
582 */
583 function initialize( registered_shell_api ) {
584 var parsed_values;
585 var editor_node;
586 var transport;
587
588 shell_api = registered_shell_api;
589 editor_node = shell_api.root.querySelector( '[data-wpbc-time-choices-editor]' );
590 transport = shell_api.root.querySelector( '[data-wpbc-time-choices-draft]' );
591 if ( ! editor_node || ! transport ) {
592 return;
593 }
594
595 try {
596 parsed_values = JSON.parse( transport.value || '{}' );
597 } catch ( parse_error ) {
598 parsed_values = {};
599 }
600
601 editor = {
602 node: editor_node,
603 transport: transport,
604 field_id: editor_node.dataset.fieldId || config.field_id || 'start_end_times',
605 validation_rule: editor_node.dataset.validationRule || 'independent_lists',
606 max_times: parseInt( transport.dataset.maxTimes || String( config.max_times_per_list || 288 ), 10 ),
607 time_increment: parseInt( transport.dataset.timeIncrement || String( config.time_increment || 5 ), 10 ),
608 list_order: [],
609 lists: {}
610 };
611
612 editor.node.querySelectorAll( '[data-wpbc-time-choice-list]' ).forEach( function ( list_node ) {
613 var list_id = list_node.dataset.wpbcTimeChoiceList;
614 var list = initialize_list( list_node, parsed_values[ list_id ] );
615
616 if ( list ) {
617 editor.list_order.push( list_id );
618 editor.lists[ list_id ] = list;
619 }
620 } );
621
622 if ( ! editor.list_order.length ) {
623 editor = null;
624 return;
625 }
626
627 editor.node.addEventListener( 'click', handle_click );
628 editor.node.addEventListener( 'change', handle_change );
629 editor.node.addEventListener( 'keydown', handle_keydown );
630 render_editor();
631 editor.list_order.forEach( initialize_sortable_list );
632 }
633
634 /**
635 * Validate required, unique, allow-listed, and route-specific choices.
636 *
637 * @return {HTMLElement|null} First invalid control or null.
638 */
639 function validate() {
640 var first_invalid = null;
641
642 if ( ! editor ) {
643 return null;
644 }
645
646 editor.node.querySelectorAll( '[aria-invalid="true"]' ).forEach( function ( control ) {
647 control.removeAttribute( 'aria-invalid' );
648 } );
649
650 editor.list_order.some( function ( list_id ) {
651 var list = editor.lists[ list_id ];
652 var duplicate_index;
653
654 if ( ! list.values.length ) {
655 first_invalid = set_choice_error( get_message( list_id + '_required', 'Add at least one choice.' ), list_id, -1 );
656 return true;
657 }
658
659 if ( list.values.some( function ( choice_value ) { return -1 === list.option_values.indexOf( choice_value ); } ) ) {
660 first_invalid = set_choice_error( get_message( 'time_value_invalid', 'Choose a supported value for every choice.' ), list_id, 0 );
661 return true;
662 }
663
664 duplicate_index = find_duplicate_index( list.values );
665 if ( -1 !== duplicate_index ) {
666 first_invalid = set_choice_error( get_message( 'time_duplicate', 'The same choice cannot appear twice in one list.' ), list_id, duplicate_index );
667 return true;
668 }
669
670 return false;
671 } );
672
673 if ( ! first_invalid && 'end_after_start' === editor.validation_rule && editor.lists.start_times && editor.lists.end_times ) {
674 var start_minutes = editor.lists.start_times.values.map( time_to_minutes );
675 var end_minutes = editor.lists.end_times.values.map( time_to_minutes );
676
677 if ( start_minutes.some( function ( value ) { return null === value; } ) || end_minutes.some( function ( value ) { return null === value; } ) || Math.max.apply( null, end_minutes ) <= Math.min.apply( null, start_minutes ) ) {
678 first_invalid = set_choice_error( get_message( 'time_pair_invalid', 'Add an end time that is later than at least one start time.' ), 'end_times', 0 );
679 }
680 }
681
682 sync_time_choices_draft();
683 return first_invalid;
684 }
685
686 return {
687 initialize: initialize,
688 sync: sync_time_choices_draft,
689 validate: validate
690 };
691 }
692
693 window.wpbc_setup_editor_modules = window.wpbc_setup_editor_modules || {};
694 window.wpbc_setup_editor_modules[ module_config.module_id || 'start_end_times' ] = {
695 create: create_time_choices_adapter
696 };
697
698 if ( window.wpbc_setup_wizard_api && 'function' === typeof window.wpbc_setup_wizard_api.register_step_adapter ) {
699 window.wpbc_setup_wizard_api.register_step_adapter( create_time_choices_adapter( module_config ) );
700 }
701 }( window ) );
702