mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.7 KiB
168 lines
4.7 KiB
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))
|
|
}
|
|
}
|
|
}
|