Forráskód Böngészése

Tidy up combobox hiding logic

Simone Todaro 2 éve
szülő
commit
af8b45462e
2 módosított fájl, 46 hozzáadás és 372 törlés
  1. 36 358
      index.html
  2. 10 14
      packages/ui/src/combobox.js

+ 36 - 358
index.html

@@ -9,8 +9,8 @@
 <script src="./packages/alpinejs/dist/cdn.js" defer></script>
 <script src="//cdn.tailwindcss.com"></script>
 
-<div class="px-5 py-16 min-h-[20rem] flex items-center justify-center">
-                                                    <div x-data="{
+<div
+    x-data="{
         query: '',
         selected: null,
         frameworks: [
@@ -56,385 +56,63 @@
                 : this.frameworks.filter((framework) => {
                     return framework.name.toLowerCase().includes(this.query.toLowerCase())
                 })
-        },
-        init() {
-            this.$watch('selected', (value) => {
-                console.log('watcher', value)
-                if (value) this.query = ''
-            })
-        },
-    }">
-    <div x-combobox="" x-model="selected">
-        <label x-combobox:label="" class="block text-sm text-gray-600" id="alpine-combobox-label-1">
+        }
+    }"
+
+    class="flex h-full w-screen justify-center bg-gray-50 p-12"
+>
+    <div x-combobox x-model="selected">
+        <label x-combobox:label class="block text-sm text-gray-600">
             Select framework
         </label>
 
         <div class="mt-1 relative">
             <div class="flex items-center justify-between gap-2 w-64 bg-white pl-5 pr-3 py-2.5 rounded-md shadow">
-                <input x-combobox:input="" :display-value="framework => framework.name" @change="query = $event.target.value" class="border-none p-0 focus:outline-none focus:ring-0" placeholder="Search..." id="alpine-combobox-input-1" role="combobox" tabindex="0" aria-expanded="false" aria-labelledby="alpine-combobox-label-1" aria-controls="alpine-combobox-options-1">
-                <button x-combobox:button="" class="absolute inset-y-0 right-0 flex items-center pr-2" id="alpine-combobox-button-1" aria-haspopup="true" aria-labelledby="alpine-combobox-label-1 alpine-combobox-button-1" aria-expanded="false" tabindex="-1" type="button" aria-controls="alpine-combobox-options-1">
+                <input
+                    x-combobox:input
+                    :display-value="framework => framework.name"
+                    @change="query = $event.target.value"
+                    class="border-none p-0 focus:outline-none focus:ring-0"
+                    placeholder="Search..."
+                />
+                <button x-combobox:button class="absolute inset-y-0 right-0 flex items-center pr-2">
                     <!-- Heroicons up/down -->
-                    <svg class="shrink-0 w-5 h-5 text-gray-500" viewBox="0 0 20 20" fill="none" stroke="currentColor"><path d="M7 7l3-3 3 3m0 6l-3 3-3-3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
+                    <svg class="shrink-0 w-5 h-5 text-gray-500" viewBox="0 0 20 20" fill="none" stroke="currentColor"><path d="M7 7l3-3 3 3m0 6l-3 3-3-3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
                 </button>
             </div>
 
-            <div x-combobox:options="" class="absolute right-0 w-64 max-h-60 mt-2 z-10 origin-top-right overflow-hidden bg-white border border-gray-200 rounded-md shadow-md outline-none" x-transition="" id="alpine-combobox-options-1" role="combobox" aria-labelledby="alpine-combobox-label-1" style="display: none;">
+            <div x-combobox:options x-cloak class="absolute right-0 w-64 max-h-60 mt-2 z-10 origin-top-right overflow-hidden bg-white border border-gray-200 rounded-md shadow-md outline-none" x-transition>
                 <ul class="divide-y divide-gray-100">
-                    <template x-for="framework in filteredFrameworks" :key="framework.id" hidden="">
-                        <li x-combobox:option="" :value="framework" :disabled="framework.disabled" :class="{
+                    <template
+                        x-for="framework in filteredFrameworks"
+                        :key="framework.id"
+                        hidden
+                    >
+                        <li
+                            x-combobox:option
+                            :value="framework"
+                            :disabled="framework.disabled"
+                            :class="{
                                 'bg-cyan-500/10 text-gray-900': $comboboxOption.isActive,
                                 'text-gray-600': ! $comboboxOption.isActive,
                                 'opacity-50 cursor-not-allowed': $comboboxOption.isDisabled,
-                            }" class="flex items-center cursor-default justify-between gap-2 w-full px-4 py-2 text-sm transition-colors">
+                            }"
+                            class="flex items-center cursor-default justify-between gap-2 w-full px-4 py-2 text-sm transition-colors"
+                        >
                             <span x-text="framework.name"></span>
 
-                            <span x-show="$comboboxOption.isSelected" class="text-cyan-600 font-bold">✓</span>
+                            <span x-show="$comboboxOption.isSelected" class="text-cyan-600 font-bold">&check;</span>
                         </li>
                     </template>
                 </ul>
 
-                <p x-show="filteredFrameworks.length == 0" class="px-4 py-2 text-sm text-gray-600" style="display: none;">No frameworks match your query.</p>
-            </div>
-        </div>
-        <div>local selected: <span x-text="selected?.name">Ruby on Rails</span></div>
-        <div>internal selected: <span x-text="$combobox.value?.name">Django</span></div>
-    </div>
-</div>
-                                            </div>
-<!-- <div
-            x-data="{ active: null, people: [
-                { id: 1, name: 'Wade Cooper' },
-                { id: 2, name: 'Arlene Mccoy' },
-                { id: 3, name: 'Devon Webb', disabled: true },
-                { id: 4, name: 'Tom Cook' },
-                { id: 5, name: 'Tanya Fox', disabled: true },
-                { id: 6, name: 'Hellen Schmidt' },
-                { id: 7, name: 'Caroline Schultz' },
-                { id: 8, name: 'Mason Heaney' },
-                { id: 9, name: 'Claudie Smitham' },
-                { id: 10, name: 'Emil Schaefer' },
-            ]}"
-            x-listbox
-            x-model="active"
-        >
-            <label x-listbox:label>Assigned to</label>
-
-            <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
-
-            <ul x-listbox:options options>
-                <template x-for="person in people" :key="person.id">
-                    <li
-                        :option="person.id"
-                        x-listbox:option
-                        :value="person"
-                        :disabled="person.disabled"
-                        :class="{
-                            'selected': $listboxOption.isSelected,
-                            'active': $listboxOption.isActive,
-                        }"
-                    >
-                        <span x-text="person.name"></span>
-                        <span x-show="$listboxOption.isActive">*</span>
-                    </li>
-                </template>
-            </ul>
-        </div> -->
-
-<!-- <div
-            x-data="{ active: null, people: [
-                { id: 1, name: 'Wade Cooper' },
-                { id: 2, name: 'Arlene Mccoy' },
-                { id: 3, name: 'Devon Webb' },
-                { id: 4, name: 'Tom Cook' },
-                { id: 5, name: 'Tanya Fox', disabled: true },
-                { id: 6, name: 'Hellen Schmidt' },
-                { id: 7, name: 'Caroline Schultz' },
-                { id: 8, name: 'Mason Heaney' },
-                { id: 9, name: 'Claudie Smitham' },
-                { id: 10, name: 'Emil Schaefer' },
-            ]}"
-            x-combobox
-            x-model="active"
-        >
-            <label x-combobox:label>Assigned to</label>
-
-            <input x-combobox:input :display-value="person => person.name" x-text="active ? active.name : 'Select Person'" type="text">
-            <button x-combobox:button x-text="active ? active.name : 'Select Person'"></button>
-
-            <ul x-combobox:options>
-                <template x-for="person in people" :key="person.id">
-                    <li
-                        :option="person.id"
-                        x-combobox:option
-                        :value="person"
-                        :disabled="person.disabled"
-                    >
-                        <span x-text="person.name"></span>
-                    </li>
-                </template>
-            </ul>
-        </div> -->
-
-<!-- <div
-            x-data="{
-                selected: null,
-                query: '',
-                people: [
-                    { id: 1, name: 'Wade Cooper' },
-                    { id: 2, name: 'Arlene Mccoy' },
-                    { id: 3, name: 'Devon Webb' },
-                    { id: 4, name: 'Tom Cook' },
-                    { id: 5, name: 'Tanya Fox', disabled: true },
-                    { id: 6, name: 'Hellen Schmidt' },
-                    { id: 7, name: 'Caroline Schultz' },
-                    { id: 8, name: 'Mason Heaney' },
-                    { id: 9, name: 'Claudie Smitham' },
-                    { id: 10, name: 'Emil Schaefer' },
-                ],
-                get filteredPeople() {
-                    return this.query.value === ''
-                        ? this.people
-                        : this.people.filter((person) => {
-                            return person.name.toLowerCase().includes(this.query.toLowerCase())
-                        })
-                }
-            }"
-            x-combobox
-            x-model="selected"
-            nullable
-        >
-            <span x-text="$combobox.active && $combobox.active.name"></span>
-            <label x-combobox:label>Assigned to</label>
-
-            <div>
-                <input x-combobox:input @change="query = $event.target.value" :display-value="() => person => person ? person.name : ''" type="text">
-                <button x-combobox:button x-text="selected ? selected.name : 'Select Person'">Select Person</button>
-            </div>
-
-            <ul x-combobox:options>
-                <template x-for="person in filteredPeople" :key="person.id">
-                    <li
-                        x-combobox:option
-                        :value="person"
-                        :disabled="person.disabled"
-                        :class="$comboboxOption.isActive && 'foo'"
-                        >
-                        <span x-show="$comboboxOption.isSelected">-</span>
-                        <span x-text="person.name"></span>
-                        <span x-show="$comboboxOption.isActive">*</span>
-                    </li>
-                </template>
-            </ul>
-        </div> -->
-
-<!-- Multiple -->
-<!-- <div
-            x-data="{
-                selected: [],
-                query: '',
-                people: [
-                    { id: 1, name: 'Wade Cooper' },
-                    { id: 2, name: 'Arlene Mccoy' },
-                    { id: 3, name: 'Devon Webb' },
-                    { id: 4, name: 'Tom Cook' },
-                    { id: 5, name: 'Tanya Fox', disabled: true },
-                    { id: 6, name: 'Hellen Schmidt' },
-                    { id: 7, name: 'Caroline Schultz' },
-                    { id: 8, name: 'Mason Heaney' },
-                    { id: 9, name: 'Claudie Smitham' },
-                    { id: 10, name: 'Emil Schaefer' },
-                ],
-                get filteredPeople() {
-                    return this.query.value === ''
-                        ? this.people
-                        : this.people.filter((person) => {
-                            return person.name.toLowerCase().includes(this.query.toLowerCase())
-                        })
-                }
-            }"
-            x-combobox
-            multiple
-            by="id"
-            x-model="selected"
-        >
-            <span x-text="$combobox.active && $combobox.active.name"></span>
-            <label x-combobox:label>Assigned to</label>
-
-            <div>
-                <div x-effect="selected.forEach(i => console.log(JSON.stringify(i)))">
-                    <template x-for="selectedPerson in selected" :key="selectedPerson.id">
-                        <button x-text="selectedPerson.name" @click="selected = selected.filter(i => i.id !== selectedPerson.id)"></button>
-                    </template>
-                </div>
-                <input x-combobox:input @change="query = $event.target.value" :display-value="() => person => person.map(i => i.name).join(', ')" type="text">
-                <button x-combobox:button x-text="selected.length ? selected.map(i => i.name).join(', ') : 'Selected Person'">Select Person</button>
+                <p x-show="filteredFrameworks.length == 0" class="px-4 py-2 text-sm text-gray-600">No frameworks match your query.</p>
             </div>
-
-            <ul x-combobox:options>
-                <template x-for="person in filteredPeople" :key="person.id">
-                    <li
-                        x-combobox:option
-                        :value="person"
-                        :disabled="person.disabled"
-                        :class="$comboboxOption.isActive && 'foo'"
-                    >
-                        <span x-show="$comboboxOption.isSelected">-</span>
-                        <span x-text="person.name"></span>
-                        <span x-show="$comboboxOption.isActive">*</span>
-                    </li>
-                </template>
-            </ul>
-        </div> -->
-
-<div
-    x-data="{
-        query: '',
-        people: [
-            { id: 1, name: 'Wade Cooper' },
-            { id: 2, name: 'Arlene Mccoy' },
-            { id: 3, name: 'Devon Webb' },
-            { id: 4, name: 'Tom Cook' },
-            { id: 5, name: 'Tanya Fox' },
-            { id: 6, name: 'Hellen Schmidt' },
-            { id: 7, name: 'Caroline Schultz' },
-            { id: 8, name: 'Mason Heaney' },
-            { id: 9, name: 'Claudie Smitham' },
-            { id: 10, name: 'Emil Schaefer' },
-        ],
-        activePersons: [],
-        get queryPerson() {
-            if (! this.query) return null
-
-            return {
-                id: 11, name: this.query,
-            }
-        },
-        onSubmit(e) {
-            e.preventDefault()
-            console.log([...new FormData(e.currentTarget).entries()])
-        },
-        removePerson(person) {
-            this.activePersons = this.activePersons.filter((p) => p !== person)
-        }
-    }"
-    class="flex h-full w-screen justify-center space-x-4 bg-gray-50 p-12"
->
-    <div class="w-full max-w-4xl">
-        <div class="space-y-1">
-            <form @submit="onSubmit">
-                <div x-combobox x-model="activePersons" name="people" multiple>
-                    <label x-combobox:label class="block text-sm font-medium leading-5 text-gray-700">
-                        Assigned to
-                    </label>
-
-                    <div class="relative">
-                        <div>Query: <span x-text="query"></span></div>
-                        <span class="inline-block w-full rounded-md shadow-sm">
-                            <div class="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-2 pr-10 text-left transition duration-150 ease-in-out focus-within:border-blue-700 focus-within:outline-none focus-within:ring-1 focus-within:ring-blue-700 sm:text-sm sm:leading-5">
-                                <span class="block flex flex-wrap gap-2">
-                                    <span x-show="activePersons.length === 0" class="p-0.5">Empty</span>
-                                    <template x-for="person in activePersons" :key="person.id">
-                                        <span class="flex items-center gap-1 rounded bg-blue-50 px-2 py-0.5">
-                                            <span x-text="person.name"></span>
-                                            <svg class="h-4 w-4 cursor-pointer" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" @click.stop.prevent="removePerson(person)">
-                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
-                                            </svg>
-                                        </span>
-                                    </template>
-                                    <input x-combobox:input @change="query = $event.target.value" class="border-none p-0 focus:ring-0" placeholder="Search..." />
-                                </span>
-                                <button x-combobox:button class="absolute inset-y-0 right-0 flex items-center pr-2">
-                                    <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="none" stroke="currentColor">
-                                        <path d="M7 7l3-3 3 3m0 6l-3 3-3-3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
-                                    </svg>
-                                </button>
-                            </div>
-                        </span>
-
-                        <div class="absolute mt-1 w-full rounded-md bg-white shadow-lg">
-                            <ul x-combobox:options hold class="shadow-xs max-h-60 overflow-auto rounded-md py-1 text-base leading-6 focus:outline-none sm:text-sm sm:leading-5">
-                                <template
-                                    x-for="person in people.filter((person) =>
-                                        person.name.toLowerCase().includes(query.toLowerCase())
-                                    )"
-                                    :key="person.id"
-                                >
-                                    <li x-combobox:option :value="person" class="relative cursor-default select-none py-2 pl-3 pr-9 focus:outline-none" :class="$comboboxOption.isActive ? 'bg-indigo-600 text-white' : 'text-gray-900'">
-                                        <span x-text="person.name" class="block truncate" :class="{ 'font-semibold': $comboboxOption.isSelected, 'font-normal': !$comboboxOption.isSelected }">
-                                        </span>
-                                        <span x-show="$comboboxOption.isSelected" class="absolute inset-y-0 right-0 flex items-center pr-4" :class="{ 'text-white': $comboboxOption.isActive, 'text-indigo-600': !$comboboxOption.isActive }">
-                                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
-                                                <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
-                                            </svg>
-                                        </span>
-                                    </li>
-                                </template>
-
-                                <!-- <template x-if="queryPerson">
-                                    <li x-combobox:option :value="queryPerson" class="relative cursor-default select-none py-2 pl-3 pr-9 focus:outline-none" :class="$comboboxOption.isActive ? 'bg-indigo-600 text-white' : 'text-gray-900'">
-                                        <span x-text="'Create ' + queryPerson.name" class="block truncate" :class="{ 'font-semibold': $comboboxOption.isSelected, 'font-normal': !$comboboxOption.isSelected }">
-                                        </span>
-                                        <span x-show="$comboboxOption.isSelected" class="absolute inset-y-0 right-0 flex items-center pr-4" :class="{ 'text-white': $comboboxOption.isActive, 'text-indigo-600': !$comboboxOption.isActive }">
-                                            <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
-                                                <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
-                                            </svg>
-                                        </span>
-                                    </li>
-                                </template> -->
-                            </ul>
-                        </div>
-                    </div>
-                </div>
-                <button class="mt-2 inline-flex items-center rounded border border-gray-300 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
-                    Submit
-                </button>
-            </form>
         </div>
+        <div>local selected: <span x-text="selected?.name"></span></div>
+        <div>internal selected: <span x-text="$combobox.value?.name"></span></div>
     </div>
 </div>
 
-<!-- <div x-data="{ active: true }">
-            <button @click="active = ! active">switch active</button>
-            <div
-                x-foo
-                :doo="active"
-                :class="$foo.isActive ? 'active' : 'not active'"
-            >
-                <div x-text="$foo.isActive ? 'active' : 'not active'"></div>
-            </div>
-        </div> -->
-
-<script>
-
-            // document.addEventListener('alpine:init', () => {
-            //     let directive = el => {
-            //         el.__isActive = Alpine.reactive({ state: false })
-
-            //         Alpine.effect(() => {
-            //             el.__isActive.state = Alpine.bound(el, 'doo');
-            //         })
-
-            //         el.__isOption.ready = true
-            //     }
-
-            //     directive.inline = el => {
-            //         el.__isOption = Alpine.reactive({ ready: false })
-            //     }
-
-            //     Alpine.directive('foo', directive)
-
-            //     Alpine.magic('foo', el => {
-            //         if (el.__isOption && ! el.__isOption.ready) {
-            //             return { isActive: true }
-            //         }
-
-            //         let optionEl = Alpine.findClosest(el, el => el.__isOption)
-
-            //         return {
-            //             isActive: optionEl.__isActive.state,
-            //         }
-            //     })
-            // })
-</script>
 
 </html>

+ 10 - 14
packages/ui/src/combobox.js

@@ -116,10 +116,8 @@ function handleRoot(el, Alpine) {
                 __startTyping() {
                     this.__isTyping = true
                 },
-                __stopTyping(resetInput = true) {
+                __stopTyping() {
                     this.__isTyping = false
-
-                    if (resetInput) this.$data.__resetInput()
                 },
                 __resetInput() {
                     let input = this.$refs['__input']
@@ -127,9 +125,6 @@ function handleRoot(el, Alpine) {
                     if (! input) return
 
                     let value = this.$data.__getCurrentValue()
-
-                    input.value = ''
-                    input.dispatchEvent(new Event('change'))
                     input.value = value
                 },
                 __getCurrentValue() {
@@ -240,8 +235,8 @@ function handleRoot(el, Alpine) {
                 && ! this.$refs.__button.contains(e.target)
                 && ! this.$refs.__options.contains(e.target)
             ) {
-                this.$data.__resetInput()
                 this.$data.__close()
+                this.$data.__resetInput()
             }
         }
     })
@@ -296,7 +291,7 @@ function handleInput(el, Alpine) {
             }
         },
         'x-on:blur'() {
-            this.$data.__stopTyping(false)
+            this.$data.__stopTyping()
         },
         'x-on:keydown'(e) {
             queueMicrotask(() => this.$data.__context.activateByKeyEvent(e, false, () => this.$data.__isOpen, () => this.$data.__open(), (state) => this.$data.__isTyping = state))
@@ -308,21 +303,22 @@ function handleInput(el, Alpine) {
 
             if (! this.$data.__isMultiple) {
                 this.$data.__close()
-                this.$data.__resetInput()
             }
+
+            this.$data.__resetInput()
         },
         'x-on:keydown.escape.prevent'(e) {
             if (! this.$data.__static) e.stopPropagation()
 
+            this.$data.__stopTyping()
             this.$data.__close()
+            this.$data.__resetInput()
 
-            this.$data.__stopTyping()
         },
         'x-on:keydown.tab'() {
             this.$data.__stopTyping()
-            this.$data.__resetInput()
-
             if (this.$data.__isOpen) { this.$data.__close() }
+            this.$data.__resetInput()
         },
         'x-on:keydown.backspace'(e) {
             if (this.$data.__isMultiple) return
@@ -371,8 +367,8 @@ function handleButton(el, Alpine) {
         'x-on:click'(e) {
             if (this.$data.__isDisabled) return
             if (this.$data.__isOpen) {
-                this.$data.__resetInput()
                 this.$data.__close()
+                this.$data.__resetInput()
             } else {
                 e.preventDefault()
                 this.$data.__open()
@@ -467,8 +463,8 @@ function handleOption(el, Alpine) {
             this.$data.__selectOption(this.$el)
 
             if (! this.$data.__isMultiple) {
-                this.$data.__resetInput()
                 this.$data.__close()
+                this.$data.__resetInput()
             }
 
             this.$nextTick(() => this.$refs['__input'].focus({ preventScroll: true }))