Skip to content

Commit

Permalink
Merge pull request #40 from exadel-inc/dev
Browse files Browse the repository at this point in the history
dev -> main-beta
  • Loading branch information
ala-n committed Feb 4, 2021
2 parents 58e02ae + 28aaa9a commit 35d5854
Show file tree
Hide file tree
Showing 19 changed files with 1,167 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/modules/draft/all.less
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
@import "./esl-select/core.less";

@import "./esl-carousel/all.less";
1 change: 1 addition & 0 deletions src/modules/draft/all.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './esl-select/core';
export * from './esl-carousel/all';
12 changes: 12 additions & 0 deletions src/modules/draft/esl-select/core.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Variables defaults, can be overwritten on consumer side
@esl-select-check: white;
@esl-select-primary: green;
@esl-select-secondary: lightgray;
@esl-select-background: white;

// ESL Select parts
@import "./core/esl-select";
@import "./core/esl-select-text";
@import "./core/esl-select-dropdown";
@import "./core/esl-select-list";
@import "./core/esl-select-item";
6 changes: 6 additions & 0 deletions src/modules/draft/esl-select/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './core/esl-select';
export * from './core/esl-select-model';
export * from './core/esl-select-text';
export * from './core/esl-select-item';
export * from './core/esl-select-list';
export * from './core/esl-select-dropdown';
27 changes: 27 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-dropdown.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
esl-select-dropdown {
display: block;
position: absolute;
top: 0;

height: 50vh;
min-height: 350px;

z-index: 100;

border: 1px solid @esl-select-secondary;
background: @esl-select-background;

visibility: hidden;
opacity: 0;
pointer-events: none;

transition: opacity .25s linear, visibility 0s linear .25s;

&[open] {
visibility: visible;
pointer-events: all;
opacity: 1;

transition: opacity .25s linear, visibility 0s linear 0s;
}
}
81 changes: 81 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-dropdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {ESLBasePopup, PopupActionParams} from '../../../esl-base-popup/core/esl-base-popup';
import {bind} from '../../../esl-utils/decorators/bind';
import {rafDecorator} from '../../../esl-utils/async/raf';
import {ESLSelectList} from './esl-select-list';
import {ESLSelectModel} from './esl-select-model';
import {attr} from '../../../esl-base-element/decorators/attr';

export class ESLSelectDropdown extends ESLBasePopup {
public static readonly is = 'esl-select-dropdown';

@attr() public selectAllLabel: string;
public model: ESLSelectModel;

protected $list: ESLSelectList;
protected _disposeTimeout: number;
protected _deferredUpdatePosition = rafDecorator(() => this.updatePosition());

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
get closeOnEsc() { return true; }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
get closeOnOutsideAction() { return true; }

constructor() {
super();
this.$list = document.createElement(ESLSelectList.is) as ESLSelectList;
}

protected setInitialState() {}

protected connectedCallback() {
super.connectedCallback();
this.appendChild(this.$list);
}
protected disconnectedCallback() {
super.disconnectedCallback();
this.removeChild(this.$list);
}

protected bindEvents() {
super.bindEvents();
window.addEventListener('resize', this._deferredUpdatePosition);
}
protected unbindEvents() {
super.unbindEvents();
window.removeEventListener('resize', this._deferredUpdatePosition);
}

protected onShow(params: PopupActionParams) {
document.body.appendChild(this);
this._disposeTimeout && window.clearTimeout(this._disposeTimeout);

this.$list.model = this.model;
this.$list.selectAllLabel = this.selectAllLabel;

super.onShow(params);
const focusable = this.querySelector('[tabindex]') as HTMLElement;
focusable && focusable.focus();
this.updatePosition();
}
protected onHide(params: PopupActionParams) {
const select = this.activator;
select && setTimeout(() => select.focus(), 0);
super.onHide(params);
this._disposeTimeout = window.setTimeout(() => {
document.body.removeChild(this);
}, 1000);
}

@bind
public updatePosition() {
if (!this.activator) return;
const windowY = window.scrollY || window.pageYOffset;
const rect = this.activator.getBoundingClientRect();

this.style.top = `${windowY + rect.y + rect.height}px`;
this.style.left = `${rect.left}px`;
this.style.width = `${rect.width}px`;
}
}
58 changes: 58 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-item.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
esl-select-item {
display: block;
cursor: pointer;
position: relative;
padding: 15px 15px 15px 72px;
line-height: 20px;
vertical-align: middle;

&:focus {
outline: none;
}
&:focus-visible {
outline: solid 1px lightblue;
}

&::before {
content: '';
cursor: pointer;
position: absolute;
top: 15px;
left: 15px;
width: 20px;
height: 20px;
border: 2px solid @esl-select-primary;
border-radius: 4px;
background-color: @esl-select-background;
}
&::after {
content: '';
cursor: pointer;
position: absolute;
top: 20px;
left: 19px;
width: 12px;
height: 6px;

border: 2px solid @esl-select-check;
border-top: none;
border-right: none;
transform: rotate(-45deg);

opacity: 0;
transition: opacity 0.4s ease-in;
}

&[selected] {
&::before {
background-color: @esl-select-primary;
}
&::after {
opacity: 1;
}
}

&.separator {
border-bottom: 1px solid @esl-select-secondary;
}
}
33 changes: 33 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {attr, boolAttr, ESLBaseElement} from '../../../esl-base-element/core';

export class ESLSelectItem extends ESLBaseElement {
public static readonly is: string = 'esl-select-item';

public static get observedAttributes() {
return ['selected'];
}

@attr() public value: string;
@boolAttr() public selected: boolean;

protected connectedCallback() {
super.connectedCallback();
this.tabIndex = 0;
this.setAttribute('role', 'checkbox');
this.setAttribute('aria-selected', String(this.selected));
}

protected attributeChangedCallback(attrName: string) {
if (attrName === 'selected') {
this.setAttribute('aria-selected', String(this.selected));
}
}

public static build(option: HTMLOptionElement) {
const item = document.createElement(ESLSelectItem.is) as ESLSelectItem;
item.value = option.value;
item.selected = option.selected;
item.textContent = option.text;
return item;
}
}
23 changes: 23 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-list.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
esl-select-list {
display: flex;
flex-flow: column;
position: relative;
max-height: 100%;
overflow: hidden;

esl-scrollbar.esl-scrollbar {
top: 56px;
bottom: 6px;
}

.esl-select-list-container {
flex: 1 1 auto;
height: 100%;
margin-right: 16px;
}

.esl-select-item.esl-select-all-item {
flex: 0 0 auto;
border-bottom: 1px solid @esl-select-secondary;
}
}
127 changes: 127 additions & 0 deletions src/modules/draft/esl-select/core/esl-select-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {attr, ESLBaseElement} from '../../../esl-base-element/core';
import {ESLScrollbar} from '../../../esl-scrollbar/core';
import {ESLSelectModel} from './esl-select-model';
import {ESLSelectItem} from './esl-select-item';
import {bind} from '../../../esl-utils/decorators/bind';
import {ENTER, SPACE} from '../../../esl-utils/dom/keycodes';

export class ESLSelectList extends ESLBaseElement {
public static readonly is = 'esl-select-list';
public static get observedAttributes() { return ['select-all-label']; }

protected _model: ESLSelectModel;

@attr({defaultValue: 'Select All'})
public selectAllLabel: string;

protected $items: ESLSelectItem[];
protected $list: HTMLDivElement;
protected $scroll: ESLScrollbar;
protected $selectAll: ESLSelectItem;

constructor() {
super();
this.$list = document.createElement('div');
this.$list.setAttribute('role', 'list');
this.$list.classList.add('esl-scrollable-content');
this.$list.classList.add('esl-select-list-container');
this.$scroll = document.createElement(ESLScrollbar.is) as ESLScrollbar;
this.$scroll.target = '::prev';
this.$selectAll = document.createElement(ESLSelectItem.is) as ESLSelectItem;
this.$selectAll.classList.add('esl-select-all-item');
}

get model() {
return this._model;
}
set model(mod: ESLSelectModel) {
this.unbindEvents();
this._model = mod;
this.bindEvents();
this.rerender();
}

protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string) {
if (!this.connected || newVal === oldVal) return;
if (attrName === 'select-all-label') {
this.$selectAll.textContent = newVal;
}
}

protected connectedCallback() {
super.connectedCallback();

this.appendChild(this.$selectAll);
this.appendChild(this.$list);
this.appendChild(this.$scroll);

this.rerender();
this.bindEvents();
}
protected disconnectedCallback() {
super.disconnectedCallback();

this.appendChild(this.$selectAll);
this.appendChild(this.$list);
this.appendChild(this.$scroll);

this.unbindEvents();
}

public bindEvents() {
if (!this.model) return;
this.model.addListener(this.sync);
this.addEventListener('click', this._onClick);
this.addEventListener('keypress', this._onKeyboard);
}
public unbindEvents() {
if (!this.model) return;
this.model.removeListener(this.sync);
this.removeEventListener('click', this._onClick);
this.removeEventListener('keypress', this._onKeyboard);
}

@bind
public rerender() {
if (!this.model) return;
this.$items = this.model.options.map(ESLSelectItem.build);
this.$selectAll.selected = this.$items.every((item) => item.selected);
this.$selectAll.textContent = this.selectAllLabel;

this.$list.innerHTML = '';
const activeItems = this.$items.filter((option) => option.selected);
const inactiveItems = this.$items.filter((option) => !option.selected);

activeItems.forEach((item) => this.$list.appendChild(item));
activeItems.slice(-1).forEach((item) => item.classList.add('separator'));
inactiveItems.forEach((item) => this.$list.appendChild(item));
}

@bind
public sync() {
this.$items.forEach((item) => {
item.selected = this.model.check(item.value);
});
this.$selectAll.selected = this.$items.every((item) => item.selected);
}

@bind
protected _onClick(e: MouseEvent | KeyboardEvent) {
const target = e.target;
if (!target || !(target instanceof ESLSelectItem)) return;
if (target.classList.contains('esl-select-all-item')) {
this.model.toggleAll(!target.selected);
} else {
this.model.toggle(target.value, !target.selected);
}
}

@bind
protected _onKeyboard(e: KeyboardEvent) {
const keycode = e.which || e.keyCode;
if (SPACE === keycode || ENTER === keycode) {
this._onClick(e);
e.preventDefault();
}
}
}
Loading

0 comments on commit 35d5854

Please sign in to comment.