PluginProbe
Booking Calendar / 11.2
Booking Calendar v11.2
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-form-builder / field-packs / time-range / _src / field-rangetime-wptpl.js

field-rangetime-wptpl.js in Booking Calendar 11.2, at includes/page-form-builder/field-packs/time-range/_src/field-rangetime-wptpl.js

729 lines 31.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // WPBC BFB Pack: Range Time (time-slots)
2 // Uses shared helpers from WPBC_BFB_Core.Time
3 // Version 1.7.2 — add Booking Form + Booking Data exporters for "rangetime"
4 // File: ../includes/page-form-builder/field-packs/time-range/_out/field-rangetime-wptpl.js
5 // 31.10.2025 13:54
6
7 (function (w, d) {
8 'use strict';
9
10 var Core = w.WPBC_BFB_Core || {};
11 var Registry = Core.WPBC_BFB_Field_Renderer_Registry;
12 var Base = Core.WPBC_BFB_Field_Base || null;
13 var Time = (Core && Core.Time) ? Core.Time : null;
14
15 if ( !Registry || typeof Registry.register !== 'function' || !Base || !Time ) {
16 if (w._wpbc && w._wpbc.dev && w._wpbc.dev.error) {
17 w._wpbc.dev.error('wpbc_bfb_field_rangetime', 'Missing Core registry/base/time-utils');
18 }
19 return;
20 }
21
22 // ---------------------------------------------------------------------
23 // Small helpers local to this pack
24 // ---------------------------------------------------------------------
25 function emit_inspector_change(el){
26 if (!el) return;
27 if (el.__wpbc_emitting) return;
28 el.__wpbc_emitting = true;
29 try {
30 if (w.jQuery) { w.jQuery(el).trigger('input').trigger('change'); }
31 el.dispatchEvent(new Event('input', {bubbles:true}));
32 el.dispatchEvent(new Event('change', {bubbles:true}));
33 } finally {
34 el.__wpbc_emitting = false;
35 }
36 Time.schedule_init_timeselector();
37 }
38
39 function get_current_format(panel){
40 var fmt = 'ampm';
41 panel.querySelectorAll('.js-rt-label-fmt').forEach(function (r) {
42 if (r.checked) fmt = (r.value === '24h') ? '24h' : 'ampm';
43 });
44 return fmt;
45 }
46
47 function update_gen_visibility(panel){
48 var fmt = get_current_format(panel);
49 var g24 = panel.querySelector('.js-rt-24h');
50 var gam = panel.querySelector('.js-rt-ampm');
51 if (g24) { g24.style.display = (fmt === '24h') ? '' : 'none'; g24.hidden = (fmt !== '24h'); }
52 if (gam) { gam.style.display = (fmt === 'ampm') ? '' : 'none'; gam.hidden = (fmt !== 'ampm'); }
53 }
54
55 function enforce_locked_name(panel){
56 var hidden = panel && panel.querySelector('.js-locked-name[data-inspector-key="name"]');
57 if (!hidden) return;
58 hidden.value = (hidden.value && hidden.value.trim() !== '') ? hidden.value : 'rangetime';
59 emit_inspector_change(hidden);
60 }
61
62 // ---------------------------------------------------------------------
63 // Canvas observer (to reinvoke external timeselector and sync inspectors)
64 // ---------------------------------------------------------------------
65 (function attachCanvasObserver(){
66 var root = d.body, mo;
67
68 function runSync(){
69 try { w.__wpbc_rt_mo_pause && w.__wpbc_rt_mo_pause(); } catch(e){}
70 d.querySelectorAll('.wpbc_bfb__inspector_rangetime').forEach(function(panel){
71 try { wpbc_bfb_field_rangetime.sync_state_from_rows(panel); } catch(e){}
72 });
73 try { w.__wpbc_rt_mo_resume && w.__wpbc_rt_mo_resume(); } catch(e){}
74 }
75
76 function handle(muts){
77 var found = false;
78 for (var i=0;i<muts.length && !found;i++){
79 var m = muts[i];
80 // select itself modified
81 if (m.type === 'childList' && m.target && m.target.matches && m.target.matches('.wpbc_bfb__preview-rangetime')) found = true;
82 // added nodes?
83 for (var j=0;j<m.addedNodes.length && !found;j++){
84 var n = m.addedNodes[j];
85 if (n.nodeType !== 1) continue;
86 if ( n.matches && (n.matches('.wpbc_bfb__preview-rangetime') || n.matches('.wpbc_bfb__preview-timepicker')) ) { found = true; }
87 else if ( n.querySelector && (n.querySelector('.wpbc_bfb__preview-rangetime') || n.querySelector('.wpbc_bfb__preview-timepicker')) ) { found = true; }
88 }
89 // attribute changes on select
90 if (!found && m.type === 'attributes' && m.target && m.target.matches && m.target.matches('.wpbc_bfb__preview-rangetime')) found = true;
91 }
92 if (found) Time.schedule_init_timeselector();
93 if (found) runSync();
94 }
95
96 try {
97 mo = new MutationObserver(handle);
98 mo.observe(root, {
99 childList:true, subtree:true, attributes:true, attributeFilter:['value','class']
100 });
101 } catch(e) {}
102
103 w.__wpbc_rt_mo_pause = function(){ try { mo && mo.disconnect(); } catch(e){} };
104 w.__wpbc_rt_mo_resume = function(){ try {
105 mo && mo.observe(root, { childList:true, subtree:true, attributes:true, attributeFilter:['value','class'] });
106 } catch(e){} };
107 })();
108
109 // ---------------------------------------------------------------------
110 // Renderer
111 // ---------------------------------------------------------------------
112 const wpbc_bfb_field_rangetime = class extends Base {
113 static template_id = 'wpbc-bfb-field-rangetime';
114 static kind = 'rangetime';
115
116 static get_defaults(){
117 return {
118 kind : 'field',
119 type : 'rangetime',
120 label : 'Time',
121 name : 'rangetime',
122 html_id : '',
123 required : true,
124 multiple : false,
125 size : null,
126 cssclass : '',
127 help : '',
128 default_value : '',
129 placeholder : '--- Select time ---',
130 value_differs : true,
131 min_width : '240px',
132 options : [
133 { label: '10:00 AM - 12:00 PM', value: '10:00 - 12:00', selected: false },
134 { label: '12:00 PM - 02:00 PM', value: '12:00 - 14:00', selected: false },
135 { label: '02:00 PM - 04:00 PM', value: '14:00 - 16:00', selected: false },
136 { label: '04:00 PM - 06:00 PM', value: '16:00 - 18:00', selected: false },
137 { label: '06:00 PM - 08:00 PM', value: '18:00 - 20:00', selected: false }
138 ],
139 gen_label_fmt : 'ampm',
140 gen_start_24h : '09:00',
141 gen_end_24h : '18:00',
142 gen_start_ampm_t: '09:00',
143 gen_end_ampm_t : '18:00',
144 gen_step_h : 0,
145 gen_step_m : 30
146 };
147 }
148
149 /**
150 * Executed after the field is dropped from the palette.
151 * Locks canonical name and disables future auto-naming.
152 *
153 * @param {Object} data
154 * @param {HTMLElement} el
155 * @param {Object} ctx
156 */
157 static on_field_drop(data, el, ctx){
158 if (super.on_field_drop) super.on_field_drop(data, el, ctx);
159 try {
160 // Keep data model canonical
161 if (data && typeof data === 'object') {
162 data.name = 'rangetime';
163 data.required = true;
164 }
165 // Lock dataset flags on the dropped element
166 if (el && el.dataset) {
167 el.dataset.name = 'rangetime';
168 el.dataset.autoname = '0';
169 el.dataset.fresh = '0';
170 el.dataset.name_user_touched = '1';
171 el.setAttribute('data-required','true');
172 }
173 // Sync the preview control (<select>) immediately
174 var sel = el && el.querySelector('select.wpbc_bfb__preview-rangetime');
175 if (sel) sel.setAttribute('name','rangetime');
176 // If Inspector is present, update the hidden "name" proxy and notify
177 var hidden = d.querySelector('.wpbc_bfb__inspector_rangetime .js-locked-name[data-inspector-key="name"]');
178 if (hidden) { hidden.value = 'rangetime'; hidden.setAttribute('data-locked','1'); if (typeof emit_inspector_change==='function') emit_inspector_change(hidden); }
179 } catch(e){}
180 }
181
182 // Bind once for all inspector panels (events).
183 static bind_inspector_events_once(){
184 if (this._bound_once) return;
185 this._bound_once = true;
186
187 // Persist placeholder when toggling global picker
188 d.addEventListener( 'change', function (ev) {
189 var tgl = ev.target && ev.target.closest( '.wpbc_bfb__inspector_rangetime .js-toggle-timeslot-picker' );
190 if ( !tgl ) return;
191 var panel = tgl.closest( '.wpbc_bfb__inspector_rangetime' );
192 var ph = panel && panel.querySelector( '.js-placeholder' );
193 if ( ph ) emit_inspector_change( ph );
194 } );
195
196 // AM/PM <-> 24h switch
197 d.addEventListener('change', function(ev){
198 var radio = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .js-rt-label-fmt');
199 if (!radio) return;
200
201 var panel = radio.closest('.wpbc_bfb__inspector_rangetime');
202 if (!panel) return;
203
204 var fmt = get_current_format(panel); // 'ampm' | '24h'
205 var proxy = panel.querySelector('.js-rt-label-fmt-value');
206 if (proxy){ proxy.value = fmt; emit_inspector_change(proxy); }
207
208 update_gen_visibility(panel);
209
210 // Sync generator values across groups.
211 if (fmt === '24h'){
212 var s_t = (panel.querySelector('.js-gen-start-ampm-time')||{}).value || '';
213 var e_t = (panel.querySelector('.js-gen-end-ampm-time')||{}).value || '';
214 var s_m = Time.parse_hhmm_24h(s_t), e_m = Time.parse_hhmm_24h(e_t);
215 var s24 = isNaN(s_m) ? '' : Time.format_minutes_24h(s_m);
216 var e24 = isNaN(e_m) ? '' : Time.format_minutes_24h(e_m);
217 var s24el = panel.querySelector('.js-gen-start-24h');
218 var e24el = panel.querySelector('.js-gen-end-24h');
219 if (s24el){ s24el.value = s24; emit_inspector_change(s24el); }
220 if (e24el){ e24el.value = e24; emit_inspector_change(e24el); }
221 } else {
222 var s24t = (panel.querySelector('.js-gen-start-24h')||{}).value || '';
223 var e24t = (panel.querySelector('.js-gen-end-24h')||{}).value || '';
224 var s_m2 = Time.parse_hhmm_24h(s24t), e_m2 = Time.parse_hhmm_24h(e24t);
225 var sam = isNaN(s_m2) ? '' : Time.format_minutes_24h(s_m2);
226 var eam = isNaN(e_m2) ? '' : Time.format_minutes_24h(e_m2);
227 var st_el = panel.querySelector('.js-gen-start-ampm-time');
228 var et_el = panel.querySelector('.js-gen-end-ampm-time');
229 if (st_el){ st_el.value = sam; emit_inspector_change(st_el); }
230 if (et_el){ et_el.value = eam; emit_inspector_change(et_el); }
231 }
232
233 Time.rebuild_all_rows_to_format(panel, fmt);
234 if (fmt === '24h') Time.apply_imask_in_container_24h(panel);
235 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
236 Core.UI.pulse_query_debounced( panel, '.js-rt-generate' ); // avoid reflow spam while typing
237 });
238
239 // Mask on focus (24h)
240 d.addEventListener('focusin', function(ev){
241 var el = ev.target && ev.target.closest('.js-rt-mask[data-mask-kind="24h"]');
242 if (el && !el._imask) Time.apply_imask_to_input(el);
243 });
244
245 // Duration range <-> number sync
246 d.addEventListener('input', function(ev){
247 var range = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime [data-len-group] [data-len-range]');
248 if (!range) return;
249 var group = range.closest('[data-len-group]');
250 var num = group && group.querySelector('[data-len-value]');
251 if (num){
252 num.value = range.value;
253 if (num.hasAttribute('data-inspector-key')) emit_inspector_change(num);
254 }
255 const panel = (range || num).closest('.wpbc_bfb__inspector_rangetime');
256 Core.UI.pulse_query_debounced( panel, '.js-rt-generate' ); // avoid reflow spam while typing
257 });
258 d.addEventListener('input', function(ev){
259 var num = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime [data-len-group] [data-len-value]');
260 if (!num) return;
261 var group = num.closest('[data-len-group]');
262 var range = group && group.querySelector('[data-len-range]');
263 if (range) range.value = num.value;
264 const panel = (range || num).closest('.wpbc_bfb__inspector_rangetime');
265 Core.UI.pulse_query_debounced( panel, '.js-rt-generate' ); // avoid reflow spam while typing
266 });
267
268 // Pulse when any generator input changes (both formats + step)
269 d.addEventListener('input', function(ev){
270 const genInput = ev.target && ev.target.closest(
271 '.wpbc_bfb__inspector_rangetime .js-gen-start-24h,' +
272 '.wpbc_bfb__inspector_rangetime .js-gen-end-24h,' +
273 '.wpbc_bfb__inspector_rangetime .js-gen-start-ampm-time,' +
274 '.wpbc_bfb__inspector_rangetime .js-gen-end-ampm-time,' +
275 '.wpbc_bfb__inspector_rangetime .js-gen-step-h,' +
276 '.wpbc_bfb__inspector_rangetime .js-gen-step-m'
277 );
278 if (!genInput) return;
279 const panel = genInput.closest('.wpbc_bfb__inspector_rangetime');
280 Core.UI.pulse_query_debounced( panel, '.js-rt-generate' ); // avoid reflow spam while typing
281 });
282
283 // Generate slots
284 d.addEventListener('click', function(ev){
285 var btn = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .js-rt-generate');
286 if (!btn) return;
287 ev.preventDefault();
288
289 var panel = btn.closest('.wpbc_bfb__inspector_rangetime');
290 if (!panel) return;
291
292 var fmt = get_current_format(panel);
293 var start_m, end_m;
294 if (fmt === '24h'){
295 var s24 = (panel.querySelector('.js-gen-start-24h')||{}).value || '';
296 var e24 = (panel.querySelector('.js-gen-end-24h')||{}).value || '';
297 start_m = Time.parse_hhmm_24h(s24);
298 end_m = Time.parse_hhmm_24h(e24);
299 } else {
300 var s_am = (panel.querySelector('.js-gen-start-ampm-time')||{}).value || '';
301 var e_am = (panel.querySelector('.js-gen-end-ampm-time')||{}).value || '';
302 start_m = Time.parse_hhmm_24h(s_am); // input[type=time] => "HH:MM"
303 end_m = Time.parse_hhmm_24h(e_am);
304 }
305 var step_h = parseInt((panel.querySelector('.js-gen-step-h')||{}).value, 10) || 0;
306 var step_m = parseInt((panel.querySelector('.js-gen-step-m')||{}).value, 10) || 0;
307 var step = Math.max(1, step_h * 60 + step_m);
308
309 var slots = Time.build_time_slots(start_m, end_m, step, fmt);
310 wpbc_bfb_field_rangetime.replace_options_in_panel(panel, slots);
311 Time.rebuild_all_rows_to_format(panel, fmt);
312 if (fmt === '24h') Time.apply_imask_in_container_24h(panel);
313 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
314 });
315
316 // Clear
317 d.addEventListener('click', function(ev){
318 var btn = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .js-rt-clear');
319 if (!btn) return;
320 ev.preventDefault();
321 var panel = btn.closest('.wpbc_bfb__inspector_rangetime');
322 if (!panel) return;
323 wpbc_bfb_field_rangetime.replace_options_in_panel(panel, []);
324 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
325 Core.UI.pulse_query_debounced(panel, '.js-rt-generate' ); // avoid reflow spam while typing
326 });
327
328 // Add option
329 d.addEventListener('click', function(ev){
330 var btn = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .js-rt-add-option');
331 if (!btn) return;
332 ev.preventDefault();
333 var panel = btn.closest('.wpbc_bfb__inspector_rangetime');
334 if (!panel) return;
335
336 wpbc_bfb_field_rangetime.add_option_row(panel);
337 var fmt = get_current_format(panel);
338 if (fmt === '24h') Time.apply_imask_in_container_24h(panel);
339 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
340 });
341
342 // Row dropdown actions
343 d.addEventListener('click', function(ev){
344 var a = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .rt_dropdown_action');
345 if (!a) return;
346 ev.preventDefault();
347
348 var action = a.getAttribute('data-rt-action') || '';
349 var row = a.closest('.wpbc_bfb__options_row');
350 var panel = a.closest('.wpbc_bfb__inspector_rangetime');
351 if (!row || !panel) return;
352
353 if (action === 'add_after') {
354 wpbc_bfb_field_rangetime.insert_row_after(panel, row, null);
355 } else if (action === 'duplicate') {
356 var fmt = get_current_format(panel);
357 var data = wpbc_bfb_field_rangetime.read_row(row, fmt);
358 wpbc_bfb_field_rangetime.insert_row_after(panel, row, data);
359 } else if (action === 'remove') {
360 var listEl = row.parentNode;
361 listEl.removeChild(row);
362 wpbc_bfb_field_rangetime.reindex_rows(listEl);
363 }
364 var fmt2 = get_current_format(panel);
365 Time.rebuild_all_rows_to_format(panel, fmt2);
366 if (fmt2 === '24h') Time.apply_imask_in_container_24h(panel);
367 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
368 });
369
370 // Row edits -> sync
371 d.addEventListener('input', function(ev){
372 var row = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .wpbc_bfb__options_row');
373 if (!row) return;
374 var panel = row.closest('.wpbc_bfb__inspector_rangetime');
375 wpbc_bfb_field_rangetime.sync_state_from_rows(panel);
376 });
377 d.addEventListener('change', function(ev){
378 var row = ev.target && ev.target.closest('.wpbc_bfb__inspector_rangetime .wpbc_bfb__options_row');
379 if (!row) return;
380
381 if (ev.target.classList.contains('wpbc_bfb__opt-selected-chk')){
382 var panel = row.closest('.wpbc_bfb__inspector_rangetime');
383 if (panel && !panel.querySelector('.js-opt-multiple:checked')) {
384 panel.querySelectorAll('.wpbc_bfb__opt-selected-chk').forEach(function(chk){
385 if (chk !== ev.target) {
386 chk.checked = false;
387 chk.setAttribute('aria-checked', 'false');
388 }
389 });
390 ev.target.setAttribute('aria-checked', 'true');
391 }
392 }
393 var panel2 = row.closest('.wpbc_bfb__inspector_rangetime');
394 wpbc_bfb_field_rangetime.sync_state_from_rows(panel2);
395 });
396
397 // Init on load for existing panels
398 var init = function(){
399 d.querySelectorAll('.wpbc_bfb__inspector_rangetime').forEach(function(panel){
400 update_gen_visibility(panel);
401 var fmt = get_current_format(panel);
402 if (fmt === '24h') Time.apply_imask_in_container_24h(panel);
403
404 // placeholder disabled state mirror (if multiple is shown in future)
405 var mul = panel.querySelector('.js-opt-multiple');
406 var ph = panel.querySelector('.js-placeholder');
407 var phNote = panel.querySelector('.js-placeholder-note');
408 if (mul && ph) ph.disabled = !!mul.checked;
409 if (mul && phNote) phNote.style.display = mul.checked ? 'none' : '';
410
411 enforce_locked_name(panel);
412 var ph_init = panel.querySelector('.js-placeholder');
413 if (ph_init) emit_inspector_change(ph_init);
414 });
415 };
416 (d.readyState === 'loading') ? d.addEventListener('DOMContentLoaded', init) : init();
417
418 // Re-apply when Inspector re-renders a panel
419 d.addEventListener('wpbc_bfb_inspector_ready', function(ev){
420 var panel = ev && ev.detail && ev.detail.panel;
421 if (!panel) return;
422 var rtPanel = panel.closest
423 ? (panel.matches('.wpbc_bfb__inspector_rangetime') ? panel : panel.closest('.wpbc_bfb__inspector_rangetime'))
424 : null;
425 if (!rtPanel) return;
426
427 update_gen_visibility(rtPanel);
428 var fmt = get_current_format(rtPanel);
429 if (fmt === '24h') Time.apply_imask_in_container_24h(rtPanel);
430
431 enforce_locked_name(rtPanel);
432 var ph_init = rtPanel.querySelector('.js-placeholder');
433 if (ph_init) emit_inspector_change(ph_init);
434 });
435 }
436
437 // ------- Rows & state --------
438 static build_row_html(data, fmt, panel){
439 var label = (data && data.label) || '';
440 var value = (data && data.value) || '';
441 var parts = String(value).split(/\s*-\s*/);
442 var start = parts[0] || '';
443 var end = parts[1] || '';
444 var uid = 'wpbc_ins_rt_' + Math.random().toString(36).slice(2,10);
445 var sel = !!(data && (true === data.selected || 'true' === data.selected || 1 === data.selected || '1' === data.selected));
446 var i18n = {
447 add : (panel && panel.dataset.i18nAdd) || 'Add New',
448 duplicate: (panel && panel.dataset.i18nDuplicate) || 'Duplicate',
449 remove : (panel && panel.dataset.i18nRemove) || 'Remove',
450 def : (panel && panel.dataset.i18nDefault) || (w.wpbc_i18n_default || 'Default'),
451 reorder : (panel && panel.dataset.i18nReorder) || 'Drag to reorder',
452 rowlabel : (panel && panel.dataset.i18nRowlabel) || 'Label (e.g. 10:00 AM - 10:30 AM)'
453 };
454 var time_inputs_html;
455 if (fmt === '24h') {
456 time_inputs_html =
457 '<input type="text" class="wpbc_bfb__opt-start js-rt-mask" data-mask-kind="24h" placeholder="HH:MM" value="' + Time.esc_attr(start) + '">' +
458 '<span class="wpbc_bfb__opt-sep" aria-hidden="true">-</span>' +
459 '<input type="text" class="wpbc_bfb__opt-end js-rt-mask" data-mask-kind="24h" placeholder="HH:MM" value="' + Time.esc_attr(end) + '">';
460 } else {
461 time_inputs_html =
462 '<input type="time" class="wpbc_bfb__opt-start js-rt-start-time" step="300" value="' + Time.esc_attr(start) + '">' +
463 '<span class="wpbc_bfb__opt-sep" aria-hidden="true">-</span>' +
464 '<input type="time" class="wpbc_bfb__opt-end js-rt-end-time" step="300" value="' + Time.esc_attr(end) + '">';
465 }
466
467 return '' +
468 '<div class="wpbc_bfb__options_row" data-index="0">' +
469 '<span class="wpbc_bfb__drag-handle" title="' + i18n.reorder + '"><span class="wpbc_icn_drag_indicator"></span></span>' +
470 '<input type="text" class="wpbc_bfb__opt-label" placeholder="' + i18n.rowlabel + '" value="' + Time.esc_attr(label) + '">' +
471 time_inputs_html +
472 '<input type="text" class="wpbc_bfb__opt-value" value="' + Time.esc_attr(value || (start && end ? (start + ' - ' + end) : '')) + '" hidden>' +
473 '<div class="wpbc_bfb__opt-selected">' +
474 '<div class="inspector__control wpbc_ui__toggle">' +
475 '<input type="checkbox" class="wpbc_bfb__opt-selected-chk inspector__input" id="' + uid + '" role="switch" ' + (sel ? 'checked aria-checked="true"' : 'aria-checked="false"') + '>' +
476 '<label class="wpbc_ui__toggle_icon_radio" for="' + uid + '"></label>' +
477 '<label class="wpbc_ui__toggle_label" for="' + uid + '">' + i18n.def + '</label>' +
478 '</div>' +
479 '</div>' +
480 '<div class="wpbc_ui_el wpbc_ui_el_container wpbc_ui_el__dropdown">' +
481 '<a href="javascript:void(0)" data-toggle="wpbc_dropdown" aria-expanded="false" class="ul_dropdown_menu_toggle">' +
482 '<i class="menu_icon icon-1x wpbc_icn_more_vert"></i>' +
483 '</a>' +
484 '<ul class="ul_dropdown_menu" role="menu" style="right:0px; left:auto;">' +
485 '<li><a class="ul_dropdown_menu_li_action rt_dropdown_action" data-rt-action="add_after" href="javascript:void(0)">' + i18n.add + '<i class="menu_icon icon-1x wpbc_icn_add_circle"></i></a></li>' +
486 '<li><a class="ul_dropdown_menu_li_action rt_dropdown_action" data-rt-action="duplicate" href="javascript:void(0)">' + i18n.duplicate + '<i class="menu_icon icon-1x wpbc_icn_content_copy"></i></a></li>' +
487 '<li class="divider"></li>' +
488 '<li><a class="ul_dropdown_menu_li_action rt_dropdown_action" data-rt-action="remove" href="javascript:void(0)">' + i18n.remove + '<i class="menu_icon icon-1x wpbc_icn_delete_outline"></i></a></li>' +
489 '</ul>' +
490 '</div>' +
491 '</div>';
492 }
493
494 static add_option_row(panel){
495 var list = panel.querySelector('.wpbc_bfb__options_list');
496 if (!list) return;
497 var fmt = get_current_format(panel);
498 var s = 9 * 60, e = s + 30;
499 var start = Time.format_minutes_24h(s);
500 var end = Time.format_minutes_24h(e);
501 var label = (fmt === '24h') ? (start + ' - ' + end) : (Time.format_minutes_ampm(s) + ' - ' + Time.format_minutes_ampm(e));
502 var html = wpbc_bfb_field_rangetime.build_row_html({ label: label, value: start + ' - ' + end, selected:false }, fmt, panel);
503
504 var tmp = d.createElement('div'); tmp.innerHTML = html;
505 var row = tmp.firstElementChild;
506 list.appendChild(row);
507 wpbc_bfb_field_rangetime.reindex_rows(list);
508 if (fmt === '24h') {
509 Time.apply_imask_to_input(row.querySelector('.wpbc_bfb__opt-start'));
510 Time.apply_imask_to_input(row.querySelector('.wpbc_bfb__opt-end'));
511 }
512 }
513
514 static insert_row_after(panel, after_row, data){
515 var list = panel.querySelector('.wpbc_bfb__options_list');
516 if (!list) return;
517 var fmt = get_current_format(panel);
518 if (!data){
519 var s = 9 * 60, e = s + 30;
520 var start = Time.format_minutes_24h(s), end = Time.format_minutes_24h(e);
521 data = {
522 label : (fmt === '24h') ? (start + ' - ' + end) : (Time.format_minutes_ampm(s) + ' - ' + Time.format_minutes_ampm(e)),
523 value : start + ' - ' + end,
524 selected: false
525 };
526 }
527 var html = wpbc_bfb_field_rangetime.build_row_html(data, fmt, panel);
528 var tmp = d.createElement('div'); tmp.innerHTML = html;
529 var row = tmp.firstElementChild;
530 after_row.parentNode.insertBefore(row, after_row.nextSibling);
531 wpbc_bfb_field_rangetime.reindex_rows(list);
532 if (fmt === '24h') {
533 Time.apply_imask_to_input(row.querySelector('.wpbc_bfb__opt-start'));
534 Time.apply_imask_to_input(row.querySelector('.wpbc_bfb__opt-end'));
535 }
536 }
537
538 static read_row(row, fmt){
539 var label = (row.querySelector('.wpbc_bfb__opt-label')||{}).value || '';
540 var s_el = row.querySelector('.wpbc_bfb__opt-start');
541 var e_el = row.querySelector('.wpbc_bfb__opt-end');
542 var s_m = Time.parse_hhmm_24h(s_el ? s_el.value : '');
543 var e_m = Time.parse_hhmm_24h(e_el ? e_el.value : '');
544 if (isNaN(s_m)) s_m = Time.parse_minutes(s_el ? s_el.value : '');
545 if (isNaN(e_m)) e_m = Time.parse_minutes(e_el ? e_el.value : '');
546
547 // Allow export even when end <= start (overnight ranges)
548 var value = (!isNaN(s_m) && !isNaN(e_m)) ? (Time.format_minutes_24h(s_m) + ' - ' + Time.format_minutes_24h(e_m)) : '';
549 var sel = !!((row.querySelector('.wpbc_bfb__opt-selected-chk')||{}).checked);
550 return { label: label, value: value, selected: sel };
551 }
552
553 static sync_state_from_rows(panel){
554 if (!panel) return;
555 var list = panel.querySelector('.wpbc_bfb__options_list');
556 var state = panel.querySelector('.wpbc_bfb__options_state');
557 if (!list || !state) return;
558
559 var isMultiple = !!panel.querySelector('.js-opt-multiple:checked');
560 var seenSelected = false;
561 var fmt = get_current_format(panel);
562 var out = [];
563
564 list.querySelectorAll('.wpbc_bfb__options_row').forEach(function (row) {
565 var obj = wpbc_bfb_field_rangetime.read_row(row, fmt);
566 if (!isMultiple && obj.selected) {
567 if (!seenSelected) {
568 seenSelected = true;
569 } else {
570 obj.selected = false;
571 var chk = row.querySelector('.wpbc_bfb__opt-selected-chk');
572 if (chk){ chk.checked = false; chk.setAttribute('aria-checked', 'false'); }
573 }
574 }
575 out.push(obj);
576 var hv = row.querySelector('.wpbc_bfb__opt-value');
577 if (hv) hv.value = obj.value || '';
578 });
579
580 try { state.value = JSON.stringify(out); } catch(e){ state.value = '[]'; }
581 emit_inspector_change(state);
582 }
583
584 static replace_options_in_panel(panel, slots){
585 var list = panel.querySelector('.wpbc_bfb__options_list');
586 var state = panel.querySelector('.wpbc_bfb__options_state');
587 if (!list || !state) return;
588
589 var fmt = get_current_format(panel);
590 list.innerHTML = '';
591 (slots || []).forEach(function(opt){
592 var html = wpbc_bfb_field_rangetime.build_row_html(opt, fmt, panel);
593 var tmp = d.createElement('div'); tmp.innerHTML = html;
594 list.appendChild(tmp.firstElementChild);
595 });
596 wpbc_bfb_field_rangetime.reindex_rows(list);
597 if (fmt === '24h') Time.apply_imask_in_container_24h(panel);
598
599 try { state.value = JSON.stringify(slots || []); } catch(e){ state.value = '[]'; }
600 emit_inspector_change(state);
601 }
602
603 static reindex_rows(list){
604 if (!list) return;
605 var i = 0;
606 list.querySelectorAll('.wpbc_bfb__options_row').forEach(function(row){
607 row.setAttribute('data-index', String(i++));
608 });
609 }
610 };
611
612 try { Registry.register('rangetime', wpbc_bfb_field_rangetime); }
613 catch (e) { if (w._wpbc && w._wpbc.dev && w._wpbc.dev.error) w._wpbc.dev.error('wpbc_bfb_field_rangetime.register', e); }
614
615 wpbc_bfb_field_rangetime.bind_inspector_events_once();
616 w.WPBC_BFB_Field_RangeTime = wpbc_bfb_field_rangetime;
617
618
619
620 // -----------------------------------------------------------------------------------------------------------------
621 // Export for "Booking Form" (Advanced Form shortcode)
622 // -----------------------------------------------------------------------------------------------------------------
623 /**
624 * Booking Form exporter callback for "rangetime".
625 *
626 * Mirrors the legacy behavior:
627 * WPBC_BFB_Exporter.emit_time_select( name, field, req_mark, id_opt, cls_opts, emit_label_then );
628 *
629 * So the final shortcode body and label handling are identical to the old
630 * switch/case path in builder-exporter.js, just moved into this pack.
631 */
632 function register_rangetime_booking_form_exporter() {
633
634 var Exp = w.WPBC_BFB_Exporter;
635 if ( ! Exp || typeof Exp.register !== 'function' ) { return; }
636 if ( typeof Exp.has_exporter === 'function' && Exp.has_exporter( 'rangetime' ) ) { return; }
637
638 var S = Core.WPBC_BFB_Sanitize || {};
639 var esc_html = S.escape_html || function( v ){ return String( v ); };
640
641 /**
642 * @type {WPBC_BFB_ExporterCallback}
643 */
644 var exporter_callback = function( field, emit, extras ) {
645 extras = extras || {};
646
647 var cfg = extras.cfg || {};
648 var ctx = extras.ctx;
649
650 // Shared label wrapper: prefer global helper; fall back to local behavior.
651 var emit_label_then = function( body ) {
652 // Preferred path: centralized helper in builder-exporter.js
653 if ( Exp && typeof Exp.emit_label_then === 'function' ) {
654 Exp.emit_label_then( field, emit, body, cfg );
655 return;
656 }
657 };
658
659 // Required marker (same semantics as other text-like fields).
660 var is_req = Exp.is_required( field );
661 var req_mark = is_req ? '*' : '';
662
663 // Name / id / classes from shared helpers so they stay in sync.
664 var name = Exp.compute_name( 'rangetime', field );
665 var id_opt = Exp.id_option( field, ctx );
666 var cls_opts = Exp.class_options( field );
667
668 // Prefer the dedicated time helper to keep exact legacy shortcode shape.
669 if ( typeof Exp.emit_time_select === 'function' ) {
670 Exp.emit_time_select( name, field, req_mark, id_opt, cls_opts, emit_label_then );
671 return;
672 }
673 };
674
675 Exp.register( 'rangetime', exporter_callback );
676 }
677
678 if ( w.WPBC_BFB_Exporter && typeof w.WPBC_BFB_Exporter.register === 'function' ) {
679 register_rangetime_booking_form_exporter();
680 } else if ( typeof document !== 'undefined' ) {
681 document.addEventListener( 'wpbc:bfb:exporter-ready', register_rangetime_booking_form_exporter, { once: true } );
682 }
683
684
685 // -----------------------------------------------------------------------------------------------------------------
686 // Export for "Booking Data" (Content of booking fields data)
687 // -----------------------------------------------------------------------------------------------------------------
688 /**
689 * Booking Data exporter callback for "rangetime".
690 *
691 * Default behavior:
692 * <b>Label</b>: <f>[rangetime]</f><br>
693 *
694 * The exported token name is kept fully in sync with the Booking Form exporter
695 * via Exp.compute_name('rangetime', field).
696 */
697 function register_rangetime_booking_data_exporter() {
698
699 var C = w.WPBC_BFB_ContentExporter;
700 if ( ! C || typeof C.register !== 'function' ) { return; }
701 if ( typeof C.has_exporter === 'function' && C.has_exporter( 'rangetime' ) ) { return; }
702
703 C.register( 'rangetime', function( field, emit, extras ) {
704 extras = extras || {};
705 var cfg = extras.cfg || {};
706
707 var Exp = w.WPBC_BFB_Exporter;
708 if ( ! Exp || typeof Exp.compute_name !== 'function' ) { return; }
709
710 // Keep exported name identical to the Booking Form exporter.
711 var name = Exp.compute_name( 'rangetime', field );
712 if ( ! name ) { return; }
713
714 var raw_label = ( typeof field.label === 'string' ) ? field.label : '';
715 var label = raw_label.trim() || name;
716
717 // Shared helper: <b>Label</b>: <f>[name]</f><br>
718 C.emit_line_bold_field( emit, label, name, cfg );
719 } );
720 }
721
722 if ( w.WPBC_BFB_ContentExporter && typeof w.WPBC_BFB_ContentExporter.register === 'function' ) {
723 register_rangetime_booking_data_exporter();
724 } else if ( typeof document !== 'undefined' ) {
725 document.addEventListener( 'wpbc:bfb:content-exporter-ready', register_rangetime_booking_data_exporter, { once: true } );
726 }
727
728 })(window, document);
729