menu

Questions & Answers

How does MaterialUI do this?

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 class="input-autocomplete-address">
        {{ 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" class="input-autocomplete-dropdown">
        <div class="input-autocomplete-results">
          <div v-for="result in results" :key="result.id" @click="onClick(result)" class="input-autocomplete-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>
Comments:
2023-01-24 23:20:02
@LawrenceCherone I have added my source for vue autocomplete component I am manually building
2023-01-24 23:20:02
based on the code in github, github.com/mui/material-ui/blob/master/packages/mui-base/src‌​/… it appears that they are providing a custom event to handle when blur happens. I don't have time to do a deep dive right this moment, but I hope this points you in the right direction. It looks like they are calling this event for onBlur(), and then using flags from other events to determine when they can keep the focus alive.
2023-01-24 23:20:02
and here at github.com/mui/material-ui/blob/master/packages/mui-base/src‌​/… they are using preventDefault().
Answers(2) :

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>

Comments:
2023-01-24 23:20:02
This has a lot of other problems (I dont want to trigger the blur event). I dont think materialUI is doing this anyway because if you put the cursor in the middle of some text it never loses it's position, so I dont think its refocused (I could be wrong)

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>
Comments:
2023-01-24 23:20:02
I don't believe refocusing isn't the answer, please check my comment in the other answer below.