<div class="form-item form-item--select ">
    <label for="select-multi" class="form-item__label">
        Select
    </label>
    <div class="select-multiple">
        <div class="select-multiple__inner">
            <select id="select-multi" multiple class="select-multiple__select " name="Select-multiple" data-placeholder="Choose categories">
                <option value="option-1">Option 1</option>
                <option value="option-2">Option 2</option>
                <option value="option-3">Option 3</option>
                <option value="option-4">Option 4</option>
                <option value="option-5">Option 5</option>
                <option value="option-6">Option 6</option>
                <option value="option-7">Option 7</option>
                <option value="option-8">Option 8</option>
                <option value="option-9">Option 9</option>
                <option value="option-10">Option 10</option>
            </select>
        </div>
    </div>
</div>
<div class="form-item form-item--select {{getmodifiers modifiers "form-item"}}">
    {{#if label}}
        <label for="{{id}}" class="form-item__label">
            {{label}}
            {{#if required}}
            *
            {{/if}}
        </label>
    {{/if}}
    <div class="select-multiple">
        <div class="select-multiple__inner">
            <select id="{{id}}" multiple class="select-multiple__select {{ additionalClasses }}" {{{ getattributes attr }}}>
                {{#each options}}
                    <option value="{{value}}" {{#if selected}}selected{{/if}}>{{name}}</option>
                {{/each}}
            </select>
        </div>
    </div>
    {{#if errorMsg}}
        <div class="form-item__error-msg">{{ errorMsg }}</div>
    {{/if}}
</div>
{
  "label": "Select",
  "id": "select-multi",
  "attr": {
    "name": "Select-multiple",
    "data-placeholder": "Choose categories"
  },
  "options": [
    {
      "name": "Option 1",
      "value": "option-1"
    },
    {
      "name": "Option 2",
      "value": "option-2"
    },
    {
      "name": "Option 3",
      "value": "option-3"
    },
    {
      "name": "Option 4",
      "value": "option-4"
    },
    {
      "name": "Option 5",
      "value": "option-5"
    },
    {
      "name": "Option 6",
      "value": "option-6"
    },
    {
      "name": "Option 7",
      "value": "option-7"
    },
    {
      "name": "Option 8",
      "value": "option-8"
    },
    {
      "name": "Option 9",
      "value": "option-9"
    },
    {
      "name": "Option 10",
      "value": "option-10"
    }
  ]
}
  • Content:
    import getEvent from '../../../functions/getEvent';
    
    const getDistanceToBottom = (top, elementHeight, windowHeight) => {
        return windowHeight - top - elementHeight;
    };
    
    var SelectMultiple = function(el) {
        this.opts = {
            maxHeight: 400,
            breakpoints: {
                769: {
                    maxHeight: 200
                }
            }
        };
    
        this.el = el;
        this.open = false;
        this.options = [];
        this.navigationIndex = -1;
        this.clickOnOutside = this.closeOnOutside.bind(this);
    
        this.init();
    };
    
    SelectMultiple.prototype = {
        getOptionsByBreakpoint: function(windowWidth) {
            let options = this.opts;
    
            for (const key in this.opts.breakpoints) {
                if (key > windowWidth) {
                    options = options.breakpoints[key];
                }
            }
    
            return options;
        },
    
        alignDropdown: function() {
            const elementHeight = this.el.offsetHeight;
            const windowHeight = window.innerHeight;
            const rect = this.el.getBoundingClientRect();
            const options = this.getOptionsByBreakpoint(window.innerWidth);
            const distanceToTop = rect.top || 0;
            const distanceToBottom = getDistanceToBottom(rect.top, elementHeight, windowHeight) || 0;
            let maxHeight = 0;
    
            if (distanceToTop > distanceToBottom) {
                this.optionsElement.classList.add('select-multiple__options--up');
                maxHeight = distanceToTop;
            } else {
                this.optionsElement.classList.remove('select-multiple__options--up');
                maxHeight = distanceToBottom;
            }
    
            if (maxHeight > options.maxHeight) {
                maxHeight = options.maxHeight;
            }
    
            this.optionsElement.style.maxHeight = maxHeight + 'px';
        },
    
        insertWrapper: function() {
            this.wrapper = document.createElement('div');
            this.wrapper.classList.add('select-multiple__wrapper');
            this.el.appendChild(this.wrapper);
        },
    
        insertToggler: function() {
            this.toggler = document.createElement('button');
            this.toggler.classList.add('select-multiple__toggler');
            this.toggler.textContent = this.select.getAttribute('data-placeholder') || '';
            this.wrapper.appendChild(this.toggler);
        },
    
        insertOptionAndLabel: function(optionElement) {
            const option = document.createElement('a');
    
            option.setAttribute('data-value', optionElement.getAttribute('value'));
    
            if (optionElement.selected) {
                option.setAttribute('data-checked', true);
            }
    
            option.classList.add('select-multiple__option');
            option.innerHTML = optionElement.innerHTML;
    
            this.optionsElement.appendChild(option);
    
            this.options.push({
                name: optionElement.innerHTML.toLowerCase(),
                optionElement: option,
                originalElement: optionElement
            });
        },
    
        insertListOfOptions: function() {
            const options = this.select.querySelectorAll('option');
            this.optionsElement = document.createElement('div');
            this.optionsElement.classList.add('select-multiple__options');
            this.el.appendChild(this.optionsElement);
    
            for (let i = 0; i < options.length; i++) {
                this.insertOptionAndLabel(options[i]);
            }
    
            this.select.style.display = 'none';
        },
    
        selectOption: function(e) {
            const focusedOption = this.el.querySelector('.select-multiple__option--focused');
            const event = getEvent('click');
    
            if (focusedOption) {
                focusedOption.dispatchEvent(event);
                e.preventDefault();
            }
        },
    
        onKeydown: function(e) {
            const pressedKey = e.code || e.key;
    
            if (pressedKey === 'ArrowUp') {
                this.navigateOptions('up');
                e.preventDefault();
            } else if (pressedKey === 'ArrowDown') {
                this.navigateOptions('down');
                e.preventDefault();
            } else if (pressedKey === 'Enter') {
                this.selectOption(e);
            } else if (pressedKey === 'Backspace') {
                this.removeLastPill(e);
            } else if (pressedKey === 'Escape' || pressedKey === 'Tab') {
                this.closeOptions();
            }
        },
    
        fitOptionInParent: function(option) {
            const label = option;
            const labelHeight = label.offsetHeight;
            const labelOffset = label.getBoundingClientRect();
            const parentOffset = this.optionsElement.getBoundingClientRect();
            const parentHeight = this.optionsElement.offsetHeight;
    
            if (labelOffset.top + labelHeight > parentOffset.top + parentHeight) {
                this.optionsElement.scrollTop = label.offsetTop + label.offsetHeight - this.optionsElement.offsetHeight;
            } else if (labelOffset.top < parentOffset.top) {
                this.optionsElement.scrollTop = label.offsetTop;
            }
        },
    
        navigateOptions: function(direction) {
            const self = this;
            const previousFocused = self.optionsElement.querySelector('.select-multiple__option--focused');
            const notChecked = self.optionsElement.querySelector('.select-multiple__option:not([data-checked]):not(.searchable-select__option--hidden)');
            let next;
    
            if (!notChecked || notChecked.length < 1) {
                return;
            }
    
            const findNextVisible = function(el) {
                let next = el.nextSibling;
    
                // If on end select the first one
                if (!next) {
                    next = self.optionsElement.querySelector('.select-multiple__option');
                }
    
                if (next.getAttribute('data-checked') || next.classList.contains('select-multiple__option--hidden')) {
                    return findNextVisible(next);
                } else {
                    return next;
                }
            };
    
            const findPreviousVisible = function(el) {
                let previous = el.previousSibling;
    
                // If on first select the last one
                if (!previous) {
                    previous = self.optionsElement.querySelectorAll('.select-multiple__option');
                    previous = previous[previous.length - 1];
                }
    
                if (previous.getAttribute('data-checked') || previous.classList.contains('select-multiple__option--hidden')) {
                    return findPreviousVisible(previous);
                } else {
                    return previous;
                }
            };
    
            if (!previousFocused) {
                next = self.optionsElement.querySelector('.select-multiple__option:not([data-checked])');
            } else if (direction === 'down') {
                previousFocused.classList.remove('select-multiple__option--focused');
                next = findNextVisible(previousFocused);
            } else if (direction === 'up') {
                previousFocused.classList.remove('select-multiple__option--focused');
                next = findPreviousVisible(previousFocused);
            }
    
            next.classList.add('select-multiple__option--focused');
            self.fitOptionInParent(next);
        },
    
        addPills: function() {
            const checked = this.optionsElement.querySelectorAll('[data-checked]');
    
            for (let i = 0; i < checked.length; i++) {
                this.addPill(checked[i]);
            }
        },
    
        addPill: function(option) {
            const pill = option.cloneNode(true);
    
            pill.setAttribute('data-checked', true);
            pill.setAttribute('class', '');
            pill.classList.add('select-multiple__pill');
            this.wrapper.insertBefore(pill, this.wrapper.firstChild);
            pill.addEventListener('click', this.optionChanged.bind(this));
        },
    
        removePill: function(option) {
            const pill = this.wrapper.querySelector('.select-multiple__pill[data-value="' + option.getAttribute('data-value') + '"]');
    
            if (pill) {
                this.wrapper.removeChild(pill);
            }
        },
    
        removeLastPill: function(e) {
            const pills = this.wrapper.querySelectorAll('.select-multiple__pill');
            const event = getEvent('click');
    
            // Only do this if search input is 0 and there is pills present.
            if (pills.length > 0) {
                pills[pills.length - 1].dispatchEvent(event);
                e.preventDefault();
            }
        },
    
        optionChanged: function(e, noFocus) {
            let target = e.target;
            let original = this.el.querySelector('[value="' + e.target.getAttribute('data-value') + '"]');
            let checked = target.getAttribute('data-checked');
            const event = getEvent('change');
    
            if (target.classList.contains('select-multiple__pill')) {
                target = this.optionsElement.querySelector('[data-value="' + target.getAttribute('data-value') + '"]');
            }
    
            checked = !checked;
    
            if (target.classList.contains('select-multiple__option--focused')) {
                this.navigateOptions('down');
            }
    
            e.preventDefault();
            if (checked) {
                original.selected = true;
            } else {
                original.selected = false;
            }
    
            this.select.dispatchEvent(event);
        },
    
        updateSelected: function() {
            for (let i = 0; i < this.options.length; i++) {
                if (this.options[i].originalElement.selected && !this.options[i].optionElement.getAttribute('data-checked')) {
                    this.addPill(this.options[i].optionElement);
                    this.options[i].optionElement.setAttribute('data-checked', true);
                } else if (!this.options[i].originalElement.selected && this.options[i].optionElement.getAttribute('data-checked')) {
                    this.removePill(this.options[i].optionElement);
                    this.options[i].optionElement.removeAttribute('data-checked');
                }
            }
        },
    
        openOptions: function() {
            if (!this.open) {
                this.open = true;
                this.optionsElement.classList.add('select-multiple__options--open');
                this.navigationIndex = -1;
    
                document.removeEventListener('click', this.clickOnOutside);
                setTimeout(() => {
                    document.addEventListener('click', this.clickOnOutside);
                }, 150);
            }
        },
    
        closeOptions: function() {
            if (this.open) {
                /* Remove focused option when closing */
                const focusedOption = this.el.querySelector('.select-multiple__option--focused');
                if (focusedOption) {
                    focusedOption.classList.remove('select-multiple__option--focused');
                }
    
                document.removeEventListener('click', this.clickOnOutside);
                this.open = false;
                this.optionsElement.classList.remove('select-multiple__options--open');
                this.navigationIndex = -1;
            }
        },
    
        closeOnOutside: function(e) {
            const { target } = e;
            if (target !== this.optionsElement &&
                target.parentNode !== this.optionsElement &&
                target !== this.wrapper &&
                target.parentNode !== this.wrapper &&
                !target.classList.contains('select-multiple__pill')) {
                    this.closeOptions();
            }
        },
    
        addEventListeners: function() {
            const self = this;
    
            window.addEventListener('resize', self.alignDropdown.bind(self));
            window.addEventListener('scroll', () => {
                if (!self.open) {
                    self.alignDropdown();
                }
            });
            self.wrapper.addEventListener('keydown', self.onKeydown.bind(self));
            self.toggler.addEventListener('click', function(e) {
                if (!self.open) {
                    self.openOptions();
                } else {
                    self.closeOptions();
                }
                e.preventDefault();
            });
    
            for (let i = 0; i < self.options.length; i++) {
                self.options[i].optionElement.addEventListener('click', self.optionChanged.bind(self));
            }
    
            self.select.addEventListener('change', function(e) {
                self.updateSelected();
            });
        },
    
        init: function() {
            this.select = this.el.querySelector('.select-multiple__select');
            this.insertWrapper();
            this.insertListOfOptions();
            this.addPills();
            this.insertToggler();
            this.alignDropdown();
            this.addEventListeners();
        }
    };
    
    export default SelectMultiple;
    
  • URL: /components/raw/select-multiple/SelectMultiple.js
  • Filesystem Path: src/components/form/select-multiple/SelectMultiple.js
  • Size: 12.3 KB
  • Content:
    import SelectMultiple from './SelectMultiple';
    const els = document.querySelectorAll('.select-multiple__inner');
    
    for (let i = 0; i < els.length; i++) {
        new SelectMultiple(els[i]);
    }
    
  • URL: /components/raw/select-multiple/index.js
  • Filesystem Path: src/components/form/select-multiple/index.js
  • Size: 187 Bytes
  • Content:
    .select-multiple {
        position: relative;
        margin-bottom: size(2);
    }
    
    .select-multiple__wrapper {
        border: 1px solid $color-gray-2;
        width: 100%;
        padding: size(1) size(4) size(0.5) size(2);
        background: $color-white;
        min-height: 50px;
    }
    
    .select-multiple__toggler {
        width: size(2.5);
        width: 100%;
        height: 100%;
        display: block;
        position: absolute;
        right: 12px;
        top: 0;
        background-image: url('./img/icon-chevron-down.svg');
        background-size: size(2) size(2);
        background-position: center center;
        background-repeat: no-repeat;
    
    
        background-position: calc(100% - #{size(2)}) center;
        right: 0;
        text-align: left;
        padding: 0 size(2);
        font-weight: $font-weight-bold;
    }
    
    /* Hides button text when pills exist */
    .select-multiple__pill + .select-multiple__toggler {
        font-size: 0;
    }
    
    .select-multiple__pill {
        background: $color-green-dark;
        color: $color-white;
        font-size: size(1.75);
        line-height: size(2.25);
        font-weight: bold;
        padding: size(1) 30px size(0.75) size(1);
        display: inline-block;
        margin: 0 4px 4px 0;
        position: relative;
        z-index: $z-promoted;
        cursor: default;
    
        &:hover {
            text-decoration: none;
        }
    
        &:after {
            content: ' ';
            position: absolute;
            right: size(1.5);
            top: 50%;
            margin-top: -5px;
            width: size(1.5);
            height: size(1.5);
            background-image: url('./img/close.svg');
            background-repeat: no-repeat;
            background-position: center center;
        }
    }
    
    .select-multiple__options {
        position: absolute;
        top: 100%;
        width: 100%;
        background: $color-white;
        margin-top: -1px;
        border: 1px solid $color-gray-2;
        border-top-width: 0px;
        overflow-x: hidden;
        overflow-y: auto;
        left: -9999px;
        z-index: 999;
        opacity: 0;
        transition: left 0ms, opacity 100ms;
        transition-delay: 100ms, 0ms;
        -webkit-overflow-scrolling: touch;
    
        &--up {
            top: auto;
            bottom: 100%;
            border-bottom-width: 0;
            border-top-width: 1px;
        }
    }
    
    .select-multiple__options--open {
        opacity: 1;
        left: 0;
        transition-delay: 0ms, 0ms;
    }
    
    .select-multiple__option {
        display: block;
        padding: size(0.75) size(2);
        cursor: default;
    
        @include breakpoint($s) {
            padding: size(1) size(2);
        }
    
        &--hidden {
            display: none;
        }
    }
    
    .select-multiple__option--focused {
        background: $color-gray-2;
    }
    
    .select-multiple__option:hover {
        background: $color-gray-1;
        color: $color-black;
        text-decoration: none;
    }
    
    .select-multiple__option[data-checked] {
        display: none;
    }
    
    .select-multiple__option--checked {
        background: $color-green-dark;
        color: $color-white;
        font-weight: bold;
    }
    
  • URL: /components/raw/select-multiple/select-multiple.scss
  • Filesystem Path: src/components/form/select-multiple/select-multiple.scss
  • Size: 2.8 KB

No notes defined.