HEX
Server: Microsoft-IIS/10.0
System: Windows NT ITPWINWEBSVR22 10.0 build 20348 (Windows Server 2022) AMD64
User: www.conferencesearch.co.uk (0)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: D:/web/matomo.vdk/plugins/CorePluginsAdmin/vue/src/FormField/FieldExpandableSelect.vue
<!--
  Matomo - free/libre analytics platform

  @link    https://matomo.org
  @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
-->

<template>
  <div class="expandableSelector" v-focus-anywhere-but-here="{ blur: onBlur }">
    <div @click="showSelect = !showSelect" class="select-wrapper">
      <svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
        <path d="M7 10l5 5 5-5z"></path><path d="M0 0h24v24H0z" fill="none"></path>
      </svg>
      <input type="text" class="select-dropdown" readonly="readonly" :value="modelValueText"/>
    </div>

    <div v-show="showSelect" class="expandableList z-depth-2">

      <div class="searchContainer">
        <input
          type="text"
          placeholder="Search"
          v-model="searchTerm"
          class="expandableSearch browser-default"
          v-focus-if="{ focused: showSelect }"
        />
      </div>
      <ul class="collection firstLevel">
        <li
          v-for="(options, index) in availableOptions"
          class="collection-item"
          v-show="options.values.filter(x =>
           x.value.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1).length"
          :key="index"
        >
          <h4
            class="expandableListCategory"
            @click="onCategoryClicked(options)"
          >
            {{ options.group }}
            <span
              class="secondary-content"
              :class='{
                "icon-chevron-right": showCategory !== options.group,
                "icon-chevron-down": showCategory === options.group
              }'
            />
          </h4>

          <ul v-show="showCategory === options.group || searchTerm" class="collection secondLevel">
            <li
              class="expandableListItem collection-item valign-wrapper"
              v-for="children in options.values.filter(x =>
              x.value.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1)"
              :key="children.key"
              @click="onValueClicked(children)"
            >
              <span class="primary-content">{{ children.value }}</span>
              <span
                v-show="children.tooltip"
                :title="children.tooltip"
                class="secondary-content icon-help"
              ></span>
            </li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { FocusAnywhereButHere, FocusIf } from 'CoreHome';
import AbortableModifiers from './AbortableModifiers';

interface SelectValueInfo {
  key: unknown;
}

interface AvailableOptions {
  group: string;
  key: string|number;
  value: unknown;
  tooltip?: string;
}

interface Option {
  key: string|number;
  value: unknown;
  tooltip?: string;
}

interface OptionGroup {
  group: string;
  values: Option[];
}

export function getAvailableOptions(
  availableValues: Record<string, unknown>|null,
): OptionGroup[] {
  const flatValues: OptionGroup[] = [];

  if (!availableValues) {
    return flatValues;
  }

  const groups: Record<string, OptionGroup> = {};
  Object.values(availableValues).forEach((uncastedValue) => {
    const value = uncastedValue as AvailableOptions;
    const group = value.group || '';

    if (!(group in groups) || !groups[group]) {
      groups[group] = { values: [], group };
    }

    const formatted: Option = { key: value.key, value: value.value };

    if ('tooltip' in value && value.tooltip) {
      formatted.tooltip = value.tooltip;
    }

    groups[group].values.push(formatted);
  });

  Object.values(groups).forEach((group) => {
    if (group.values.length) {
      flatValues.push(group);
    }
  });

  return flatValues;
}

export default defineComponent({
  props: {
    modelValue: [Number, String],
    modelModifiers: Object,
    availableOptions: Array,
    title: String,
  },
  directives: {
    FocusAnywhereButHere,
    FocusIf,
  },
  inheritAttrs: false,
  emits: ['update:modelValue'],
  data() {
    return {
      showSelect: false,
      searchTerm: '',
      showCategory: '',
    };
  },
  computed: {
    modelValueText() {
      if (this.title) {
        return this.title;
      }

      const key = this.modelValue;
      const availableOptions = (this.availableOptions || []) as OptionGroup[];

      let keyItem!: { key: string|number, value: unknown }|undefined;
      availableOptions.some((option) => {
        keyItem = option.values.find((item) => item.key === key);
        return keyItem; // stop iterating if found
      });

      if (keyItem) {
        return keyItem.value ? `${keyItem.value}` : '';
      }
      return key ? `${key}` : '';
    },
  },
  methods: {
    onBlur() {
      this.showSelect = false;
    },
    onCategoryClicked(options: AvailableOptions) {
      if (this.showCategory === options.group) {
        this.showCategory = '';
      } else {
        this.showCategory = options.group;
      }
    },
    onValueClicked(selectedValue: SelectValueInfo) {
      this.showSelect = false;

      if (!(this.modelModifiers as AbortableModifiers)?.abortable) {
        this.$emit('update:modelValue', selectedValue.key);
        return;
      }

      const emitEventData = {
        value: selectedValue.key,
        abort() {
          // empty (not necessary to reset anything since the DOM will not change for this UI
          // element until modelValue does)
        },
      };

      this.$emit('update:modelValue', emitEventData);
    },
  },
});
</script>