Home > Software design >  How does MaterialUI do this?
How does MaterialUI do this?

Time:01-25

If you look at their autocomplete component: https://mui.com/material-ui/react-autocomplete/

After you click a suggestion in the dropdown, the input box keeps focus... How do they do that? In every variation of this in my own vue app (not using material UI) I can't get the click event to stop an input from losing focus.

I have tried googling this for quite some time and there is no clear solution that I see. For example, people suggest mousedown/touchstart but that would break scrolling (via dragging the dropdown). MaterialUI obviously doesn't have this problem, and doesn't seem to be using mousedown.

I've tried analyzing the events using Chrome dev tools and I can only see a single click event, but with minified code it's difficult to tell what's going on.

Vuetify also does this: https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VAutocomplete/VAutocomplete.ts

Edit Here's what Im doing:

  <app-input-autocomplete 
    @autocomplete-select="onSelect"
    @autocomplete-close="onClose"
    :open="open">
    <template #default="{ result }">
      <div >
        {{ result.address }}
      </div>
    </template>
  </app-input-autocomplete>

and then in app-input-autocomplete:

<template>
  <app-input
    @focus="onFocus"
    @blur="onBlur"
    v-bind="$attrs">
    <template #underInput>
      <div ref="dropdown" v-show="open" >
        <div >
          <div v-for="result in results" :key="result.id" @click="onClick(result)" >
            <slot :result="result" />
          </div>
        </div>
      </div>
    </template>
  </app-input>
</template>

<script>
import { ref, toRef } from 'vue';
import AppInput from '@/components/AppInput.vue';
import { onClickOutside } from '@vueuse/core';

export default {
  components: {
    AppInput,
  },
  inheritAttrs: false,
  props: {
    open: {
      type: Boolean,
      default: false,
    },
    results: {
      type: Array,
      default: () => ([]),
    },
  },
  emits: ['autocomplete-close', 'autocomplete-select'],
  setup(props, { emit }) {
    const dropdown = ref(null);

    const open = toRef(props, 'open');

    const focused = ref(false);

    onClickOutside(dropdown, () => {
      if (!focused.value && open.value) {
        emit('autocomplete-close');
      }
    });

    return {
      dropdown,
      focused,
    };
  },
  methods: {
    onFocus() {
      this.focused = true;
    },
    onBlur() {
      this.focused = false;
    },
    onClick(result) {
      this.$emit('autocomplete-select', result);
    },
  },
};
</script>

CodePudding user response:

Maybe something like this simple demo :

const app = Vue.createApp({
  data() {
    return {
      items: [1,2,3],
      selected: null,
      show: false
    };
  },
  methods: {
    keep(item) {
      this.show = false
      this.selected = item
      this.$refs.sel.focus()
    }
  },
})
app.mount('#demo')
select {
  width: 100px;
}
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="demo">
  <input ref="sel" v-model="selected" @click="show = !show" />
  <div v-if="show">
    <div v-for="item in items"  @click="keep(item)">
      {{ item }}
    </div>
  </div>
</div>

CodePudding user response:

It's simple. You need to define a Template Ref in your vue application and pass the ref to the input element. every time user selects any option(or suggestion) you need to focus on the input element programmatically.

Vue 2:

<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input" />
</template>

Vue 3:

<script>
import { ref } from 'vue'

export default {
  setup() {
    const inputRef = ref(null)

    return {
      inputRef
    }
  },
  mounted() {
    this.inputRef.value.focus()
  }
}
</script>

<template>
  <input ref="inputRef" />
</template>
  • Related