@ekzotika

Как перемещаться по элементам вложенных массивов, используя стрелки?

Я пытаюсь передвигаться по дочерним элементам массива, используя стрелки вверх / вниз на клавиатуре, но я не понимаю, как конкретно обратиться к дочерним элементам.

Массив:

options: [
  {name: 'Выход детали из строя в процессе эксплуатации', value: null, children: [
    {name: 'Увеличение зазора, люфт (дробь/стуки)', value: 53},
    {name: 'Обрыв детали', value: 54}
  ]},
  {name: 'Поломка при установке', value: null},
  {name: 'Брак до установки', value: null, children: [
    {name: 'Недокомплект', value: 55},
    {name: 'Заводской брак (замятия, отсутствие резьбы, пробой пыльника и т.д.)',value: 56 }
  ]},
],

Вывод списка:

<div v-if="areOptionsVisible"
     :style="{maxHeight: maxHeight, overflow: 'auto', zIndex: zIndex}"
     class="w-autocomplete__items">
    <div v-for="option in filteredOptions" class="w-autocomplete__item_first" >
        {{ option.name }}

            <div v-for="item in option.children" class="w-autocomplete__item"
                :class="{'w-autocomplete__item_active': currentIndex === item}"
                @mouseenter="setActive(item)"
                 @keyup.up="changeCurrent('up', item)"
                 @keyup.down="changeCurrent('down', item)"
                 @click="doChoose(item)">
                {{ item.name }}
            </div>
    </div>
</div>

Методы:

computed: {
        filteredOptions(){
            if (this.searchText === ''){
                return this.options;
            } else{
               return this.options.flatMap(option => {
                   return option.children;
                }).filter(elem => {
                return elem && elem.name.toLowerCase().includes(this.searchText.toLowerCase());
             });
            }
        },
    },

    methods: {
        setActive(index) {
            this.currentIndex = index;
        },
        changeCurrent(direction) {


            if (this.currentIndex === null) {
                this.currentIndex = 0;
            } else if (direction === 'up') {
                if (this.currentIndex - 1 >= 0) {
                    this.currentIndex -= 1;
                }
            } else if (direction === 'down') {
                if (this.currentIndex + 1 <= this.options.length - 1) {
                    this.currentIndex += 1;
                }
            }
        },
        doChoose(item) {
            this.chosenItem = this.currentIndex;
            this.areOptionsVisible = false;
            this.searchText = item.name;
        },
               
    },

currentIndex - это объект, каждый конкретный дочерний элемент при наведении. Я хочу двигаться со стрелками так же, как при наведении, исключая элементы верхнего уровня. Как это сделать?
  • Вопрос задан
  • 292 просмотра
Пригласить эксперта
Ответы на вопрос 1
0xD34F
@0xD34F Куратор тега Vue.js
Вариант раз. Нужно знать два индекса - элемента внешнего массива и элемента внутреннего, добавим в компонент соответствующее свойство:

data: () => ({
  active: [ 0, -1 ],
  ...

При переходе к следующему/предыдущему элементу изменяем внутренний индекс, если вышли за границу внутреннего массива, переходим к следующим/предыдущим элементам внешнего массива, пока не встретится такой, у которого внутри что-то есть; внутренний индекс при этом сбрасываем в крайнее положение - чтобы соответствовал первому или последнему элементу вложенного массива, в зависимости от направления движения:

methods: {
  onKeyDown(e) {
    const { items } = this;
    const step = ({
      ArrowUp: -1,
      ArrowDown: 1,
    })[e.key];

    if (step && items.some(n => n.children?.length)) {
      let iItem = this.active[0];
      let iChild = this.active[1] + step;

      while (!items[iItem].children?.[iChild]) {
        iItem = (iItem + step + items.length) % items.length;
        iChild = step === 1 ? 0 : ~-items[iItem].children?.length;
      }

      this.active = [ iItem, iChild ];
    }
  },
  ...

Осталось выделить как активный тот DOM-элемент, индексы которого совпадают с сохранёнными в экземпляре компонента, назначаем класс при равенстве обоих пар значений:

<ul>
  <li v-for="(item, iItem) in items">
    <ul>
      <li
        v-for="(child, iChild) in item.children"
        :class="{ active: active[0] === iItem && active[1] === iChild }"
        ...

Вариант два - развернём в общий плоский массив элементы вложенных массивов:

computed: {
  children() {
    return this.items.flatMap(n => n.children ?? []);
  },
  ...

Тогда можно будет обойтись одним индексом:

data: () => ({
  active: null,
  ...

Которому просто делаем +/- 1:

methods: {
  onKeyDown({ key }) {
    const { length } = this.children;
    const step = 
      key === 'ArrowUp'   ? -1 :
      key === 'ArrowDown' ?  1 :
                             0;

    if (step && length) {
      const active = this.active ?? (step === 1 ? -1 : length);
      this.active = Math.max(0, Math.min(length - 1, active + step));
    }
  },
  ...

Если элемент вложенного массива совпадает с тем, который можно достать из плоского массива, воспользовавшись индексом, добавим соответствующему DOM-элементу класс:

<ul>
  <li v-for="item in items">
    <ul>
      <li
        v-for="child in item.children"
        :class="{ active: children[active] === child }"
        ...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы