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/directives/x-model.js')
-rw-r--r--alpinejs/packages/alpinejs/src/directives/x-model.js120
1 files changed, 120 insertions, 0 deletions
diff --git a/alpinejs/packages/alpinejs/src/directives/x-model.js b/alpinejs/packages/alpinejs/src/directives/x-model.js
new file mode 100644
index 0000000..b35e124
--- /dev/null
+++ b/alpinejs/packages/alpinejs/src/directives/x-model.js
@@ -0,0 +1,120 @@
+import { evaluateLater } from '../evaluator'
+import { directive } from '../directives'
+import { mutateDom } from '../mutation'
+import bind from '../utils/bind'
+import on from '../utils/on'
+
+directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
+ let evaluate = evaluateLater(el, expression)
+ let assignmentExpression = `${expression} = rightSideOfExpression($event, ${expression})`
+ let evaluateAssignment = evaluateLater(el, assignmentExpression)
+
+ // If the element we are binding to is a select, a radio, or checkbox
+ // we'll listen for the change event instead of the "input" event.
+ var event = (el.tagName.toLowerCase() === 'select')
+ || ['checkbox', 'radio'].includes(el.type)
+ || modifiers.includes('lazy')
+ ? 'change' : 'input'
+
+ let assigmentFunction = generateAssignmentFunction(el, modifiers, expression)
+
+ let removeListener = on(el, event, modifiers, (e) => {
+ evaluateAssignment(() => {}, { scope: {
+ '$event': e,
+ rightSideOfExpression: assigmentFunction
+ }})
+ })
+
+ cleanup(() => removeListener())
+
+ // Allow programmatic overiding of x-model.
+ let evaluateSetModel = evaluateLater(el, `${expression} = __placeholder`)
+ el._x_model = {
+ get() {
+ let result
+ evaluate(value => result = value)
+ return result
+ },
+ set(value) {
+ evaluateSetModel(() => {}, { scope: { '__placeholder': value }})
+ },
+ }
+
+ el._x_forceModelUpdate = () => {
+ evaluate(value => {
+ // If nested model key is undefined, set the default value to empty string.
+ if (value === undefined && expression.match(/\./)) value = ''
+
+ // @todo: This is nasty
+ window.fromModel = true
+ mutateDom(() => bind(el, 'value', value))
+ delete window.fromModel
+ })
+ }
+
+ effect(() => {
+ // Don't modify the value of the input if it's focused.
+ if (modifiers.includes('unintrusive') && document.activeElement.isSameNode(el)) return
+
+ el._x_forceModelUpdate()
+ })
+})
+
+function generateAssignmentFunction(el, modifiers, expression) {
+ if (el.type === 'radio') {
+ // Radio buttons only work properly when they share a name attribute.
+ // People might assume we take care of that for them, because
+ // they already set a shared "x-model" attribute.
+ mutateDom(() => {
+ if (! el.hasAttribute('name')) el.setAttribute('name', expression)
+ })
+ }
+
+ return (event, currentValue) => {
+ return mutateDom(() => {
+ // Check for event.detail due to an issue where IE11 handles other events as a CustomEvent.
+ // Safari autofill triggers event as CustomEvent and assigns value to target
+ // so we return event.target.value instead of event.detail
+ if (event instanceof CustomEvent && event.detail !== undefined) {
+ return event.detail || event.target.value
+ } else if (el.type === 'checkbox') {
+ // If the data we are binding to is an array, toggle its value inside the array.
+ if (Array.isArray(currentValue)) {
+ let newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value
+
+ return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => ! checkedAttrLooseCompare(el, newValue))
+ } else {
+ return event.target.checked
+ }
+ } else if (el.tagName.toLowerCase() === 'select' && el.multiple) {
+ return modifiers.includes('number')
+ ? Array.from(event.target.selectedOptions).map(option => {
+ let rawValue = option.value || option.text
+ return safeParseNumber(rawValue)
+ })
+ : Array.from(event.target.selectedOptions).map(option => {
+ return option.value || option.text
+ })
+ } else {
+ let rawValue = event.target.value
+ return modifiers.includes('number')
+ ? safeParseNumber(rawValue)
+ : (modifiers.includes('trim') ? rawValue.trim() : rawValue)
+ }
+ })
+ }
+}
+
+function safeParseNumber(rawValue) {
+ let number = rawValue ? parseFloat(rawValue) : null
+
+ return isNumeric(number) ? number : rawValue
+}
+
+function checkedAttrLooseCompare(valueA, valueB) {
+ return valueA == valueB
+}
+
+function isNumeric(subject){
+ return ! Array.isArray(subject) && ! isNaN(subject)
+}