export default class extends HTMLElement { connectedCallback () { this.targetId = this.dataset.targetId this.fieldId = this.dataset.fieldId this.action = (this.dataset.action || '').trim() this.expectedValue = this.dataset.value this.targetEl = document.getElementById(this.targetId) this.sourceEl = document.getElementById(this.fieldId) this.bindListeners() this.evaluateAndApply() } disconnectedCallback () { this.unbindListeners() } bindListeners () { this.eventsFor(this.sourceEl).forEach((ev) => { this.sourceEl.addEventListener(ev, this.evaluateAndApply) }) } unbindListeners () { this.eventsFor(this.sourceEl).forEach((ev) => { this.sourceEl.removeEventListener(ev, this.evaluateAndApply) }) } eventsFor (el) { if (!el) return [] const tag = el.tagName.toLowerCase() if (tag === 'textarea') return ['input'] if (tag === 'input') return ['input', 'change'] return ['change'] } evaluateAndApply = () => { const fieldConditions = document.querySelectorAll(`field-condition[data-target-id="${this.targetId}"]`) const result = [...fieldConditions].reduce((acc, cond) => { if (cond.dataset.operation === 'or') { acc.push(acc.pop() || cond.checkCondition()) } else { acc.push(cond.checkCondition()) } return acc }, []) this.apply(!result.includes(false)) } checkCondition () { const action = this.action const actual = this.getSourceValue() const expected = this.expectedValue if (action === 'empty' || action === 'unchecked') return this.isEmpty(actual) if (action === 'not_empty' || action === 'checked') return !this.isEmpty(actual) if (['equal', 'not_equal', 'greater_than', 'less_than'].includes(action) && this.sourceEl?.getAttribute('type') === 'number') { if (this.isEmpty(actual) || this.isEmpty(expected)) return false const actualNumber = parseFloat(actual) const expectedNumber = parseFloat(expected) if (Number.isNaN(actualNumber) || Number.isNaN(expectedNumber)) return false if (action === 'equal') return Math.abs(actualNumber - expectedNumber) < Number.EPSILON if (action === 'not_equal') return Math.abs(actualNumber - expectedNumber) > Number.EPSILON if (action === 'greater_than') return actualNumber > expectedNumber if (action === 'less_than') return actualNumber < expectedNumber return false } if (action === 'equal') { const list = Array.isArray(actual) ? actual : [actual] return list.filter((v) => v !== null && v !== undefined).map(String).includes(String(expected)) } if (action === 'contains') return this.contains(actual, expected) if (action === 'not_equal') { const list = Array.isArray(actual) ? actual : [actual] return !list.filter((v) => v !== null && v !== undefined).map(String).includes(String(expected)) } if (action === 'does_not_contain') return !this.contains(actual, expected) return true } getSourceValue () { const el = this.sourceEl if (!el) return const tag = el.tagName.toLowerCase() const type = (el.getAttribute('type') || '').toLowerCase() if (tag === 'select') return el.value if (tag === 'textarea') return el.value if (tag === 'input' && type === 'checkbox') return el.checked ? (el.value || '1') : null if (tag === 'input') return el.value return el.value ?? null } isEmpty (obj) { if (obj == null) return true if (Array.isArray(obj)) { return obj.length === 0 } if (typeof obj === 'string') { return obj.trim().length === 0 } if (typeof obj === 'object') { return Object.keys(obj).length === 0 } if (obj === false) { return true } return false } contains (actual, expected) { if (expected === null || expected === undefined) return false const exp = String(expected) if (Array.isArray(actual)) return actual.filter((v) => v !== null && v !== undefined).map(String).includes(exp) if (typeof actual === 'string') return actual.includes(exp) return actual !== null && actual !== undefined && String(actual) === exp } apply (passed) { const controls = this.targetEl.matches('input, select, textarea, button') ? [this.targetEl] : Array.from(this.targetEl.querySelectorAll('input, select, textarea, button')) if (passed) { this.targetEl.style.display = '' this.targetEl.labels.forEach((label) => { label.style.display = '' }) controls.forEach((c) => (c.disabled = false)) } else { this.targetEl.style.display = 'none' this.targetEl.labels.forEach((label) => { label.style.display = 'none' }) controls.forEach((c) => (c.disabled = true)) } } }