chromium/ash/webui/scanning/resources/select_mixin.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assert} from 'chrome://resources/ash/common/assert.js';
import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

type Constructor<T> = new (...args: any[]) => T;
export type AbstractConstructor<T> = abstract new (...args: any[]) => T;

/**
 * Helper functions for custom elements that implement a scan setting using a
 * single select element. Elements that use this behavior are required to
 * implement getOptionAtIndex(), sortOptions(), and isDefaultOption().
 */
export const SelectMixin = dedupingMixin(
    <T extends Constructor<PolymerElement>, U>(superClass: T): Function&
    {prototype: SelectMixinInterface<U>} => {
      abstract class SelectMixin extends superClass implements
          SelectMixinInterface<U> {
        static get properties() {
          return {
            disabled: Boolean,

            options: {
              type: Array,
              value: () => [],
            },

            selectedOption: {
              type: String,
              notify: true,
            },
          };
        }

        static get observers() {
          return ['optionsChanged(options.*)'];
        }

        disabled: boolean;
        options: U[];
        selectedOption: string;

        abstract getOptionAtIndex(index: number): string;
        abstract isDefaultOption(option: U): boolean;
        abstract sortOptions(): void;

        /**
         * Get the index of the default option if it exists. If not, use the
         * index of the first option in the options array.
         */
        private getDefaultSelectedIndex(): number {
          assert(this.options.length > 0);

          const defaultIndex = this.options.findIndex((option: U) => {
            return this.isDefaultOption(option);
          });

          return defaultIndex === -1 ? 0 : defaultIndex;
        }

        private getSelectElement(): HTMLSelectElement {
          return this.shadowRoot!.querySelector('select')!;
        }

        /**
         * Sorts the options and sets the selected option when options change.
         */
        private optionsChanged() {
          if (this.options.length > 1) {
            this.sortOptions();
          }

          if (this.options.length > 0) {
            const selectedOptionIndex = this.getDefaultSelectedIndex();
            this.selectedOption = this.getOptionAtIndex(selectedOptionIndex);
            this.getSelectElement().selectedIndex = selectedOptionIndex;
          }
        }
      }

      return SelectMixin;
    });

export interface SelectMixinInterface<T> {
  disabled: boolean;
  options: T[];
  selectedOption: string;
  getOptionAtIndex(index: number): string;
  isDefaultOption(option: T): boolean;
  sortOptions(): void;
}