| 1 |
// File: /includes/page-form-builder/field-packs/divider/_out/divider.js |
| 2 |
(function (w) { |
| 3 |
'use strict'; |
| 4 |
|
| 5 |
/** @type {any} */ |
| 6 |
var Core = w.WPBC_BFB_Core || {}; |
| 7 |
var registry = Core.WPBC_BFB_Field_Renderer_Registry; |
| 8 |
var Base = Core.WPBC_BFB_Field_Base; |
| 9 |
|
| 10 |
if (!registry || typeof registry.register !== 'function' || !Base) { |
| 11 |
_wpbc?.dev?.error?.('wpbc_bfb_field_divider', 'Core registry/base missing'); |
| 12 |
return; |
| 13 |
} |
| 14 |
|
| 15 |
/** |
| 16 |
* WPBC BFB: Field Renderer for "divider" (Schema-driven) |
| 17 |
* - Inspector is rendered by Factory (from PHP schema). |
| 18 |
* - No wp.template usage for preview. |
| 19 |
* - Renders either a horizontal <hr> or a vertical <div> with border-left. |
| 20 |
*/ |
| 21 |
class wpbc_bfb_field_divider extends Base { |
| 22 |
|
| 23 |
/** |
| 24 |
* Return default props for "divider" field. |
| 25 |
* Must stay in sync with PHP schema defaults. |
| 26 |
* |
| 27 |
* @returns {{type:string,orientation:'horizontal'|'vertical',line_style:'solid'|'dashed'|'dotted',thickness_px:number,length:string,align:string,color:string,margin_top_px:number,margin_bottom_px:number,margin_left_px:number,margin_right_px:number,cssclass_extra:string,name:string,html_id:string,help:string,usage_key:string}} |
| 28 |
*/ |
| 29 |
static get_defaults() { |
| 30 |
return { |
| 31 |
type : 'divider', |
| 32 |
orientation : 'horizontal', // 'horizontal' | 'vertical' |
| 33 |
line_style : 'solid', // 'solid' | 'dashed' | 'dotted' |
| 34 |
thickness_px : 1, |
| 35 |
length : '100%', // width for horizontal, height for vertical |
| 36 |
align : 'center', // 'left' | 'center' | 'right' (or vertical equivalents; normalized later) |
| 37 |
color : '#e0e0e0', |
| 38 |
margin_top_px : 2, |
| 39 |
margin_bottom_px: 2, |
| 40 |
margin_left_px : 2, |
| 41 |
margin_right_px : 2, |
| 42 |
cssclass_extra : '', |
| 43 |
name : '', |
| 44 |
html_id : '', |
| 45 |
help : '', |
| 46 |
usage_key : 'divider' |
| 47 |
}; |
| 48 |
} |
| 49 |
|
| 50 |
/** |
| 51 |
* Normalize align tokens for the current orientation. |
| 52 |
* - For horizontal: accepts left|center|right or top|middle|bottom → maps to left|center|right. |
| 53 |
* - For vertical : accepts top|middle|bottom or left|center|right → maps to top|middle|bottom. |
| 54 |
* |
| 55 |
* @param {'horizontal'|'vertical'} orientation |
| 56 |
* @param {string|undefined|null} align_raw |
| 57 |
* @returns {'left'|'center'|'right'|'top'|'middle'|'bottom'} |
| 58 |
*/ |
| 59 |
static normalize_align(orientation, align_raw) { |
| 60 |
const a = String(align_raw ?? '').toLowerCase(); |
| 61 |
const H = { top: 'left', middle: 'center', bottom: 'right' }; |
| 62 |
const V = { left: 'top', center: 'middle', right: 'bottom' }; |
| 63 |
const isV = orientation === 'vertical'; |
| 64 |
if (isV) return (a in V) ? /** @type any */(V)[a] : (['top','middle','bottom'].includes(a) ? /** @type any */(a) : 'middle'); |
| 65 |
return (a in H) ? /** @type any */(H)[a] : (['left','center','right'].includes(a) ? /** @type any */(a) : 'center'); |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Render the preview markup into the field element. |
| 70 |
* |
| 71 |
* @param {HTMLElement} el Field root element inside the canvas. |
| 72 |
* @param {Object} data Field props (already normalized by schema). |
| 73 |
* @param {{builder?:any, sanit?:any}} [ctx] Context object. |
| 74 |
* @returns {void} |
| 75 |
*/ |
| 76 |
static render(el, data, ctx) { |
| 77 |
if (!el) return; |
| 78 |
|
| 79 |
// Normalize against defaults first. |
| 80 |
const d = this.normalize_data(data); |
| 81 |
|
| 82 |
// ----- Core sanitize helpers (static) ----- |
| 83 |
const eh = (v) => Core.WPBC_BFB_Sanitize.escape_html(v); |
| 84 |
const sid = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_id(v); |
| 85 |
const sname = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_name(v); |
| 86 |
const sclass = (v) => Core.WPBC_BFB_Sanitize.sanitize_css_classlist(v); |
| 87 |
|
| 88 |
/** |
| 89 |
* @param {any} v |
| 90 |
* @param {number} def |
| 91 |
* @param {number} [min] |
| 92 |
* @param {number} [max] |
| 93 |
* @returns {number} |
| 94 |
*/ |
| 95 |
const to_int = (v, def, min, max) => { |
| 96 |
let n = parseInt(v, 10); |
| 97 |
if (isNaN(n)) n = def; |
| 98 |
if (typeof min === 'number' && n < min) n = min; |
| 99 |
if (typeof max === 'number' && n > max) n = max; |
| 100 |
return n; |
| 101 |
}; |
| 102 |
|
| 103 |
/** |
| 104 |
* @param {any} v |
| 105 |
* @param {string} def |
| 106 |
* @returns {string} |
| 107 |
*/ |
| 108 |
const to_str = (v, def) => (v === undefined || v === null) ? def : String(v); |
| 109 |
|
| 110 |
// Coerce props. |
| 111 |
/** @type {'horizontal'|'vertical'} */ |
| 112 |
const orientation = (to_str(d.orientation, 'horizontal') === 'vertical') ? 'vertical' : 'horizontal'; |
| 113 |
/** @type {'solid'|'dashed'|'dotted'} */ |
| 114 |
const line_style = ({ solid:1, dashed:1, dotted:1 }[ to_str(d.line_style, 'solid') ]) ? d.line_style : 'solid'; |
| 115 |
const align_norm = wpbc_bfb_field_divider.normalize_align(orientation, to_str(d.align, orientation === 'vertical' ? 'middle' : 'center')); // accept both sets, then normalize to the active orientation. |
| 116 |
const thickness_px = to_int(d.thickness_px, 1, 1, 20); |
| 117 |
const length_val = Core.WPBC_BFB_Sanitize.sanitize_css_len(d.length, '100%'); |
| 118 |
const color_val = Core.WPBC_BFB_Sanitize.sanitize_hex_color(d.color, '#e0e0e0'); |
| 119 |
|
| 120 |
const m_top = to_int(d.margin_top_px, 8, 0, 200); |
| 121 |
const m_bottom = to_int(d.margin_bottom_px, 8, 0, 200); |
| 122 |
const m_left = to_int(d.margin_left_px, 8, 0, 200); |
| 123 |
const m_right = to_int(d.margin_right_px, 8, 0, 200); |
| 124 |
|
| 125 |
const html_id = d.html_id ? sid(String(d.html_id)) : ''; |
| 126 |
const name_val = d.name ? sname(String(d.name)) : ''; |
| 127 |
const cls_extra = sclass(String(d.cssclass_extra || '')); |
| 128 |
|
| 129 |
// Persist useful props on dataset (do not mutate wrapper classes directly). |
| 130 |
if (el.dataset.orientation !== orientation) el.dataset.orientation = orientation; |
| 131 |
if (el.dataset.line_style !== line_style) el.dataset.line_style = line_style; |
| 132 |
if (String(el.dataset.thickness_px) !== String(thickness_px)) el.dataset.thickness_px = String(thickness_px); |
| 133 |
if (el.dataset.length !== length_val) el.dataset.length = length_val; |
| 134 |
if (el.dataset.align !== align_norm) el.dataset.align = align_norm; |
| 135 |
if (el.dataset.color !== color_val) el.dataset.color = color_val; |
| 136 |
|
| 137 |
if (String(el.dataset.margin_top_px) !== String(m_top)) el.dataset.margin_top_px = String(m_top); |
| 138 |
if (String(el.dataset.margin_bottom_px) !== String(m_bottom)) el.dataset.margin_bottom_px = String(m_bottom); |
| 139 |
if (String(el.dataset.margin_left_px) !== String(m_left)) el.dataset.margin_left_px = String(m_left); |
| 140 |
if (String(el.dataset.margin_right_px) !== String(m_right)) el.dataset.margin_right_px = String(m_right); |
| 141 |
|
| 142 |
if (el.dataset.cssclass_extra !== cls_extra) el.dataset.cssclass_extra = cls_extra; |
| 143 |
if (el.dataset.html_id !== html_id) el.dataset.html_id = html_id; |
| 144 |
if (el.dataset.name !== name_val) el.dataset.name = name_val; |
| 145 |
|
| 146 |
// Attribute fragments for the line element. |
| 147 |
const id_attr = html_id ? ` id="${eh(html_id)}"` : ''; |
| 148 |
const name_attr = name_val ? ` name="${eh(name_val)}"` : ''; |
| 149 |
const cls_attr = cls_extra ? ` class="${eh(cls_extra)}"` : ''; |
| 150 |
|
| 151 |
// Compute style for margins (outer wrapper). |
| 152 |
const outer_style_attr = ` style="margin:${m_top}px ${m_right}px ${m_bottom}px ${m_left}px;"`; |
| 153 |
|
| 154 |
// Build inner line markup based on orientation. |
| 155 |
let line_html = ''; |
| 156 |
if (orientation === 'horizontal') { |
| 157 |
// Horizontal: <hr> using border-top. |
| 158 |
const ml = (align_norm === 'left') ? '0' : 'auto'; |
| 159 |
const mr = (align_norm === 'right') ? '0' : 'auto'; |
| 160 |
const hr_style = [ |
| 161 |
'border:none', |
| 162 |
'height:0', |
| 163 |
`border-top:${thickness_px}px ${line_style} ${color_val}`, |
| 164 |
`width:${length_val}`, |
| 165 |
`margin-left:${ml}`, |
| 166 |
`margin-right:${mr}` |
| 167 |
].join(';'); |
| 168 |
line_html = `<hr${cls_attr}${id_attr}${name_attr} style="${hr_style}">`; |
| 169 |
} else { |
| 170 |
// Vertical: <div> using border-left and height. |
| 171 |
let v_align = ''; |
| 172 |
v_align = (align_norm === 'top') ? 'position:absolute;top:0;' : v_align; |
| 173 |
v_align = (align_norm === 'middle') ? 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);' : v_align; |
| 174 |
v_align = (align_norm === 'bottom') ? 'position:absolute;bottom:0;' : v_align; |
| 175 |
|
| 176 |
const vr_style = [ |
| 177 |
`border-left:${thickness_px}px ${line_style} ${color_val}`, |
| 178 |
`height:${length_val}`, |
| 179 |
`${v_align}`, |
| 180 |
'padding-left:0' |
| 181 |
].join(';'); |
| 182 |
line_html = `<div${cls_attr}${id_attr}${name_attr} role="separator" aria-orientation="vertical" style="${vr_style}"></div>`; |
| 183 |
} |
| 184 |
|
| 185 |
// Optional help text below the line. |
| 186 |
const help_html = d.help ? `<div class="wpbc_bfb__help">${eh(String(d.help))}</div>` : ''; |
| 187 |
|
| 188 |
// Render preview HTML. |
| 189 |
el.innerHTML = ` |
| 190 |
<span class="wpbc_bfb__noaction wpbc_bfb__no-drag-zone" inert=""> |
| 191 |
<div class="wpbc_bfb__field-preview"${outer_style_attr}> |
| 192 |
${line_html} |
| 193 |
</div> |
| 194 |
${help_html} |
| 195 |
</span> |
| 196 |
`; |
| 197 |
|
| 198 |
// Overlay (handles/toolbars). |
| 199 |
Core.UI?.WPBC_BFB_Overlay?.ensure?.(ctx?.builder, el); |
| 200 |
} |
| 201 |
|
| 202 |
/** |
| 203 |
* Optional hook executed after field is dropped from the palette. |
| 204 |
* |
| 205 |
* @param {Object} data |
| 206 |
* @param {HTMLElement} el |
| 207 |
* @param {{palette_item?: HTMLElement}} [ctx] |
| 208 |
* @returns {void} |
| 209 |
*/ |
| 210 |
static on_field_drop(data, el, ctx) { |
| 211 |
// Keep base behavior (auto-name, etc.). |
| 212 |
super.on_field_drop?.(data, el, ctx); |
| 213 |
|
| 214 |
// Apply orientation from palette hint (data-orientation) once on drop. |
| 215 |
try { |
| 216 |
const src = ctx?.palette_item; |
| 217 |
if (src) { |
| 218 |
const ori = src.getAttribute('data-orientation'); |
| 219 |
if (ori === 'horizontal' || ori === 'vertical') { |
| 220 |
el.dataset.orientation = ori; |
| 221 |
} |
| 222 |
} |
| 223 |
} catch (e) {} |
| 224 |
} |
| 225 |
} |
| 226 |
|
| 227 |
try { |
| 228 |
registry.register('divider', wpbc_bfb_field_divider); |
| 229 |
} catch (e) { _wpbc?.dev?.error?.('wpbc_bfb_field_divider.register', e); } |
| 230 |
|
| 231 |
|
| 232 |
// ----------------------------------------------------------------------------- |
| 233 |
// Exporters – booking form + “Content of booking fields data” |
| 234 |
// ----------------------------------------------------------------------------- |
| 235 |
|
| 236 |
/** |
| 237 |
* Register booking form exporter for "divider". |
| 238 |
* Mirrors the previous legacy export logic but now kept inside the pack. |
| 239 |
*/ |
| 240 |
function register_divider_form_exporter() { |
| 241 |
if (!w.WPBC_BFB_Exporter || typeof w.WPBC_BFB_Exporter.register !== 'function') { |
| 242 |
return; |
| 243 |
} |
| 244 |
|
| 245 |
w.WPBC_BFB_Exporter.register('divider', function (field, emit, extras) { |
| 246 |
if (!field || typeof emit !== 'function') return; |
| 247 |
|
| 248 |
const core = (extras && extras.core) || w.WPBC_BFB_Core || {}; |
| 249 |
const Sanit = core.WPBC_BFB_Sanitize || {}; |
| 250 |
|
| 251 |
const esc_html = Sanit.escape_html ? Sanit.escape_html.bind(Sanit) : (s) => String(s); |
| 252 |
const cls_sanit = Sanit.sanitize_css_classlist ? Sanit.sanitize_css_classlist.bind(Sanit) : (s) => String(s); |
| 253 |
const sid = Sanit.sanitize_html_id ? Sanit.sanitize_html_id.bind(Sanit) : (s) => String(s); |
| 254 |
const sname = Sanit.sanitize_html_name ? Sanit.sanitize_html_name.bind(Sanit) : (s) => String(s); |
| 255 |
|
| 256 |
/** |
| 257 |
* @param {any} v |
| 258 |
* @param {number} def |
| 259 |
* @param {number} [min] |
| 260 |
* @param {number} [max] |
| 261 |
* @returns {number} |
| 262 |
*/ |
| 263 |
const to_int = (v, def, min, max) => { |
| 264 |
let n = parseInt(v, 10); |
| 265 |
if (isNaN(n)) n = def; |
| 266 |
if (typeof min === 'number' && n < min) n = min; |
| 267 |
if (typeof max === 'number' && n > max) n = max; |
| 268 |
return n; |
| 269 |
}; |
| 270 |
|
| 271 |
/** |
| 272 |
* @param {any} v |
| 273 |
* @param {string} def |
| 274 |
* @returns {string} |
| 275 |
*/ |
| 276 |
const to_str = (v, def) => (v == null || v === '') ? def : String(v); |
| 277 |
|
| 278 |
// Normalize against defaults (schema-driven). |
| 279 |
const d = wpbc_bfb_field_divider.normalize_data(field); |
| 280 |
|
| 281 |
// ---- read & sanitize props (keep in sync with preview/schema) ---- |
| 282 |
/** @type {'horizontal'|'vertical'} */ |
| 283 |
const orientation = (to_str(d.orientation, 'horizontal') === 'vertical') ? 'vertical' : 'horizontal'; |
| 284 |
const line_style_in = to_str(d.line_style, 'solid'); |
| 285 |
/** @type {'solid'|'dashed'|'dotted'} */ |
| 286 |
const line_style = ({ solid:1, dashed:1, dotted:1 })[ line_style_in ] ? line_style_in : 'solid'; |
| 287 |
|
| 288 |
const thickness_px = to_int(d.thickness_px, 1, 1, 20); |
| 289 |
const length_val = to_str(d.length, '100%'); |
| 290 |
const align_norm = wpbc_bfb_field_divider.normalize_align( |
| 291 |
orientation, |
| 292 |
to_str(d.align, orientation === 'vertical' ? 'middle' : 'center') |
| 293 |
); // accept both sets; normalize to orientation-appropriate token. |
| 294 |
const color_val = to_str(d.color, '#e0e0e0'); |
| 295 |
|
| 296 |
const mt = to_int(d.margin_top_px, 8, 0, 200); |
| 297 |
const mb = to_int(d.margin_bottom_px, 8, 0, 200); |
| 298 |
const ml = to_int(d.margin_left_px, 8, 0, 200); |
| 299 |
const mr = to_int(d.margin_right_px, 8, 0, 200); |
| 300 |
|
| 301 |
const html_id = d.html_id ? sid(String(d.html_id)) : ''; |
| 302 |
const name_val = d.name ? sname(String(d.name)) : ''; |
| 303 |
const cls_extra = cls_sanit(String(d.cssclass_extra || d.cssclass || d.class || '')); |
| 304 |
|
| 305 |
const id_attr = html_id ? ` id="${esc_html(html_id)}"` : ''; |
| 306 |
const name_attr = name_val ? ` name="${esc_html(name_val)}"` : ''; |
| 307 |
|
| 308 |
// Wrap styles (margins applied on the wrapper, not the line itself). |
| 309 |
// In vertical mode we also make the wrapper flex and stretch so height:100% resolves. |
| 310 |
const wrapper_styles = [ |
| 311 |
`margin:${mt}px ${mr}px ${mb}px ${ml}px`, |
| 312 |
(orientation === 'vertical') ? 'display:flex' : '', |
| 313 |
(orientation === 'vertical') ? 'align-self:stretch' : '' |
| 314 |
].filter(Boolean).join('; '); |
| 315 |
const wrapper_attr = wrapper_styles ? ` style="${esc_html(wrapper_styles)}"` : ''; |
| 316 |
|
| 317 |
let line_html = ''; |
| 318 |
if (orientation === 'horizontal') { |
| 319 |
// Horizontal divider via <hr> with border-top |
| 320 |
const ml_auto = (align_norm === 'left') ? '0' : 'auto'; |
| 321 |
const mr_auto = (align_norm === 'right') ? '0' : 'auto'; |
| 322 |
const hr_styles = [ |
| 323 |
'border:none', |
| 324 |
'height:0', |
| 325 |
`border-top:${thickness_px}px ${line_style} ${color_val}`, |
| 326 |
`width:${length_val}`, |
| 327 |
`margin-left:${ml_auto}`, |
| 328 |
`margin-right:${mr_auto}` |
| 329 |
].join('; '); |
| 330 |
line_html = `<hr${id_attr}${name_attr} class="wpbc_bfb_divider wpbc_bfb_divider--h${cls_extra ? ' ' + esc_html(cls_extra) : ''}" style="${esc_html(hr_styles)}">`; |
| 331 |
} else { |
| 332 |
// Vertical divider via <div> with border-left; height = length |
| 333 |
// (positioning handled client-side; export keeps the same inline style set) |
| 334 |
// align_norm is 'top'|'middle'|'bottom' here. |
| 335 |
let vr_styles_align = ''; |
| 336 |
vr_styles_align = (align_norm === 'top') ? 'position: absolute;top: 0;' : vr_styles_align; |
| 337 |
vr_styles_align = (align_norm === 'middle') ? 'position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);' : vr_styles_align; |
| 338 |
vr_styles_align = (align_norm === 'bottom') ? 'position: absolute;bottom:0;' : vr_styles_align; |
| 339 |
|
| 340 |
const vr_styles = [ |
| 341 |
`border-left:${thickness_px}px ${line_style} ${color_val}`, |
| 342 |
`height:${length_val}`, |
| 343 |
'padding-left:0', |
| 344 |
vr_styles_align |
| 345 |
].join('; '); |
| 346 |
|
| 347 |
line_html = `<div${id_attr}${name_attr} class="wpbc_bfb_divider wpbc_bfb_divider--v${cls_extra ? ' ' + esc_html(cls_extra) : ''}" role="separator" aria-orientation="vertical" style="${esc_html(vr_styles)}"></div>`; |
| 348 |
} |
| 349 |
|
| 350 |
// Output: a small wrapper so CSS can target export safely (no builder-only classes). |
| 351 |
emit( |
| 352 |
`<div class="wpbc_bfb_divider_wrap" data-bfb-type="divider" data-orientation="${orientation}"${wrapper_attr}>${line_html}</div>` |
| 353 |
); |
| 354 |
|
| 355 |
// NOTE: Help text is appended by WPBC_BFB_Exporter.render_field_node() after this exporter runs. |
| 356 |
}); |
| 357 |
} |
| 358 |
|
| 359 |
/** |
| 360 |
* Register empty content exporter for "divider" so it explicitly outputs nothing |
| 361 |
* in “Content of booking fields data”. |
| 362 |
*/ |
| 363 |
function register_divider_content_exporter() { |
| 364 |
if (!w.WPBC_BFB_ContentExporter || typeof w.WPBC_BFB_ContentExporter.register !== 'function') { |
| 365 |
return; |
| 366 |
} |
| 367 |
|
| 368 |
// Divider is purely visual; nothing to show in content template. |
| 369 |
w.WPBC_BFB_ContentExporter.register('divider', function (_field, _emit, _ctx) { |
| 370 |
// Intentionally empty – explicit no-op for content export. |
| 371 |
}); |
| 372 |
} |
| 373 |
|
| 374 |
// Immediate registration if exporters are already loaded. |
| 375 |
try { |
| 376 |
register_divider_form_exporter(); |
| 377 |
} catch (e) { _wpbc?.dev?.error?.('wpbc_bfb_field_divider.exporter', e); } |
| 378 |
|
| 379 |
try { |
| 380 |
register_divider_content_exporter(); |
| 381 |
} catch (e) { _wpbc?.dev?.error?.('wpbc_bfb_field_divider.content_exporter', e); } |
| 382 |
|
| 383 |
// Deferred registration when builder-exporter.js fires its ready events. |
| 384 |
document.addEventListener('wpbc:bfb:exporter-ready', register_divider_form_exporter); |
| 385 |
document.addEventListener('wpbc:bfb:content-exporter-ready', register_divider_content_exporter); |
| 386 |
|
| 387 |
|
| 388 |
// ----------------------------------------------------------------------------- |
| 389 |
// Inspector UX: enable/disable align options depending on orientation |
| 390 |
// ----------------------------------------------------------------------------- |
| 391 |
/** |
| 392 |
* Attach inspector logic to enable/disable align options based on orientation. |
| 393 |
* Uses data-inspector-key selectors produced by the Factory inspector. |
| 394 |
*/ |
| 395 |
(function attach_align_toggle() { |
| 396 |
|
| 397 |
/** |
| 398 |
* Find a <select> for a given inspector key within a root. |
| 399 |
* @param {ParentNode} root |
| 400 |
* @param {string} key |
| 401 |
* @returns {HTMLSelectElement|null} |
| 402 |
*/ |
| 403 |
function get_sel(root, key) { |
| 404 |
return /** @type {HTMLSelectElement|null} */ (root.querySelector(`select[data-inspector-key="${key}"]`)); |
| 405 |
} |
| 406 |
|
| 407 |
/** |
| 408 |
* Locate the inspector root (falls back to document). |
| 409 |
* @returns {ParentNode} |
| 410 |
*/ |
| 411 |
function find_inspector_root() { |
| 412 |
return document.querySelector('.wpbc_bfb__inspector') || document; |
| 413 |
} |
| 414 |
|
| 415 |
/** |
| 416 |
* Enable/disable align options depending on orientation. |
| 417 |
* If the current value becomes invalid, remap to a valid counterpart. |
| 418 |
* |
| 419 |
* @param {HTMLSelectElement} oriSel |
| 420 |
* @param {HTMLSelectElement} alignSel |
| 421 |
* @returns {void} |
| 422 |
*/ |
| 423 |
function set_disabled_options(oriSel, alignSel) { |
| 424 |
if (!oriSel || !alignSel) return; |
| 425 |
const ori = String(oriSel.value) === 'vertical' ? 'vertical' : 'horizontal'; |
| 426 |
const H = new Set(['left', 'center', 'right']); |
| 427 |
const V = new Set(['top', 'middle', 'bottom']); |
| 428 |
|
| 429 |
Array.from(alignSel.options).forEach((opt) => { |
| 430 |
const v = String(opt.value); |
| 431 |
const disable = (ori === 'vertical' && H.has(v)) || (ori === 'horizontal' && V.has(v)); |
| 432 |
opt.disabled = disable; |
| 433 |
opt.hidden = disable; // hide visually as well |
| 434 |
}); |
| 435 |
|
| 436 |
// If current value is disabled, remap to a valid counterpart. |
| 437 |
const cur = String(alignSel.value); |
| 438 |
if ((ori === 'vertical' && H.has(cur)) || (ori === 'horizontal' && V.has(cur))) { |
| 439 |
const mapH = { top: 'left', middle: 'center', bottom: 'right' }; |
| 440 |
const mapV = { left: 'top', center: 'middle', right: 'bottom' }; |
| 441 |
alignSel.value = (ori === 'vertical') ? (mapV[/** @type keyof typeof mapV */(cur)] || 'middle') |
| 442 |
: (mapH[/** @type keyof typeof mapH */(cur)] || 'center'); |
| 443 |
// Trigger change so data binding updates. |
| 444 |
alignSel.dispatchEvent(new Event('change', { bubbles: true })); |
| 445 |
} |
| 446 |
} |
| 447 |
|
| 448 |
/** |
| 449 |
* Refresh orientation/align coupling for the current inspector. |
| 450 |
* @returns {void} |
| 451 |
*/ |
| 452 |
function refresh() { |
| 453 |
const root = find_inspector_root(); |
| 454 |
const oriSel = get_sel(root, 'orientation'); |
| 455 |
const alignSel = get_sel(root, 'align'); |
| 456 |
if (oriSel && alignSel) set_disabled_options(oriSel, alignSel); |
| 457 |
} |
| 458 |
|
| 459 |
// React to changes of the Orientation control. |
| 460 |
document.addEventListener('change', (e) => { |
| 461 |
const t = /** @type {Element|null} */ (e.target); |
| 462 |
if (!t) return; |
| 463 |
if (t.matches('select[data-inspector-key="orientation"]')) { |
| 464 |
requestAnimationFrame(refresh); |
| 465 |
} |
| 466 |
}, true); |
| 467 |
|
| 468 |
// Also react to inspector re-renders. |
| 469 |
const mo = new MutationObserver(() => requestAnimationFrame(refresh)); |
| 470 |
mo.observe(document.body, { childList: true, subtree: true }); |
| 471 |
|
| 472 |
// Initial pass. |
| 473 |
requestAnimationFrame(refresh); |
| 474 |
})(); |
| 475 |
|
| 476 |
})(window); |
| 477 |
|