Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo-mod-jslibs-dist.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'alpinejs/packages/alpinejs/src/utils/bind.js')
-rw-r--r--alpinejs/packages/alpinejs/src/utils/bind.js149
1 files changed, 149 insertions, 0 deletions
diff --git a/alpinejs/packages/alpinejs/src/utils/bind.js b/alpinejs/packages/alpinejs/src/utils/bind.js
new file mode 100644
index 0000000..fc89b20
--- /dev/null
+++ b/alpinejs/packages/alpinejs/src/utils/bind.js
@@ -0,0 +1,149 @@
+import { reactive } from '../reactivity'
+import { setClasses } from './classes'
+import { setStyles } from './styles'
+
+export default function bind(el, name, value, modifiers = []) {
+ // Register bound data as pure observable data for other APIs to use.
+ if (! el._x_bindings) el._x_bindings = reactive({})
+
+ el._x_bindings[name] = value
+
+ name = modifiers.includes('camel') ? camelCase(name) : name
+
+ switch (name) {
+ case 'value':
+ bindInputValue(el, value)
+ break;
+
+ case 'style':
+ bindStyles(el, value)
+ break;
+
+ case 'class':
+ bindClasses(el, value)
+ break;
+
+ default:
+ bindAttribute(el, name, value)
+ break;
+ }
+}
+
+function bindInputValue(el, value) {
+ if (el.type === 'radio') {
+ // Set radio value from x-bind:value, if no "value" attribute exists.
+ // If there are any initial state values, radio will have a correct
+ // "checked" value since x-bind:value is processed before x-model.
+ if (el.attributes.value === undefined) {
+ el.value = value
+ }
+
+ // @todo: yuck
+ if (window.fromModel) {
+ el.checked = checkedAttrLooseCompare(el.value, value)
+ }
+ } else if (el.type === 'checkbox') {
+ // If we are explicitly binding a string to the :value, set the string,
+ // If the value is a boolean/array/number/null/undefined, leave it alone, it will be set to "on"
+ // automatically.
+ if (Number.isInteger(value)) {
+ el.value = value
+ } else if (! Number.isInteger(value) && ! Array.isArray(value) && typeof value !== 'boolean' && ! [null, undefined].includes(value)) {
+ el.value = String(value)
+ } else {
+ if (Array.isArray(value)) {
+ el.checked = value.some(val => checkedAttrLooseCompare(val, el.value))
+ } else {
+ el.checked = !!value
+ }
+ }
+ } else if (el.tagName === 'SELECT') {
+ updateSelect(el, value)
+ } else {
+ if (el.value === value) return
+
+ el.value = value
+ }
+}
+
+function bindClasses(el, value) {
+ if (el._x_undoAddedClasses) el._x_undoAddedClasses()
+
+ el._x_undoAddedClasses = setClasses(el, value)
+}
+
+function bindStyles(el, value) {
+ if (el._x_undoAddedStyles) el._x_undoAddedStyles()
+
+ el._x_undoAddedStyles = setStyles(el, value)
+}
+
+function bindAttribute(el, name, value) {
+ if ([null, undefined, false].includes(value) && attributeShouldntBePreservedIfFalsy(name)) {
+ el.removeAttribute(name)
+ } else {
+ if (isBooleanAttr(name)) value = name
+
+ setIfChanged(el, name, value)
+ }
+}
+
+function setIfChanged(el, attrName, value) {
+ if (el.getAttribute(attrName) != value) {
+ el.setAttribute(attrName, value)
+ }
+}
+
+function updateSelect(el, value) {
+ const arrayWrappedValue = [].concat(value).map(value => { return value + '' })
+
+ Array.from(el.options).forEach(option => {
+ option.selected = arrayWrappedValue.includes(option.value)
+ })
+}
+
+function camelCase(subject) {
+ return subject.toLowerCase().replace(/-(\w)/g, (match, char) => char.toUpperCase())
+}
+
+function checkedAttrLooseCompare(valueA, valueB) {
+ return valueA == valueB
+}
+
+function isBooleanAttr(attrName) {
+ // As per HTML spec table https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute
+ // Array roughly ordered by estimated usage
+ const booleanAttributes = [
+ 'disabled','checked','required','readonly','hidden','open', 'selected',
+ 'autofocus', 'itemscope', 'multiple', 'novalidate','allowfullscreen',
+ 'allowpaymentrequest', 'formnovalidate', 'autoplay', 'controls', 'loop',
+ 'muted', 'playsinline', 'default', 'ismap', 'reversed', 'async', 'defer',
+ 'nomodule'
+ ]
+
+ return booleanAttributes.includes(attrName)
+}
+
+function attributeShouldntBePreservedIfFalsy(name) {
+ return ! ['aria-pressed', 'aria-checked', 'aria-expanded', 'aria-selected'].includes(name)
+}
+
+export function getBinding(el, name, fallback) {
+ // First let's get it out of Alpine bound data.
+ if (el._x_bindings && el._x_bindings[name] !== undefined) return el._x_bindings[name]
+
+ // If not, we'll return the literal attribute.
+ let attr = el.getAttribute(name)
+
+ // Nothing bound:
+ if (attr === null) return typeof fallback === 'function' ? fallback() : fallback
+
+ if (isBooleanAttr(name)) {
+ return !! [name, 'true'].includes(attr)
+ }
+
+ // The case of a custom attribute with no value. Ex: <div manual>
+ if (attr === '') return true
+
+ return attr
+}