diff --git a/eslint.config.mjs b/eslint.config.mjs index 3d29dc8106..068163e8a4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,7 @@ export default [ '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-unused-expressions': 'error', '@typescript-eslint/no-this-alias': 'warn', - '@typescript-eslint/no-empty-object-type': 'error', + '@typescript-eslint/no-empty-object-type': ['error' , { allowWithName: 'BaseOptions$' }], '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-unsafe-function-type': 'error' } diff --git a/src/autocomplete.ts b/src/autocomplete.ts index e07ee62335..66db5eae7d 100644 --- a/src/autocomplete.ts +++ b/src/autocomplete.ts @@ -3,7 +3,7 @@ import { Dropdown, DropdownOptions } from "./dropdown"; import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface AutocompleteData { - /** + /** * A primitive value that can be converted to string. * If "text" is not provided, it will also be used as "option text" as well */ @@ -66,9 +66,9 @@ export interface AutocompleteOptions extends BaseOptions { * @default {} */ dropdownOptions: Partial; -}; +} -let _defaults: AutocompleteOptions = { +const _defaults: AutocompleteOptions = { data: [], // Autocomplete data set onAutocomplete: null, // Callback for when autocompleted dropdownOptions: { @@ -82,7 +82,7 @@ let _defaults: AutocompleteOptions = { onSearch: (text: string, autocomplete: Autocomplete) => { const normSearch = text.toLocaleLowerCase(); autocomplete.setMenuItems( - autocomplete.options.data.filter((option) => + autocomplete.options.data.filter((option) => option.id.toString().toLocaleLowerCase().includes(normSearch) || option.text?.toLocaleLowerCase().includes(normSearch) ) @@ -118,7 +118,7 @@ export class Autocomplete extends Component { ...Autocomplete.defaults, ...options }; - + this.isOpen = false; this.count = 0; this.activeIndex = -1; @@ -168,8 +168,8 @@ export class Autocomplete extends Component { _setupEventHandlers() { this.el.addEventListener('blur', this._handleInputBlur); - this.el.addEventListener('keyup', this._handleInputKeyupAndFocus); - this.el.addEventListener('focus', this._handleInputKeyupAndFocus); + this.el.addEventListener('keyup', this._handleInputKeyup); + this.el.addEventListener('focus', this._handleInputFocus); this.el.addEventListener('keydown', this._handleInputKeydown); this.el.addEventListener('click', this._handleInputClick); this.container.addEventListener( @@ -188,8 +188,8 @@ export class Autocomplete extends Component { _removeEventHandlers() { this.el.removeEventListener('blur', this._handleInputBlur); - this.el.removeEventListener('keyup', this._handleInputKeyupAndFocus); - this.el.removeEventListener('focus', this._handleInputKeyupAndFocus); + this.el.removeEventListener('keyup', this._handleInputKeyup); + this.el.removeEventListener('focus', this._handleInputFocus); this.el.removeEventListener('keydown', this._handleInputKeydown); this.el.removeEventListener('click', this._handleInputClick); this.container.removeEventListener( @@ -226,11 +226,12 @@ export class Autocomplete extends Component { this.el.parentElement.appendChild(this.container); // Initialize dropdown - let dropdownOptions = { + const dropdownOptions = { ...Autocomplete.defaults.dropdownOptions, ...this.options.dropdownOptions }; - let userOnItemClick = dropdownOptions.onItemClick; + // @todo shouldn't we conditionally check if dropdownOptions.onItemClick is set in first place? + const userOnItemClick = dropdownOptions.onItemClick; // Ensuring the select Option call when user passes custom onItemClick function to dropdown dropdownOptions.onItemClick = (li) => { if (!li) return; @@ -270,7 +271,7 @@ export class Autocomplete extends Component { } } - _handleInputKeyupAndFocus = (e: KeyboardEvent) => { + _handleInputKeyup = (e: KeyboardEvent) => { if (e.type === 'keyup') Autocomplete._keydown = false; this.count = 0; const actualValue = this.el.value.toLocaleLowerCase(); @@ -278,11 +279,21 @@ export class Autocomplete extends Component { if (Utils.keys.ENTER.includes(e.key) || Utils.keys.ARROW_UP.includes(e.key) || Utils.keys.ARROW_DOWN.includes(e.key)) return; // Check if the input isn't empty // Check if focus triggered by tab - if (this.oldVal !== actualValue && (Utils.tabPressed || e.type !== 'focus')) { + if (this.oldVal !== actualValue && (Utils.tabPressed)) { this.open(); } + this._inputChangeDetection(actualValue); + }; + + _handleInputFocus = () => { + this.count = 0; + const actualValue = this.el.value.toLocaleLowerCase(); + this._inputChangeDetection(actualValue); + } + + _inputChangeDetection = (value: string) => { // Value has changed! - if (this.oldVal !== actualValue) { + if (this.oldVal !== value) { this._setStatusLoading(); this.options.onSearch(this.el.value, this); } @@ -291,8 +302,8 @@ export class Autocomplete extends Component { this.selectedValues = []; this._triggerChanged(); } - this.oldVal = actualValue; - } + this.oldVal = value; + }; _handleInputKeydown = (e: KeyboardEvent) => { Autocomplete._keydown = true; diff --git a/src/buttons.ts b/src/buttons.ts index 3d4a6a5853..8a575fa257 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -18,7 +18,7 @@ export interface FloatingActionButtonOptions extends BaseOptions { toolbarEnabled: boolean; }; -let _defaults: FloatingActionButtonOptions = { +const _defaults: FloatingActionButtonOptions = { direction: 'top', hoverEnabled: true, toolbarEnabled: false @@ -132,7 +132,7 @@ export class FloatingActionButton extends Component _handleDocumentClick = (e: MouseEvent) => { const elem = e.target; - if (elem !== this._menu) this.close; + if (elem !== this._menu) this.close(); } /** @@ -166,7 +166,7 @@ export class FloatingActionButton extends Component this.el.classList.add('active'); const delayIncrement = 40; const duration = 275; - + this._floatingBtnsReverse.forEach((el, index) => { const delay = delayIncrement * index; el.style.transition = 'none'; @@ -198,21 +198,19 @@ export class FloatingActionButton extends Component } _animateInToolbar() { - let scaleFactor; - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - let btnRect = this.el.getBoundingClientRect(); + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + const btnRect = this.el.getBoundingClientRect(); - const backdrop = document.createElement('div'); + const backdrop = document.createElement('div'), + scaleFactor = windowWidth / backdrop[0].clientWidth, + fabColor = getComputedStyle(this._anchor).backgroundColor; // css('background-color'); backdrop.classList.add('fab-backdrop'); // $('
'); - - const fabColor = getComputedStyle(this._anchor).backgroundColor; // css('background-color'); - + backdrop.style.backgroundColor = fabColor; this._anchor.append(backdrop); this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2; this.offsetY = windowHeight - btnRect.bottom; - scaleFactor = windowWidth / backdrop[0].clientWidth; this.btnBottom = btnRect.bottom; this.btnLeft = btnRect.left; this.btnWidth = btnRect.width; @@ -229,8 +227,6 @@ export class FloatingActionButton extends Component this._anchor.style.transform = `translateY(${this.offsetY}px`; this._anchor.style.transition = 'none'; - backdrop.style.backgroundColor = fabColor; - setTimeout(() => { this.el.style.transform = ''; this.el.style.transition = 'transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s'; diff --git a/src/carousel.ts b/src/carousel.ts index e4d49ec530..636a1d750a 100644 --- a/src/carousel.ts +++ b/src/carousel.ts @@ -49,7 +49,7 @@ export interface CarouselOptions extends BaseOptions{ onCycleTo: (current: Element, dragged: boolean) => void; } -let _defaults: CarouselOptions = { +const _defaults: CarouselOptions = { duration: 200, // ms dist: -100, // zoom scale TODO: make this more intuitive as an option shift: 0, // spacing for center image @@ -72,15 +72,15 @@ export class Carousel extends Component { offset: number; target: number; images: HTMLElement[]; - itemWidth: any; - itemHeight: any; + itemWidth: number; + itemHeight: number; dim: number; - _indicators: any; + _indicators: HTMLUListElement; count: number; xform: string; verticalDragged: boolean; - reference: any; - referenceY: any; + reference: number; + referenceY: number; velocity: number; frame: number; timestamp: number; @@ -88,9 +88,9 @@ export class Carousel extends Component { amplitude: number; /** The index of the center carousel item. */ center: number = 0; - imageHeight: any; - scrollingTimeout: any; - oneTimeCallback: any; + imageHeight: number; + scrollingTimeout: number | NodeJS.Timeout; + oneTimeCallback: (current: Element, dragged: boolean) => void | null; constructor(el: HTMLElement, options: Partial) { super(el, options, Carousel); @@ -152,7 +152,7 @@ export class Carousel extends Component { // Setup cross browser string this.xform = 'transform'; ['webkit', 'Moz', 'O', 'ms'].every((prefix) => { - var e = prefix + 'Transform'; + const e = prefix + 'Transform'; if (typeof document.body.style[e] !== 'undefined') { this.xform = e; return false; @@ -446,13 +446,16 @@ export class Carousel extends Component { } _track = () => { - let now: number, elapsed: number, delta: number, v: number; - now = Date.now(); - elapsed = now - this.timestamp; + const now: number = Date.now(), + elapsed: number = now - this.timestamp, + delta: number = this.offset - this.frame, + v: number = (1000 * delta) / (1 + elapsed); + // now = Date.now(); + // elapsed = now - this.timestamp; this.timestamp = now; - delta = this.offset - this.frame; + // delta = this.offset - this.frame; this.frame = this.offset; - v = (1000 * delta) / (1 + elapsed); + // v = (1000 * delta) / (1 + elapsed); this.velocity = 0.8 * v + 0.2 * this.velocity; } @@ -476,33 +479,33 @@ export class Carousel extends Component { this.el.classList.add('scrolling'); } if (this.scrollingTimeout != null) { - window.clearTimeout(this.scrollingTimeout); + clearTimeout(this.scrollingTimeout); } - this.scrollingTimeout = window.setTimeout(() => { + this.scrollingTimeout = setTimeout(() => { this.el.classList.remove('scrolling'); }, this.options.duration); // Start actual scroll + this.offset = typeof x === 'number' ? x : this.offset; + this.center = Math.floor((this.offset + this.dim / 2) / this.dim); + + const half: number = this.count >> 1, + delta: number = this.offset - this.center * this.dim, + dir: number = delta < 0 ? 1 : -1, + tween: number = (-dir * delta * 2) / this.dim; let i: number, - half: number, - delta: number, - dir: number, - tween: number, el: HTMLElement, alignment: string, zTranslation: number, tweenedOpacity: number, centerTweenedOpacity: number; - let lastCenter = this.center; - let numVisibleOffset = 1 / this.options.numVisible; - - this.offset = typeof x === 'number' ? x : this.offset; - this.center = Math.floor((this.offset + this.dim / 2) / this.dim); + const lastCenter = this.center; + const numVisibleOffset = 1 / this.options.numVisible; - delta = this.offset - this.center * this.dim; - dir = delta < 0 ? 1 : -1; - tween = (-dir * delta * 2) / this.dim; - half = this.count >> 1; + // delta = this.offset - this.center * this.dim; + // dir = delta < 0 ? 1 : -1; + // tween = (-dir * delta * 2) / this.dim; + // half = this.count >> 1; if (this.options.fullWidth) { alignment = 'translateX(0)'; @@ -537,7 +540,7 @@ export class Carousel extends Component { el.classList.add('active'); } - let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * + const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween * i}px) translateZ(${this.options.dist * tween}px)`; @@ -556,7 +559,7 @@ export class Carousel extends Component { // Don't show wrapped items. if (!this.noWrap || this.center + i < this.count) { el = this.images[this._wrap(this.center + i)]; - let transformString = `${alignment} translateX(${this.options.shift + + const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; this._updateItemStyle(el, tweenedOpacity, -i, transformString); } @@ -571,7 +574,7 @@ export class Carousel extends Component { // Don't show wrapped items. if (!this.noWrap || this.center - i >= 0) { el = this.images[this._wrap(this.center - i)]; - let transformString = `${alignment} translateX(${-this.options.shift + + const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; this._updateItemStyle(el, tweenedOpacity, -i, transformString); } @@ -580,7 +583,7 @@ export class Carousel extends Component { // Don't show wrapped items. if (!this.noWrap || (this.center >= 0 && this.center < this.count)) { el = this.images[this._wrap(this.center)]; - let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * + const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`; this._updateItemStyle(el, centerTweenedOpacity, 0, transformString); diff --git a/src/characterCounter.ts b/src/characterCounter.ts index 450e815b9c..bafe53cc5a 100644 --- a/src/characterCounter.ts +++ b/src/characterCounter.ts @@ -1,13 +1,13 @@ -import { Component, BaseOptions, InitElements, MElement } from "./component"; +import { Component, InitElements, MElement } from './component'; -export interface CharacterCounterOptions extends BaseOptions {}; +export interface BaseOptions {} const _defaults = Object.freeze({}); type InputElement = HTMLInputElement | HTMLTextAreaElement; -export class CharacterCounter extends Component<{}> { - +export class CharacterCounter extends Component { + declare el: InputElement; /** Stores the reference to the counter HTML element. */ counterEl: HTMLSpanElement; @@ -16,7 +16,7 @@ export class CharacterCounter extends Component<{}> { /** Specifies whether the input text has valid length or not. */ isValidLength: boolean; - constructor(el: HTMLInputElement | HTMLTextAreaElement, options: Partial) { + constructor(el: HTMLInputElement | HTMLTextAreaElement, options: Partial) { super(el, {}, CharacterCounter); (this.el as any).M_CharacterCounter = this; @@ -32,7 +32,7 @@ export class CharacterCounter extends Component<{}> { this._setupEventHandlers(); } - static get defaults(): CharacterCounterOptions { + static get defaults(): BaseOptions { return _defaults; } @@ -41,19 +41,19 @@ export class CharacterCounter extends Component<{}> { * @param el HTML element. * @param options Component options. */ - static init(el: InputElement, options?: Partial): CharacterCounter; + static init(el: InputElement, options?: Partial): CharacterCounter; /** * Initializes instances of CharacterCounter. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options?: Partial): CharacterCounter[]; + static init(els: InitElements, options?: Partial): CharacterCounter[]; /** * Initializes instances of CharacterCounter. * @param els HTML elements. * @param options Component options. */ - static init(els: InputElement | InitElements, options: Partial = {}): CharacterCounter | CharacterCounter[] { + static init(els: InputElement | InitElements, options: Partial = {}): CharacterCounter | CharacterCounter[] { return super.init(els, options, CharacterCounter); } @@ -91,7 +91,7 @@ export class CharacterCounter extends Component<{}> { } updateCounter = () => { - let maxLength = parseInt(this.el.getAttribute('maxlength')), + const maxLength = parseInt(this.el.getAttribute('maxlength')), actualLength = (this.el as HTMLInputElement).value.length; this.isValidLength = actualLength <= maxLength; diff --git a/src/chips.ts b/src/chips.ts index a63101ed8c..609e387eb9 100644 --- a/src/chips.ts +++ b/src/chips.ts @@ -75,7 +75,7 @@ export interface ChipsOptions extends BaseOptions{ onChipDelete: (element: HTMLElement, chip: HTMLElement) => void; } -let _defaults: ChipsOptions = { +const _defaults: ChipsOptions = { data: [], placeholder: '', secondaryPlaceholder: '', @@ -101,10 +101,10 @@ export class Chips extends Component { /** Autocomplete instance, if any. */ autocomplete: Autocomplete; _input: HTMLInputElement; - _label: any; + _label: HTMLLabelElement; _chips: HTMLElement[]; static _keydown: boolean; - private _selectedChip: any; + private _selectedChip: HTMLElement; constructor(el: HTMLElement, options: Partial) { super(el, options, Chips); @@ -145,7 +145,7 @@ export class Chips extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: InitElements, options?: Partial): Chips; + static init(el: HTMLElement, options?: Partial): Chips; /** * Initializes instances of Chips. * @param els HTML elements. @@ -180,6 +180,7 @@ export class Chips extends Component { _setupEventHandlers() { this.el.addEventListener('click', this._handleChipClick); + // @todo why do we need this as document event listener, shouldn't we apply it to the element wrapper itself? document.addEventListener('keydown', Chips._handleChipsKeydown); document.addEventListener('keyup', Chips._handleChipsKeyup); this.el.addEventListener('blur', Chips._handleChipsBlur, true); @@ -261,7 +262,7 @@ export class Chips extends Component { } } - static _handleChipsKeyup(e: Event) { + static _handleChipsKeyup() { Chips._keydown = false; } @@ -294,7 +295,7 @@ export class Chips extends Component { } this._input.value = ''; } - else if ( + else if ( (Utils.keys.BACKSPACE.includes(e.key) || Utils.keys.ARROW_LEFT.includes(e.key)) && this._input.value === '' && this.chipsData.length @@ -436,16 +437,19 @@ export class Chips extends Component { static Init(){ if (typeof document !== 'undefined') - document.addEventListener("DOMContentLoaded", () => { // Handle removal of static chips. - document.body.addEventListener('click', e => { - if ((e.target).closest('.chip .close')) { - const chips = (e.target).closest('.chips'); - if (chips && (chips as any).M_Chips == undefined) return; - (e.target).closest('.chip').remove(); - } + document.addEventListener('DOMContentLoaded', () => { + const chips = document.querySelectorAll('.chips'); + chips.forEach((el) => { + // if (el && (el as any).M_Chips == undefined) return; + el.addEventListener('click', (e) => { + if ((e.target).classList.contains('close')) { + const chip = (e.target).closest('.chip'); + if (chip) chip.remove(); + } + }); + }); }); - }); } static { diff --git a/src/component.ts b/src/component.ts index 2abc34959c..2f039c2b44 100644 --- a/src/component.ts +++ b/src/component.ts @@ -44,7 +44,7 @@ export class Component{ console.error(Error(el + ' is not an HTML Element')); } // If exists, destroy and reinitialize in child - let ins = classDef.getInstance(el); + const ins = classDef.getInstance(el); if (!!ins) { ins.destroy(); } diff --git a/src/dropdown.ts b/src/dropdown.ts index 2c73f47a4c..1dcbfebc55 100644 --- a/src/dropdown.ts +++ b/src/dropdown.ts @@ -72,7 +72,7 @@ export interface DropdownOptions extends BaseOptions { * @default null */ onItemClick: (el: HTMLLIElement) => void; -}; +} const _defaults: DropdownOptions = { alignment: 'left', @@ -104,8 +104,8 @@ export class Dropdown extends Component implements Openable { isTouchMoving: boolean; /** The index of the item focused. */ focusedIndex: number; - filterQuery: any[]; - filterTimeout: NodeJS.Timeout; + filterQuery: string[]; + filterTimeout: NodeJS.Timeout | number; constructor(el: HTMLElement, options: Partial) { super(el, options, Dropdown); @@ -308,7 +308,7 @@ export class Dropdown extends Component implements Openable { newFocusedIndex = newFocusedIndex + direction; if ( !!this.dropdownEl.children[newFocusedIndex] && - (this.dropdownEl.children[newFocusedIndex]).tabIndex !== -1 + (this.dropdownEl.children[newFocusedIndex]).tabIndex !== -1 ) { hasFoundNewIndex = true; break; @@ -361,7 +361,7 @@ export class Dropdown extends Component implements Openable { this.filterTimeout = setTimeout(this._resetFilterQuery, 1000); } - _handleWindowResize = (e: Event) => { + _handleWindowResize = () => { // Only re-place the dropdown if it's still visible // Accounts for elements hiding via media queries if (this.el.offsetParent) { @@ -376,13 +376,17 @@ export class Dropdown extends Component implements Openable { _resetDropdownStyles() { this.dropdownEl.style.display = ''; + this._resetDropdownPositioningStyles(); + this.dropdownEl.style.transform = ''; + this.dropdownEl.style.opacity = ''; + } + + _resetDropdownPositioningStyles() { this.dropdownEl.style.width = ''; this.dropdownEl.style.height = ''; this.dropdownEl.style.left = ''; this.dropdownEl.style.top = ''; this.dropdownEl.style.transformOrigin = ''; - this.dropdownEl.style.transform = ''; - this.dropdownEl.style.opacity = ''; } // Move dropdown after container or trigger @@ -430,7 +434,7 @@ export class Dropdown extends Component implements Openable { } _getDropdownPosition(closestOverflowParent: HTMLElement) { - const offsetParentBRect = this.el.offsetParent.getBoundingClientRect(); + // const offsetParentBRect = this.el.offsetParent.getBoundingClientRect(); const triggerBRect = this.el.getBoundingClientRect(); const dropdownBRect = this.dropdownEl.getBoundingClientRect(); @@ -526,32 +530,32 @@ export class Dropdown extends Component implements Openable { this.dropdownEl.style.opacity = '0'; this.dropdownEl.style.transform = 'scale(0.3, 0.3)'; setTimeout(() => { - // easeOutQuad (opacity) & easeOutQuint + // easeOutQuad (opacity) & easeOutQuint this.dropdownEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`; // to this.dropdownEl.style.opacity = '1'; this.dropdownEl.style.transform = 'scale(1, 1)'; - }, 1); + }, 1); setTimeout(() => { if (this.options.autoFocus) this.dropdownEl.focus(); - if (typeof this.options.onOpenEnd === 'function') this.options.onOpenEnd.call(this, this.el); + if (typeof this.options.onOpenEnd === 'function') this.options.onOpenEnd.call(this, this.el); }, duration); } _animateOut() { const duration = this.options.outDuration; - // easeOutQuad (opacity) & easeOutQuint + // easeOutQuad (opacity) & easeOutQuint this.dropdownEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`; // to this.dropdownEl.style.opacity = '0'; - this.dropdownEl.style.transform = 'scale(0.3, 0.3)'; + this.dropdownEl.style.transform = 'scale(0.3, 0.3)'; setTimeout(() => { this._resetDropdownStyles(); - if (typeof this.options.onCloseEnd === 'function') this.options.onCloseEnd.call(this, this.el); + if (typeof this.options.onCloseEnd === 'function') this.options.onCloseEnd.call(this, this.el); }, duration); } - private _getClosestAncestor(el: HTMLElement, condition: Function): HTMLElement { + private _getClosestAncestor(el: HTMLElement, condition: (Function) => boolean): HTMLElement { let ancestor = el.parentNode; while (ancestor !== null && ancestor !== document) { if (condition(ancestor)) { @@ -640,13 +644,8 @@ export class Dropdown extends Component implements Openable { */ recalculateDimensions = () => { if (this.isOpen) { - this.dropdownEl.style.width = ''; - this.dropdownEl.style.height = ''; - this.dropdownEl.style.left = ''; - this.dropdownEl.style.top = ''; - this.dropdownEl.style.transformOrigin = ''; + this._resetDropdownPositioningStyles(); this._placeDropdown(); } } - } diff --git a/src/forms.ts b/src/forms.ts index 6c994e8eb5..9cf6bbda31 100644 --- a/src/forms.ts +++ b/src/forms.ts @@ -12,9 +12,9 @@ export class Forms { return; } - let hasLength = textfield.getAttribute('data-length') !== null; - let lenAttr = parseInt(textfield.getAttribute('data-length')); - let len = textfield.value.length; + const hasLength = textfield.getAttribute('data-length') !== null; + const lenAttr = parseInt(textfield.getAttribute('data-length')); + const len = textfield.value.length; if (len === 0 && textfield.validity.badInput === false && !textfield.required && textfield.classList.contains('validate')) { textfield.classList.remove('invalid'); @@ -31,13 +31,14 @@ export class Forms { /** * Resizes the given TextArea after updating the * value content dynamically. - * @param textarea TextArea to be resized + * @param e EventTarget */ - static textareaAutoResize(textarea: HTMLTextAreaElement){ - if (!textarea) { - console.error('No textarea element found'); - return; - } + static textareaAutoResize(e: EventTarget){ + const textarea = (e as HTMLTextAreaElement); + // if (!textarea) { + // console.error('No textarea element found'); + // return; + // } // Textarea Auto Resize let hiddenDiv: HTMLDivElement = document.querySelector('.hiddendiv'); if (!hiddenDiv) { @@ -74,9 +75,7 @@ export class Forms { } hiddenDiv.innerText = textarea.value + '\n'; - - const content = hiddenDiv.innerHTML.replace(/\n/g, '
'); - hiddenDiv.innerHTML = content; + hiddenDiv.innerHTML = hiddenDiv.innerHTML.replace(/\n/g, '
'); // When textarea is hidden, width goes crazy. // Approximate with half of window size @@ -128,7 +127,7 @@ export class Forms { // TAB, check if tabbing to radio or checkbox. if (Utils.keys.TAB.includes(e.key)) { target.classList.add('tabbed'); - target.addEventListener('blur', e => target.classList.remove('tabbed'), {once: true}); + target.addEventListener('blur', () => target.classList.remove('tabbed'), {once: true}); } } }); @@ -151,12 +150,12 @@ export class Forms { textarea.setAttribute('previous-length', textarea.value.length.toString()); Forms.textareaAutoResize(textarea); - textarea.addEventListener('keyup', e => Forms.textareaAutoResize(textarea)); - textarea.addEventListener('keydown', e => Forms.textareaAutoResize(textarea)); + textarea.addEventListener('keyup', (e) => Forms.textareaAutoResize(e.target)); + textarea.addEventListener('keydown', (e) => Forms.textareaAutoResize(e.target)); } static InitFileInputPath(fileInput: HTMLInputElement){ - fileInput.addEventListener('change', e => { + fileInput.addEventListener('change', () => { const fileField = fileInput.closest('.file-field'); const pathInput = fileField.querySelector('input.file-path'); const files = fileInput.files; diff --git a/src/index.ts b/src/index.ts index 04f0856064..ac3c6c4849 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { Autocomplete, AutocompleteOptions } from './autocomplete'; import { FloatingActionButton, FloatingActionButtonOptions } from './buttons'; import { Cards, CardsOptions } from './cards'; import { Carousel, CarouselOptions } from './carousel'; -import { CharacterCounter, CharacterCounterOptions } from './characterCounter'; +import { CharacterCounter/*, CharacterCounterOptions*/ } from './characterCounter'; import { Chips, ChipsOptions } from './chips'; import { Collapsible, CollapsibleOptions } from './collapsible'; import { Datepicker, DatepickerOptions } from './datepicker'; @@ -25,6 +26,7 @@ import { Waves } from './waves'; import { Range } from './range'; import { Utils } from './utils'; import { Component } from './component'; +/* eslint-enable @typescript-eslint/no-unused-vars */ export { Autocomplete } from './autocomplete'; export { FloatingActionButton } from './buttons'; @@ -90,7 +92,7 @@ export interface AutoInitOptions { * @param options Options for each component. */ export function AutoInit(context: HTMLElement = document.body, options?: Partial) { - let registry = { + const registry = { Autocomplete: context.querySelectorAll('.autocomplete:not(.no-autoinit)'), Cards: context.querySelectorAll('.cards:not(.no-autoinit)'), Carousel: context.querySelectorAll('.carousel:not(.no-autoinit)'), diff --git a/src/materialbox.ts b/src/materialbox.ts index 55859e7190..90d472768c 100644 --- a/src/materialbox.ts +++ b/src/materialbox.ts @@ -185,8 +185,8 @@ export class Materialbox extends Component { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { - top: box.top + window.pageYOffset - docElem.clientTop, - left: box.left + window.pageXOffset - docElem.clientLeft + top: box.top + window.scrollY - docElem.clientTop, + left: box.left + window.scrollX - docElem.clientLeft }; } private _updateVars(): void { @@ -278,7 +278,7 @@ export class Materialbox extends Component { if (this.attrWidth) this.el.setAttribute('width', this.attrWidth.toString()); if (this.attrHeight) this.el.setAttribute('height', this.attrHeight.toString()); this.el.removeAttribute('style'); - this.originInlineStyles && this.el.setAttribute('style', this.originInlineStyles); + if (this.originInlineStyles) this.el.setAttribute('style', this.originInlineStyles); // Remove class this.el.classList.remove('active'); this.doneAnimating = true; @@ -318,7 +318,7 @@ export class Materialbox extends Component { private _addOverlay(): void { this._overlay = document.createElement('div'); this._overlay.id = 'materialbox-overlay'; - this._overlay.addEventListener('click', e => { + this._overlay.addEventListener('click', () => { if (this.doneAnimating) this.close(); }, {once: true}); diff --git a/src/parallax.ts b/src/parallax.ts index 1662674aa3..e42ffa0e51 100644 --- a/src/parallax.ts +++ b/src/parallax.ts @@ -9,7 +9,7 @@ export interface ParallaxOptions extends BaseOptions { responsiveThreshold: number; } -let _defaults: ParallaxOptions = { +const _defaults: ParallaxOptions = { responsiveThreshold: 0 // breakpoint for swipeable }; @@ -17,8 +17,8 @@ export class Parallax extends Component { private _enabled: boolean; private _img: HTMLImageElement; static _parallaxes: Parallax[] = []; - static _handleScrollThrottled: () => any; - static _handleWindowResizeThrottled: () => any; + static _handleScrollThrottled: () => Utils; + static _handleWindowResizeThrottled: () => Utils; constructor(el: HTMLElement, options: Partial) { super(el, options, Parallax); @@ -28,7 +28,7 @@ export class Parallax extends Component { ...Parallax.defaults, ...options }; - + this._enabled = window.innerWidth > this.options.responsiveThreshold; this._img = this.el.querySelector('img'); this._updateParallax(); @@ -75,16 +75,15 @@ export class Parallax extends Component { static _handleScroll() { for (let i = 0; i < Parallax._parallaxes.length; i++) { - let parallaxInstance = Parallax._parallaxes[i]; + const parallaxInstance = Parallax._parallaxes[i]; parallaxInstance._updateParallax.call(parallaxInstance); } } static _handleWindowResize() { for (let i = 0; i < Parallax._parallaxes.length; i++) { - let parallaxInstance = Parallax._parallaxes[i]; - parallaxInstance._enabled = - window.innerWidth > parallaxInstance.options.responsiveThreshold; + const parallaxInstance = Parallax._parallaxes[i]; + parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold; } } @@ -122,13 +121,13 @@ export class Parallax extends Component { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { - top: box.top + window.pageYOffset - docElem.clientTop, - left: box.left + window.pageXOffset - docElem.clientLeft + top: box.top + window.scrollY - docElem.clientTop, + left: box.left + window.scrollX - docElem.clientLeft }; } _updateParallax() { - const containerHeight = this.el.getBoundingClientRect().height > 0 ? (this.el.parentNode as any).offsetHeight : 500; + const containerHeight = this.el.getBoundingClientRect().height > 0 ? this.el.parentElement.offsetHeight : 500; const imgHeight = this._img.offsetHeight; const parallaxDist = imgHeight - containerHeight; const bottom = this._offset(this.el).top + containerHeight; diff --git a/src/pushpin.ts b/src/pushpin.ts index 5a17e16ea6..4bf1d794b7 100644 --- a/src/pushpin.ts +++ b/src/pushpin.ts @@ -27,7 +27,7 @@ export interface PushpinOptions extends BaseOptions { onPositionChange: (position: "pinned" | "pin-top" | "pin-bottom") => void; } -let _defaults = { +const _defaults = { top: 0, bottom: Infinity, offset: 0, @@ -35,8 +35,8 @@ let _defaults = { }; export class Pushpin extends Component { - static _pushpins: any[]; - originalOffset: any; + static _pushpins: Pushpin[]; + originalOffset: number; constructor(el: HTMLElement, options: Partial) { super(el, options, Pushpin); @@ -86,7 +86,7 @@ export class Pushpin extends Component { (this.el as HTMLElement).style.top = null; this._removePinClasses(); // Remove pushpin Inst - let index = Pushpin._pushpins.indexOf(this); + const index = Pushpin._pushpins.indexOf(this); Pushpin._pushpins.splice(index, 1); if (Pushpin._pushpins.length === 0) { this._removeEventHandlers(); @@ -95,8 +95,8 @@ export class Pushpin extends Component { } static _updateElements() { - for (let elIndex in Pushpin._pushpins) { - let pInstance = Pushpin._pushpins[elIndex]; + for (const elIndex in Pushpin._pushpins) { + const pInstance = Pushpin._pushpins[elIndex]; pInstance._updatePosition(); } } @@ -110,7 +110,7 @@ export class Pushpin extends Component { } _updatePosition() { - let scrolled = Utils.getDocumentScrollTop() + this.options.offset; + const scrolled = Utils.getDocumentScrollTop() + this.options.offset; if ( this.options.top <= scrolled && diff --git a/src/scrollspy.ts b/src/scrollspy.ts index 99109f97e5..1e7f71f726 100644 --- a/src/scrollspy.ts +++ b/src/scrollspy.ts @@ -39,7 +39,7 @@ export interface ScrollSpyOptions extends BaseOptions { animationDuration: number | null; }; -let _defaults: ScrollSpyOptions = { +const _defaults: ScrollSpyOptions = { throttle: 100, scrollOffset: 200, // offset - 200 allows elements near bottom of page to scroll activeClass: 'active', @@ -159,16 +159,16 @@ export class ScrollSpy extends Component { ScrollSpy._ticks++; // viewport rectangle - let top = Utils.getDocumentScrollTop(), + const top = Utils.getDocumentScrollTop(), left = Utils.getDocumentScrollLeft(), right = left + window.innerWidth, bottom = top + window.innerHeight; // determine which elements are in view - let intersections = ScrollSpy._findElements(top, right, bottom, left); + const intersections = ScrollSpy._findElements(top, right, bottom, left); for (let i = 0; i < intersections.length; i++) { - let scrollspy = intersections[i]; - let lastTick = scrollspy.tickId; + const scrollspy = intersections[i]; + const lastTick = scrollspy.tickId; if (lastTick < 0) { // entered into view scrollspy._enter(); @@ -179,8 +179,8 @@ export class ScrollSpy extends Component { } for (let i = 0; i < ScrollSpy._elementsInView.length; i++) { - let scrollspy = ScrollSpy._elementsInView[i]; - let lastTick = scrollspy.tickId; + const scrollspy = ScrollSpy._elementsInView[i]; + const lastTick = scrollspy.tickId; if (lastTick >= 0 && lastTick !== ScrollSpy._ticks) { // exited from view scrollspy._exit(); @@ -219,18 +219,18 @@ export class ScrollSpy extends Component { } static _findElements(top: number, right: number, bottom: number, left: number): ScrollSpy[] { - let hits = []; + const hits = []; for (let i = 0; i < ScrollSpy._elements.length; i++) { - let scrollspy = ScrollSpy._elements[i]; - let currTop = top + scrollspy.options.scrollOffset || 200; + const scrollspy = ScrollSpy._elements[i]; + const currTop = top + scrollspy.options.scrollOffset || 200; if (scrollspy.el.getBoundingClientRect().height > 0) { - let elTop = ScrollSpy._offset(scrollspy.el).top, + const elTop = ScrollSpy._offset(scrollspy.el).top, elLeft = ScrollSpy._offset(scrollspy.el).left, elRight = elLeft + scrollspy.el.getBoundingClientRect().width, elBottom = elTop + scrollspy.el.getBoundingClientRect().height; - let isIntersect = !( + const isIntersect = !( elLeft > right || elRight < left || elTop > bottom || diff --git a/src/select.ts b/src/select.ts index 5e1124120a..6c505af2e1 100644 --- a/src/select.ts +++ b/src/select.ts @@ -15,7 +15,7 @@ export interface FormSelectOptions extends BaseOptions { dropdownOptions: Partial; } -let _defaults: FormSelectOptions = { +const _defaults: FormSelectOptions = { classes: '', dropdownOptions: {} }; @@ -54,7 +54,7 @@ export class FormSelect extends Component { ...FormSelect.defaults, ...options }; - + this.isMultiple = this.el.multiple; this.el.tabIndex = -1; this._values = []; @@ -276,7 +276,7 @@ export class FormSelect extends Component { const userOnOpenEnd = dropdownOptions.onOpenEnd; const userOnCloseEnd = dropdownOptions.onCloseEnd; // Add callback for centering selected option when dropdown content is scrollable - dropdownOptions.onOpenEnd = (el) => { + dropdownOptions.onOpenEnd = () => { const selectedOption = this.dropdownOptions.querySelector('.selected'); if (selectedOption) { // Focus selected option in dropdown @@ -299,7 +299,7 @@ export class FormSelect extends Component { userOnOpenEnd.call(this.dropdown, this.el); }; // Add callback for reseting "expanded" state - dropdownOptions.onCloseEnd = (el) => { + dropdownOptions.onCloseEnd = () => { this.input.ariaExpanded = 'false'; // Handle user declared onOpenEnd if needed if (userOnCloseEnd && typeof userOnCloseEnd === 'function') diff --git a/src/sidenav.ts b/src/sidenav.ts index ff30a94996..cd7e3a4e55 100644 --- a/src/sidenav.ts +++ b/src/sidenav.ts @@ -1,5 +1,5 @@ -import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; +import { Utils } from './utils'; +import { Component, BaseOptions, InitElements, MElement, Openable } from './component'; export interface SidenavOptions extends BaseOptions { /** @@ -163,12 +163,12 @@ export class Sidenav extends Component implements Openable { if (Sidenav._sidenavs.length === 0) { document.body.addEventListener('click', this._handleTriggerClick); } - var passiveIfSupported: boolean = null; + const passiveIfSupported: boolean = null; this.dragTarget.addEventListener('touchmove', this._handleDragTargetDrag, passiveIfSupported); this.dragTarget.addEventListener('touchend', this._handleDragTargetRelease); this._overlay.addEventListener('touchmove', this._handleCloseDrag, passiveIfSupported); this._overlay.addEventListener('touchend', this._handleCloseRelease); - this.el.addEventListener('touchmove', this._handleCloseDrag, passiveIfSupported); + this.el.addEventListener('touchmove', this._handleCloseDrag); // , passiveIfSupported); this.el.addEventListener('touchend', this._handleCloseRelease); this.el.addEventListener('click', this._handleCloseTriggerClick); // Add resize for side nav fixed @@ -204,7 +204,7 @@ export class Sidenav extends Component implements Openable { const sidenavId = Utils.getIdFromTrigger(trigger); const sidenavInstance = (document.getElementById(sidenavId) as any).M_Sidenav; if (sidenavInstance) { - sidenavInstance.open(trigger); + sidenavInstance.open(); } e.preventDefault(); } @@ -238,18 +238,8 @@ export class Sidenav extends Component implements Openable { private _handleDragTargetDrag = (e) => { // Check if draggable - if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) { - return; - } - // If not being dragged, set initial drag start variables - if (!this.isDragged) { - this._startDrag(e); - } - // Run touchmove updates - this._dragMoveUpdate(e); - // Calculate raw deltaX - let totalDeltaX = this._xPos - this._startingXpos; - // dragDirection is the attempted user drag direction + if (!this._isDraggable()) return; + let totalDeltaX = this._calculateDelta(e); const dragDirection = totalDeltaX > 0 ? 'right' : 'left'; // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX)); @@ -272,7 +262,7 @@ export class Sidenav extends Component implements Openable { // Set transform and opacity styles this.el.style.transform = `${transformPrefix} translateX(${transformX}px)`; this._overlay.style.opacity = this.percentOpen.toString(); - } + }; private _handleDragTargetRelease = () => { if (this.isDragged) { @@ -284,40 +274,39 @@ export class Sidenav extends Component implements Openable { this.isDragged = false; this._verticallyScrolling = false; } - } + }; private _handleCloseDrag = (e) => { - if (this.isOpen) { - // Check if draggable - if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) { - return; - } - // If not being dragged, set initial drag start variables - if (!this.isDragged) { - this._startDrag(e); - } - // Run touchmove updates - this._dragMoveUpdate(e); - // Calculate raw deltaX - let totalDeltaX = this._xPos - this._startingXpos; - // dragDirection is the attempted user drag direction - let dragDirection = totalDeltaX > 0 ? 'right' : 'left'; - // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction - totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX)); - if (this.options.edge !== dragDirection) { - totalDeltaX = 0; - } - let transformX = -totalDeltaX; - if (this.options.edge === 'right') { - transformX = -transformX; - } - // Calculate open/close percentage of sidenav, with open = 1 and close = 0 - this.percentOpen = Math.min(1, 1 - totalDeltaX / this._width); - // Set transform and opacity styles - this.el.style.transform = `translateX(${transformX}px)`; - this._overlay.style.opacity = this.percentOpen.toString(); + // Check if open and draggable + if (!this.isOpen || !this._isDraggable()) return; + let totalDeltaX = this._calculateDelta(e); + // dragDirection is the attempted user drag direction + const dragDirection = totalDeltaX > 0 ? 'right' : 'left'; + totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX)); + if (this.options.edge !== dragDirection) { + totalDeltaX = 0; } - } + let transformX = -totalDeltaX; + if (this.options.edge === 'right') { + transformX = -transformX; + } + // Calculate open/close percentage of sidenav, with open = 1 and close = 0 + this.percentOpen = Math.min(1, 1 - totalDeltaX / this._width); + // Set transform and opacity styles + this.el.style.transform = `translateX(${transformX}px)`; + this._overlay.style.opacity = this.percentOpen.toString(); + }; + + private _calculateDelta = (e) => { + // If not being dragged, set initial drag start variables + if (!this.isDragged) { + this._startDrag(e); + } + // Run touchmove updates + this._dragMoveUpdate(e); + // Calculate raw deltaX + return this._xPos - this._startingXpos; + }; private _handleCloseRelease = () => { if (this.isOpen && this.isDragged) { @@ -329,7 +318,7 @@ export class Sidenav extends Component implements Openable { this.isDragged = false; this._verticallyScrolling = false; } - } + }; // Handles closing of Sidenav when element with class .sidenav-close private _handleCloseTriggerClick = (e) => { @@ -337,7 +326,7 @@ export class Sidenav extends Component implements Openable { if (closeTrigger && !this._isCurrentlyFixed()) { this.close(); } - } + }; private _handleWindowResize = () => { // Only handle horizontal resizes @@ -350,7 +339,7 @@ export class Sidenav extends Component implements Openable { } this.lastWindowWidth = window.innerWidth; this.lastWindowHeight = window.innerHeight; - } + }; private _setupClasses() { if (this.options.edge === 'right') { @@ -368,6 +357,10 @@ export class Sidenav extends Component implements Openable { if (this._isCurrentlyFixed()) this.open(); } + private _isDraggable() { + return this.options.draggable && !this._isCurrentlyFixed() && !this._verticallyScrolling; + } + private _isCurrentlyFixed() { return this.isFixed && window.innerWidth > 992; } @@ -413,7 +406,7 @@ export class Sidenav extends Component implements Openable { this._setAriaHidden(); this._setTabIndex(); } - } + }; /** * Closes Sidenav. @@ -442,7 +435,7 @@ export class Sidenav extends Component implements Openable { this._setAriaHidden(); this._setTabIndex(); } - } + }; private _animateIn() { this._animateSidenavIn(); @@ -480,6 +473,7 @@ export class Sidenav extends Component implements Openable { const endPercent = this.options.edge === 'left' ? -1 : 1; let slideOutPercent = 0; if (this.isDragged) { + // @todo unused variable slideOutPercent = this.options.edge === 'left' ? endPercent + this.percentOpen @@ -527,20 +521,24 @@ export class Sidenav extends Component implements Openable { private _setAriaHidden = () => { this.el.ariaHidden = this.isOpen ? 'false' : 'true'; - const navWrapper = document.querySelector('.nav-wrapper ul') as any; - if (navWrapper) navWrapper.ariaHidden = this.isOpen; - } + const navWrapper = document.querySelector('.nav-wrapper ul') as HTMLUListElement; + if (navWrapper) navWrapper.ariaHidden = this.isOpen.toString(); + }; private _setTabIndex = () => { const navLinks = document.querySelectorAll('.nav-wrapper ul li a'); const sideNavLinks = document.querySelectorAll('.sidenav li a'); if (navLinks) - navLinks.forEach((navLink: HTMLAnchorElement) => { navLink.tabIndex = this.isOpen ? -1 : 0}); + navLinks.forEach((navLink: HTMLAnchorElement) => { + navLink.tabIndex = this.isOpen ? -1 : 0; + }); if (sideNavLinks) - sideNavLinks.forEach((sideNavLink: HTMLAnchorElement) => { sideNavLink.tabIndex = this.isOpen ? 0 : -1;}); - } + sideNavLinks.forEach((sideNavLink: HTMLAnchorElement) => { + sideNavLink.tabIndex = this.isOpen ? 0 : -1; + }); + }; - static { + static { Sidenav._sidenavs = []; } } diff --git a/src/slider.ts b/src/slider.ts index 880a4a3230..dc91a439ef 100644 --- a/src/slider.ts +++ b/src/slider.ts @@ -42,7 +42,7 @@ export interface SliderOptions extends BaseOptions { indicatorLabelFunc: (index: number, current: boolean) => string } -let _defaults: SliderOptions = { +const _defaults: SliderOptions = { indicators: true, height: 400, duration: 500, diff --git a/src/tabs.ts b/src/tabs.ts index d1d65b9197..92af8b9493 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -26,7 +26,7 @@ export interface TabsOptions extends BaseOptions { responsiveThreshold: number; }; -let _defaults: TabsOptions = { +const _defaults: TabsOptions = { duration: 300, onShow: null, swipeable: false, @@ -39,9 +39,9 @@ export class Tabs extends Component { _indicator: HTMLLIElement; _tabWidth: number; _tabsWidth: number; - _tabsCarousel: any; - _activeTabLink: any; - _content: any; + _tabsCarousel: Carousel; + _activeTabLink: HTMLAnchorElement; + _content: HTMLElement; constructor(el: HTMLElement, options: Partial) { super(el, options, Tabs); @@ -135,12 +135,12 @@ export class Tabs extends Component { if (!tabLink) return; - var tab = tabLink.parentElement; + let tab = tabLink.parentElement; while (tab && !tab.classList.contains('tab')) { tabLink = tabLink.parentElement as HTMLAnchorElement; tab = tab.parentElement; } - + // Handle click on tab link only if (!tabLink || !tab.classList.contains('tab')) return; // is disabled? @@ -204,10 +204,11 @@ export class Tabs extends Component { this._activeTabLink = Array.from(this._tabLinks).find((a: HTMLAnchorElement) => a.getAttribute('href') === location.hash); // If no match is found, use the first link or any with class 'active' as the initial active tab. if (!this._activeTabLink) { - this._activeTabLink = this.el.querySelector('li.tab a.active'); - } - if (this._activeTabLink.length === 0) { - this._activeTabLink = this.el.querySelector('li.tab a'); + let activeTabLink = this.el.querySelector('li.tab a.active'); + if (!activeTabLink) { + activeTabLink = this.el.querySelector('li.tab a'); + } + this._activeTabLink = (activeTabLink as HTMLAnchorElement); } Array.from(this._tabLinks).forEach((a: HTMLAnchorElement) => a.classList.remove('active')); this._activeTabLink.classList.add('active'); @@ -215,7 +216,7 @@ export class Tabs extends Component { this._index = Math.max(Array.from(this._tabLinks).indexOf(this._activeTabLink), 0); if (this._activeTabLink && this._activeTabLink.hash) { this._content = document.querySelector(this._activeTabLink.hash); - if (this._content) + if (this._content) this._content.classList.add('active'); } } @@ -230,7 +231,7 @@ export class Tabs extends Component { if (a.hash) { const currContent = document.querySelector(a.hash); currContent.classList.add('carousel-item'); - tabsContent.push(currContent); + tabsContent.push(currContent); } }); @@ -271,7 +272,7 @@ export class Tabs extends Component { const tabsWrapper = this._tabsCarousel.el; this._tabsCarousel.destroy(); // Unwrap - tabsWrapper.after(tabsWrapper.children); + tabsWrapper.append(tabsWrapper.parentElement); tabsWrapper.remove(); } diff --git a/src/timepicker.ts b/src/timepicker.ts index 9eaf4d9fc1..ee0fda09e6 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -71,7 +71,7 @@ export interface TimepickerOptions extends BaseOptions { onSelect: (hour: number, minute: number) => void; } -let _defaults: TimepickerOptions = { +const _defaults: TimepickerOptions = { dialRadius: 135, outerRadius: 105, innerRadius: 70, @@ -102,8 +102,8 @@ export class Timepicker extends Component { declare el: HTMLInputElement; id: string; modalEl: HTMLElement; - plate: any; - digitalClock: any; + plate: HTMLElement; + digitalClock: HTMLElement; inputHours: HTMLInputElement; inputMinutes: HTMLInputElement; x0: number; @@ -116,22 +116,29 @@ export class Timepicker extends Component { * @default 'hours' */ currentView: Views; - hand: any; + hand: SVGElement; minutesView: HTMLElement; - hours: any; - minutes: any; + hours: number; + minutes: number; /** The selected time. */ time: string; /** * If the time is AM or PM on twelve-hour clock. * @default 'PM' */ +<<<<<<< HEAD amOrPm: 'AM' | 'PM'; static _template: any; +======= + amOrPm: "AM" | "PM"; + static _template: string; + /** If the picker is open. */ + isOpen: boolean; +>>>>>>> d4d05f635d5212f9b81ae5aa11f15b3a0818d532 /** Vibrate device when dragging clock hand. */ vibrate: 'vibrate' | 'webkitVibrate' | null; _canvas: HTMLElement; - hoursView: any; + hoursView: HTMLElement; spanAmPm: HTMLSpanElement; footer: HTMLElement; private _amBtn: HTMLElement; @@ -140,8 +147,7 @@ export class Timepicker extends Component { bearing: Element; g: Element; toggleViewTimer: string | number | NodeJS.Timeout; - canvas: any; - vibrateTimer: any; + vibrateTimer: NodeJS.Timeout | number; constructor(el: HTMLInputElement, options: Partial) { super(el, options, Timepicker); @@ -194,7 +200,7 @@ export class Timepicker extends Component { } static _createSVGEl(name: string) { - let svgNS = 'http://www.w3.org/2000/svg'; + const svgNS = 'http://www.w3.org/2000/svg'; return document.createElementNS(svgNS, name); } @@ -256,13 +262,13 @@ export class Timepicker extends Component { _handleClockClickStart = (e) => { e.preventDefault(); - let clockPlateBR = this.plate.getBoundingClientRect(); - let offset = { x: clockPlateBR.left, y: clockPlateBR.top }; + const clockPlateBR = this.plate.getBoundingClientRect(); + const offset = { x: clockPlateBR.left, y: clockPlateBR.top }; this.x0 = offset.x + this.options.dialRadius; this.y0 = offset.y + this.options.dialRadius; this.moved = false; - let clickPos = Timepicker._Pos(e); + const clickPos = Timepicker._Pos(e); this.dx = clickPos.x - this.x0; this.dy = clickPos.y - this.y0; @@ -278,9 +284,9 @@ export class Timepicker extends Component { _handleDocumentClickMove = (e) => { e.preventDefault(); - let clickPos = Timepicker._Pos(e); - let x = clickPos.x - this.x0; - let y = clickPos.y - this.y0; + const clickPos = Timepicker._Pos(e); + const x = clickPos.x - this.x0; + const y = clickPos.y - this.y0; this.moved = true; this.setHand(x, y, false); }; @@ -289,9 +295,9 @@ export class Timepicker extends Component { e.preventDefault(); document.removeEventListener('mouseup', this._handleDocumentClickEnd); document.removeEventListener('touchend', this._handleDocumentClickEnd); - let clickPos = Timepicker._Pos(e); - let x = clickPos.x - this.x0; - let y = clickPos.y - this.y0; + const clickPos = Timepicker._Pos(e); + const x = clickPos.x - this.x0; + const y = clickPos.y - this.y0; if (this.moved && x === this.dx && y === this.dy) { this.setHand(x, y); } @@ -380,7 +386,7 @@ export class Timepicker extends Component { const doneButton = this._createButton(this.options.i18n.done, ''); doneButton.classList.add('timepicker-close'); - doneButton.addEventListener('click', this.done); + doneButton.addEventListener('click', this._finishSelection); confirmationBtnsContainer.appendChild(doneButton); } @@ -406,24 +412,24 @@ export class Timepicker extends Component { _buildSVGClock() { // Draw clock hands and others - let dialRadius = this.options.dialRadius; - let tickRadius = this.options.tickRadius; - let diameter = dialRadius * 2; - let svg = Timepicker._createSVGEl('svg'); + const dialRadius = this.options.dialRadius; + const tickRadius = this.options.tickRadius; + const diameter = dialRadius * 2; + const svg = Timepicker._createSVGEl('svg'); svg.setAttribute('class', 'timepicker-svg'); svg.setAttribute('width', diameter.toString()); svg.setAttribute('height', diameter.toString()); - let g = Timepicker._createSVGEl('g'); + const g = Timepicker._createSVGEl('g'); g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')'); - let bearing = Timepicker._createSVGEl('circle'); + const bearing = Timepicker._createSVGEl('circle'); bearing.setAttribute('class', 'timepicker-canvas-bearing'); bearing.setAttribute('cx', '0'); bearing.setAttribute('cy', '0'); bearing.setAttribute('r', '4'); - let hand = Timepicker._createSVGEl('line'); + const hand = Timepicker._createSVGEl('line'); hand.setAttribute('x1', '0'); hand.setAttribute('y1', '0'); - let bg = Timepicker._createSVGEl('circle'); + const bg = Timepicker._createSVGEl('circle'); bg.setAttribute('class', 'timepicker-canvas-bg'); bg.setAttribute('r', tickRadius.toString()); g.appendChild(hand); @@ -438,14 +444,15 @@ export class Timepicker extends Component { } _buildHoursView() { - const $tick = document.createElement('div'); - $tick.classList.add('timepicker-tick'); + // const $tick = document.createElement('div'); + // $tick.classList.add('timepicker-tick'); // Hours view if (this.options.twelveHour) { for (let i = 1; i < 13; i += 1) { - const tick = $tick.cloneNode(true); + // const tick = $tick.cloneNode(true); const radian = (i / 6) * Math.PI; const radius = this.options.outerRadius; +<<<<<<< HEAD tick.style.left = this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px'; tick.style.top = @@ -453,13 +460,17 @@ export class Timepicker extends Component { tick.innerHTML = i === 0 ? '00' : i.toString(); this.hoursView.appendChild(tick); // tick.on(mousedownEvent, mousedown); +======= + this._buildHoursTick(i, radian, radius); +>>>>>>> d4d05f635d5212f9b81ae5aa11f15b3a0818d532 } } else { for (let i = 0; i < 24; i += 1) { - const tick = $tick.cloneNode(true); + // const tick = $tick.cloneNode(true); const radian = (i / 6) * Math.PI; const inner = i > 0 && i < 13; const radius = inner ? this.options.innerRadius : this.options.outerRadius; +<<<<<<< HEAD tick.style.left = this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px'; tick.style.top = @@ -467,10 +478,22 @@ export class Timepicker extends Component { tick.innerHTML = i === 0 ? '00' : i.toString(); this.hoursView.appendChild(tick); // tick.on(mousedownEvent, mousedown); +======= + this._buildHoursTick(i, radian, radius); +>>>>>>> d4d05f635d5212f9b81ae5aa11f15b3a0818d532 } } } + _buildHoursTick(i: number, radian: number, radius: number) { + const tick = document.createElement('div'); + tick.classList.add('timepicker-tick'); + tick.style.left = this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px'; + tick.style.top = this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px'; + tick.innerHTML = i === 0 ? '00' : i.toString(); + this.hoursView.appendChild(tick); + } + _buildMinutesView() { const _tick = document.createElement('div'); _tick.classList.add('timepicker-tick'); @@ -523,7 +546,7 @@ export class Timepicker extends Component { value[1] = value[1].replace('AM', '').replace('PM', ''); } if (value[0] === 'now') { - let now = new Date(+new Date() + this.options.fromNow); + const now = new Date(+new Date() + this.options.fromNow); value = [now.getHours().toString(), now.getMinutes().toString()]; if (this.options.twelveHour) { this.amOrPm = parseInt(value[0]) >= 12 && parseInt(value[0]) < 24 ? 'PM' : 'AM'; @@ -540,12 +563,13 @@ export class Timepicker extends Component { /** * Show hours or minutes view on timepicker. * @param view The name of the view you want to switch to, 'hours' or 'minutes'. + * @param delay */ showView = (view: Views, delay: number = null) => { if (view === 'minutes' && getComputedStyle(this.hoursView).visibility === 'visible') { // raiseCallback(this.options.beforeHourSelect); } - let isHours = view === 'hours', + const isHours = view === 'hours', nextView = isHours ? this.hoursView : this.minutesView, hideView = isHours ? this.minutesView : this.hoursView; this.currentView = view; @@ -574,7 +598,7 @@ export class Timepicker extends Component { }; resetClock(delay) { - let view = this.currentView, + const view = this.currentView, value = this[view], isHours = view === 'hours', unit = Math.PI / (isHours ? 6 : 30), @@ -582,14 +606,13 @@ export class Timepicker extends Component { radius = isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius, x = Math.sin(radian) * radius, - y = -Math.cos(radian) * radius, - self = this; + y = -Math.cos(radian) * radius; if (delay) { - this.canvas?.classList.add('timepicker-canvas-out'); + this._canvas?.classList.add('timepicker-canvas-out'); setTimeout(() => { - self.canvas?.classList.remove('timepicker-canvas-out'); - self.setHand(x, y); + this._canvas?.classList.remove('timepicker-canvas-out'); + this.setHand(x, y); }, delay); } else { this.setHand(x, y); @@ -612,7 +635,7 @@ export class Timepicker extends Component { this.minutes = value; } else { this.minutes = new Date().getMinutes(); - this.inputMinutes.value = this.minutes; + this.inputMinutes.value = this.minutes.toString(); } this.drawClockFromTimeInput(this.minutes, isHours); } @@ -632,11 +655,11 @@ export class Timepicker extends Component { } setHand(x, y, roundBy5: boolean = false) { - let radian = Math.atan2(x, -y), - isHours = this.currentView === 'hours', + const isHours = this.currentView === 'hours', unit = Math.PI / (isHours || roundBy5 ? 6 : 30), z = Math.sqrt(x * x + y * y), - inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2, + inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2; + let radian = Math.atan2(x, -y), radius = inner ? this.options.innerRadius : this.options.outerRadius; if (this.options.twelveHour) { @@ -703,7 +726,7 @@ export class Timepicker extends Component { } setClockAttributes(radian: number, radius: number) { - let cx1 = Math.sin(radian) * (radius - this.options.tickRadius), + const cx1 = Math.sin(radian) * (radius - this.options.tickRadius), cy1 = -Math.cos(radian) * (radius - this.options.tickRadius), cx2 = Math.sin(radian) * radius, cy2 = -Math.cos(radian) * radius; @@ -728,9 +751,37 @@ export class Timepicker extends Component { this.inputHours.value = (this.hours % (this.options.twelveHour ? 12 : 24)).toString(); } +<<<<<<< HEAD done = (e = null, clearValue = null) => { +======= + _finishSelection = () => { + this.done(); + } + + /** + * Open timepicker. + */ + open = () => { + if (this.isOpen) return; + this.isOpen = true; + this._updateTimeFromInput(); + this.showView('hours'); + this.modal.open(undefined); + } + + /** + * Close timepicker. + */ + close = () => { + if (!this.isOpen) return; + this.isOpen = false; + this.modal.close(); + } + + done = (clearValue = null) => { +>>>>>>> d4d05f635d5212f9b81ae5aa11f15b3a0818d532 // Set input value - let last = this.el.value; + const last = this.el.value; let value = clearValue ? '' : Timepicker._addLeadingZero(this.hours) + ':' + Timepicker._addLeadingZero(this.minutes); @@ -749,6 +800,7 @@ export class Timepicker extends Component { }; clear = () => { +<<<<<<< HEAD this.done(null, true); }; @@ -767,6 +819,9 @@ export class Timepicker extends Component { 'Timepicker.close() is deprecated. Remove this method and wrap in modal yourself.' ); return this; +======= + this.done(true); +>>>>>>> d4d05f635d5212f9b81ae5aa11f15b3a0818d532 } static { diff --git a/src/toasts.ts b/src/toasts.ts index bf823a6c44..3da7f19ef2 100644 --- a/src/toasts.ts +++ b/src/toasts.ts @@ -44,7 +44,7 @@ export interface ToastOptions extends BaseOptions { activationPercent: number; } -let _defaults: ToastOptions = { +const _defaults: ToastOptions = { text: '', displayLength: 4000, inDuration: 300, @@ -68,7 +68,7 @@ export class Toast { panning: boolean; options: ToastOptions; message: string; - counterInterval: NodeJS.Timeout; + counterInterval: NodeJS.Timeout | number; wasSwiped: boolean; startingXPos: number; xPos: number; @@ -77,7 +77,7 @@ export class Toast { velocityX: number; static _toasts: Toast[]; - static _container: any; + static _container: HTMLElement; static _draggedToast: Toast; constructor(options: Partial) { @@ -93,7 +93,7 @@ export class Toast { } // Create new toast Toast._toasts.push(this); - let toastElement = this._createToast(); + const toastElement = this._createToast(); (toastElement as any).M_Toast = this; this.el = toastElement; this._animateIn(); @@ -161,13 +161,13 @@ export class Toast { static _onDragEnd() { if (!!Toast._draggedToast) { - let toast = Toast._draggedToast; + const toast = Toast._draggedToast; toast.panning = false; toast.el.classList.remove('panning'); - let totalDeltaX = toast.xPos - toast.startingXPos; - let activationDistance = toast.el.offsetWidth * toast.options.activationPercent; - let shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || toast.velocityX > 1; + const totalDeltaX = toast.xPos - toast.startingXPos; + const activationDistance = toast.el.offsetWidth * toast.options.activationPercent; + const shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || toast.velocityX > 1; // Remove toast if (shouldBeDismissed) { @@ -196,7 +196,7 @@ export class Toast { * dismiss all toasts. */ static dismissAll() { - for (let toastIndex in Toast._toasts) { + for (const toastIndex in Toast._toasts) { Toast._toasts[toastIndex].dismiss(); } } @@ -260,8 +260,8 @@ export class Toast { * Dismiss toast with animation. */ dismiss() { - window.clearInterval(this.counterInterval); - let activationDistance = this.el.offsetWidth * this.options.activationPercent; + clearInterval(this.counterInterval); + const activationDistance = this.el.offsetWidth * this.options.activationPercent; if (this.wasSwiped) { this.el.style.transition = 'transform .05s, opacity .05s'; diff --git a/src/tooltip.ts b/src/tooltip.ts index e54bd91211..8d417163df 100644 --- a/src/tooltip.ts +++ b/src/tooltip.ts @@ -230,7 +230,8 @@ export class Tooltip extends Component { tooltipWidth = tooltip.offsetWidth, margin = this.options.margin; - (this.xMovement = 0), (this.yMovement = 0); + this.xMovement = 0; + this.yMovement = 0; let targetTop = origin.getBoundingClientRect().top + Utils.getDocumentScrollTop(); let targetLeft = origin.getBoundingClientRect().left + Utils.getDocumentScrollLeft(); @@ -356,7 +357,7 @@ export class Tooltip extends Component { } _getAttributeOptions(): Partial { - let attributeOptions: Partial = { }; + const attributeOptions: Partial = { }; const tooltipTextOption = this.el.getAttribute('data-tooltip'); const tooltipId = this.el.getAttribute('data-tooltip-id'); const positionOption = this.el.getAttribute('data-position'); diff --git a/src/utils.ts b/src/utils.ts index bbcca5d7d6..76af43801a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -51,6 +51,8 @@ export class Utils { * Detects when document is focused. * @param e Event instance. */ + /* eslint-disabled as of required event type condition check */ + /* eslint-disable-next-line */ static docHandleFocus(e: FocusEvent) { if (Utils.keyDown) { document.body.classList.add('keyboard-focused'); @@ -61,6 +63,8 @@ export class Utils { * Detects when document is not focused. * @param e Event instance. */ + /* eslint-disabled as of required event type condition check */ + /* eslint-disable-next-line */ static docHandleBlur(e: FocusEvent) { document.body.classList.remove('keyboard-focused'); } @@ -84,25 +88,25 @@ export class Utils { * @param offset Element offset. */ static checkWithinContainer(container: HTMLElement, bounding: Bounding, offset: number): Edges { - let edges = { + const edges = { top: false, right: false, bottom: false, left: false }; - let containerRect = container.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); // If body element is smaller than viewport, use viewport height instead. - let containerBottom = + const containerBottom = container === document.body ? Math.max(containerRect.bottom, window.innerHeight) : containerRect.bottom; - let scrollLeft = container.scrollLeft; - let scrollTop = container.scrollTop; + const scrollLeft = container.scrollLeft; + const scrollTop = container.scrollTop; - let scrolledX = bounding.left - scrollLeft; - let scrolledY = bounding.top - scrollTop; + const scrolledX = bounding.left - scrollLeft; + const scrolledY = bounding.top - scrollTop; // Check for container and viewport for each edge if (scrolledX < containerRect.left + offset || scrolledX < offset) { @@ -138,7 +142,7 @@ export class Utils { * @param offset Element offset. */ static checkPossibleAlignments(el: HTMLElement, container: HTMLElement, bounding: Bounding, offset: number) { - let canAlign: { + const canAlign: { top: boolean, right: boolean, bottom: boolean, @@ -158,18 +162,18 @@ export class Utils { spaceOnLeft: null }; - let containerAllowsOverflow = getComputedStyle(container).overflow === 'visible'; - let containerRect = container.getBoundingClientRect(); - let containerHeight = Math.min(containerRect.height, window.innerHeight); - let containerWidth = Math.min(containerRect.width, window.innerWidth); - let elOffsetRect = el.getBoundingClientRect(); + const containerAllowsOverflow = getComputedStyle(container).overflow === 'visible'; + const containerRect = container.getBoundingClientRect(); + const containerHeight = Math.min(containerRect.height, window.innerHeight); + const containerWidth = Math.min(containerRect.width, window.innerWidth); + const elOffsetRect = el.getBoundingClientRect(); - let scrollLeft = container.scrollLeft; - let scrollTop = container.scrollTop; + const scrollLeft = container.scrollLeft; + const scrollTop = container.scrollTop; - let scrolledX = bounding.left - scrollLeft; - let scrolledYTopEdge = bounding.top - scrollTop; - let scrolledYBottomEdge = bounding.top + elOffsetRect.height - scrollTop; + const scrolledX = bounding.left - scrollLeft; + const scrolledYTopEdge = bounding.top - scrollTop; + const scrolledYBottomEdge = bounding.top + elOffsetRect.height - scrollTop; // Check for container and viewport for left canAlign.spaceOnRight = !containerAllowsOverflow @@ -239,21 +243,22 @@ export class Utils { * @param wait Wait time. * @param options Additional options. */ - static throttle(func: Function, wait: number, options: Partial<{leading:boolean,trailing:boolean}> = null) { - let context: object, args: IArguments, result: any; - let timeout = null; - let previous = 0; - options || (options = {}); - let later = function() { + static throttle(func: (Function: object) => void, wait: number, options: Partial<{leading:boolean,trailing:boolean}> = {}) { + let context: object, + args: IArguments, + result, + timeout = null, + previous = 0; + const later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; result = func.apply(context, args); context = args = null; }; return function() { - let now = new Date().getTime(); + const now = new Date().getTime(); if (!previous && options.leading === false) previous = now; - let remaining = wait - (now - previous); + const remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) {