Caleb Porzio 3 年之前
父节点
当前提交
23eebc1441

+ 1 - 0
packages/alpinejs/src/directives.js

@@ -165,6 +165,7 @@ let directiveOrder = [
     'init',
     'for',
     'model',
+    'modelable',
     'transition',
     'show',
     'if',

+ 1 - 0
packages/alpinejs/src/directives/index.js

@@ -1,4 +1,5 @@
 import './x-transition'
+import './x-modelable'
 import './x-teleport'
 import './x-ignore'
 import './x-effect'

+ 28 - 0
packages/alpinejs/src/directives/x-modelable.js

@@ -0,0 +1,28 @@
+import { evaluateLater } from '../evaluator'
+import { directive } from '../directives'
+
+directive('modelable', (el, { expression }, { effect, evaluate, evaluateLater }) => {
+    let func = evaluateLater(expression)
+    let innerGet = () => { let result; func(i => result = i); return result; }
+    let evaluateInnerSet = evaluateLater(`${expression} = __placeholder`)
+    let innerSet = val => evaluateInnerSet(() => {}, { scope: { '__placeholder': val }})
+
+    let initialValue = innerGet()
+
+    // Allow packages like Livewire to hook into $modelable. Ex: `wire:model.defer`
+    if (el._x_modelable_hook) initialValue = el._x_modelable_hook(initialValue)
+
+    innerSet(initialValue)
+
+    queueMicrotask(() => {
+        if (! el._x_model) return
+    
+        // let outerGetter = el._x_model.get
+        // let outerSetter = el._x_model.set
+        let outerGet = el._x_model.get
+        let outerSet = el._x_model.set
+    
+        effect(() => innerSet(outerGet()))
+        effect(() => outerSet(innerGet()))
+    })
+})

+ 66 - 0
tests/cypress/integration/directives/x-modelable.spec.js

@@ -0,0 +1,66 @@
+import { haveText, html, test } from '../../utils'
+
+test('can expose data for x-model binding',
+    html`
+        <div x-data="{ outer: 'foo' }">
+            <div x-data="{ inner: 'bar' }" x-modelable="inner" x-model="outer">
+                <h1 x-text="outer"></h1>
+                <h2 x-text="inner"></h2>
+
+                <button @click="inner = 'bob'" id="1">change inner</button>
+                <button @click="outer = 'lob'" id="2">change outer</button>
+            </div>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('foo'))
+        get('h2').should(haveText('foo'))
+        get('#1').click()
+        get('h1').should(haveText('bob'))
+        get('h2').should(haveText('bob'))
+        get('#2').click()
+        get('h1').should(haveText('lob'))
+        get('h2').should(haveText('lob'))
+    }
+)
+
+test('Something like Livewire can hook into x-modelable',
+    html`
+        <h1 x-data="{ value: 'bar' }" x-modelable="value" x-init="
+            () => {}; $el._x_modelable_hook = (val) => {
+                return val.toUpperCase()
+            }
+        ">
+            <span x-text="value"></span>
+        </h1>
+    `,
+    ({ get }) => {
+        get('span').should(haveText('BAR'))
+    }
+)
+
+test('x-modelable works when inside x-bind and x-model is outside',
+    html`
+        <div x-data="{ outer: 'foo', thing: {
+            ['x-modelable']: 'inner',
+        } }">
+            <div x-data="{ inner: 'bar' }" x-bind="thing" x-model="outer">
+                <h1 x-text="outer"></h1>
+                <h2 x-text="inner"></h2>
+
+                <button @click="inner = 'bob'" id="1">change inner</button>
+                <button @click="outer = 'lob'" id="2">change outer</button>
+            </div>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('foo'))
+        get('h2').should(haveText('foo'))
+        get('#1').click()
+        get('h1').should(haveText('bob'))
+        get('h2').should(haveText('bob'))
+        get('#2').click()
+        get('h1').should(haveText('lob'))
+        get('h2').should(haveText('lob'))
+    }
+)