Ver Fonte

Custom directive order (#3307)

* Add failing test

* Allow custom order for custom directives

* Update documentation
Simone Todaro há 2 anos atrás
pai
commit
258a60a291

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

@@ -17,6 +17,23 @@ let directiveHandlers = {}
 
 
 export function directive(name, callback) {
 export function directive(name, callback) {
     directiveHandlers[name] = callback
     directiveHandlers[name] = callback
+
+    return {
+        before(directive) {
+            if (!directiveHandlers[directive]) {
+                console.warn(
+                    "Cannot find directive `${directive}`. "
+                    + "`${name}` will use the default order of execution"
+                );
+                return;
+            }
+            const pos = directiveOrder.indexOf(directive)
+                ?? directiveOrder.indexOf('DEFAULT');
+            if (pos >= 0) {
+                directiveOrder.splice(pos, 0, name);
+            }
+        }
+    }
 }
 }
 
 
 export function directives(el, attributes, originalAttributeOverride) {
 export function directives(el, attributes, originalAttributeOverride) {

+ 18 - 0
packages/docs/src/en/advanced/extending.md

@@ -224,6 +224,24 @@ Alpine.directive('...', (el, {}, { cleanup }) => {
 
 
 Now if the directive is removed from this element or the element is removed itself, the event listener will be removed as well.
 Now if the directive is removed from this element or the element is removed itself, the event listener will be removed as well.
 
 
+<a name="custom-order"></a>
+### Custom order
+
+By default, any new directive will run after the majority of the standard ones (with the exception of `x-teleport`). This is usually acceptable but some times you might need to run your custom directive before another specific one.
+This can be achieved by chaining the `.before() function to `Alpine.directive()` and specifing which directive needs to run after your custom one.
+ 
+```js
+Alpine.directive('foo', (el, { value, modifiers, expression }) => {
+    Alpine.addScopeToNode(el, {foo: 'bar'})
+}).before('bind')
+```
+```alpine
+<div x-data>
+    <span x-foo x-bind:foo="foo"></span>
+</div>
+```
+> Note, the directive name must be written without the `x-` prefix (or any other custom prefix you may use).
+
 <a name="custom-magics"></a>
 <a name="custom-magics"></a>
 ## Custom magics
 ## Custom magics
 
 

+ 15 - 1
tests/cypress/integration/custom-directives.spec.js

@@ -1,4 +1,4 @@
-import { haveText, html, test } from '../utils'
+import { haveText, haveAttribute, html, test } from '../utils'
 
 
 test('can register custom directive',
 test('can register custom directive',
     [html`
     [html`
@@ -49,3 +49,17 @@ test('directives are auto cleaned up',
         get('h1').should(haveText('7'))
         get('h1').should(haveText('7'))
     }
     }
 )
 )
+
+test('can register a custom directive before an existing one',
+    [html`
+        <div x-data>
+            <span x-foo x-bind:foo="foo"></span>
+        </div>
+    `,
+    `
+        Alpine.directive('foo', (el, { value, modifiers, expression }) => {
+            Alpine.addScopeToNode(el, {foo: 'bar'})
+        }).before('bind')
+    `],
+    ({ get }) => get('span').should(haveAttribute('foo', 'bar'))
+)