Przeglądaj źródła

Accessibility improvements

Simone Todaro 2 lat temu
rodzic
commit
634b856661
1 zmienionych plików z 82 dodań i 80 usunięć
  1. 82 80
      packages/ui/src/combobox.js

+ 82 - 80
packages/ui/src/combobox.js

@@ -85,24 +85,6 @@ function handleRoot(el, Alpine) {
                 __inputName: null,
                 __isTyping: false,
                 __hold: false,
-                __pointer: {
-                    lastPosition: [-1, -1],
-
-                    wasMoved(e) {
-                        let newPosition = [e.screenX, e.screenY]
-
-                        if (this.lastPosition[0] === newPosition[0] && this.lastPosition[1] === newPosition[1]) {
-                            return false
-                        }
-
-                        this.lastPosition = newPosition
-                        return true
-                    },
-
-                    update(e) {
-                        this.lastPosition = [e.screenX, e.screenY]
-                    },
-                },
 
                 /**
                  * Combobox initialization...
@@ -140,18 +122,20 @@ function handleRoot(el, Alpine) {
                     if (resetInput) this.$data.__resetInput()
                 },
                 __resetInput() {
-                    let input = this.$refs.__input
+                    let input = this.$refs['__input']
+
                     if (! input) return
 
                     let value = this.$data.__getCurrentValue()
 
-                    input.value = value
+                    input.value = ''
                     input.dispatchEvent(new Event('change'))
+                    input.value = value
                 },
                 __getCurrentValue() {
-                    if (! this.$refs.__input) return ''
+                    if (! this.$refs['__input']) return ''
                     if (! this.__value) return ''
-                    if (this.$data.__displayValue && this.__value !== undefined) return this.$data.__displayValue(this.__value)
+                    if (this.$data.__displayValue) return this.$data.__displayValue(this.__value)
                     if (typeof this.__value === 'string') return this.__value
                     return ''
                 },
@@ -165,7 +149,7 @@ function handleRoot(el, Alpine) {
                     // Probably because Alpine adds an extra tick when x-showing for @click.outside
                     let nextTick = callback => requestAnimationFrame(() => requestAnimationFrame(callback))
 
-                    nextTick(() => this.$refs.__input.focus({ preventScroll: true }))
+                    nextTick(() => this.$refs['__input'].focus({ preventScroll: true }))
                 },
                 __close() {
                     this.__isOpen = false
@@ -249,9 +233,8 @@ function handleRoot(el, Alpine) {
                 },
             }
         },
-
         // Register event listeners..
-        '@mousedown.window'(e) {
+        'x-on:mousedown.window'(e) {
             if (
                 !! ! this.$refs.__input.contains(e.target)
                 && ! this.$refs.__button.contains(e.target)
@@ -268,24 +251,36 @@ function handleInput(el, Alpine) {
     Alpine.bind(el, {
         // Setup...
         'x-ref': '__input',
-        ':id'() { return this.$id('alpine-combobox-input') },
+        'x-bind:id'() {
+            return this.$id('alpine-combobox-input')
+        },
 
         // Accessibility attributes...
         'role': 'combobox',
         'tabindex': '0',
+        'aria-autocomplete': 'list',
+
         // We need to defer this evaluation a bit because $refs that get declared later
         // in the DOM aren't available yet when x-ref is the result of an Alpine.bind object.
-        async ':aria-controls'() { return await microtask(() => this.$refs.__options && this.$refs.__options.id) },
-        ':aria-expanded'() { return this.$data.__isDisabled ? undefined : this.$data.__isOpen },
-        ':aria-multiselectable'() { return this.$data.__isMultiple ? true : undefined },
-        ':aria-activedescendant'() {
+        async 'x-bind:aria-controls'() {
+            return await microtask(() => this.$refs.__options && this.$refs.__options.id)
+        },
+        'x-bind:aria-expanded'() {
+            return this.$data.__isDisabled ? undefined : this.$data.__isOpen
+        },
+        'x-bind:aria-multiselectable'() {
+            return this.$data.__isMultiple ? true : undefined
+        },
+        'x-bind:aria-activedescendant'() {
             if (! this.$data.__context.hasActive()) return
 
             let active = this.$data.__context.getActiveItem()
 
             return active ? active.el.id : null
         },
-        ':aria-labelledby'() { return this.$refs.__label ? this.$refs.__label.id : (this.$refs.__button ? this.$refs.__button.id : null) },
+        'x-bind:aria-labelledby'() {
+            return this.$refs.__label ? this.$refs.__label.id : (this.$refs.__button ? this.$refs.__button.id : null)
+        },
 
         // Initialize...
         'x-init'() {
@@ -294,37 +289,42 @@ function handleInput(el, Alpine) {
         },
 
         // Register listeners...
-        '@input.stop'(e) {
-            this.$data.__open(); this.$dispatch('change')
+        'x-on:input.stop'(e) {
+            if(this.$data.__isTyping) {
+                this.$data.__open();
+                this.$dispatch('change')
+            }
+        },
+        'x-on:blur'() {
+            this.$data.__stopTyping(false)
         },
-        '@blur'() { this.$data.__stopTyping(false) },
-        '@keydown'(e) {
+        'x-on:keydown'(e) {
             queueMicrotask(() => this.$data.__context.activateByKeyEvent(e, false, () => this.$data.__isOpen, () => this.$data.__open(), (state) => this.$data.__isTyping = state))
-         },
-        '@keydown.enter.prevent.stop'() {
+        },
+        'x-on:keydown.enter.prevent.stop'() {
             this.$data.__selectActive()
 
-            this.$data.isTyping = false
+            this.$data.__stopTyping()
 
             if (! this.$data.__isMultiple) {
                 this.$data.__close()
                 this.$data.__resetInput()
             }
         },
-        '@keydown.escape.prevent'(e) {
+        'x-on:keydown.escape.prevent'(e) {
             if (! this.$data.__static) e.stopPropagation()
 
             this.$data.__close()
 
             this.$data.__stopTyping()
         },
-        '@keydown.tab'() {
+        'x-on:keydown.tab'() {
             this.$data.__stopTyping()
             this.$data.__resetInput()
 
             if (this.$data.__isOpen) { this.$data.__close() }
         },
-        '@keydown.backspace'(e) {
+        'x-on:keydown.backspace'(e) {
             if (this.$data.__isMultiple) return
             if (! this.$data.__nullable) return
 
@@ -350,23 +350,25 @@ function handleButton(el, Alpine) {
     Alpine.bind(el, {
         // Setup...
         'x-ref': '__button',
-        ':id'() { return this.$id('alpine-combobox-button') },
+        'x-bind:id'() {
+            return this.$id('alpine-combobox-button')
+        },
 
         // Accessibility attributes...
         'aria-haspopup': 'true',
         // We need to defer this evaluation a bit because $refs that get declared later
         // in the DOM aren't available yet when x-ref is the result of an Alpine.bind object.
-        async ':aria-controls'() { return await microtask(() => this.$refs.__options && this.$refs.__options.id) },
-        ':aria-labelledby'() { return this.$refs.__label ? [this.$refs.__label.id, this.$el.id].join(' ') : null },
-        ':aria-expanded'() { return this.$data.__isDisabled ? null : this.$data.__isOpen },
-        ':disabled'() { return this.$data.__isDisabled },
+        async 'x-bind:aria-controls'() { return await microtask(() => this.$refs.__options && this.$refs.__options.id) },
+        'x-bind:aria-labelledby'() { return this.$refs.__label ? [this.$refs.__label.id, this.$el.id].join(' ') : null },
+        'x-bind:aria-expanded'() { return this.$data.__isDisabled ? null : this.$data.__isOpen },
+        'x-bind:disabled'() { return this.$data.__isDisabled },
         'tabindex': '-1',
 
         // Initialize....
         'x-init'() { if (this.$el.tagName.toLowerCase() === 'button' && ! this.$el.hasAttribute('type')) this.$el.type = 'button' },
 
         // Register listeners...
-        '@click'(e) {
+        'x-on:click'(e) {
             if (this.$data.__isDisabled) return
             if (this.$data.__isOpen) {
                 this.$data.__resetInput()
@@ -384,8 +386,10 @@ function handleButton(el, Alpine) {
 function handleLabel(el, Alpine) {
     Alpine.bind(el, {
         'x-ref': '__label',
-        ':id'() { return this.$id('alpine-combobox-label') },
-        '@click'() { this.$refs.__input.focus({ preventScroll: true }) },
+        'x-bind:id'() {
+            return this.$id('alpine-combobox-label')
+        },
+        'x-on:click'() { this.$refs.__input.focus({ preventScroll: true }) },
     })
 }
 
@@ -393,12 +397,14 @@ function handleOptions(el, Alpine) {
     Alpine.bind(el, {
         // Setup...
         'x-ref': '__options',
-        ':id'() { return this.$id('alpine-combobox-options') },
+        'x-bind:id'() {
+            return this.$id('alpine-combobox-options')
+        },
 
         // Accessibility attributes...
         'role': 'combobox',
-        ':aria-labelledby'() { return this.$refs.__label ? this.$refs.__label.id : (this.$refs.__button ? this.$refs.__button.id : null) },
-        ':aria-activedescendant'() {
+        'x-bind:aria-labelledby'() { return this.$refs.__label ? this.$refs.__label.id : (this.$refs.__button ? this.$refs.__button.id : null) },
+        'x-bind:aria-activedescendant'() {
             if (! this.$data.__context.hasActive()) return
 
             let active = this.$data.__context.getActiveItem()
@@ -415,37 +421,38 @@ function handleOptions(el, Alpine) {
             }
         },
 
-        'x-show'() { return this.$data.__isStatic ? true : this.$data.__isOpen },
+        'x-show'() {
+            return this.$data.__isStatic ? true : this.$data.__isOpen
+        },
     })
 }
 
 function handleOption(el, Alpine) {
     Alpine.bind(el, {
         // Setup...
-        'x-id'() { return ['alpine-combobox-option'] },
-        ':id'() { return this.$id('alpine-combobox-option') },
+        'x-id'() {
+            return ['alpine-combobox-option']
+        },
+        'x-bind:id'() {
+            return this.$id('alpine-combobox-option')
+        },
 
         // Accessibility attributes...
         'role': 'option',
-        ':tabindex'() { return this.$comboboxOption.isDisabled ? undefined : '-1' },
-        ':aria-selected'() { return this.$comboboxOption.isSelected },
-        ':aria-disabled'() { return this.$comboboxOption.isDisabled },
+        'x-bind:tabindex'() { return this.$comboboxOption.isDisabled ? undefined : '-1' },
+        'x-bind:aria-selected'() { return this.$comboboxOption.isSelected },
+        'x-bind:aria-disabled'() { return this.$comboboxOption.isDisabled },
 
         // Initialize...
         'x-data'() {
             return {
                 init() {
-                    let key = el.__optionKey = (Math.random() + 1).toString(36).substring(7)
+                    let key = this.$el.__optionKey = (Math.random() + 1).toString(36).substring(7)
 
-                    let value = Alpine.extractProp(el, 'value')
-                    let disabled = Alpine.extractProp(el, 'disabled', false, false)
+                    let value = Alpine.extractProp(this.$el, 'value')
+                    let disabled = Alpine.extractProp(this.$el, 'disabled', false, false)
 
-                    this.$data.__context.registerItem(key, el, value, disabled)
-
-                    // @todo: make sure the "destroy" hook is good enough and we don't need this...
-                    // el._x_forCleanup = () => {
-                        // this.$data.__context.unregisterItem(key)
-                    // }
+                    this.$data.__context.registerItem(key, this.$el, value, disabled)
                 },
                 destroy() {
                     this.$data.__context.unregisterItem(this.$el.__optionKey)
@@ -454,30 +461,24 @@ function handleOption(el, Alpine) {
         },
 
         // Register listeners...
-        '@click'() {
+        'x-on:click'() {
             if (this.$comboboxOption.isDisabled) return;
 
-            this.$data.__selectOption(el)
+            this.$data.__selectOption(this.$el)
 
             if (! this.$data.__isMultiple) {
                 this.$data.__resetInput()
                 this.$data.__close()
             }
 
-            this.$nextTick(() => this.$refs.__input.focus({ preventScroll: true }))
+            this.$nextTick(() => this.$refs['__input'].focus({ preventScroll: true }))
         },
+        'x-on:mousemove'(e) {
+            if (this.$data.__context.isActiveEl(this.$el)) return
 
-        // @todo: this is a memory leak for _x_cleanups...
-        '@mouseenter'(e) {
-            this.$data.__pointer.update(e)
+            this.$data.__context.activateEl(this.$el)
         },
-        '@mousemove'(e) {
-            if (!this.$data.__pointer.wasMoved(e)) return
-
-            this.$data.__context.activateEl(el)
-        },
-        '@mouseleave'(e) {
-            if (!this.$data.__pointer.wasMoved(e)) return
+        'x-on:mouseleave'(e) {
             if (this.$data.__hold) return
 
             this.$data.__context.deactivate()
@@ -485,6 +486,7 @@ function handleOption(el, Alpine) {
     })
 }
 
+
 // Little utility to defer a callback into the microtask queue...
 function microtask(callback) {
     return new Promise(resolve => queueMicrotask(() => resolve(callback())))