From 4131b77777f4ab17d43d95c3da77b0a023c5f38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Tue, 20 Jun 2023 18:06:28 -0300 Subject: [PATCH 1/3] feat(components): type definitions - Added type definition for generic component (base class); - Added type definition for static and virtual class methods; - Added type definitions for object options; - Added overload definitions to "init" methods; - Added initial version for code docs (props); - Dropped support to jQuery selector in "getInstance" method; - Fixed "Collapsible" destructor. --- src/autocomplete.ts | 166 ++++++++++++++++++++++----- src/buttons.ts | 156 +++++++++++--------------- src/carousel.ts | 163 ++++++++++++++++++++------- src/characterCounter.ts | 57 +++++++--- src/chips.ts | 152 ++++++++++++++++++++----- src/collapsible.ts | 112 ++++++++++++++----- src/component.ts | 108 ++++++++++++++++-- src/datepicker.ts | 241 ++++++++++++++++++++++++++++++++++++---- src/dropdown.ts | 169 ++++++++++++++++++++++------ src/global.ts | 90 ++++++++------- src/materialbox.ts | 97 +++++++++++++--- src/modal.ts | 136 +++++++++++++++++++---- src/parallax.ts | 53 +++++++-- src/pushpin.ts | 69 ++++++++++-- src/range.ts | 57 +++++++--- src/scrollspy.ts | 74 +++++++++--- src/select.ts | 110 ++++++++++++------ src/sidenav.ts | 112 ++++++++++++++++--- src/slider.ts | 116 +++++++++++++++---- src/tabs.ts | 100 +++++++++++++---- src/tapTarget.ts | 89 +++++++++++---- src/timepicker.ts | 173 +++++++++++++++++++++++++--- src/toasts.ts | 146 ++++++++++++++---------- src/tooltip.ts | 133 ++++++++++++++++++---- 24 files changed, 2249 insertions(+), 630 deletions(-) diff --git a/src/autocomplete.ts b/src/autocomplete.ts index a6c672e48d..a87c545983 100644 --- a/src/autocomplete.ts +++ b/src/autocomplete.ts @@ -1,8 +1,74 @@ -import { Component } from "./component"; import { M } from "./global"; -import { Dropdown } from "./dropdown"; +import { Dropdown, DropdownOptions } from "./dropdown"; +import { Component, BaseOptions, InitElements } 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 + */ + id: string | number; + /** + * This optional attribute is used as "display value" for the current entry. + * When provided, it will also be taken into consideration by the standard search function. + */ + text?: string; + /** + * This optional attribute is used to provide a valid image URL to the current option. + */ + image?: string; + /** + * Optional attributes which describes the option. + */ + description?: string; +} + +export interface AutocompleteOptions extends BaseOptions { + /** + * Data object defining autocomplete options with + * optional icon strings. + */ + data: AutocompleteData[]; + /** + * Flag which can be set if multiple values can be selected. The Result will be an Array. + * @default false + */ + isMultiSelect: boolean; + /** + * Callback for when autocompleted. + */ + onAutocomplete: (entries: AutocompleteData[]) => void; + /** + * Minimum number of characters before autocomplete starts. + * @default 1 + */ + minLength: number; + /** + * The height of the Menu which can be set via css-property. + * @default '300px' + */ + maxDropDownHeight: string; + /** + * Function is called when the input text is altered and data can also be loaded asynchronously. + * If the results are collected the items in the list can be updated via the function setMenuItems(collectedItems). + * @param text Searched text. + * @param autocomplete Current autocomplete instance. + */ + onSearch: (text: string, autocomplete: Autocomplete) => void; + /** + * If true will render the key from each item directly as HTML. + * User input MUST be properly sanitized first. + * @default false + */ + allowUnsafeHTML: boolean; + /** + * Pass options object to select dropdown initialization. + * @default {} + */ + dropdownOptions: Partial; +}; -let _defaults = { +let _defaults: AutocompleteOptions = { data: [], // Autocomplete data set onAutocomplete: null, // Callback for when autocompleted dropdownOptions: { @@ -26,29 +92,38 @@ let _defaults = { }; -export class Autocomplete extends Component { - el: HTMLInputElement; +export class Autocomplete extends Component { + declare el: HTMLInputElement; + /** If the autocomplete is open. */ isOpen: boolean; + /** Number of matching autocomplete options. */ count: number; + /** Index of the current selected option. */ activeIndex: number; - private oldVal: any; + private oldVal: string; private $active: HTMLElement|null; private _mousedown: boolean; container: HTMLElement; + /** Instance of the dropdown plugin for this autocomplete. */ dropdown: Dropdown; static _keydown: boolean; - selectedValues: any[]; - menuItems: any[]; + selectedValues: AutocompleteData[]; + menuItems: AutocompleteData[]; - constructor(el, options) { - super(Autocomplete, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Autocomplete); (this.el as any).M_Autocomplete = this; - this.options = {...Autocomplete.defaults, ...options}; + + this.options = { + ...Autocomplete.defaults, + ...options + }; + this.isOpen = false; this.count = 0; this.activeIndex = -1; - this.oldVal; + this.oldVal = ""; this.selectedValues = []; this.menuItems = []; this.$active = null; @@ -56,15 +131,34 @@ export class Autocomplete extends Component { this._setupDropdown(); this._setupEventHandlers(); } - static get defaults() { + + static get defaults(): AutocompleteOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + + /** + * Initializes instance of Autocomplete. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Autocomplete; + /** + * Initializes instances of Autocomplete. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Autocomplete[]; + /** + * Initializes instances of Autocomplete. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Autocomplete | Autocomplete[] { + return super.init(els, options, Autocomplete); } - static getInstance(el) { - let domElem = el.jquery ? el[0] : el; - return domElem.M_Autocomplete; + + static getInstance(el: HTMLElement): Autocomplete { + return (el as any).M_Autocomplete; } destroy() { @@ -253,7 +347,7 @@ export class Autocomplete extends Component { this._mousedown = false; } - _highlightPartialText(input, label) { + _highlightPartialText(input: string, label: string) { const start = label.toLowerCase().indexOf('' + input.toLowerCase() + ''); const end = start + input.length - 1; //custom filters may return results where the string does not match any part @@ -263,9 +357,9 @@ export class Autocomplete extends Component { return [label.slice(0, start), label.slice(start, end + 1), label.slice(end + 1)]; } - _createDropdownItem(entry) { + _createDropdownItem(entry: AutocompleteData) { const item = document.createElement('li'); - item.setAttribute('data-id', entry.id); + item.setAttribute('data-id', entry.id); item.setAttribute( 'style', 'display:grid; grid-auto-flow: column; user-select: none; align-items: center;' @@ -366,7 +460,7 @@ export class Autocomplete extends Component { _refreshInputText() { if (this.selectedValues.length === 1) { const entry = this.selectedValues[0]; - this.el.value = entry.text || entry.id; // Write Text to Input + this.el.value = entry.text || entry.id; // Write Text to Input } } @@ -377,7 +471,10 @@ export class Autocomplete extends Component { this.options.onAutocomplete.call(this, this.selectedValues); } - open() { + /** + * Show autocomplete. + */ + open = () => { const inputText = this.el.value.toLowerCase(); this._resetAutocomplete(); if (inputText.length >= this.options.minLength) { @@ -393,17 +490,28 @@ export class Autocomplete extends Component { else this.dropdown.recalculateDimensions(); // Recalculate dropdown when its already open } - close() { + /** + * Hide autocomplete. + */ + close = () => { this.dropdown.close(); } - setMenuItems(menuItems) { + /** + * Updates the visible or selectable items shown in the menu. + * @param menuItems Items to be available. + */ + setMenuItems(menuItems: AutocompleteData[]) { this.menuItems = menuItems; this.open(); this._updateSelectedInfo(); } - setValues(entries) { + /** + * Sets selected values. + * @param entries + */ + setValues(entries: AutocompleteData[]) { this.selectedValues = entries; this._updateSelectedInfo(); if (!this.options.isMultiSelect) { @@ -412,7 +520,11 @@ export class Autocomplete extends Component { this._triggerChanged(); } - selectOption(id) { + /** + * Select a specific autocomplete option via id-property. + * @param id The id of a data-entry. + */ + selectOption(id: number | string) { const entry = this.menuItems.find((item) => item.id == id); if (!entry) return; // Toggle Checkbox diff --git a/src/buttons.ts b/src/buttons.ts index a11150df20..37d37e34db 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -1,31 +1,57 @@ -import { Component } from "./component"; import anim from "animejs"; -let _defaults = { +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface FloatingActionButtonOptions extends BaseOptions { + /** + * Direction FAB menu opens. + * @default "top" + */ + direction: "top" | "right" | "bottom" | "left"; + /** + * true: FAB menu appears on hover, false: FAB menu appears on click. + * @default true + */ + hoverEnabled: boolean; + /** + * Enable transit the FAB into a toolbar on click. + * @default false + */ + toolbarEnabled: boolean; +}; + +let _defaults: FloatingActionButtonOptions = { direction: 'top', hoverEnabled: true, toolbarEnabled: false }; -export class FloatingActionButton extends Component { - el: HTMLElement; +export class FloatingActionButton extends Component implements Openable { + /** + * Describes open/close state of FAB. + */ isOpen: boolean; + private _anchor: HTMLAnchorElement; private _menu: HTMLElement|null; private _floatingBtns: HTMLElement[]; private _floatingBtnsReverse: HTMLElement[]; + offsetY: number; offsetX: number; btnBottom: number; btnLeft: number; btnWidth: number; - constructor(el, options) { - super(FloatingActionButton, el, options); - + constructor(el: HTMLElement, options: Partial) { + super(el, options, FloatingActionButton); (this.el as any).M_FloatingActionButton = this; - this.options = {...FloatingActionButton.defaults, ...options}; + this.options = { + ...FloatingActionButton.defaults, + ...options + }; + this.isOpen = false; this._anchor = this.el.querySelector('a'); this._menu = this.el.querySelector('ul'); @@ -50,13 +76,29 @@ export class FloatingActionButton extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of FloatingActionButton. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): FloatingActionButton + /** + * Initializes instances of FloatingActionButton. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): FloatingActionButton[]; + /** + * Initializes instances of FloatingActionButton. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): FloatingActionButton | FloatingActionButton[] { + return super.init(els, options, FloatingActionButton); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_FloatingActionButton; + static getInstance(el: HTMLElement): FloatingActionButton { + return (el as any).M_FloatingActionButton; } destroy() { @@ -90,16 +132,15 @@ export class FloatingActionButton extends Component { } } - _handleDocumentClick = (e) => { - const elem = e.target; + _handleDocumentClick = (e: MouseEvent) => { + const elem = e.target; if (elem !== this._menu) this.close; - /* - if (!elem.closest(this.$menu)) { - this.close(); - }*/ } - open = () => { + /** + * Open FAB. + */ + open = (): void => { if (this.isOpen) return; if (this.options.toolbarEnabled) this._animateInToolbar(); @@ -108,12 +149,14 @@ export class FloatingActionButton extends Component { this.isOpen = true; } - close = () => { + /** + * Close FAB. + */ + close = (): void => { if (!this.isOpen) return; if (this.options.toolbarEnabled) { window.removeEventListener('scroll', this.close, true); document.body.removeEventListener('click', this._handleDocumentClick, true); - this._animateOutToolbar(); } else { this._animateOutFAB(); @@ -189,7 +232,7 @@ export class FloatingActionButton extends Component { this._anchor.style.transform = `translateY(${this.offsetY}px`; this._anchor.style.transition = 'none'; - (backdrop).style.backgroundColor = fabColor; + backdrop.style.backgroundColor = fabColor; setTimeout(() => { this.el.style.transform = ''; @@ -214,71 +257,4 @@ export class FloatingActionButton extends Component { }, 100); }, 0); } - - - - - _animateOutToolbar() { - return; - /* - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - let backdrop = this.$el.find('.fab-backdrop'); - let fabColor = this.$anchor.css('background-color'); - - this.offsetX = this.btnLeft - windowWidth / 2 + this.btnWidth / 2; - this.offsetY = windowHeight - this.btnBottom; - - // Hide backdrop - this.$el.removeClass('active'); - this.$el.css({ - 'background-color': 'transparent', - transition: 'none' - }); - // this.$anchor.css({ - // transition: 'none' - // }); - backdrop.css({ - transform: 'scale(0)', - 'background-color': fabColor - }); - - // this.$menu - // .children('li') - // .children('a') - // .css({ - // opacity: '' - // }); - - setTimeout(() => { - backdrop.remove(); - - // Set initial state. - this.$el.css({ - 'text-align': '', - width: '', - bottom: '', - left: '', - overflow: '', - 'background-color': '', - transform: 'translate3d(' + -this.offsetX + 'px,0,0)' - }); - // this.$anchor.css({ - // overflow: '', - // transform: 'translate3d(0,' + this.offsetY + 'px,0)' - // }); - - setTimeout(() => { - this.$el.css({ - transform: 'translate3d(0,0,0)', - transition: 'transform .2s' - }); - // this.$anchor.css({ - // transform: 'translate3d(0,0,0)', - // transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' - // }); - }, 20); - }, 200); - */ - } } diff --git a/src/carousel.ts b/src/carousel.ts index f0b91e2580..9f3c410ac3 100644 --- a/src/carousel.ts +++ b/src/carousel.ts @@ -1,7 +1,55 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface CarouselOptions extends BaseOptions{ + /** + * Transition duration in milliseconds. + * @default 200 + */ + duration: number; + /** + * Perspective zoom. If 0, all items are the same size. + * @default -100 + */ + dist: number; + /** + * Set the spacing of the center item. + * @default 0 + */ + shift: number; + /** + * Set the padding between non center items. + * @default 0 + */ + padding: number; + /** + * Set the number of visible items. + * @default 5 + */ + numVisible: number; + /** + * Make the carousel a full width slider like the second example. + * @default false + */ + fullWidth: boolean; + /** + * Set to true to show indicators. + * @default false + */ + indicators: boolean; + /** + * Don't wrap around and cycle through items. + * @default false + */ + noWrap: boolean; + /** + * Callback for when a new slide is cycled to. + * @default null + */ + onCycleTo: (current: Element, dragged: boolean) => void; +} -let _defaults = { +let _defaults: CarouselOptions = { duration: 200, // ms dist: -100, // zoom scale TODO: make this more intuitive as an option shift: 0, // spacing for center image @@ -13,12 +61,13 @@ let _defaults = { onCycleTo: null // Callback for when a new slide is cycled to. }; -export class Carousel extends Component { - el: HTMLElement; +export class Carousel extends Component { hasMultipleSlides: boolean; showIndicators: boolean; - noWrap: any; + noWrap: boolean; + /** If the carousel is being clicked or tapped. */ pressed: boolean; + /** If the carousel is currently being dragged. */ dragged: boolean; offset: number; target: number; @@ -37,15 +86,20 @@ export class Carousel extends Component { timestamp: number; ticker: NodeJS.Timer; amplitude: number; + /** The index of the center carousel item. */ center: number = 0; imageHeight: any; scrollingTimeout: any; oneTimeCallback: any; - constructor(el: Element, options: Object) { - super(Carousel, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Carousel); (this.el as any).M_Carousel = this; - this.options = {...Carousel.defaults, ...options}; + + this.options = { + ...Carousel.defaults, + ...options + }; // Setup this.hasMultipleSlides = this.el.querySelectorAll('.carousel-item').length > 1; @@ -109,17 +163,33 @@ export class Carousel extends Component { this._scroll(this.offset); } - static get defaults() { + static get defaults(): CarouselOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Carousel. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Carousel; + /** + * Initializes instances of Carousel. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Carousel[]; + /** + * Initializes instances of Carousel. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Carousel | Carousel[] { + return super.init(els, options, Carousel); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Carousel; + static getInstance(el: HTMLElement): Carousel { + return (el as any).M_Carousel; } destroy() { @@ -168,7 +238,7 @@ export class Carousel extends Component { _handleThrottledResize = (() => M.throttle(function(){ this._handleResize(); }, 200, null).bind(this))(); - _handleCarouselTap = (e) => { + _handleCarouselTap = (e: MouseEvent | TouchEvent) => { // Fixes firefox draggable image bug if (e.type === 'mousedown' && (e.target).tagName === 'IMG') { e.preventDefault(); @@ -186,8 +256,8 @@ export class Carousel extends Component { this.ticker = setInterval(this._track, 100); } - _handleCarouselDrag = (e) => { - let x, y, delta, deltaY; + _handleCarouselDrag = (e: MouseEvent | TouchEvent) => { + let x: number, y: number, delta: number, deltaY: number; if (this.pressed) { x = this._xpos(e); y = this._ypos(e); @@ -218,7 +288,7 @@ export class Carousel extends Component { } } - _handleCarouselRelease = (e) => { + _handleCarouselRelease = (e: MouseEvent | TouchEvent) => { if (this.pressed) { this.pressed = false; } else { @@ -249,7 +319,7 @@ export class Carousel extends Component { return false; } - _handleCarouselClick = (e) => { + _handleCarouselClick = (e: MouseEvent | TouchEvent) => { // Disable clicks if carousel was dragged. if (this.dragged) { e.preventDefault(); @@ -268,7 +338,7 @@ export class Carousel extends Component { // fixes https://github.com/materializecss/materialize/issues/180 if (clickedIndex < 0) { // relative X position > center of carousel = clicked at the right part of the carousel - if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) { + if ((e as MouseEvent).clientX - (e.target as HTMLElement).getBoundingClientRect().left > this.el.clientWidth / 2) { this.next(); } else { this.prev(); @@ -335,22 +405,22 @@ export class Carousel extends Component { } } - _xpos(e) { + _xpos(e: MouseEvent | TouchEvent) { // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientX; } // mouse event - return e.clientX; + return (e as MouseEvent).clientX; } - _ypos(e) { + _ypos(e: MouseEvent | TouchEvent) { // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientY; } // mouse event - return e.clientY; + return (e as MouseEvent).clientY; } _wrap(x: number) { @@ -362,7 +432,7 @@ export class Carousel extends Component { } _track = () => { - let now, elapsed, delta, v; + let now: number, elapsed: number, delta: number, v: number; now = Date.now(); elapsed = now - this.timestamp; this.timestamp = now; @@ -373,7 +443,7 @@ export class Carousel extends Component { } _autoScroll = () => { - let elapsed, delta; + let elapsed: number, delta: number; if (this.amplitude) { elapsed = Date.now() - this.timestamp; delta = this.amplitude * Math.exp(-elapsed / this.options.duration); @@ -399,16 +469,16 @@ export class Carousel extends Component { }, this.options.duration); // Start actual scroll - let i, - half, - delta, - dir, - tween, - el, - alignment, - zTranslation, - tweenedOpacity, - centerTweenedOpacity; + 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; @@ -521,7 +591,7 @@ export class Carousel extends Component { el.style.visibility = 'visible'; } - _cycleTo(n: number, callback: Function = null) { + _cycleTo(n: number, callback: CarouselOptions["onCycleTo"] = null) { let diff = (this.center % this.count) - n; // Account for wraparound. if (!this.noWrap) { @@ -555,6 +625,10 @@ export class Carousel extends Component { } } + /** + * Move carousel to next slide or go forward a given amount of slides. + * @param n How many times the carousel slides. + */ next(n: number = 1) { if (n === undefined || isNaN(n)) { n = 1; @@ -567,6 +641,10 @@ export class Carousel extends Component { this._cycleTo(index); } + /** + * Move carousel to previous slide or go back a given amount of slides. + * @param n How many times the carousel slides. + */ prev(n: number = 1) { if (n === undefined || isNaN(n)) { n = 1; @@ -579,7 +657,12 @@ export class Carousel extends Component { this._cycleTo(index); } - set(n: number, callback: Function) { + /** + * Move carousel to nth slide. + * @param n Index of slide. + * @param callback "onCycleTo" optional callback. + */ + set(n: number, callback?: CarouselOptions["onCycleTo"]) { if (n === undefined || isNaN(n)) { n = 0; } diff --git a/src/characterCounter.ts b/src/characterCounter.ts index 029fd11290..cb3eb179c0 100644 --- a/src/characterCounter.ts +++ b/src/characterCounter.ts @@ -1,33 +1,64 @@ -import { Component } from "./component"; +import { Component, BaseOptions, InitElements } from "./component"; -let _defaults = {}; +export interface CharacterCounterOptions extends BaseOptions {}; -export class CharacterCounter extends Component { +const _defaults = Object.freeze({}); + +type InputElement = HTMLInputElement | HTMLTextAreaElement; + +export class CharacterCounter extends Component<{}> { + + declare el: InputElement; + /** Stores the reference to the counter HTML element. */ + counterEl: HTMLSpanElement; + /** Specifies whether the input is valid or not. */ isInvalid: boolean; + /** Specifies whether the input text has valid length or not. */ isValidLength: boolean; - counterEl: HTMLSpanElement; - constructor(el: Element, options: Object) { - super(CharacterCounter, el, options); + constructor(el: HTMLInputElement | HTMLTextAreaElement, options: Partial) { + super(el, {}, CharacterCounter); (this.el as any).M_CharacterCounter = this; - this.options = {...CharacterCounter.defaults, ...options}; + + this.options = { + ...CharacterCounter.defaults, + ...options + }; + this.isInvalid = false; this.isValidLength = false; + this._setupCounter(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): CharacterCounterOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of CharacterCounter. + * @param el HTML element. + * @param options Component options. + */ + 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[]; + /** + * Initializes instances of CharacterCounter. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InputElement | InitElements, options: Partial): CharacterCounter | CharacterCounter[] { + return super.init(els, options, CharacterCounter); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_CharacterCounter; + static getInstance(el: InputElement): CharacterCounter { + return (el as any).M_CharacterCounter; } destroy() { diff --git a/src/chips.ts b/src/chips.ts index d29f408621..5a76d204c2 100644 --- a/src/chips.ts +++ b/src/chips.ts @@ -1,8 +1,76 @@ -import { Component } from "./component"; import { M } from "./global"; -import { Autocomplete } from "./autocomplete"; +import { Autocomplete, AutocompleteOptions } from "./autocomplete"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ChipData { + /** + * Unique identifier. + */ + id: number|string; + /** + * Chip text. If not specified, "id" will be used. + */ + text?: string; + /** + * Chip image (URL). + */ + image?: string; +} + +export interface ChipsOptions extends BaseOptions{ + /** + * Set the chip data. + * @default [] + */ + data: ChipData[]; + /** + * Set first placeholder when there are no tags. + * @default "" + */ + placeholder: string; + /** + * Set second placeholder when adding additional tags. + * @default "" + */ + secondaryPlaceholder: string; + /** + * Set autocomplete options. + * @default {} + */ + autocompleteOptions: Partial; + /** + * Toggles abililty to add custom value not in autocomplete list. + * @default false + */ + autocompleteOnly: boolean; + /** + * Set chips limit. + * @default Infinity + */ + limit: number; + /** + * Specifies class to be used in "close" button (useful when working with Material Symbols icon set). + * @default 'material-icons' + */ + closeIconClass: string; + /** + * Callback for chip add. + * @default null + */ + onChipAdd: (element: HTMLElement, chip: HTMLElement) => void; + /** + * Callback for chip select. + * @default null + */ + onChipSelect: (element: HTMLElement, chip: HTMLElement) => void; + /** + * Callback for chip delete. + * @default null + */ + onChipDelete: (element: HTMLElement, chip: HTMLElement) => void; +} -let _defaults = { +let _defaults: ChipsOptions = { data: [], placeholder: '', secondaryPlaceholder: '', @@ -15,20 +83,16 @@ let _defaults = { onChipDelete: null }; -interface DataBit { - id: string, // required - text?: string, - image?: string, - description?: string, -} - function gGetIndex(el: HTMLElement): number { return [...el.parentNode.children].indexOf(el); } -export class Chips extends Component { - chipsData: DataBit[]; +export class Chips extends Component { + /** Array of the current chips data. */ + chipsData: ChipData[]; + /** If the chips has autocomplete enabled. */ hasAutocomplete: boolean; + /** Autocomplete instance, if any. */ autocomplete: Autocomplete; _input: HTMLInputElement; _label: any; @@ -36,10 +100,14 @@ export class Chips extends Component { static _keydown: boolean; private _selectedChip: any; - constructor(el, options) { - super(Chips, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Chips); (this.el as any).M_Chips = this; - this.options = {...Chips.defaults, ...options}; + + this.options = { + ...Chips.defaults, + ...options + }; this.el.classList.add('chips', 'input-field'); this.chipsData = []; @@ -67,13 +135,29 @@ export class Chips extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Chips. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Chips; + /** + * Initializes instances of Chips. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Chips[]; + /** + * Initializes instances of Chips. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Chips | Chips[] { + return super.init(els, options, Chips); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Chips; + static getInstance(el: HTMLElement): Chips { + return (el as any).M_Chips; } getData() { @@ -107,7 +191,7 @@ export class Chips extends Component { this._input.removeEventListener('keydown', this._handleInputKeydown); } - _handleChipClick = (e: Event) => { + _handleChipClick = (e: MouseEvent) => { const _chip = (e.target).closest('.chip'); const clickedClose = (e.target).classList.contains('close'); if (_chip) { @@ -213,11 +297,11 @@ export class Chips extends Component { } } - _renderChip(chip: DataBit): HTMLDivElement { + _renderChip(chip: ChipData): HTMLDivElement { if (!chip.id) return; const renderedChip = document.createElement('div'); renderedChip.classList.add('chip'); - renderedChip.innerText = chip.text || chip.id; + renderedChip.innerText = chip.text || chip.id; renderedChip.setAttribute('tabindex', "0"); const closeIcon = document.createElement('i'); closeIcon.classList.add(this.options.closeIconClass, 'close'); @@ -245,7 +329,11 @@ export class Chips extends Component { _setupAutocomplete() { this.options.autocompleteOptions.onAutocomplete = (items) => { - if (items.length > 0) this.addChip(items[0]); + if (items.length > 0) this.addChip({ + id: items[0].id, + text: items[0].text, + image: items[0].image + }); this._input.value = ''; this._input.focus(); }; @@ -278,13 +366,17 @@ export class Chips extends Component { } } - _isValidAndNotExist(chip: DataBit) { + _isValidAndNotExist(chip: ChipData) { const isValid = !!chip.id; const doesNotExist = !this.chipsData.some(item => item.id == chip.id); return isValid && doesNotExist; } - addChip(chip: DataBit) { + /** + * Add chip to input. + * @param chip Chip data object + */ + addChip(chip: ChipData) { if (!this._isValidAndNotExist(chip) || this.chipsData.length >= this.options.limit) return; const renderedChip = this._renderChip(chip); this._chips.push(renderedChip); @@ -298,6 +390,10 @@ export class Chips extends Component { } } + /** + * Delete nth chip. + * @param chipIndex Index of chip + */ deleteChip(chipIndex: number) { const chip = this._chips[chipIndex]; this._chips[chipIndex].remove(); @@ -310,6 +406,10 @@ export class Chips extends Component { } } + /** + * Select nth chip. + * @param chipIndex Index of chip + */ selectChip(chipIndex: number) { const chip = this._chips[chipIndex]; this._selectedChip = chip; diff --git a/src/collapsible.ts b/src/collapsible.ts index cfe01db1c2..7196424734 100644 --- a/src/collapsible.ts +++ b/src/collapsible.ts @@ -1,34 +1,68 @@ -import { M } from "./global"; -import { Component } from "./component"; import anim from "animejs"; -interface CollapsibleOptions { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface CollapsibleOptions extends BaseOptions { + /** + * If accordion versus collapsible. + * @default true + */ accordion: boolean; - onOpenStart: Function|undefined; - onOpenEnd: Function|undefined; - onCloseStart: Function|undefined; - onCloseEnd: Function|undefined; + /** + * Transition in duration in milliseconds. + * @default 300 + */ inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 300 + */ outDuration: number; + /** + * Callback function called before collapsible is opened. + * @default null + */ + onOpenStart: (el: Element) => void; + /** + * Callback function called after collapsible is opened. + * @default null + */ + onOpenEnd: (el: Element) => void; + /** + * Callback function called before collapsible is closed. + * @default null + */ + onCloseStart: (el: Element) => void; + /** + * Callback function called after collapsible is closed. + * @default null + */ + onCloseEnd: (el: Element) => void; } const _defaults: CollapsibleOptions = { accordion: true, - onOpenStart: undefined, - onOpenEnd: undefined, - onCloseStart: undefined, - onCloseEnd: undefined, + onOpenStart: null, + onOpenEnd: null, + onCloseStart: null, + onCloseEnd: null, inDuration: 300, outDuration: 300 }; -export class Collapsible extends Component { +export class Collapsible extends Component { private _headers: HTMLElement[]; - constructor(el: HTMLElement, options: CollapsibleOptions) { - super(Collapsible, el, options); - this.el['M_Collapsible'] = this; - this.options = {...Collapsible.defaults, ...options}; + constructor(el: HTMLElement, options: Partial) { + super(el, options, Collapsible); + (this.el as any).M_Collapsible = this; + + this.options = { + ...Collapsible.defaults, + ...options + }; + // Setup tab indices this._headers = Array.from(this.el.querySelectorAll('li > .collapsible-header')); this._headers.forEach(el => el.tabIndex = 0); @@ -42,22 +76,38 @@ export class Collapsible extends Component { activeBodies.forEach(el => el.style.display = 'block'); // Expandables } - static get defaults() { + static get defaults(): CollapsibleOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Collapsible. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Collapsible; + /** + * Initializes instances of Collapsible. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Collapsible[]; + /** + * Initializes instances of Collapsible. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Collapsible | Collapsible[] { + return super.init(els, options, Collapsible); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Collapsible; + static getInstance(el: HTMLElement): Collapsible { + return (el as any).M_Collapsible; } destroy() { this._removeEventHandlers(); - this.el['M_Collapsible'].M_Collapsible = undefined; + (this.el as any).M_Collapsible = undefined; } _setupEventHandlers() { @@ -70,8 +120,8 @@ export class Collapsible extends Component { this._headers.forEach(header => header.removeEventListener('keydown', this._handleCollapsibleKeydown)); } - _handleCollapsibleClick = (e) => { - const header = e.target.closest('.collapsible-header'); + _handleCollapsibleClick = (e: MouseEvent | KeyboardEvent) => { + const header = (e.target as HTMLElement).closest('.collapsible-header'); if (e.target && header) { const collapsible = header.closest('.collapsible'); if (collapsible !== this.el) return; @@ -154,7 +204,11 @@ export class Collapsible extends Component { }); } - open(index: number) { + /** + * Open collapsible section. + * @param n Nth section to open. + */ + open = (index: number) => { const listItems = Array.from(this.el.children).filter(c => c.tagName === 'LI'); const li = listItems[index]; if (li && !li.classList.contains('active')) { @@ -176,7 +230,11 @@ export class Collapsible extends Component { } } - close(index: number) { + /** + * Close collapsible section. + * @param n Nth section to close. + */ + close = (index: number) => { const li = Array.from(this.el.children).filter(c => c.tagName === 'LI')[index]; if (li && li.classList.contains('active')) { // onCloseStart callback diff --git a/src/component.ts b/src/component.ts index 23528dbb50..a94d35ad07 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,9 +1,45 @@ +/** + * Base options for component initialization. + */ +export interface BaseOptions {}; -export class Component { +export type InitElements = NodeListOf | HTMLCollectionOf; +type ComponentConstructor, O extends BaseOptions> = { + new (el: HTMLElement, options: Partial): T +}; +type ComponentType, O extends BaseOptions> = ComponentConstructor & typeof Component; - constructor(classDef, protected el: Element, protected options) { - // Display error if el is valid HTML Element - if (!(el instanceof Element)) { +export interface I18nOptions { + cancel: string; + clear: string; + done: string; +} + +export interface Openable { + isOpen: boolean; + open(): void; + close(): void; +}; + +/** + * Base class implementation for Materialize components. + */ +export class Component{ + /** + * The DOM element the plugin was initialized with. + */ + el: HTMLElement; + /** + * The options the instance was initialized with. + */ + options: O; + + /** + * Constructs component instance and set everything up. + */ + constructor(el: HTMLElement, options: Partial, classDef: ComponentType, O>){ + // Display error if el is not a valid HTML Element + if (!(el instanceof HTMLElement)) { console.error(Error(el + ' is not an HTML Element')); } // If exists, destroy and reinitialize in child @@ -14,18 +50,70 @@ export class Component { this.el = el; } - static init(classDef, els, options) { + /** + * Initializes component instance. + * @param el HTML element. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(el: I, options: O, classDef: ComponentType): C; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: InitElements, options: Partial, classDef: ComponentType): C[]; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[]; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[] { let instances = null; - if (els instanceof Element) { + if (els instanceof HTMLElement) { instances = new classDef(els, options); } - else if (!!els && (els.jquery || els.cash || els instanceof NodeList || els instanceof HTMLCollection)) { - let instancesArr = []; + else if (!!els && els.length) { + instances = []; for (let i = 0; i < els.length; i++) { - instancesArr.push(new classDef(els[i], options)); + instances.push(new classDef(els[i], options)); } - instances = instancesArr; } return instances; } + + /** + * @returns default options for component instance. + */ + static get defaults(): BaseOptions{ return {}; } + + /** + * Retrieves component instance for the given element. + * @param el Associated HTML Element. + */ + static getInstance(el: HTMLElement): Component { + throw new Error("This method must be implemented."); + } + + /** + * Destroy plugin instance and teardown. + */ + destroy(): void { throw new Error("This method must be implemented."); } } diff --git a/src/datepicker.ts b/src/datepicker.ts index b5be4d7426..6f46fbaf10 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -1,9 +1,155 @@ -import { Component } from "./component"; import { M } from "./global"; import { Modal } from "./modal"; import { FormSelect } from "./select"; +import { BaseOptions, Component, InitElements, I18nOptions } from "./component"; + +export interface DateI18nOptions extends I18nOptions { + previousMonth: string; + nextMonth: string; + months: string[]; + monthsShort: string[]; + weekdays: string[]; + weekdaysShort: string[]; + weekdaysAbbrev: string[]; +}; + +export interface DatepickerOptions extends BaseOptions { + /** + * Automatically close picker when date is selected. + * @default false + */ + autoClose: boolean; + /** + * The date output format for the input field value + * or a function taking the date and outputting the + * formatted date string. + * @default 'mmm dd, yyyy' + */ + format: string | ((d: Date) => string); + /** + * Used to create date object from current input string. + * @default null + */ + parse: ((value: string, format: string) => Date) | null; + /** + * The initial date to view when first opened. + * @default null + */ + defaultDate: Date | null; + /** + * Make the `defaultDate` the initial selected value. + * @default false + */ + setDefaultDate: boolean; + /** + * Prevent selection of any date on the weekend. + * @default false + */ + disableWeekends: boolean; + /** + * Custom function to disable certain days. + * @default null + */ + disableDayFn: ((day: Date) => boolean) | null; + /** + * First day of week (0: Sunday, 1: Monday etc). + * @default 0 + */ + firstDay: number; + /** + * The earliest date that can be selected. + * @default null + */ + minDate: Date | null; + /** + * The latest date that can be selected. + * @default null + */ + maxDate: Date | null; + /** + * Number of years either side, or array of upper/lower range. + * @default 10 + */ + yearRange: number | number[]; + /** + * Sort year range in reverse order. + * @default false + */ + yearRangeReverse: boolean; + /** + * Changes Datepicker to RTL. + * @default false + */ + isRTL: boolean; + /** + * Show month after year in Datepicker title. + * @default false + */ + showMonthAfterYear: boolean; + /** + * Render days of the calendar grid that fall in the next + * or previous month. + * @default false + */ + showDaysInNextAndPreviousMonths: boolean; + /** + * Specify a DOM element OR selector for a DOM element to render + * the calendar in, by default it will be placed before the input. + * @default null + */ + container: HTMLElement | string | null; + /** + * Show the clear button in the datepicker. + * @default false + */ + showClearBtn: boolean; + /** + * Internationalization options. + */ + i18n: Partial; + /** + * An array of string returned by `Date.toDateString()`, + * indicating there are events in the specified days. + * @default [] + */ + events: string[]; + /** + * Callback function when date is selected, + * first parameter is the newly selected date. + * @default null + */ + onSelect: ((selectedDate: Date) => void) | null; + /** + * Callback function when Datepicker is opened. + * @default null + */ + onOpen: (() => void) | null; + /** + * Callback function when Datepicker is closed. + * @default null + */ + onClose: (() => void) | null; + /** + * Callback function when Datepicker HTML is refreshed. + * @default null + */ + onDraw: (() => void) | null; + + /** Field used for internal calculations DO NOT CHANGE IT */ + minYear?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + maxYear?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + minMonth?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + maxMonth?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + startRange?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + endRange?: any; +} -let _defaults = { +let _defaults: DatepickerOptions = { // Close when date is selected autoClose: false, // the default output format for the input field value @@ -32,6 +178,7 @@ let _defaults = { startRange: null, endRange: null, isRTL: false, + yearRangeReverse: false, // Render the month after year in the calendar title showMonthAfterYear: false, // Render days of the calendar grid that fall in the next or previous month @@ -88,29 +235,37 @@ let _defaults = { onDraw: null }; -export class Datepicker extends Component { +export class Datepicker extends Component { el: HTMLInputElement id: string; + /** If the picker is open. */ isOpen: boolean; modal: Modal; calendarEl: HTMLElement; + /** CLEAR button instance. */ clearBtn: HTMLElement; + /** DONE button instance */ doneBtn: HTMLElement; cancelBtn: HTMLElement; modalEl: HTMLElement; yearTextEl: HTMLElement; dateTextEl: HTMLElement; - date: any; + /** The selected Date. */ + date: Date; formats: any; calendars: any; private _y: any; private _m: any; static _template: string; - constructor(el, options) { - super(Datepicker, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Datepicker); (this.el as any).M_Datepicker = this; - this.options = {...Datepicker.defaults, ...options}; + + this.options = { + ...Datepicker.defaults, + ...options + }; // make sure i18n defaults are not lost when only few i18n option properties are passed if (!!options && options.hasOwnProperty('i18n') && typeof options.i18n === 'object') { @@ -152,8 +307,25 @@ export class Datepicker extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Datepicker. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Datepicker; + /** + * Initializes instances of Datepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Datepicker[]; + /** + * Initializes instances of Datepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Datepicker | Datepicker[] { + return super.init(els, options, Datepicker); } static _isDate(obj) { @@ -185,9 +357,8 @@ export class Datepicker extends Component { return a.getTime() === b.getTime(); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Datepicker; + static getInstance(el: HTMLElement): Datepicker { + return (el as any).M_Datepicker; } destroy() { @@ -201,11 +372,11 @@ export class Datepicker extends Component { destroySelects() { let oldYearSelect = this.calendarEl.querySelector('.orig-select-year'); if (oldYearSelect) { - FormSelect.getInstance(oldYearSelect).destroy(); + FormSelect.getInstance(oldYearSelect as HTMLElement).destroy(); } let oldMonthSelect = this.calendarEl.querySelector('.orig-select-month'); if (oldMonthSelect) { - FormSelect.getInstance(oldMonthSelect).destroy(); + FormSelect.getInstance(oldMonthSelect as HTMLElement).destroy(); } } @@ -220,7 +391,7 @@ export class Datepicker extends Component { if (this.options.container) { const optEl = this.options.container; this.options.container = - optEl instanceof HTMLElement ? optEl : document.querySelector(optEl); + optEl instanceof HTMLElement ? optEl : document.querySelector(optEl) as HTMLElement; this.options.container.append(this.modalEl); } else { @@ -238,6 +409,9 @@ export class Datepicker extends Component { }); } + /** + * Gets a string representation of the selected date. + */ toString(format: string | ((d: Date) => string) = null): string { format = format || this.options.format; if (typeof format === 'function') return format(this.date); @@ -250,7 +424,12 @@ export class Datepicker extends Component { return formattedDate; } - setDate(date, preventOnSelect: boolean = false) { + /** + * Set a date on the datepicker. + * @param date Date to set on the datepicker. + * @param preventOnSelect Undocumented as of 5 March 2018. + */ + setDate(date: Date | string = null, preventOnSelect: boolean = false) { if (!date) { this.date = null; this._renderDateDisplay(); @@ -279,6 +458,9 @@ export class Datepicker extends Component { } } + /** + * Sets current date as the input value. + */ setInputValue() { this.el.value = this.toString(); this.el.dispatchEvent(new CustomEvent('change', {detail: {firedBy: this}})); @@ -290,11 +472,15 @@ export class Datepicker extends Component { let day = i18n.weekdaysShort[displayDate.getDay()]; let month = i18n.monthsShort[displayDate.getMonth()]; let date = displayDate.getDate(); - this.yearTextEl.innerHTML = displayDate.getFullYear(); + this.yearTextEl.innerHTML = displayDate.getFullYear().toString(); this.dateTextEl.innerHTML = `${day}, ${month} ${date}`; } - gotoDate(date) { + /** + * Change date view to a specific date on the datepicker. + * @param date Date to show on the datepicker. + */ + gotoDate(date: Date) { let newCalendar = true; if (!Datepicker._isDate(date)) { return; @@ -649,8 +835,8 @@ export class Datepicker extends Component { this.calendarEl.innerHTML = html; // Init Materialize Select - let yearSelect = this.calendarEl.querySelector('.orig-select-year'); - let monthSelect = this.calendarEl.querySelector('.orig-select-month'); + let yearSelect = this.calendarEl.querySelector('.orig-select-year') as HTMLElement; + let monthSelect = this.calendarEl.querySelector('.orig-select-month') as HTMLElement; FormSelect.init(yearSelect, { classes: 'select-year', dropdownOptions: { container: document.body, constrainWidth: false } @@ -665,7 +851,7 @@ export class Datepicker extends Component { monthSelect.addEventListener('change', () => this._handleMonthChange); if (typeof this.options.onDraw === 'function') { - this.options.onDraw(this); + this.options.onDraw.call(this); } } @@ -814,7 +1000,10 @@ export class Datepicker extends Component { // Prevent change event from being fired when triggered by the plugin if (e['detail']?.firedBy === this) return; if (this.options.parse) { - date = this.options.parse(this.el.value, this.options.format); + date = this.options.parse(this.el.value, + typeof this.options.format === "function" + ? this.options.format(new Date(this.el.value)) + : this.options.format); } else { date = new Date(Date.parse(this.el.value)); @@ -836,7 +1025,10 @@ export class Datepicker extends Component { this.close(); } - open() { + /** + * Open datepicker. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; if (typeof this.options.onOpen === 'function') { @@ -847,6 +1039,9 @@ export class Datepicker extends Component { return this; } + /** + * Close datepicker. + */ close = () => { if (!this.isOpen) return; this.isOpen = false; diff --git a/src/dropdown.ts b/src/dropdown.ts index 24d397e300..b4770cf141 100644 --- a/src/dropdown.ts +++ b/src/dropdown.ts @@ -1,8 +1,82 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -const _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface DropdownOptions extends BaseOptions { + /** + * Defines the edge the menu is aligned to. + * @default 'left' + */ + alignment: 'left' | 'right'; + /** + * If true, automatically focus dropdown el for keyboard. + * @default true + */ + autoFocus: boolean; + /** + * If true, constrainWidth to the size of the dropdown activator. + * @default true + */ + constrainWidth: boolean; + /** + * Provide an element that will be the bounding container of the dropdown. + * @default null + */ + container: Element; + /** + * If false, the dropdown will show below the trigger. + * @default true + */ + coverTrigger: boolean; + /** + * If true, close dropdown on item click. + * @default true + */ + closeOnClick: boolean; + /** + * If true, the dropdown will open on hover. + * @default false + */ + hover: boolean; + /** + * The duration of the transition enter in milliseconds. + * @default 150 + */ + inDuration: number; + /** + * The duration of the transition out in milliseconds. + * @default 250 + */ + outDuration: number; + /** + * Function called when dropdown starts entering. + * @default null + */ + onOpenStart: (el: HTMLElement) => void; + /** + * Function called when dropdown finishes entering. + * @default null + */ + onOpenEnd: (el: HTMLElement) => void; + /** + * Function called when dropdown starts exiting. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Function called when dropdown finishes exiting. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Function called when item is clicked. + * @default null + */ + onItemClick: (el: HTMLLIElement) => void; +}; + +const _defaults: DropdownOptions = { alignment: 'left', autoFocus: true, constrainWidth: true, @@ -19,26 +93,34 @@ const _defaults = { onItemClick: null }; -export class Dropdown extends Component { - el: HTMLElement; +export class Dropdown extends Component implements Openable { static _dropdowns: Dropdown[] = []; + /** ID of the dropdown element. */ id: string; + /** The DOM element of the dropdown. */ dropdownEl: HTMLElement; + /** If the dropdown is open. */ isOpen: boolean; + /** If the dropdown content is scrollable. */ isScrollable: boolean; isTouchMoving: boolean; + /** The index of the item focused. */ focusedIndex: number; filterQuery: any[]; filterTimeout: NodeJS.Timeout; - constructor(el, options) { - super(Dropdown, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Dropdown); (this.el as any).M_Dropdown = this; + Dropdown._dropdowns.push(this); this.id = M.getIdFromTrigger(el); this.dropdownEl = document.getElementById(this.id); - //this.$dropdownEl = $(this.dropdownEl); - this.options = {...Dropdown.defaults, ...options}; + + this.options = { + ...Dropdown.defaults, + ...options + }; this.isOpen = false; this.isScrollable = false; @@ -52,17 +134,33 @@ export class Dropdown extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): DropdownOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Dropdown. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Dropdown; + /** + * Initializes instances of Dropdown. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Dropdown[]; + /** + * Initializes instances of Dropdown. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Dropdown | Dropdown[] { + return super.init(els, options, Dropdown); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Dropdown; + static getInstance(el: HTMLElement): Dropdown { + return (el as any).M_Dropdown; } destroy() { @@ -114,7 +212,7 @@ export class Dropdown extends Component { this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydown); } - _handleClick = (e) => { + _handleClick = (e: MouseEvent) => { e.preventDefault(); this.open(); } @@ -123,8 +221,8 @@ export class Dropdown extends Component { this.open(); } - _handleMouseLeave = (e) => { - const toEl = e.toElement || e.relatedTarget; + _handleMouseLeave = (e: MouseEvent) => { + const toEl = e.relatedTarget as HTMLElement; const leaveToDropdownContent = !!toEl.closest('.dropdown-content'); let leaveToActiveDropdownTrigger = false; const closestTrigger = toEl.closest('.dropdown-trigger'); @@ -141,7 +239,7 @@ export class Dropdown extends Component { } } - _handleDocumentClick = (e) => { + _handleDocumentClick = (e: MouseEvent) => { const target = e.target; if ( this.options.closeOnClick && @@ -173,17 +271,17 @@ export class Dropdown extends Component { } } - _handleDocumentTouchmove = (e) => { + _handleDocumentTouchmove = (e: TouchEvent) => { const target = e.target; if (target.closest('.dropdown-content')) { this.isTouchMoving = true; } } - _handleDropdownClick = (e) => { + _handleDropdownClick = (e: MouseEvent) => { // onItemClick callback if (typeof this.options.onItemClick === 'function') { - const itemEl = e.target.closest('li'); + const itemEl = (e.target).closest('li'); this.options.onItemClick.call(this, itemEl); } } @@ -273,7 +371,7 @@ export class Dropdown extends Component { } // Move dropdown after container or trigger - _moveDropdown(containerEl = null) { + _moveDropdown(containerEl: HTMLElement = null) { if (!!this.options.container) { this.options.container.append(this.dropdownEl); } @@ -315,7 +413,7 @@ export class Dropdown extends Component { } } - _getDropdownPosition(closestOverflowParent) { + _getDropdownPosition(closestOverflowParent: HTMLElement) { const offsetParentBRect = this.el.offsetParent.getBoundingClientRect(); const triggerBRect = this.el.getBoundingClientRect(); const dropdownBRect = this.dropdownEl.getBoundingClientRect(); @@ -449,20 +547,20 @@ export class Dropdown extends Component { }); } - private _getClosestAncestor(el: Element, condition: Function): Element { + private _getClosestAncestor(el: HTMLElement, condition: Function): HTMLElement { let ancestor = el.parentNode; while (ancestor !== null && ancestor !== document) { if (condition(ancestor)) { - return ancestor; + return ancestor; } - ancestor = ancestor.parentNode; + ancestor = ancestor.parentElement; } return null; }; _placeDropdown() { // Container here will be closest ancestor with overflow: hidden - let closestOverflowParent: HTMLElement = this._getClosestAncestor(this.dropdownEl, (ancestor: HTMLElement) => { + let closestOverflowParent: HTMLElement = this._getClosestAncestor(this.dropdownEl, (ancestor: HTMLElement) => { return !['HTML','BODY'].includes(ancestor.tagName) && getComputedStyle(ancestor).overflow !== 'visible'; }); // Fallback @@ -493,7 +591,10 @@ export class Dropdown extends Component { } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`; } - open() { + /** + * Open dropdown. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; // onOpenStart callback @@ -508,7 +609,10 @@ export class Dropdown extends Component { this._setupTemporaryEventHandlers(); } - close() { + /** + * Close dropdown. + */ + close = () => { if (!this.isOpen) return; this.isOpen = false; this.focusedIndex = -1; @@ -523,7 +627,10 @@ export class Dropdown extends Component { } } - recalculateDimensions() { + /** + * While dropdown is open, you can recalculate its dimensions if its contents have changed. + */ + recalculateDimensions = () => { if (this.isOpen) { this.dropdownEl.style.width = ''; this.dropdownEl.style.height = ''; diff --git a/src/global.ts b/src/global.ts index d9c942673d..9075ede485 100644 --- a/src/global.ts +++ b/src/global.ts @@ -159,46 +159,50 @@ export class M { } //--- - static AutoInit(context:Element = null) { - let root = !!context ? context : document.body; + /** + * Automatically initialize components. + * @param context Root element to initialize. Defaults to `document.body`. + */ + static AutoInit(context: HTMLElement = document.body) { + let root = context; let registry = { - Autocomplete: root.querySelectorAll('.autocomplete:not(.no-autoinit)'), - Carousel: root.querySelectorAll('.carousel:not(.no-autoinit)'), - Chips: root.querySelectorAll('.chips:not(.no-autoinit)'), - Collapsible: root.querySelectorAll('.collapsible:not(.no-autoinit)'), - Datepicker: root.querySelectorAll('.datepicker:not(.no-autoinit)'), - Dropdown: root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)'), - Materialbox: root.querySelectorAll('.materialboxed:not(.no-autoinit)'), - Modal: root.querySelectorAll('.modal:not(.no-autoinit)'), - Parallax: root.querySelectorAll('.parallax:not(.no-autoinit)'), - Pushpin: root.querySelectorAll('.pushpin:not(.no-autoinit)'), - ScrollSpy: root.querySelectorAll('.scrollspy:not(.no-autoinit)'), - FormSelect: root.querySelectorAll('select:not(.no-autoinit)'), - Sidenav: root.querySelectorAll('.sidenav:not(.no-autoinit)'), - Tabs: root.querySelectorAll('.tabs:not(.no-autoinit)'), - TapTarget: root.querySelectorAll('.tap-target:not(.no-autoinit)'), - Timepicker: root.querySelectorAll('.timepicker:not(.no-autoinit)'), - Tooltip: root.querySelectorAll('.tooltipped:not(.no-autoinit)'), - FloatingActionButton: root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)'), + Autocomplete: >root.querySelectorAll('.autocomplete:not(.no-autoinit)'), + Carousel: >root.querySelectorAll('.carousel:not(.no-autoinit)'), + Chips: >root.querySelectorAll('.chips:not(.no-autoinit)'), + Collapsible: >root.querySelectorAll('.collapsible:not(.no-autoinit)'), + Datepicker: >root.querySelectorAll('.datepicker:not(.no-autoinit)'), + Dropdown: >root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)'), + Materialbox: >root.querySelectorAll('.materialboxed:not(.no-autoinit)'), + Modal: >root.querySelectorAll('.modal:not(.no-autoinit)'), + Parallax: >root.querySelectorAll('.parallax:not(.no-autoinit)'), + Pushpin: >root.querySelectorAll('.pushpin:not(.no-autoinit)'), + ScrollSpy: >root.querySelectorAll('.scrollspy:not(.no-autoinit)'), + FormSelect: >root.querySelectorAll('select:not(.no-autoinit)'), + Sidenav: >root.querySelectorAll('.sidenav:not(.no-autoinit)'), + Tabs: >root.querySelectorAll('.tabs:not(.no-autoinit)'), + TapTarget: >root.querySelectorAll('.tap-target:not(.no-autoinit)'), + Timepicker: >root.querySelectorAll('.timepicker:not(.no-autoinit)'), + Tooltip: >root.querySelectorAll('.tooltipped:not(.no-autoinit)'), + FloatingActionButton: >root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)'), }; - M.Autocomplete.init(registry.Autocomplete, null); - M.Carousel.init(registry.Carousel, null); - M.Chips.init(registry.Chips, null); - M.Collapsible.init(registry.Collapsible, null); - M.Datepicker.init(registry.Datepicker, null); - M.Dropdown.init(registry.Dropdown, null); - M.Materialbox.init(registry.Materialbox, null); - M.Modal.init(registry.Modal, null); - M.Parallax.init(registry.Parallax, null); - M.Pushpin.init(registry.Pushpin, null); - M.ScrollSpy.init(registry.ScrollSpy, null); - M.FormSelect.init(registry.FormSelect, null); - M.Sidenav.init(registry.Sidenav, null); - M.Tabs.init(registry.Tabs, null); - M.TapTarget.init(registry.TapTarget, null); - M.Timepicker.init(registry.Timepicker, null); - M.Tooltip.init(registry.Tooltip, null); - M.FloatingActionButton.init(registry.FloatingActionButton, null); + M.Autocomplete.init(registry.Autocomplete, {}); + M.Carousel.init(registry.Carousel, {}); + M.Chips.init(registry.Chips, {}); + M.Collapsible.init(registry.Collapsible, {}); + M.Datepicker.init(registry.Datepicker, {}); + M.Dropdown.init(registry.Dropdown, {}); + M.Materialbox.init(registry.Materialbox, {}); + M.Modal.init(registry.Modal, {}); + M.Parallax.init(registry.Parallax, {}); + M.Pushpin.init(registry.Pushpin, {}); + M.ScrollSpy.init(registry.ScrollSpy, {}); + M.FormSelect.init(registry.FormSelect, {}); + M.Sidenav.init(registry.Sidenav, {}); + M.Tabs.init(registry.Tabs, {}); + M.TapTarget.init(registry.TapTarget, {}); + M.Timepicker.init(registry.Timepicker, {}); + M.Tooltip.init(registry.Tooltip, {}); + M.FloatingActionButton.init(registry.FloatingActionButton, {}); } static objectSelectorString(obj: any): string { @@ -324,7 +328,7 @@ export class M { return canAlign; } - static getOverflowParent(element) { + static getOverflowParent(element: HTMLElement) { if (element == null) { return null; } @@ -334,7 +338,7 @@ export class M { return this.getOverflowParent(element.parentElement); } - static getIdFromTrigger(trigger: Element): string { + static getIdFromTrigger(trigger: HTMLElement): string { let id = trigger.getAttribute('data-target'); if (!id) { id = trigger.getAttribute('href'); @@ -348,14 +352,14 @@ export class M { } static getDocumentScrollTop(): number { - return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0; }; static getDocumentScrollLeft(): number { - return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; + return window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft || 0; } - public static throttle(func, wait, options = null) { + public static throttle(func: Function, wait: number, options = null) { let context, args, result; let timeout = null; let previous = 0; diff --git a/src/materialbox.ts b/src/materialbox.ts index 02e088869d..9a41727f8f 100644 --- a/src/materialbox.ts +++ b/src/materialbox.ts @@ -1,8 +1,42 @@ -import { Component } from "./component"; import anim from "animejs"; + import { M } from "./global"; +import { BaseOptions, Component, InitElements } from "./component"; + +export interface MaterialboxOptions extends BaseOptions { + /** + * Transition in duration in milliseconds. + * @default 275 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 200 + */ + outDuration: number; + /** + * Callback function called before materialbox is opened. + * @default null + */ + onOpenStart: (el: Element) => void; + /** + * Callback function called after materialbox is opened. + * @default null + */ + onOpenEnd: (el: Element) => void; + /** + * Callback function called before materialbox is closed. + * @default null + */ + onCloseStart: (el: Element) => void; + /** + * Callback function called after materialbox is closed. + * @default null + */ + onCloseEnd: (el: Element) => void; +} -const _defaults = { +const _defaults: MaterialboxOptions = { inDuration: 275, outDuration: 200, onOpenStart: null, @@ -11,12 +45,16 @@ const _defaults = { onCloseEnd: null }; -export class Materialbox extends Component { - el: HTMLElement; +export class Materialbox extends Component { + /** If the materialbox overlay is showing. */ overlayActive: boolean; + /** If the materialbox is no longer being animated. */ doneAnimating: boolean; + /** Caption, if specified. */ caption: string; + /** Original width of image. */ originalWidth: number; + /** Original height of image. */ originalHeight: number; private originInlineStyles: string; private placeholder: HTMLElement; @@ -30,10 +68,15 @@ export class Materialbox extends Component { private _overlay: HTMLElement; private _photoCaption: HTMLElement; - constructor(el, options) { - super(Materialbox, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Materialbox); (this.el as any).M_Materialbox = this; - this.options = {...Materialbox.defaults, ...options}; + + this.options = { + ...Materialbox.defaults, + ...options + }; + this.overlayActive = false; this.doneAnimating = true; this.placeholder = document.createElement('div'); @@ -48,17 +91,33 @@ export class Materialbox extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): MaterialboxOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of MaterialBox. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Materialbox; + /** + * Initializes instances of MaterialBox. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Materialbox[]; + /** + * Initializes instances of MaterialBox. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Materialbox | Materialbox[]{ + return super.init(els, options, Materialbox); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Materialbox; + static getInstance(el: HTMLElement): Materialbox { + return (el as any).M_Materialbox; } destroy() { @@ -111,7 +170,7 @@ export class Materialbox extends Component { } } - private _offset(el) { + private _offset(el: HTMLElement) { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { @@ -199,7 +258,10 @@ export class Materialbox extends Component { this.caption = this.el.getAttribute('data-caption') || ''; } - open() { + /** + * Open materialbox. + */ + open = () => { this._updateVars(); this.originalWidth = this.el.getBoundingClientRect().width; this.originalHeight = this.el.getBoundingClientRect().height; @@ -298,7 +360,10 @@ export class Materialbox extends Component { window.addEventListener('keyup', this._handleWindowEscape); } - close() { + /** + * Close materialbox. + */ + close = () => { this._updateVars(); this.doneAnimating = false; // onCloseStart callback diff --git a/src/modal.ts b/src/modal.ts index 33fa07dde5..4b8101ccc2 100644 --- a/src/modal.ts +++ b/src/modal.ts @@ -1,6 +1,65 @@ -import { Component } from "./component"; import anim from "animejs"; + import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ModalOptions extends BaseOptions { + /** + * Opacity of the modal overlay. + * @default 0.5 + */ + opacity: number; + /** + * Transition in duration in milliseconds. + * @default 250 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 250 + */ + outDuration: number; + /** + * Prevent page from scrolling while modal is open. + * @default true + */ + preventScrolling: boolean; + /** + * Callback function called before modal is opened. + * @default null + */ + onOpenStart: (this: Modal, el: HTMLElement) => void; + /** + * Callback function called after modal is opened. + * @default null + */ + onOpenEnd: (this: Modal, el: HTMLElement) => void; + /** + * Callback function called before modal is closed. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is closed. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Allow modal to be dismissed by keyboard or overlay click. + * @default true + */ + dismissible: boolean; + /** + * Starting top offset. + * @default '4%' + */ + startingTop: string; + /** + * Ending top offset. + * @default '10%' + */ + endingTop: string; +} const _defaults = { opacity: 0.5, @@ -16,20 +75,33 @@ const _defaults = { endingTop: '10%' }; -export class Modal extends Component { - el: HTMLElement; +export class Modal extends Component { + static _modalsOpen: number; static _count: number; - isOpen: boolean; + + /** + * ID of the modal element. + */ id: string; + /** + * If the modal is open. + */ + isOpen: boolean; + private _openingTrigger: any; private _overlay: HTMLElement; private _nthModalOpened: number; - constructor(el, options) { - super(Modal, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Modal); (this.el as any).M_Modal = this; - this.options = {...Modal.defaults, ...options}; + + this.options = { + ...Modal.defaults, + ...options + }; + this.isOpen = false; this.id = this.el.id; this._openingTrigger = undefined; @@ -45,13 +117,29 @@ export class Modal extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Modal. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Modal; + /** + * Initializes instances of Modal. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Modal[]; + /** + * Initializes instances of Modal. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Modal | Modal[] { + return super.init(els, options, Modal); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Modal; + static getInstance(el: HTMLElement): Modal { + return (el as any).M_Modal; } destroy() { @@ -78,10 +166,10 @@ export class Modal extends Component { this.el.removeEventListener('click', this._handleModalCloseClick); } - _handleTriggerClick = (e) => { - const trigger = e.target.closest('.modal-trigger'); + _handleTriggerClick = (e: MouseEvent) => { + const trigger = (e.target as HTMLElement).closest('.modal-trigger'); if (!trigger) return; - const modalId = M.getIdFromTrigger(trigger); + const modalId = M.getIdFromTrigger(trigger as HTMLElement); const modalInstance = (document.getElementById(modalId) as any).M_Modal; if (modalInstance) modalInstance.open(trigger); e.preventDefault(); @@ -91,8 +179,8 @@ export class Modal extends Component { if (this.options.dismissible) this.close(); } - _handleModalCloseClick = (e) => { - const closeTrigger = e.target.closest('.modal-close'); + _handleModalCloseClick = (e: MouseEvent) => { + const closeTrigger = (e.target as HTMLElement).closest('.modal-close'); if (closeTrigger) this.close(); } @@ -100,9 +188,9 @@ export class Modal extends Component { if (M.keys.ESC.includes(e.key) && this.options.dismissible) this.close(); } - _handleFocus = (e) => { + _handleFocus = (e: FocusEvent) => { // Only trap focus if this modal is the last model opened (prevents loops in nested modals). - if (!this.el.contains(e.target) && this._nthModalOpened === Modal._modalsOpen) { + if (!this.el.contains(e.target as HTMLElement) && this._nthModalOpened === Modal._modalsOpen) { this.el.focus(); } } @@ -185,7 +273,10 @@ export class Modal extends Component { anim(exitAnimOptions); } - open(trigger: HTMLElement|undefined): Modal { + /** + * Open modal. + */ + open = (trigger?: HTMLElement): Modal => { if (this.isOpen) return; this.isOpen = true; Modal._modalsOpen++; @@ -216,7 +307,10 @@ export class Modal extends Component { return this; } - close() { + /** + * Close modal. + */ + close = () => { if (!this.isOpen) return; this.isOpen = false; Modal._modalsOpen--; diff --git a/src/parallax.ts b/src/parallax.ts index 412a2b83f6..01a6eba2a5 100644 --- a/src/parallax.ts +++ b/src/parallax.ts @@ -1,21 +1,34 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ParallaxOptions extends BaseOptions { + /** + * The minimum width of the screen, in pixels, where the parallax functionality starts working. + * @default 0 + */ + responsiveThreshold: number; +} -let _defaults = { +let _defaults: ParallaxOptions = { responsiveThreshold: 0 // breakpoint for swipeable }; -export class Parallax extends Component { +export class Parallax extends Component { private _enabled: boolean; private _img: HTMLImageElement; static _parallaxes: Parallax[] = []; static _handleScrollThrottled: () => any; static _handleWindowResizeThrottled: () => any; - constructor(el, options) { - super(Parallax, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Parallax); (this.el as any).M_Parallax = this; - this.options = {...Parallax.defaults, ...options}; + + this.options = { + ...Parallax.defaults, + ...options + }; + this._enabled = window.innerWidth > this.options.responsiveThreshold; this._img = this.el.querySelector('img'); this._updateParallax(); @@ -24,17 +37,33 @@ export class Parallax extends Component { Parallax._parallaxes.push(this); } - static get defaults() { + static get defaults(): ParallaxOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Parallax. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Parallax; + /** + * Initializes instances of Parallax. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Parallax[]; + /** + * Initializes instances of Parallax. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Parallax | Parallax[] { + return super.init(els, options, Parallax); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Parallax; + static getInstance(el: HTMLElement): Parallax { + return (el as any).M_Parallax; } destroy() { diff --git a/src/pushpin.ts b/src/pushpin.ts index 84165410cc..39b3e51747 100644 --- a/src/pushpin.ts +++ b/src/pushpin.ts @@ -1,5 +1,31 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface PushpinOptions extends BaseOptions { + /** + * The distance in pixels from the top of the page where + * the element becomes fixed. + * @default 0 + */ + top: number; + /** + * The distance in pixels from the top of the page where + * the elements stops being fixed. + * @default Infinity + */ + bottom: number; + /** + * The offset from the top the element will be fixed at. + * @default 0 + */ + offset: number; + /** + * Callback function called when pushpin position changes. + * You are provided with a position string. + * @default null + */ + onPositionChange: (position: "pinned" | "pin-top" | "pin-bottom") => void; +} let _defaults = { top: 0, @@ -8,31 +34,52 @@ let _defaults = { onPositionChange: null }; -export class Pushpin extends Component { +export class Pushpin extends Component { static _pushpins: any[]; originalOffset: any; - constructor(el, options) { - super(Pushpin, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Pushpin); (this.el as any).M_Pushpin = this; - this.options = {...Pushpin.defaults, ...options}; + + this.options = { + ...Pushpin.defaults, + ...options + }; + this.originalOffset = (this.el as HTMLElement).offsetTop; Pushpin._pushpins.push(this); this._setupEventHandlers(); this._updatePosition(); } - static get defaults() { + static get defaults(): PushpinOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Pushpin. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Pushpin; + /** + * Initializes instances of Pushpin. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Pushpin[]; + /** + * Initializes instances of Pushpin. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Pushpin | Pushpin[] { + return super.init(els, options, Pushpin); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Pushpin; + static getInstance(el: HTMLElement): Pushpin { + return (el as any).M_Pushpin; } destroy() { diff --git a/src/range.ts b/src/range.ts index 7da90015d5..420705fef3 100644 --- a/src/range.ts +++ b/src/range.ts @@ -1,36 +1,60 @@ -import { Component } from "./component"; import anim from "animejs"; -const _defaults = {}; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface RangeOptions extends BaseOptions {}; + +const _defaults: RangeOptions = {}; // TODO: !!!!! -export class Range extends Component { - el: HTMLInputElement; +export class Range extends Component { + declare el: HTMLInputElement; private _mousedown: boolean; value: HTMLElement; thumb: HTMLElement; - constructor(el, options) { - super(Range, el, options); + constructor(el: HTMLInputElement, options: Partial) { + super(el, options, Range); (this.el as any).M_Range = this; - this.options = {...Range.defaults, ...options}; + + this.options = { + ...Range.defaults, + ...options + }; + this._mousedown = false; this._setupThumb(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): RangeOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Range. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLInputElement, options: Partial): Range; + /** + * Initializes instances of Range. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Range[]; + /** + * Initializes instances of Range. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLInputElement | InitElements, options: Partial): Range | Range[] { + return super.init(els, options, Range); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Range; + static getInstance(el: HTMLInputElement): Range { + return (el as any).M_Range; } destroy() { @@ -77,7 +101,7 @@ export class Range extends Component { this.thumb.style.left = offsetLeft+'px'; } - _handleRangeMousedownTouchstart = (e) => { + _handleRangeMousedownTouchstart = (e: MouseEvent | TouchEvent) => { // Set indicator value this.value.innerHTML = this.el.value; this._mousedown = true; @@ -165,7 +189,10 @@ export class Range extends Component { return percent * width; } + /** + * Initializes every range input in the current document. + */ static Init(){ - Range.init(document.querySelectorAll('input[type=range]'), {}); + Range.init((document.querySelectorAll('input[type=range]')) as NodeListOf, {}); } } diff --git a/src/scrollspy.ts b/src/scrollspy.ts index 6192376449..aabb6fd450 100644 --- a/src/scrollspy.ts +++ b/src/scrollspy.ts @@ -1,16 +1,39 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -let _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ScrollSpyOptions extends BaseOptions { + /** + * Throttle of scroll handler. + * @default 100 + */ + throttle: number; + /** + * Offset for centering element when scrolled to. + * @default 200 + */ + scrollOffset: number; + /** + * Class applied to active elements. + * @default 'active' + */ + activeClass: string; + /** + * Used to find active element. + * @default id => 'a[href="#' + id + '"]' + */ + getActiveElement: (id: string) => string; +}; + +let _defaults: ScrollSpyOptions = { throttle: 100, scrollOffset: 200, // offset - 200 allows elements near bottom of page to scroll activeClass: 'active', getActiveElement: (id: string): string => { return 'a[href="#'+id+'"]'; } }; -export class ScrollSpy extends Component { - el: HTMLElement; +export class ScrollSpy extends Component { static _elements: ScrollSpy[]; static _count: number; static _increment: number; @@ -20,10 +43,15 @@ export class ScrollSpy extends Component { static _visibleElements: any[]; static _ticks: number; - constructor(el, options) { - super(ScrollSpy, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, ScrollSpy); (this.el as any).M_ScrollSpy = this; - this.options = {...ScrollSpy.defaults, ...options}; + + this.options = { + ...ScrollSpy.defaults, + ...options + }; + ScrollSpy._elements.push(this); ScrollSpy._count++; ScrollSpy._increment++; @@ -33,17 +61,33 @@ export class ScrollSpy extends Component { this._handleWindowScroll(); } - static get defaults() { + static get defaults(): ScrollSpyOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of ScrollSpy. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): ScrollSpy; + /** + * Initializes instances of ScrollSpy. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): ScrollSpy[]; + /** + * Initializes instances of ScrollSpy. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): ScrollSpy | ScrollSpy[] { + return super.init(els, options, ScrollSpy); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_ScrollSpy; + static getInstance(el: HTMLElement): ScrollSpy { + return (el as any).M_ScrollSpy; } destroy() { @@ -75,7 +119,7 @@ export class ScrollSpy extends Component { _handleThrottledResize = (() => M.throttle(function(){ this._handleWindowScroll(); }, 200).bind(this))(); - _handleTriggerClick = (e) => { + _handleTriggerClick = (e: MouseEvent) => { const trigger = e.target; for (let i = ScrollSpy._elements.length - 1; i >= 0; i--) { const scrollspy = ScrollSpy._elements[i]; diff --git a/src/select.ts b/src/select.ts index 3829db54ca..445107d3f8 100644 --- a/src/select.ts +++ b/src/select.ts @@ -1,34 +1,60 @@ -import { Component } from "./component"; -import { Dropdown } from "./dropdown"; import { M } from "./global"; +import { Dropdown, DropdownOptions } from "./dropdown"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface FormSelectOptions extends BaseOptions { + /** + * Classes to be added to the select wrapper element. + * @default "" + */ + classes: string; + /** + * Pass options object to select dropdown initialization. + * @default {} + */ + dropdownOptions: Partial; +} -let _defaults = { +let _defaults: FormSelectOptions = { classes: '', dropdownOptions: {} }; type ValueStruct = { - el: any, - optionEl: HTMLOptionElement, + el: HTMLOptionElement, + optionEl: HTMLElement, } -export class FormSelect extends Component { - el: HTMLSelectElement; +export class FormSelect extends Component { + declare el: HTMLSelectElement; + /** If this is a multiple select. */ isMultiple: boolean; - private _values: ValueStruct[]; + /** + * Label associated with the current select element. + * Is "null", if not detected. + */ labelEl: HTMLLabelElement; - //private _labelFor: boolean; + /** Dropdown UL element. */ dropdownOptions: HTMLUListElement; + /** Text input that shows current selected option. */ input: HTMLInputElement; + /** Instance of the dropdown plugin for this select. */ dropdown: Dropdown; + /** The select wrapper element. */ wrapper: HTMLDivElement; - selectOptions: HTMLElement[]; + selectOptions: (HTMLOptionElement|HTMLOptGroupElement)[]; + private _values: ValueStruct[]; - constructor(el, options) { - super(FormSelect, el, options); + constructor(el: HTMLSelectElement, options: FormSelectOptions) { + super(el, options, FormSelect); if (this.el.classList.contains('browser-default')) return; (this.el as any).M_FormSelect = this; - this.options = {...FormSelect.defaults, ...options}; + + this.options = { + ...FormSelect.defaults, + ...options + }; + this.isMultiple = this.el.multiple; this.el.tabIndex = -1; this._values = []; @@ -38,17 +64,33 @@ export class FormSelect extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): FormSelectOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of FormSelect. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): FormSelect; + /** + * Initializes instances of FormSelect. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): FormSelect[]; + /** + * Initializes instances of FormSelect. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): FormSelect | FormSelect[] { + return super.init(els, options, FormSelect); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_FormSelect; + static getInstance(el: HTMLElement): FormSelect { + return (el as any).M_FormSelect; } destroy() { @@ -82,14 +124,14 @@ export class FormSelect extends Component { this._setValueToInput(); } - _handleOptionClick = (e) => { + _handleOptionClick = (e: MouseEvent | KeyboardEvent) => { e.preventDefault(); - const virtualOption = e.target.closest('li'); + const virtualOption = (e.target as HTMLLIElement).closest('li'); this._selectOptionElement(virtualOption); e.stopPropagation(); } - _arraysEqual(a, b) { + _arraysEqual(a: T[], b: (E|T)[]) { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; @@ -138,7 +180,7 @@ export class FormSelect extends Component { this.wrapper = document.createElement('div'); this.wrapper.classList.add('select-wrapper', 'input-field'); if (this.options.classes.length > 0) { - this.wrapper.classList.add(this.options.classes.split(' ')); + this.wrapper.classList.add(...this.options.classes.split(' ')); } this.el.before(this.wrapper); @@ -150,7 +192,7 @@ export class FormSelect extends Component { if (this.el.disabled) this.wrapper.classList.add('disabled'); - this.selectOptions = Array.from(this.el.children).filter(el => ['OPTION','OPTGROUP'].includes(el.tagName)); + this.selectOptions = <(HTMLOptGroupElement|HTMLOptionElement)[]>Array.from(this.el.children).filter(el => ['OPTION','OPTGROUP'].includes(el.tagName)); // Create dropdown this.dropdownOptions = document.createElement('ul'); @@ -166,7 +208,7 @@ export class FormSelect extends Component { if (realOption.tagName === 'OPTION') { // Option const virtualOption = this._createAndAppendOptionWithIcon(realOption, this.isMultiple ? 'multiple' : undefined); - this._addOptionToValues(realOption, virtualOption); + this._addOptionToValues(realOption as HTMLOptionElement, virtualOption); } else if (realOption.tagName === 'OPTGROUP') { // Optgroup @@ -318,7 +360,7 @@ export class FormSelect extends Component { if (this.labelEl) this.input.after(this.labelEl); } - _addOptionToValues(realOption, virtualOption) { + _addOptionToValues(realOption: HTMLOptionElement, virtualOption: HTMLElement) { this._values.push({ el: realOption, optionEl: virtualOption }); } @@ -362,19 +404,19 @@ export class FormSelect extends Component { return li; } - _selectValue(value) { + _selectValue(value: ValueStruct) { value.el.selected = true; value.optionEl.classList.add('selected'); value.optionEl.ariaSelected = 'true'; // setAttribute("aria-selected", true); - const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); + const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = true; } - _deselectValue(value) { + _deselectValue(value: ValueStruct) { value.el.selected = false; value.optionEl.classList.remove('selected'); value.optionEl.ariaSelected = 'false'; //setAttribute("aria-selected", false); - const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); + const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = false; } @@ -382,21 +424,21 @@ export class FormSelect extends Component { this._values.forEach(value => this._deselectValue(value)); } - _isValueSelected(value) { + _isValueSelected(value: ValueStruct) { const realValues = this.getSelectedValues(); return realValues.some((realValue) => realValue === value.el.value); } - _toggleEntryFromArray(value) { + _toggleEntryFromArray(value: ValueStruct) { if (this._isValueSelected(value)) this._deselectValue(value); else this._selectValue(value); } - _getSelectedOptions() { + _getSelectedOptions(): HTMLOptionElement[] { // remove null, false, ... values - return Array.prototype.filter.call(this.el.selectedOptions, (realOption) => realOption); + return Array.prototype.filter.call(this.el.selectedOptions, (realOption: HTMLOptionElement) => realOption); } _setValueToInput() { diff --git a/src/sidenav.ts b/src/sidenav.ts index c93183476d..91f2f75c4c 100644 --- a/src/sidenav.ts +++ b/src/sidenav.ts @@ -1,8 +1,58 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -const _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface SidenavOptions extends BaseOptions { + /** + * Side of screen on which Sidenav appears. + * @default 'left' + */ + edge: 'left' | 'right'; + /** + * Allow swipe gestures to open/close Sidenav. + * @default true + */ + draggable: boolean; + /** + * Width of the area where you can start dragging. + * @default '10px' + */ + dragTargetWidth: string; + /** + * Length in ms of enter transition. + * @default 250 + */ + inDuration: number; + /** + * Length in ms of exit transition. + * @default 200 + */ + outDuration: number; + /** + * Prevent page from scrolling while sidenav is open. + * @default true + */ + preventScrolling: boolean; + /** + * Function called when sidenav starts entering. + */ + onOpenStart: (elem: HTMLElement) => void; + /** + * Function called when sidenav finishes entering. + */ + onOpenEnd: (elem: HTMLElement) => void; + /** + * Function called when sidenav starts exiting. + */ + onCloseStart: (elem: HTMLElement) => void; + /** + * Function called when sidenav finishes exiting. + */ + onCloseEnd: (elem: HTMLElement) => void; +} + +const _defaults: SidenavOptions = { edge: 'left', draggable: true, dragTargetWidth: '10px', @@ -15,14 +65,17 @@ const _defaults = { preventScrolling: true }; -export class Sidenav extends Component { - id: any; +export class Sidenav extends Component implements Openable { + id: string; + /** Describes open/close state of Sidenav. */ isOpen: boolean; + /** Describes if sidenav is fixed. */ isFixed: boolean; + /** Describes if Sidenav is being dragged. */ isDragged: boolean; lastWindowWidth: number; lastWindowHeight: number; - static _sidenavs: any; + static _sidenavs: Sidenav[]; private _overlay: HTMLElement; dragTarget: Element; private _startingXpos: number; @@ -35,11 +88,16 @@ export class Sidenav extends Component { private velocityX: number; private percentOpen: number; - constructor(el, options) { - super(Sidenav, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Sidenav); (this.el as any).M_Sidenav = this; + + this.options = { + ...Sidenav.defaults, + ...options + }; + this.id = this.el.id; - this.options = {...Sidenav.defaults, ...options}; this.isOpen = false; this.isFixed = this.el.classList.contains('sidenav-fixed'); this.isDragged = false; @@ -54,17 +112,33 @@ export class Sidenav extends Component { Sidenav._sidenavs.push(this); } - static get defaults() { + static get defaults(): SidenavOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Sidenav. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Sidenav; + /** + * Initializes instances of Sidenav. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Sidenav[]; + /** + * Initializes instances of Sidenav. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Sidenav | Sidenav[] { + return super.init(els, options, Sidenav); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Sidenav; + static getInstance(el: HTMLElement): Sidenav { + return (el as any).M_Sidenav; } destroy() { @@ -315,7 +389,10 @@ export class Sidenav extends Component { document.body.style.overflow = ''; } - open() { + /** + * Opens Sidenav. + */ + open = () => { if (this.isOpen === true) return; this.isOpen = true; // Run onOpenStart callback @@ -346,6 +423,9 @@ export class Sidenav extends Component { } } + /** + * Closes Sidenav. + */ close = () => { if (this.isOpen === false) return; this.isOpen = false; diff --git a/src/slider.ts b/src/slider.ts index 4924aba546..d52f3d191c 100644 --- a/src/slider.ts +++ b/src/slider.ts @@ -1,8 +1,50 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -let _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface SliderOptions extends BaseOptions { + /** + * Set to false to hide slide indicators. + * @default true + */ + indicators: boolean; + /** + * Set height of slider. + * @default 400 + */ + height: number; + /** + * Set the duration of the transition animation in ms. + * @default 500 + */ + duration: number; + /** + * Set the duration between transitions in ms. + * @default 6000 + */ + interval: number; + /** + * If slider should pause when keyboard focus is received. + * @default true + */ + pauseOnFocus: boolean; + /** + * If slider should pause when is hovered by a pointer. + * @default true + */ + pauseOnHover: boolean; + /** + * Optional function used to generate ARIA label to indicators (for accessibility purposes). + * @param index Current index, starting from "1". + * @param current A which indicates whether it is the current element or not + * @returns a string to be used as label indicator. + * @default null + */ + indicatorLabelFunc: (index: number, current: boolean) => string +} + +let _defaults: SliderOptions = { indicators: true, height: 400, duration: 500, @@ -12,24 +54,28 @@ let _defaults = { indicatorLabelFunc: null // Function which will generate a label for the indicators (ARIA) }; -export class Slider extends Component { - el: HTMLElement; +export class Slider extends Component { + /** Index of current slide. */ + activeIndex: number; + interval: string | number | NodeJS.Timeout; + eventPause: any; _slider: HTMLUListElement; _slides: HTMLLIElement[]; - activeIndex: number; _activeSlide: HTMLLIElement; _indicators: HTMLLIElement[]; - interval: string | number | NodeJS.Timeout; - eventPause: any; _hovered: boolean; _focused: boolean; _focusCurrent: boolean; _sliderId: string; - constructor(el, options) { - super(Slider, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Slider); (this.el as any).M_Slider = this; - this.options = {...Slider.defaults, ...options}; + + this.options = { + ...Slider.defaults, + ...options + }; // init props this.interval = null; @@ -128,13 +174,29 @@ export class Slider extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Slider. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Slider; + /** + * Initializes instances of Slider. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Slider[]; + /** + * Initializes instances of Slider. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Slider | Slider[] { + return super.init(els, options, Slider); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Slider; + static getInstance(el: HTMLElement): Slider { + return (el as any).M_Slider; } destroy() { @@ -176,7 +238,7 @@ export class Slider extends Component { } } - _handleIndicatorClick = (e) => { + _handleIndicatorClick = (e: MouseEvent) => { const el = (e.target).parentElement; const currIndex = [...el.parentNode.children].indexOf(el); this._focusCurrent = true; @@ -369,11 +431,17 @@ export class Slider extends Component { this.interval = null; } - pause() { + /** + * Pause slider autoslide. + */ + pause = () => { this._pause(false); } - start() { + /** + * Start slider autoslide. + */ + start = () => { clearInterval(this.interval); this.interval = setInterval( this._handleInterval, @@ -382,7 +450,10 @@ export class Slider extends Component { this.eventPause = false; } - next() { + /** + * Move to next slider. + */ + next = () => { let newIndex = this.activeIndex + 1; // Wrap around indices. if (newIndex >= this._slides.length) newIndex = 0; @@ -390,7 +461,10 @@ export class Slider extends Component { this.set(newIndex); } - prev() { + /** + * Move to prev slider. + */ + prev = () => { let newIndex = this.activeIndex - 1; // Wrap around indices. if (newIndex >= this._slides.length) newIndex = 0; diff --git a/src/tabs.ts b/src/tabs.ts index 4e5bbb4613..56c26316c1 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -1,17 +1,42 @@ -import { Component } from "./component"; -import { Carousel } from "./carousel"; import anim from "animejs"; -let _defaults = { +import { Carousel } from "./carousel"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface TabsOptions extends BaseOptions { + /** + * Transition duration in milliseconds. + * @default 300 + */ + duration: number; + /** + * Callback for when a new tab content is shown. + * @default null + */ + onShow: (newContent: Element) => void; + /** + * Set to true to enable swipeable tabs. + * This also uses the responsiveThreshold option. + * @default false + */ + swipeable: boolean; + /** + * The maximum width of the screen, in pixels, + * where the swipeable functionality initializes. + * @default infinity + */ + responsiveThreshold: number; +}; + +let _defaults: TabsOptions = { duration: 300, onShow: null, swipeable: false, - responsiveThreshold: Infinity, // breakpoint for swipeable + responsiveThreshold: Infinity // breakpoint for swipeable }; -export class Tabs extends Component { - el: HTMLElement; - _tabLinks: any; +export class Tabs extends Component { + _tabLinks: NodeListOf; _index: number; _indicator: any; _tabWidth: number; @@ -20,11 +45,15 @@ export class Tabs extends Component { _activeTabLink: any; _content: any; - constructor(el, options: any) { - super(Tabs, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Tabs); (this.el as any).M_Tabs = this; - this.options = {...Tabs.defaults, ...options}; + this.options = { + ...Tabs.defaults, + ...options + }; + this._tabLinks = this.el.querySelectorAll('li.tab > a'); this._index = 0; this._setupActiveTabLink(); @@ -39,17 +68,33 @@ export class Tabs extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TabsOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Tabs. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Tabs; + /** + * Initializes instances of Tabs. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Tabs[]; + /** + * Initializes instances of Tabs. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Tabs | Tabs[] { + return super.init(els, options, Tabs); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Tabs; + static getInstance(el: HTMLElement): Tabs { + return (el as any).M_Tabs; } destroy() { @@ -64,6 +109,11 @@ export class Tabs extends Component { (this.el as any).M_Tabs = undefined; } + /** + * The index of tab that is currently shown. + */ + get index(){ return this._index; } + _setupEventHandlers() { window.addEventListener('resize', this._handleWindowResize); this.el.addEventListener('click', this._handleTabClick); @@ -82,8 +132,8 @@ export class Tabs extends Component { } } - _handleTabClick = (e) => { - const tabLink = e.target; + _handleTabClick = (e: MouseEvent) => { + const tabLink = e.target as HTMLAnchorElement; const tab = tabLink.parentElement; // Handle click on tab link only if (!tabLink || !tab.classList.contains('tab')) return; @@ -231,9 +281,9 @@ export class Tabs extends Component { _teardownNormalTabs() { // show Tabs Content - this._tabLinks.forEach(a => { + this._tabLinks.forEach((a) => { if (a.hash) { - const currContent = document.querySelector(a.hash); + const currContent = document.querySelector(a.hash) as HTMLElement; if (currContent) currContent.style.display = ''; } }); @@ -252,6 +302,10 @@ export class Tabs extends Component { return Math.floor(el.offsetLeft); } + /** + * Recalculate tab indicator position. This is useful when + * the indicator position is not correct. + */ updateTabIndicator() { this._setTabsAndTabWidth(); this._animateIndicator(this._index); @@ -282,7 +336,11 @@ export class Tabs extends Component { anim(animOptions); } - select(tabId) { + /** + * Show tab content that corresponds to the tab with the id. + * @param tabId The id of the tab that you want to switch to. + */ + select(tabId: string) { const tab = Array.from(this._tabLinks).find((a: HTMLAnchorElement) => a.getAttribute('href') === '#'+tabId); if (tab) (tab).click(); } diff --git a/src/tapTarget.ts b/src/tapTarget.ts index 321a3d9c8b..0bede02be3 100644 --- a/src/tapTarget.ts +++ b/src/tapTarget.ts @@ -1,43 +1,80 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; -let _defaults = { - onOpen: undefined, - onClose: undefined +export interface TapTargetOptions extends BaseOptions { + /** + * Callback function called when Tap Target is opened. + * @default null + */ + onOpen: (origin: HTMLElement) => void; + /** + * Callback function called when Tap Target is closed. + * @default null + */ + onClose: (origin: HTMLElement) => void; }; -export class TapTarget extends Component { - el: HTMLElement +let _defaults: TapTargetOptions = { + onOpen: null, + onClose: null +}; + +export class TapTarget extends Component implements Openable { + /** + * If the tap target is open. + */ isOpen: boolean; + private wrapper: HTMLElement; private _origin: HTMLElement; private originEl: HTMLElement; private waveEl: HTMLElement & Element & Node; private contentEl: HTMLElement; - constructor(el, options) { - super(TapTarget, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, TapTarget); (this.el as any).M_TapTarget = this; - this.options = {...TapTarget.defaults, ...options}; + + this.options = { + ...TapTarget.defaults, + ...options + }; + this.isOpen = false; // setup - this._origin = document.querySelector('#'+this.el.getAttribute('data-target')); + this._origin = document.querySelector(`#${el.dataset.target}`); this._setup(); this._calculatePositioning(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TapTargetOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of TapTarget. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): TapTarget; + /** + * Initializes instances of TapTarget. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): TapTarget[]; + /** + * Initializes instances of TapTarget. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): TapTarget | TapTarget[] { + return super.init(els, options, TapTarget); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_TapTarget; + static getInstance(el: HTMLElement): TapTarget { + return (el as any).M_TapTarget; } destroy() { @@ -72,8 +109,8 @@ export class TapTarget extends Component { this._calculatePositioning(); } - _handleDocumentClick = (e) => { - if (!e.target.closest('.tap-target-wrapper')) { + _handleDocumentClick = (e: MouseEvent | TouchEvent) => { + if (!(e.target as HTMLElement).closest('.tap-target-wrapper')) { this.close(); e.preventDefault(); e.stopPropagation(); @@ -115,7 +152,7 @@ export class TapTarget extends Component { } } - private _offset(el) { + private _offset(el: HTMLElement) { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { @@ -204,7 +241,10 @@ export class TapTarget extends Component { this.waveEl.style.height = tapTargetWaveHeight+'px'; } - open() { + /** + * Open Tap Target. + */ + open = () => { if (this.isOpen) return; // onOpen callback if (typeof this.options.onOpen === 'function') { @@ -214,9 +254,12 @@ export class TapTarget extends Component { this.wrapper.classList.add('open'); document.body.addEventListener('click', this._handleDocumentClick, true); document.body.addEventListener('touchend', this._handleDocumentClick); - } + }; - close() { + /** + * Close Tap Target. + */ + close = () => { if (!this.isOpen) return; // onClose callback if (typeof this.options.onClose === 'function') { @@ -226,5 +269,5 @@ export class TapTarget extends Component { this.wrapper.classList.remove('open'); document.body.removeEventListener('click', this._handleDocumentClick, true); document.body.removeEventListener('touchend', this._handleDocumentClick); - } + }; } diff --git a/src/timepicker.ts b/src/timepicker.ts index 1f064c2e9f..27d5d09a96 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -1,8 +1,103 @@ -import { Component } from "./component"; import { M } from "./global"; import { Modal } from "./modal"; +import { Component, BaseOptions, InitElements, I18nOptions } from "./component"; + +export type Views = "hours" | "minutes"; + +export interface TimepickerOptions extends BaseOptions { + /** + * Dial radius. + * @default 135 + */ + dialRadius: number; + /** + * Outer radius. + * @default 105 + */ + outerRadius: number; + /** + * Inner radius. + * @default 70 + */ + innerRadius: number; + /** + * Tick radius. + * @default 20 + */ + tickRadius: number; + /** + * Duration of the transition from/to the hours/minutes view. + * @default 350 + */ + duration: number; + /** + * Specify a DOM element OR selector for a DOM element to render + * the time picker in, by default it will be placed before the input. + * @default null + */ + container: HTMLElement | string | null; + /** + * Show the clear button in the Timepicker. + * @default false + */ + showClearBtn: boolean; + /** + * Default time to set on the timepicker 'now' or '13:14'. + * @default 'now'; + */ + defaultTime: string; + /** + * Millisecond offset from the defaultTime. + * @default 0 + */ + fromNow: number; + /** + * Internationalization options. + */ + i18n: Partial; + /** + * Automatically close picker when minute is selected. + * @default false; + */ + autoClose: boolean; + /** + * Use 12 hour AM/PM clock instead of 24 hour clock. + * @default true + */ + twelveHour: boolean; + /** + * Vibrate device when dragging clock hand. + * @default true + */ + vibrate: boolean; + /** + * Callback function called before modal is opened. + * @default null + */ + onOpenStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is opened. + * @default null + */ + onOpenEnd: (el: HTMLElement) => void; + /** + * Callback function called before modal is closed. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is closed. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Callback function when a time is selected. + * @default null + */ + onSelect: (hour: number, minute: number) => void; +} -let _defaults = { +let _defaults: TimepickerOptions = { dialRadius: 135, outerRadius: 105, innerRadius: 70, @@ -34,7 +129,7 @@ type Point = { y: number }; -export class Timepicker extends Component { +export class Timepicker extends Component { el: HTMLInputElement; id: string; modal: Modal; @@ -48,16 +143,27 @@ export class Timepicker extends Component { moved: boolean; dx: number; dy: number; - currentView: string; + /** + * Current view on the timepicker. + * @default 'hours' + */ + currentView: Views; hand: any; minutesView: HTMLElement; hours: any; minutes: any; + /** The selected time. */ time: string; - amOrPm: any; + /** + * If the time is AM or PM on twelve-hour clock. + * @default 'PM' + */ + amOrPm: "AM" | "PM"; static _template: any; + /** If the picker is open. */ isOpen: boolean; - vibrate: string; + /** Vibrate device when dragging clock hand. */ + vibrate: "vibrate" | "webkitVibrate" | null; _canvas: HTMLElement; hoursView: any; spanAmPm: HTMLSpanElement; @@ -71,10 +177,15 @@ export class Timepicker extends Component { canvas: any; vibrateTimer: any; - constructor(el, options) { - super(Timepicker, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Timepicker); (this.el as any).M_Timepicker = this; - this.options = {...Timepicker.defaults, ...options}; + + this.options = { + ...Timepicker.defaults, + ...options + }; + this.id = M.guid(); this._insertHTMLIntoDOM(); this._setupModal(); @@ -84,12 +195,29 @@ export class Timepicker extends Component { this._pickerSetup(); } - static get defaults() { + static get defaults(): TimepickerOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Timepicker. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Timepicker; + /** + * Initializes instances of Timepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Timepicker[]; + /** + * Initializes instances of Timepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Timepicker | Timepicker[] { + return super.init(els, options, Timepicker); } static _addLeadingZero(num) { @@ -109,9 +237,8 @@ export class Timepicker extends Component { return { x: e.clientX, y: e.clientY }; } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Timepicker; + static getInstance(el: HTMLElement): Timepicker { + return (el as any).M_Timepicker; } destroy() { @@ -335,7 +462,7 @@ export class Timepicker extends Component { hand.setAttribute('y1', '0'); let bg = Timepicker._createSVGEl('circle'); bg.setAttribute('class', 'timepicker-canvas-bg'); - bg.setAttribute('r', tickRadius); + bg.setAttribute('r', tickRadius.toString()); g.appendChild(hand); g.appendChild(bg); g.appendChild(bearing); @@ -445,7 +572,11 @@ export class Timepicker extends Component { this._updateAmPmView(); } - showView = (view, delay: number = null) => { + /** + * Show hours or minutes view on timepicker. + * @param view The name of the view you want to switch to, 'hours' or 'minutes'. + */ + showView = (view: Views, delay: number = null) => { if (view === 'minutes' && getComputedStyle(this.hoursView).visibility === 'visible') { // raiseCallback(this.options.beforeHourSelect); } @@ -628,7 +759,10 @@ export class Timepicker extends Component { this.bg.setAttribute('cy', cy2.toString()); } - open() { + /** + * Open timepicker. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; this._updateTimeFromInput(); @@ -636,6 +770,9 @@ export class Timepicker extends Component { this.modal.open(undefined); } + /** + * Close timepicker. + */ close = () => { if (!this.isOpen) return; this.isOpen = false; diff --git a/src/toasts.ts b/src/toasts.ts index 2bc2b0eab3..e4d131bd0c 100644 --- a/src/toasts.ts +++ b/src/toasts.ts @@ -1,44 +1,94 @@ import anim from "animejs"; -let _defaults = { +import { BaseOptions, InitElements } from "./component"; + +export interface ToastOptions extends BaseOptions { + /** + * The content of the Toast. + * @default "" + */ + text: string; + /** + * If true will render the text content directly as HTML. + * User input MUST be properly sanitized first. + * @default false + */ + allowUnsafeHTML: boolean; + /** + * Length in ms the Toast stays before dismissal. + * @default 4000 + */ + displayLength: number; + /** + * Transition in duration in milliseconds. + * @default 300 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 375 + */ + outDuration: number; + /** + * Classes to be added to the toast element. + * @default "" + */ + classes: string; + /** + * Callback function called when toast is dismissed. + * @default null + */ + completeCallback: () => void; + /** + * The percentage of the toast's width it takes fora drag + * to dismiss a Toast. + * @default 0.8 + */ + activationPercent: number; +} + +let _defaults: ToastOptions = { text: '', displayLength: 4000, inDuration: 300, outDuration: 375, classes: '', completeCallback: null, - activationPercent: 0.8 + activationPercent: 0.8, + allowUnsafeHTML: false }; export class Toast { - static _toasts: Toast[]; - static _container: any; - static _draggedToast: Toast; - options: any; - message: string; - panning: boolean; - timeRemaining: number; + /** The toast element. */ el: HTMLDivElement; + /** + * The remaining amount of time in ms that the toast + * will stay before dismissal. + */ + timeRemaining: number; + /** + * Describes the current pan state of the Toast. + */ + panning: boolean; + options: ToastOptions; + message: string; counterInterval: NodeJS.Timeout; - wasSwiped: any; + wasSwiped: boolean; startingXPos: number; - xPos: any; + xPos: number; time: number; deltaX: number; velocityX: number; - constructor(options: any) { - this.options = {...Toast.defaults, ...options}; - //this.htmlMessage = this.options.html; - // Warn when using html - // if (!!this.options.html) - // console.warn( - // 'The html option is deprecated and will be removed in the future. See https://github.com/materializecss/materialize/pull/49' - // ); - // If the new unsafeHTML is used, prefer that - // if (!!this.options.unsafeHTML) { - // this.htmlMessage = this.options.unsafeHTML; - // } + static _toasts: Toast[]; + static _container: any; + static _draggedToast: Toast; + + constructor(options: Partial) { + this.options = { + ...Toast.defaults, + ...options + }; this.message = this.options.text; this.panning = false; this.timeRemaining = this.options.displayLength; @@ -54,13 +104,12 @@ export class Toast { this._setTimer(); } - static get defaults() { + static get defaults(): ToastOptions { return _defaults; } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Toast; + static getInstance(el: HTMLElement): Toast { + return (el as any).M_Toast; } static _createContainer() { @@ -84,7 +133,7 @@ export class Toast { Toast._container = null; } - static _onDragStart(e) { + static _onDragStart(e: TouchEvent | MouseEvent) { if (e.target && (e.target).closest('.toast')) { const toastElem = (e.target).closest('.toast'); const toast: Toast = (toastElem as any).M_Toast; @@ -98,7 +147,7 @@ export class Toast { } } - static _onDragMove(e) { + static _onDragMove(e: TouchEvent | MouseEvent) { if (!!Toast._draggedToast) { e.preventDefault(); const toast = Toast._draggedToast; @@ -139,14 +188,17 @@ export class Toast { } } - static _xPos(e) { - if (e.targetTouches && e.targetTouches.length >= 1) { + static _xPos(e: TouchEvent | MouseEvent) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientX; } // mouse event - return e.clientX; + return (e as MouseEvent).clientX; } + /** + * dismiss all toasts. + */ static dismissAll() { for (let toastIndex in Toast._toasts) { Toast._toasts[toastIndex].dismiss(); @@ -165,29 +217,9 @@ export class Toast { toast.classList.add(...this.options.classes.split(' ')); } - // Set safe text content - toast.innerText = this.message; - - // if ( - // typeof HTMLElement === 'object' - // ? this.htmlMessage instanceof HTMLElement - // : this.htmlMessage && - // typeof this.htmlMessage === 'object' && - // this.htmlMessage !== null && - // this.htmlMessage.nodeType === 1 && - // typeof this.htmlMessage.nodeName === 'string' - // ) { - // //if the htmlMessage is an HTML node, append it directly - // toast.appendChild(this.htmlMessage); - // } - // else if (!!this.htmlMessage.jquery) { - // // Check if it is jQuery object, append the node - // $(toast).append(this.htmlMessage[0]); - // } - // else { - // // Append as unsanitized html; - // $(toast).append(this.htmlMessage); - // } + // Set text content + if (this.options.allowUnsafeHTML) toast.innerHTML = this.message; + else toast.innerText = this.message; // Append toast Toast._container.appendChild(toast); @@ -207,7 +239,7 @@ export class Toast { /** * Create setInterval which automatically removes toast when timeRemaining >= 0 - * has been reached + * has been reached. */ _setTimer() { if (this.timeRemaining !== Infinity) { @@ -225,7 +257,7 @@ export class Toast { } /** - * Dismiss toast with animation + * Dismiss toast with animation. */ dismiss() { window.clearInterval(this.counterInterval); diff --git a/src/tooltip.ts b/src/tooltip.ts index e4745b34a6..0437df426e 100644 --- a/src/tooltip.ts +++ b/src/tooltip.ts @@ -2,24 +2,88 @@ import anim from "animejs"; import { M } from "./global"; import { Bounding } from "./bounding"; -import { Component } from "./component"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface TooltipOptions extends BaseOptions { + /** + * Delay time before tooltip disappears. + * @default 200 + */ + exitDelay: number; + /** + * Delay time before tooltip appears. + * @default 0 + */ + enterDelay: number; + /** + * Text string for the tooltip. + * @default "" + */ + text: string; + /** + * If true will render the text content directly as HTML. + * User input MUST be properly sanitized first. + * @default false + */ + allowUnsafeHTML: boolean; + /** + * Set distance tooltip appears away from its activator + * excluding transitionMovement. + * @default 5 + */ + margin: number; + /** + * Enter transition duration. + * @default 300 + */ + inDuration: number; + /** + * Opacity of the tooltip. + * @default 1 + */ + opacity: number; + /** + * Exit transition duration. + * @default 250 + */ + outDuration: number; + /** + * Set the direction of the tooltip. + * @default 'bottom' + */ + position: 'top' | 'right' | 'bottom' | 'left'; + /** + * Amount in px that the tooltip moves during its transition. + * @default 10 + */ + transitionMovement: number; +} -const _defaults = { +const _defaults: TooltipOptions = { exitDelay: 200, enterDelay: 0, - //html: null, text: '', - //unsafeHTML: null, + allowUnsafeHTML: false, margin: 5, inDuration: 250, outDuration: 200, position: 'bottom', - transitionMovement: 10 + transitionMovement: 10, + opacity: 1 }; -export class Tooltip extends Component { +export class Tooltip extends Component { + /** + * If tooltip is open. + */ isOpen: boolean; + /** + * If tooltip is hovered. + */ isHovered: boolean; + /** + * If tooltip is focused. + */ isFocused: boolean; tooltipEl: HTMLElement; private _exitDelayTimeout: string | number | NodeJS.Timeout; @@ -27,10 +91,15 @@ export class Tooltip extends Component { xMovement: number; yMovement: number; - constructor(el, options) { - super(Tooltip, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Tooltip); (this.el as any).M_Tooltip = this; - this.options = {...Tooltip.defaults, ...options}; + + this.options = { + ...Tooltip.defaults, + ...options + }; + this.isOpen = false; this.isHovered = false; this.isFocused = false; @@ -38,17 +107,33 @@ export class Tooltip extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TooltipOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Tooltip. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Tooltip; + /** + * Initializes instances of Tooltip. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Tooltip[]; + /** + * Initializes instances of Tooltip. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Tooltip | Tooltip[] { + return super.init(els, options, Tooltip); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Tooltip; + static getInstance(el: HTMLElement): Tooltip { + return (el as any).M_Tooltip; } destroy() { @@ -69,7 +154,9 @@ export class Tooltip extends Component { } _setTooltipContent(tooltipContentEl: HTMLElement) { - tooltipContentEl.innerText = this.options.text; + if (this.options.allowUnsafeHTML) + tooltipContentEl.innerHTML = this.options.text; + else tooltipContentEl.innerText = this.options.text; } _updateTooltipContent() { @@ -90,7 +177,10 @@ export class Tooltip extends Component { this.el.removeEventListener('blur', this._handleBlur, true); } - open(isManual) { + /** + * Show tooltip. + */ + open = (isManual: boolean) => { if (this.isOpen) return; isManual = isManual === undefined ? true : undefined; // Default value true this.isOpen = true; @@ -99,8 +189,11 @@ export class Tooltip extends Component { this._updateTooltipContent(); this._setEnterDelayTimeout(isManual); } - - close() { + + /** + * Hide tooltip. + */ + close = () => { if (!this.isOpen) return; this.isHovered = false; this.isFocused = false; @@ -166,7 +259,7 @@ export class Tooltip extends Component { tooltip.style.left = newCoordinates.x+'px'; } - _repositionWithinScreen(x, y, width, height) { + _repositionWithinScreen(x: number, y: number, width: number, height: number) { const scrollLeft = M.getDocumentScrollLeft(); const scrollTop = M.getDocumentScrollTop(); let newX = x - scrollLeft; From 32ad112c2b34a43292b2e4442797dc30a8540d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Tue, 20 Jun 2023 18:06:28 -0300 Subject: [PATCH 2/3] feat(components): type definitions - Added type definition for generic component (base class); - Added type definition for static and virtual class methods; - Added type definitions for object options; - Added overload definitions to "init" methods; - Added initial version for code docs (props); - Dropped support to jQuery selector in "getInstance" method; - Fixed "Collapsible" destructor. --- src/autocomplete.ts | 166 ++++++++++++++++++++++----- src/buttons.ts | 156 +++++++++++--------------- src/carousel.ts | 163 ++++++++++++++++++++------- src/characterCounter.ts | 57 +++++++--- src/chips.ts | 152 ++++++++++++++++++++----- src/collapsible.ts | 112 ++++++++++++++----- src/component.ts | 108 ++++++++++++++++-- src/datepicker.ts | 241 ++++++++++++++++++++++++++++++++++++---- src/dropdown.ts | 169 ++++++++++++++++++++++------ src/global.ts | 90 ++++++++------- src/materialbox.ts | 97 +++++++++++++--- src/modal.ts | 136 +++++++++++++++++++---- src/parallax.ts | 53 +++++++-- src/pushpin.ts | 69 ++++++++++-- src/range.ts | 57 +++++++--- src/scrollspy.ts | 74 +++++++++--- src/select.ts | 110 ++++++++++++------ src/sidenav.ts | 112 ++++++++++++++++--- src/slider.ts | 116 +++++++++++++++---- src/tabs.ts | 100 +++++++++++++---- src/tapTarget.ts | 89 +++++++++++---- src/timepicker.ts | 173 +++++++++++++++++++++++++--- src/toasts.ts | 136 +++++++++++++---------- src/tooltip.ts | 122 ++++++++++++++++---- 24 files changed, 2230 insertions(+), 628 deletions(-) diff --git a/src/autocomplete.ts b/src/autocomplete.ts index a6c672e48d..a87c545983 100644 --- a/src/autocomplete.ts +++ b/src/autocomplete.ts @@ -1,8 +1,74 @@ -import { Component } from "./component"; import { M } from "./global"; -import { Dropdown } from "./dropdown"; +import { Dropdown, DropdownOptions } from "./dropdown"; +import { Component, BaseOptions, InitElements } 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 + */ + id: string | number; + /** + * This optional attribute is used as "display value" for the current entry. + * When provided, it will also be taken into consideration by the standard search function. + */ + text?: string; + /** + * This optional attribute is used to provide a valid image URL to the current option. + */ + image?: string; + /** + * Optional attributes which describes the option. + */ + description?: string; +} + +export interface AutocompleteOptions extends BaseOptions { + /** + * Data object defining autocomplete options with + * optional icon strings. + */ + data: AutocompleteData[]; + /** + * Flag which can be set if multiple values can be selected. The Result will be an Array. + * @default false + */ + isMultiSelect: boolean; + /** + * Callback for when autocompleted. + */ + onAutocomplete: (entries: AutocompleteData[]) => void; + /** + * Minimum number of characters before autocomplete starts. + * @default 1 + */ + minLength: number; + /** + * The height of the Menu which can be set via css-property. + * @default '300px' + */ + maxDropDownHeight: string; + /** + * Function is called when the input text is altered and data can also be loaded asynchronously. + * If the results are collected the items in the list can be updated via the function setMenuItems(collectedItems). + * @param text Searched text. + * @param autocomplete Current autocomplete instance. + */ + onSearch: (text: string, autocomplete: Autocomplete) => void; + /** + * If true will render the key from each item directly as HTML. + * User input MUST be properly sanitized first. + * @default false + */ + allowUnsafeHTML: boolean; + /** + * Pass options object to select dropdown initialization. + * @default {} + */ + dropdownOptions: Partial; +}; -let _defaults = { +let _defaults: AutocompleteOptions = { data: [], // Autocomplete data set onAutocomplete: null, // Callback for when autocompleted dropdownOptions: { @@ -26,29 +92,38 @@ let _defaults = { }; -export class Autocomplete extends Component { - el: HTMLInputElement; +export class Autocomplete extends Component { + declare el: HTMLInputElement; + /** If the autocomplete is open. */ isOpen: boolean; + /** Number of matching autocomplete options. */ count: number; + /** Index of the current selected option. */ activeIndex: number; - private oldVal: any; + private oldVal: string; private $active: HTMLElement|null; private _mousedown: boolean; container: HTMLElement; + /** Instance of the dropdown plugin for this autocomplete. */ dropdown: Dropdown; static _keydown: boolean; - selectedValues: any[]; - menuItems: any[]; + selectedValues: AutocompleteData[]; + menuItems: AutocompleteData[]; - constructor(el, options) { - super(Autocomplete, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Autocomplete); (this.el as any).M_Autocomplete = this; - this.options = {...Autocomplete.defaults, ...options}; + + this.options = { + ...Autocomplete.defaults, + ...options + }; + this.isOpen = false; this.count = 0; this.activeIndex = -1; - this.oldVal; + this.oldVal = ""; this.selectedValues = []; this.menuItems = []; this.$active = null; @@ -56,15 +131,34 @@ export class Autocomplete extends Component { this._setupDropdown(); this._setupEventHandlers(); } - static get defaults() { + + static get defaults(): AutocompleteOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + + /** + * Initializes instance of Autocomplete. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Autocomplete; + /** + * Initializes instances of Autocomplete. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Autocomplete[]; + /** + * Initializes instances of Autocomplete. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Autocomplete | Autocomplete[] { + return super.init(els, options, Autocomplete); } - static getInstance(el) { - let domElem = el.jquery ? el[0] : el; - return domElem.M_Autocomplete; + + static getInstance(el: HTMLElement): Autocomplete { + return (el as any).M_Autocomplete; } destroy() { @@ -253,7 +347,7 @@ export class Autocomplete extends Component { this._mousedown = false; } - _highlightPartialText(input, label) { + _highlightPartialText(input: string, label: string) { const start = label.toLowerCase().indexOf('' + input.toLowerCase() + ''); const end = start + input.length - 1; //custom filters may return results where the string does not match any part @@ -263,9 +357,9 @@ export class Autocomplete extends Component { return [label.slice(0, start), label.slice(start, end + 1), label.slice(end + 1)]; } - _createDropdownItem(entry) { + _createDropdownItem(entry: AutocompleteData) { const item = document.createElement('li'); - item.setAttribute('data-id', entry.id); + item.setAttribute('data-id', entry.id); item.setAttribute( 'style', 'display:grid; grid-auto-flow: column; user-select: none; align-items: center;' @@ -366,7 +460,7 @@ export class Autocomplete extends Component { _refreshInputText() { if (this.selectedValues.length === 1) { const entry = this.selectedValues[0]; - this.el.value = entry.text || entry.id; // Write Text to Input + this.el.value = entry.text || entry.id; // Write Text to Input } } @@ -377,7 +471,10 @@ export class Autocomplete extends Component { this.options.onAutocomplete.call(this, this.selectedValues); } - open() { + /** + * Show autocomplete. + */ + open = () => { const inputText = this.el.value.toLowerCase(); this._resetAutocomplete(); if (inputText.length >= this.options.minLength) { @@ -393,17 +490,28 @@ export class Autocomplete extends Component { else this.dropdown.recalculateDimensions(); // Recalculate dropdown when its already open } - close() { + /** + * Hide autocomplete. + */ + close = () => { this.dropdown.close(); } - setMenuItems(menuItems) { + /** + * Updates the visible or selectable items shown in the menu. + * @param menuItems Items to be available. + */ + setMenuItems(menuItems: AutocompleteData[]) { this.menuItems = menuItems; this.open(); this._updateSelectedInfo(); } - setValues(entries) { + /** + * Sets selected values. + * @param entries + */ + setValues(entries: AutocompleteData[]) { this.selectedValues = entries; this._updateSelectedInfo(); if (!this.options.isMultiSelect) { @@ -412,7 +520,11 @@ export class Autocomplete extends Component { this._triggerChanged(); } - selectOption(id) { + /** + * Select a specific autocomplete option via id-property. + * @param id The id of a data-entry. + */ + selectOption(id: number | string) { const entry = this.menuItems.find((item) => item.id == id); if (!entry) return; // Toggle Checkbox diff --git a/src/buttons.ts b/src/buttons.ts index a11150df20..37d37e34db 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -1,31 +1,57 @@ -import { Component } from "./component"; import anim from "animejs"; -let _defaults = { +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface FloatingActionButtonOptions extends BaseOptions { + /** + * Direction FAB menu opens. + * @default "top" + */ + direction: "top" | "right" | "bottom" | "left"; + /** + * true: FAB menu appears on hover, false: FAB menu appears on click. + * @default true + */ + hoverEnabled: boolean; + /** + * Enable transit the FAB into a toolbar on click. + * @default false + */ + toolbarEnabled: boolean; +}; + +let _defaults: FloatingActionButtonOptions = { direction: 'top', hoverEnabled: true, toolbarEnabled: false }; -export class FloatingActionButton extends Component { - el: HTMLElement; +export class FloatingActionButton extends Component implements Openable { + /** + * Describes open/close state of FAB. + */ isOpen: boolean; + private _anchor: HTMLAnchorElement; private _menu: HTMLElement|null; private _floatingBtns: HTMLElement[]; private _floatingBtnsReverse: HTMLElement[]; + offsetY: number; offsetX: number; btnBottom: number; btnLeft: number; btnWidth: number; - constructor(el, options) { - super(FloatingActionButton, el, options); - + constructor(el: HTMLElement, options: Partial) { + super(el, options, FloatingActionButton); (this.el as any).M_FloatingActionButton = this; - this.options = {...FloatingActionButton.defaults, ...options}; + this.options = { + ...FloatingActionButton.defaults, + ...options + }; + this.isOpen = false; this._anchor = this.el.querySelector('a'); this._menu = this.el.querySelector('ul'); @@ -50,13 +76,29 @@ export class FloatingActionButton extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of FloatingActionButton. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): FloatingActionButton + /** + * Initializes instances of FloatingActionButton. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): FloatingActionButton[]; + /** + * Initializes instances of FloatingActionButton. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): FloatingActionButton | FloatingActionButton[] { + return super.init(els, options, FloatingActionButton); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_FloatingActionButton; + static getInstance(el: HTMLElement): FloatingActionButton { + return (el as any).M_FloatingActionButton; } destroy() { @@ -90,16 +132,15 @@ export class FloatingActionButton extends Component { } } - _handleDocumentClick = (e) => { - const elem = e.target; + _handleDocumentClick = (e: MouseEvent) => { + const elem = e.target; if (elem !== this._menu) this.close; - /* - if (!elem.closest(this.$menu)) { - this.close(); - }*/ } - open = () => { + /** + * Open FAB. + */ + open = (): void => { if (this.isOpen) return; if (this.options.toolbarEnabled) this._animateInToolbar(); @@ -108,12 +149,14 @@ export class FloatingActionButton extends Component { this.isOpen = true; } - close = () => { + /** + * Close FAB. + */ + close = (): void => { if (!this.isOpen) return; if (this.options.toolbarEnabled) { window.removeEventListener('scroll', this.close, true); document.body.removeEventListener('click', this._handleDocumentClick, true); - this._animateOutToolbar(); } else { this._animateOutFAB(); @@ -189,7 +232,7 @@ export class FloatingActionButton extends Component { this._anchor.style.transform = `translateY(${this.offsetY}px`; this._anchor.style.transition = 'none'; - (backdrop).style.backgroundColor = fabColor; + backdrop.style.backgroundColor = fabColor; setTimeout(() => { this.el.style.transform = ''; @@ -214,71 +257,4 @@ export class FloatingActionButton extends Component { }, 100); }, 0); } - - - - - _animateOutToolbar() { - return; - /* - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - let backdrop = this.$el.find('.fab-backdrop'); - let fabColor = this.$anchor.css('background-color'); - - this.offsetX = this.btnLeft - windowWidth / 2 + this.btnWidth / 2; - this.offsetY = windowHeight - this.btnBottom; - - // Hide backdrop - this.$el.removeClass('active'); - this.$el.css({ - 'background-color': 'transparent', - transition: 'none' - }); - // this.$anchor.css({ - // transition: 'none' - // }); - backdrop.css({ - transform: 'scale(0)', - 'background-color': fabColor - }); - - // this.$menu - // .children('li') - // .children('a') - // .css({ - // opacity: '' - // }); - - setTimeout(() => { - backdrop.remove(); - - // Set initial state. - this.$el.css({ - 'text-align': '', - width: '', - bottom: '', - left: '', - overflow: '', - 'background-color': '', - transform: 'translate3d(' + -this.offsetX + 'px,0,0)' - }); - // this.$anchor.css({ - // overflow: '', - // transform: 'translate3d(0,' + this.offsetY + 'px,0)' - // }); - - setTimeout(() => { - this.$el.css({ - transform: 'translate3d(0,0,0)', - transition: 'transform .2s' - }); - // this.$anchor.css({ - // transform: 'translate3d(0,0,0)', - // transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' - // }); - }, 20); - }, 200); - */ - } } diff --git a/src/carousel.ts b/src/carousel.ts index f0b91e2580..9f3c410ac3 100644 --- a/src/carousel.ts +++ b/src/carousel.ts @@ -1,7 +1,55 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface CarouselOptions extends BaseOptions{ + /** + * Transition duration in milliseconds. + * @default 200 + */ + duration: number; + /** + * Perspective zoom. If 0, all items are the same size. + * @default -100 + */ + dist: number; + /** + * Set the spacing of the center item. + * @default 0 + */ + shift: number; + /** + * Set the padding between non center items. + * @default 0 + */ + padding: number; + /** + * Set the number of visible items. + * @default 5 + */ + numVisible: number; + /** + * Make the carousel a full width slider like the second example. + * @default false + */ + fullWidth: boolean; + /** + * Set to true to show indicators. + * @default false + */ + indicators: boolean; + /** + * Don't wrap around and cycle through items. + * @default false + */ + noWrap: boolean; + /** + * Callback for when a new slide is cycled to. + * @default null + */ + onCycleTo: (current: Element, dragged: boolean) => void; +} -let _defaults = { +let _defaults: CarouselOptions = { duration: 200, // ms dist: -100, // zoom scale TODO: make this more intuitive as an option shift: 0, // spacing for center image @@ -13,12 +61,13 @@ let _defaults = { onCycleTo: null // Callback for when a new slide is cycled to. }; -export class Carousel extends Component { - el: HTMLElement; +export class Carousel extends Component { hasMultipleSlides: boolean; showIndicators: boolean; - noWrap: any; + noWrap: boolean; + /** If the carousel is being clicked or tapped. */ pressed: boolean; + /** If the carousel is currently being dragged. */ dragged: boolean; offset: number; target: number; @@ -37,15 +86,20 @@ export class Carousel extends Component { timestamp: number; ticker: NodeJS.Timer; amplitude: number; + /** The index of the center carousel item. */ center: number = 0; imageHeight: any; scrollingTimeout: any; oneTimeCallback: any; - constructor(el: Element, options: Object) { - super(Carousel, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Carousel); (this.el as any).M_Carousel = this; - this.options = {...Carousel.defaults, ...options}; + + this.options = { + ...Carousel.defaults, + ...options + }; // Setup this.hasMultipleSlides = this.el.querySelectorAll('.carousel-item').length > 1; @@ -109,17 +163,33 @@ export class Carousel extends Component { this._scroll(this.offset); } - static get defaults() { + static get defaults(): CarouselOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Carousel. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Carousel; + /** + * Initializes instances of Carousel. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Carousel[]; + /** + * Initializes instances of Carousel. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Carousel | Carousel[] { + return super.init(els, options, Carousel); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Carousel; + static getInstance(el: HTMLElement): Carousel { + return (el as any).M_Carousel; } destroy() { @@ -168,7 +238,7 @@ export class Carousel extends Component { _handleThrottledResize = (() => M.throttle(function(){ this._handleResize(); }, 200, null).bind(this))(); - _handleCarouselTap = (e) => { + _handleCarouselTap = (e: MouseEvent | TouchEvent) => { // Fixes firefox draggable image bug if (e.type === 'mousedown' && (e.target).tagName === 'IMG') { e.preventDefault(); @@ -186,8 +256,8 @@ export class Carousel extends Component { this.ticker = setInterval(this._track, 100); } - _handleCarouselDrag = (e) => { - let x, y, delta, deltaY; + _handleCarouselDrag = (e: MouseEvent | TouchEvent) => { + let x: number, y: number, delta: number, deltaY: number; if (this.pressed) { x = this._xpos(e); y = this._ypos(e); @@ -218,7 +288,7 @@ export class Carousel extends Component { } } - _handleCarouselRelease = (e) => { + _handleCarouselRelease = (e: MouseEvent | TouchEvent) => { if (this.pressed) { this.pressed = false; } else { @@ -249,7 +319,7 @@ export class Carousel extends Component { return false; } - _handleCarouselClick = (e) => { + _handleCarouselClick = (e: MouseEvent | TouchEvent) => { // Disable clicks if carousel was dragged. if (this.dragged) { e.preventDefault(); @@ -268,7 +338,7 @@ export class Carousel extends Component { // fixes https://github.com/materializecss/materialize/issues/180 if (clickedIndex < 0) { // relative X position > center of carousel = clicked at the right part of the carousel - if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) { + if ((e as MouseEvent).clientX - (e.target as HTMLElement).getBoundingClientRect().left > this.el.clientWidth / 2) { this.next(); } else { this.prev(); @@ -335,22 +405,22 @@ export class Carousel extends Component { } } - _xpos(e) { + _xpos(e: MouseEvent | TouchEvent) { // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientX; } // mouse event - return e.clientX; + return (e as MouseEvent).clientX; } - _ypos(e) { + _ypos(e: MouseEvent | TouchEvent) { // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientY; } // mouse event - return e.clientY; + return (e as MouseEvent).clientY; } _wrap(x: number) { @@ -362,7 +432,7 @@ export class Carousel extends Component { } _track = () => { - let now, elapsed, delta, v; + let now: number, elapsed: number, delta: number, v: number; now = Date.now(); elapsed = now - this.timestamp; this.timestamp = now; @@ -373,7 +443,7 @@ export class Carousel extends Component { } _autoScroll = () => { - let elapsed, delta; + let elapsed: number, delta: number; if (this.amplitude) { elapsed = Date.now() - this.timestamp; delta = this.amplitude * Math.exp(-elapsed / this.options.duration); @@ -399,16 +469,16 @@ export class Carousel extends Component { }, this.options.duration); // Start actual scroll - let i, - half, - delta, - dir, - tween, - el, - alignment, - zTranslation, - tweenedOpacity, - centerTweenedOpacity; + 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; @@ -521,7 +591,7 @@ export class Carousel extends Component { el.style.visibility = 'visible'; } - _cycleTo(n: number, callback: Function = null) { + _cycleTo(n: number, callback: CarouselOptions["onCycleTo"] = null) { let diff = (this.center % this.count) - n; // Account for wraparound. if (!this.noWrap) { @@ -555,6 +625,10 @@ export class Carousel extends Component { } } + /** + * Move carousel to next slide or go forward a given amount of slides. + * @param n How many times the carousel slides. + */ next(n: number = 1) { if (n === undefined || isNaN(n)) { n = 1; @@ -567,6 +641,10 @@ export class Carousel extends Component { this._cycleTo(index); } + /** + * Move carousel to previous slide or go back a given amount of slides. + * @param n How many times the carousel slides. + */ prev(n: number = 1) { if (n === undefined || isNaN(n)) { n = 1; @@ -579,7 +657,12 @@ export class Carousel extends Component { this._cycleTo(index); } - set(n: number, callback: Function) { + /** + * Move carousel to nth slide. + * @param n Index of slide. + * @param callback "onCycleTo" optional callback. + */ + set(n: number, callback?: CarouselOptions["onCycleTo"]) { if (n === undefined || isNaN(n)) { n = 0; } diff --git a/src/characterCounter.ts b/src/characterCounter.ts index 029fd11290..cb3eb179c0 100644 --- a/src/characterCounter.ts +++ b/src/characterCounter.ts @@ -1,33 +1,64 @@ -import { Component } from "./component"; +import { Component, BaseOptions, InitElements } from "./component"; -let _defaults = {}; +export interface CharacterCounterOptions extends BaseOptions {}; -export class CharacterCounter extends Component { +const _defaults = Object.freeze({}); + +type InputElement = HTMLInputElement | HTMLTextAreaElement; + +export class CharacterCounter extends Component<{}> { + + declare el: InputElement; + /** Stores the reference to the counter HTML element. */ + counterEl: HTMLSpanElement; + /** Specifies whether the input is valid or not. */ isInvalid: boolean; + /** Specifies whether the input text has valid length or not. */ isValidLength: boolean; - counterEl: HTMLSpanElement; - constructor(el: Element, options: Object) { - super(CharacterCounter, el, options); + constructor(el: HTMLInputElement | HTMLTextAreaElement, options: Partial) { + super(el, {}, CharacterCounter); (this.el as any).M_CharacterCounter = this; - this.options = {...CharacterCounter.defaults, ...options}; + + this.options = { + ...CharacterCounter.defaults, + ...options + }; + this.isInvalid = false; this.isValidLength = false; + this._setupCounter(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): CharacterCounterOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of CharacterCounter. + * @param el HTML element. + * @param options Component options. + */ + 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[]; + /** + * Initializes instances of CharacterCounter. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InputElement | InitElements, options: Partial): CharacterCounter | CharacterCounter[] { + return super.init(els, options, CharacterCounter); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_CharacterCounter; + static getInstance(el: InputElement): CharacterCounter { + return (el as any).M_CharacterCounter; } destroy() { diff --git a/src/chips.ts b/src/chips.ts index d29f408621..5a76d204c2 100644 --- a/src/chips.ts +++ b/src/chips.ts @@ -1,8 +1,76 @@ -import { Component } from "./component"; import { M } from "./global"; -import { Autocomplete } from "./autocomplete"; +import { Autocomplete, AutocompleteOptions } from "./autocomplete"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ChipData { + /** + * Unique identifier. + */ + id: number|string; + /** + * Chip text. If not specified, "id" will be used. + */ + text?: string; + /** + * Chip image (URL). + */ + image?: string; +} + +export interface ChipsOptions extends BaseOptions{ + /** + * Set the chip data. + * @default [] + */ + data: ChipData[]; + /** + * Set first placeholder when there are no tags. + * @default "" + */ + placeholder: string; + /** + * Set second placeholder when adding additional tags. + * @default "" + */ + secondaryPlaceholder: string; + /** + * Set autocomplete options. + * @default {} + */ + autocompleteOptions: Partial; + /** + * Toggles abililty to add custom value not in autocomplete list. + * @default false + */ + autocompleteOnly: boolean; + /** + * Set chips limit. + * @default Infinity + */ + limit: number; + /** + * Specifies class to be used in "close" button (useful when working with Material Symbols icon set). + * @default 'material-icons' + */ + closeIconClass: string; + /** + * Callback for chip add. + * @default null + */ + onChipAdd: (element: HTMLElement, chip: HTMLElement) => void; + /** + * Callback for chip select. + * @default null + */ + onChipSelect: (element: HTMLElement, chip: HTMLElement) => void; + /** + * Callback for chip delete. + * @default null + */ + onChipDelete: (element: HTMLElement, chip: HTMLElement) => void; +} -let _defaults = { +let _defaults: ChipsOptions = { data: [], placeholder: '', secondaryPlaceholder: '', @@ -15,20 +83,16 @@ let _defaults = { onChipDelete: null }; -interface DataBit { - id: string, // required - text?: string, - image?: string, - description?: string, -} - function gGetIndex(el: HTMLElement): number { return [...el.parentNode.children].indexOf(el); } -export class Chips extends Component { - chipsData: DataBit[]; +export class Chips extends Component { + /** Array of the current chips data. */ + chipsData: ChipData[]; + /** If the chips has autocomplete enabled. */ hasAutocomplete: boolean; + /** Autocomplete instance, if any. */ autocomplete: Autocomplete; _input: HTMLInputElement; _label: any; @@ -36,10 +100,14 @@ export class Chips extends Component { static _keydown: boolean; private _selectedChip: any; - constructor(el, options) { - super(Chips, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Chips); (this.el as any).M_Chips = this; - this.options = {...Chips.defaults, ...options}; + + this.options = { + ...Chips.defaults, + ...options + }; this.el.classList.add('chips', 'input-field'); this.chipsData = []; @@ -67,13 +135,29 @@ export class Chips extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Chips. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Chips; + /** + * Initializes instances of Chips. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Chips[]; + /** + * Initializes instances of Chips. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Chips | Chips[] { + return super.init(els, options, Chips); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Chips; + static getInstance(el: HTMLElement): Chips { + return (el as any).M_Chips; } getData() { @@ -107,7 +191,7 @@ export class Chips extends Component { this._input.removeEventListener('keydown', this._handleInputKeydown); } - _handleChipClick = (e: Event) => { + _handleChipClick = (e: MouseEvent) => { const _chip = (e.target).closest('.chip'); const clickedClose = (e.target).classList.contains('close'); if (_chip) { @@ -213,11 +297,11 @@ export class Chips extends Component { } } - _renderChip(chip: DataBit): HTMLDivElement { + _renderChip(chip: ChipData): HTMLDivElement { if (!chip.id) return; const renderedChip = document.createElement('div'); renderedChip.classList.add('chip'); - renderedChip.innerText = chip.text || chip.id; + renderedChip.innerText = chip.text || chip.id; renderedChip.setAttribute('tabindex', "0"); const closeIcon = document.createElement('i'); closeIcon.classList.add(this.options.closeIconClass, 'close'); @@ -245,7 +329,11 @@ export class Chips extends Component { _setupAutocomplete() { this.options.autocompleteOptions.onAutocomplete = (items) => { - if (items.length > 0) this.addChip(items[0]); + if (items.length > 0) this.addChip({ + id: items[0].id, + text: items[0].text, + image: items[0].image + }); this._input.value = ''; this._input.focus(); }; @@ -278,13 +366,17 @@ export class Chips extends Component { } } - _isValidAndNotExist(chip: DataBit) { + _isValidAndNotExist(chip: ChipData) { const isValid = !!chip.id; const doesNotExist = !this.chipsData.some(item => item.id == chip.id); return isValid && doesNotExist; } - addChip(chip: DataBit) { + /** + * Add chip to input. + * @param chip Chip data object + */ + addChip(chip: ChipData) { if (!this._isValidAndNotExist(chip) || this.chipsData.length >= this.options.limit) return; const renderedChip = this._renderChip(chip); this._chips.push(renderedChip); @@ -298,6 +390,10 @@ export class Chips extends Component { } } + /** + * Delete nth chip. + * @param chipIndex Index of chip + */ deleteChip(chipIndex: number) { const chip = this._chips[chipIndex]; this._chips[chipIndex].remove(); @@ -310,6 +406,10 @@ export class Chips extends Component { } } + /** + * Select nth chip. + * @param chipIndex Index of chip + */ selectChip(chipIndex: number) { const chip = this._chips[chipIndex]; this._selectedChip = chip; diff --git a/src/collapsible.ts b/src/collapsible.ts index cfe01db1c2..7196424734 100644 --- a/src/collapsible.ts +++ b/src/collapsible.ts @@ -1,34 +1,68 @@ -import { M } from "./global"; -import { Component } from "./component"; import anim from "animejs"; -interface CollapsibleOptions { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface CollapsibleOptions extends BaseOptions { + /** + * If accordion versus collapsible. + * @default true + */ accordion: boolean; - onOpenStart: Function|undefined; - onOpenEnd: Function|undefined; - onCloseStart: Function|undefined; - onCloseEnd: Function|undefined; + /** + * Transition in duration in milliseconds. + * @default 300 + */ inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 300 + */ outDuration: number; + /** + * Callback function called before collapsible is opened. + * @default null + */ + onOpenStart: (el: Element) => void; + /** + * Callback function called after collapsible is opened. + * @default null + */ + onOpenEnd: (el: Element) => void; + /** + * Callback function called before collapsible is closed. + * @default null + */ + onCloseStart: (el: Element) => void; + /** + * Callback function called after collapsible is closed. + * @default null + */ + onCloseEnd: (el: Element) => void; } const _defaults: CollapsibleOptions = { accordion: true, - onOpenStart: undefined, - onOpenEnd: undefined, - onCloseStart: undefined, - onCloseEnd: undefined, + onOpenStart: null, + onOpenEnd: null, + onCloseStart: null, + onCloseEnd: null, inDuration: 300, outDuration: 300 }; -export class Collapsible extends Component { +export class Collapsible extends Component { private _headers: HTMLElement[]; - constructor(el: HTMLElement, options: CollapsibleOptions) { - super(Collapsible, el, options); - this.el['M_Collapsible'] = this; - this.options = {...Collapsible.defaults, ...options}; + constructor(el: HTMLElement, options: Partial) { + super(el, options, Collapsible); + (this.el as any).M_Collapsible = this; + + this.options = { + ...Collapsible.defaults, + ...options + }; + // Setup tab indices this._headers = Array.from(this.el.querySelectorAll('li > .collapsible-header')); this._headers.forEach(el => el.tabIndex = 0); @@ -42,22 +76,38 @@ export class Collapsible extends Component { activeBodies.forEach(el => el.style.display = 'block'); // Expandables } - static get defaults() { + static get defaults(): CollapsibleOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Collapsible. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Collapsible; + /** + * Initializes instances of Collapsible. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Collapsible[]; + /** + * Initializes instances of Collapsible. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Collapsible | Collapsible[] { + return super.init(els, options, Collapsible); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Collapsible; + static getInstance(el: HTMLElement): Collapsible { + return (el as any).M_Collapsible; } destroy() { this._removeEventHandlers(); - this.el['M_Collapsible'].M_Collapsible = undefined; + (this.el as any).M_Collapsible = undefined; } _setupEventHandlers() { @@ -70,8 +120,8 @@ export class Collapsible extends Component { this._headers.forEach(header => header.removeEventListener('keydown', this._handleCollapsibleKeydown)); } - _handleCollapsibleClick = (e) => { - const header = e.target.closest('.collapsible-header'); + _handleCollapsibleClick = (e: MouseEvent | KeyboardEvent) => { + const header = (e.target as HTMLElement).closest('.collapsible-header'); if (e.target && header) { const collapsible = header.closest('.collapsible'); if (collapsible !== this.el) return; @@ -154,7 +204,11 @@ export class Collapsible extends Component { }); } - open(index: number) { + /** + * Open collapsible section. + * @param n Nth section to open. + */ + open = (index: number) => { const listItems = Array.from(this.el.children).filter(c => c.tagName === 'LI'); const li = listItems[index]; if (li && !li.classList.contains('active')) { @@ -176,7 +230,11 @@ export class Collapsible extends Component { } } - close(index: number) { + /** + * Close collapsible section. + * @param n Nth section to close. + */ + close = (index: number) => { const li = Array.from(this.el.children).filter(c => c.tagName === 'LI')[index]; if (li && li.classList.contains('active')) { // onCloseStart callback diff --git a/src/component.ts b/src/component.ts index 23528dbb50..a94d35ad07 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,9 +1,45 @@ +/** + * Base options for component initialization. + */ +export interface BaseOptions {}; -export class Component { +export type InitElements = NodeListOf | HTMLCollectionOf; +type ComponentConstructor, O extends BaseOptions> = { + new (el: HTMLElement, options: Partial): T +}; +type ComponentType, O extends BaseOptions> = ComponentConstructor & typeof Component; - constructor(classDef, protected el: Element, protected options) { - // Display error if el is valid HTML Element - if (!(el instanceof Element)) { +export interface I18nOptions { + cancel: string; + clear: string; + done: string; +} + +export interface Openable { + isOpen: boolean; + open(): void; + close(): void; +}; + +/** + * Base class implementation for Materialize components. + */ +export class Component{ + /** + * The DOM element the plugin was initialized with. + */ + el: HTMLElement; + /** + * The options the instance was initialized with. + */ + options: O; + + /** + * Constructs component instance and set everything up. + */ + constructor(el: HTMLElement, options: Partial, classDef: ComponentType, O>){ + // Display error if el is not a valid HTML Element + if (!(el instanceof HTMLElement)) { console.error(Error(el + ' is not an HTML Element')); } // If exists, destroy and reinitialize in child @@ -14,18 +50,70 @@ export class Component { this.el = el; } - static init(classDef, els, options) { + /** + * Initializes component instance. + * @param el HTML element. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(el: I, options: O, classDef: ComponentType): C; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: InitElements, options: Partial, classDef: ComponentType): C[]; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[]; + /** + * Initializes component instances. + * @param els HTML elements. + * @param options Component options. + * @param classDef Class definition. + */ + protected static init< + I extends HTMLElement, O extends BaseOptions, C extends Component + >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[] { let instances = null; - if (els instanceof Element) { + if (els instanceof HTMLElement) { instances = new classDef(els, options); } - else if (!!els && (els.jquery || els.cash || els instanceof NodeList || els instanceof HTMLCollection)) { - let instancesArr = []; + else if (!!els && els.length) { + instances = []; for (let i = 0; i < els.length; i++) { - instancesArr.push(new classDef(els[i], options)); + instances.push(new classDef(els[i], options)); } - instances = instancesArr; } return instances; } + + /** + * @returns default options for component instance. + */ + static get defaults(): BaseOptions{ return {}; } + + /** + * Retrieves component instance for the given element. + * @param el Associated HTML Element. + */ + static getInstance(el: HTMLElement): Component { + throw new Error("This method must be implemented."); + } + + /** + * Destroy plugin instance and teardown. + */ + destroy(): void { throw new Error("This method must be implemented."); } } diff --git a/src/datepicker.ts b/src/datepicker.ts index b5be4d7426..6f46fbaf10 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -1,9 +1,155 @@ -import { Component } from "./component"; import { M } from "./global"; import { Modal } from "./modal"; import { FormSelect } from "./select"; +import { BaseOptions, Component, InitElements, I18nOptions } from "./component"; + +export interface DateI18nOptions extends I18nOptions { + previousMonth: string; + nextMonth: string; + months: string[]; + monthsShort: string[]; + weekdays: string[]; + weekdaysShort: string[]; + weekdaysAbbrev: string[]; +}; + +export interface DatepickerOptions extends BaseOptions { + /** + * Automatically close picker when date is selected. + * @default false + */ + autoClose: boolean; + /** + * The date output format for the input field value + * or a function taking the date and outputting the + * formatted date string. + * @default 'mmm dd, yyyy' + */ + format: string | ((d: Date) => string); + /** + * Used to create date object from current input string. + * @default null + */ + parse: ((value: string, format: string) => Date) | null; + /** + * The initial date to view when first opened. + * @default null + */ + defaultDate: Date | null; + /** + * Make the `defaultDate` the initial selected value. + * @default false + */ + setDefaultDate: boolean; + /** + * Prevent selection of any date on the weekend. + * @default false + */ + disableWeekends: boolean; + /** + * Custom function to disable certain days. + * @default null + */ + disableDayFn: ((day: Date) => boolean) | null; + /** + * First day of week (0: Sunday, 1: Monday etc). + * @default 0 + */ + firstDay: number; + /** + * The earliest date that can be selected. + * @default null + */ + minDate: Date | null; + /** + * The latest date that can be selected. + * @default null + */ + maxDate: Date | null; + /** + * Number of years either side, or array of upper/lower range. + * @default 10 + */ + yearRange: number | number[]; + /** + * Sort year range in reverse order. + * @default false + */ + yearRangeReverse: boolean; + /** + * Changes Datepicker to RTL. + * @default false + */ + isRTL: boolean; + /** + * Show month after year in Datepicker title. + * @default false + */ + showMonthAfterYear: boolean; + /** + * Render days of the calendar grid that fall in the next + * or previous month. + * @default false + */ + showDaysInNextAndPreviousMonths: boolean; + /** + * Specify a DOM element OR selector for a DOM element to render + * the calendar in, by default it will be placed before the input. + * @default null + */ + container: HTMLElement | string | null; + /** + * Show the clear button in the datepicker. + * @default false + */ + showClearBtn: boolean; + /** + * Internationalization options. + */ + i18n: Partial; + /** + * An array of string returned by `Date.toDateString()`, + * indicating there are events in the specified days. + * @default [] + */ + events: string[]; + /** + * Callback function when date is selected, + * first parameter is the newly selected date. + * @default null + */ + onSelect: ((selectedDate: Date) => void) | null; + /** + * Callback function when Datepicker is opened. + * @default null + */ + onOpen: (() => void) | null; + /** + * Callback function when Datepicker is closed. + * @default null + */ + onClose: (() => void) | null; + /** + * Callback function when Datepicker HTML is refreshed. + * @default null + */ + onDraw: (() => void) | null; + + /** Field used for internal calculations DO NOT CHANGE IT */ + minYear?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + maxYear?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + minMonth?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + maxMonth?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + startRange?: any; + /** Field used for internal calculations DO NOT CHANGE IT */ + endRange?: any; +} -let _defaults = { +let _defaults: DatepickerOptions = { // Close when date is selected autoClose: false, // the default output format for the input field value @@ -32,6 +178,7 @@ let _defaults = { startRange: null, endRange: null, isRTL: false, + yearRangeReverse: false, // Render the month after year in the calendar title showMonthAfterYear: false, // Render days of the calendar grid that fall in the next or previous month @@ -88,29 +235,37 @@ let _defaults = { onDraw: null }; -export class Datepicker extends Component { +export class Datepicker extends Component { el: HTMLInputElement id: string; + /** If the picker is open. */ isOpen: boolean; modal: Modal; calendarEl: HTMLElement; + /** CLEAR button instance. */ clearBtn: HTMLElement; + /** DONE button instance */ doneBtn: HTMLElement; cancelBtn: HTMLElement; modalEl: HTMLElement; yearTextEl: HTMLElement; dateTextEl: HTMLElement; - date: any; + /** The selected Date. */ + date: Date; formats: any; calendars: any; private _y: any; private _m: any; static _template: string; - constructor(el, options) { - super(Datepicker, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Datepicker); (this.el as any).M_Datepicker = this; - this.options = {...Datepicker.defaults, ...options}; + + this.options = { + ...Datepicker.defaults, + ...options + }; // make sure i18n defaults are not lost when only few i18n option properties are passed if (!!options && options.hasOwnProperty('i18n') && typeof options.i18n === 'object') { @@ -152,8 +307,25 @@ export class Datepicker extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Datepicker. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Datepicker; + /** + * Initializes instances of Datepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Datepicker[]; + /** + * Initializes instances of Datepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Datepicker | Datepicker[] { + return super.init(els, options, Datepicker); } static _isDate(obj) { @@ -185,9 +357,8 @@ export class Datepicker extends Component { return a.getTime() === b.getTime(); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Datepicker; + static getInstance(el: HTMLElement): Datepicker { + return (el as any).M_Datepicker; } destroy() { @@ -201,11 +372,11 @@ export class Datepicker extends Component { destroySelects() { let oldYearSelect = this.calendarEl.querySelector('.orig-select-year'); if (oldYearSelect) { - FormSelect.getInstance(oldYearSelect).destroy(); + FormSelect.getInstance(oldYearSelect as HTMLElement).destroy(); } let oldMonthSelect = this.calendarEl.querySelector('.orig-select-month'); if (oldMonthSelect) { - FormSelect.getInstance(oldMonthSelect).destroy(); + FormSelect.getInstance(oldMonthSelect as HTMLElement).destroy(); } } @@ -220,7 +391,7 @@ export class Datepicker extends Component { if (this.options.container) { const optEl = this.options.container; this.options.container = - optEl instanceof HTMLElement ? optEl : document.querySelector(optEl); + optEl instanceof HTMLElement ? optEl : document.querySelector(optEl) as HTMLElement; this.options.container.append(this.modalEl); } else { @@ -238,6 +409,9 @@ export class Datepicker extends Component { }); } + /** + * Gets a string representation of the selected date. + */ toString(format: string | ((d: Date) => string) = null): string { format = format || this.options.format; if (typeof format === 'function') return format(this.date); @@ -250,7 +424,12 @@ export class Datepicker extends Component { return formattedDate; } - setDate(date, preventOnSelect: boolean = false) { + /** + * Set a date on the datepicker. + * @param date Date to set on the datepicker. + * @param preventOnSelect Undocumented as of 5 March 2018. + */ + setDate(date: Date | string = null, preventOnSelect: boolean = false) { if (!date) { this.date = null; this._renderDateDisplay(); @@ -279,6 +458,9 @@ export class Datepicker extends Component { } } + /** + * Sets current date as the input value. + */ setInputValue() { this.el.value = this.toString(); this.el.dispatchEvent(new CustomEvent('change', {detail: {firedBy: this}})); @@ -290,11 +472,15 @@ export class Datepicker extends Component { let day = i18n.weekdaysShort[displayDate.getDay()]; let month = i18n.monthsShort[displayDate.getMonth()]; let date = displayDate.getDate(); - this.yearTextEl.innerHTML = displayDate.getFullYear(); + this.yearTextEl.innerHTML = displayDate.getFullYear().toString(); this.dateTextEl.innerHTML = `${day}, ${month} ${date}`; } - gotoDate(date) { + /** + * Change date view to a specific date on the datepicker. + * @param date Date to show on the datepicker. + */ + gotoDate(date: Date) { let newCalendar = true; if (!Datepicker._isDate(date)) { return; @@ -649,8 +835,8 @@ export class Datepicker extends Component { this.calendarEl.innerHTML = html; // Init Materialize Select - let yearSelect = this.calendarEl.querySelector('.orig-select-year'); - let monthSelect = this.calendarEl.querySelector('.orig-select-month'); + let yearSelect = this.calendarEl.querySelector('.orig-select-year') as HTMLElement; + let monthSelect = this.calendarEl.querySelector('.orig-select-month') as HTMLElement; FormSelect.init(yearSelect, { classes: 'select-year', dropdownOptions: { container: document.body, constrainWidth: false } @@ -665,7 +851,7 @@ export class Datepicker extends Component { monthSelect.addEventListener('change', () => this._handleMonthChange); if (typeof this.options.onDraw === 'function') { - this.options.onDraw(this); + this.options.onDraw.call(this); } } @@ -814,7 +1000,10 @@ export class Datepicker extends Component { // Prevent change event from being fired when triggered by the plugin if (e['detail']?.firedBy === this) return; if (this.options.parse) { - date = this.options.parse(this.el.value, this.options.format); + date = this.options.parse(this.el.value, + typeof this.options.format === "function" + ? this.options.format(new Date(this.el.value)) + : this.options.format); } else { date = new Date(Date.parse(this.el.value)); @@ -836,7 +1025,10 @@ export class Datepicker extends Component { this.close(); } - open() { + /** + * Open datepicker. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; if (typeof this.options.onOpen === 'function') { @@ -847,6 +1039,9 @@ export class Datepicker extends Component { return this; } + /** + * Close datepicker. + */ close = () => { if (!this.isOpen) return; this.isOpen = false; diff --git a/src/dropdown.ts b/src/dropdown.ts index 24d397e300..b4770cf141 100644 --- a/src/dropdown.ts +++ b/src/dropdown.ts @@ -1,8 +1,82 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -const _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface DropdownOptions extends BaseOptions { + /** + * Defines the edge the menu is aligned to. + * @default 'left' + */ + alignment: 'left' | 'right'; + /** + * If true, automatically focus dropdown el for keyboard. + * @default true + */ + autoFocus: boolean; + /** + * If true, constrainWidth to the size of the dropdown activator. + * @default true + */ + constrainWidth: boolean; + /** + * Provide an element that will be the bounding container of the dropdown. + * @default null + */ + container: Element; + /** + * If false, the dropdown will show below the trigger. + * @default true + */ + coverTrigger: boolean; + /** + * If true, close dropdown on item click. + * @default true + */ + closeOnClick: boolean; + /** + * If true, the dropdown will open on hover. + * @default false + */ + hover: boolean; + /** + * The duration of the transition enter in milliseconds. + * @default 150 + */ + inDuration: number; + /** + * The duration of the transition out in milliseconds. + * @default 250 + */ + outDuration: number; + /** + * Function called when dropdown starts entering. + * @default null + */ + onOpenStart: (el: HTMLElement) => void; + /** + * Function called when dropdown finishes entering. + * @default null + */ + onOpenEnd: (el: HTMLElement) => void; + /** + * Function called when dropdown starts exiting. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Function called when dropdown finishes exiting. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Function called when item is clicked. + * @default null + */ + onItemClick: (el: HTMLLIElement) => void; +}; + +const _defaults: DropdownOptions = { alignment: 'left', autoFocus: true, constrainWidth: true, @@ -19,26 +93,34 @@ const _defaults = { onItemClick: null }; -export class Dropdown extends Component { - el: HTMLElement; +export class Dropdown extends Component implements Openable { static _dropdowns: Dropdown[] = []; + /** ID of the dropdown element. */ id: string; + /** The DOM element of the dropdown. */ dropdownEl: HTMLElement; + /** If the dropdown is open. */ isOpen: boolean; + /** If the dropdown content is scrollable. */ isScrollable: boolean; isTouchMoving: boolean; + /** The index of the item focused. */ focusedIndex: number; filterQuery: any[]; filterTimeout: NodeJS.Timeout; - constructor(el, options) { - super(Dropdown, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Dropdown); (this.el as any).M_Dropdown = this; + Dropdown._dropdowns.push(this); this.id = M.getIdFromTrigger(el); this.dropdownEl = document.getElementById(this.id); - //this.$dropdownEl = $(this.dropdownEl); - this.options = {...Dropdown.defaults, ...options}; + + this.options = { + ...Dropdown.defaults, + ...options + }; this.isOpen = false; this.isScrollable = false; @@ -52,17 +134,33 @@ export class Dropdown extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): DropdownOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Dropdown. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Dropdown; + /** + * Initializes instances of Dropdown. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Dropdown[]; + /** + * Initializes instances of Dropdown. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Dropdown | Dropdown[] { + return super.init(els, options, Dropdown); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Dropdown; + static getInstance(el: HTMLElement): Dropdown { + return (el as any).M_Dropdown; } destroy() { @@ -114,7 +212,7 @@ export class Dropdown extends Component { this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydown); } - _handleClick = (e) => { + _handleClick = (e: MouseEvent) => { e.preventDefault(); this.open(); } @@ -123,8 +221,8 @@ export class Dropdown extends Component { this.open(); } - _handleMouseLeave = (e) => { - const toEl = e.toElement || e.relatedTarget; + _handleMouseLeave = (e: MouseEvent) => { + const toEl = e.relatedTarget as HTMLElement; const leaveToDropdownContent = !!toEl.closest('.dropdown-content'); let leaveToActiveDropdownTrigger = false; const closestTrigger = toEl.closest('.dropdown-trigger'); @@ -141,7 +239,7 @@ export class Dropdown extends Component { } } - _handleDocumentClick = (e) => { + _handleDocumentClick = (e: MouseEvent) => { const target = e.target; if ( this.options.closeOnClick && @@ -173,17 +271,17 @@ export class Dropdown extends Component { } } - _handleDocumentTouchmove = (e) => { + _handleDocumentTouchmove = (e: TouchEvent) => { const target = e.target; if (target.closest('.dropdown-content')) { this.isTouchMoving = true; } } - _handleDropdownClick = (e) => { + _handleDropdownClick = (e: MouseEvent) => { // onItemClick callback if (typeof this.options.onItemClick === 'function') { - const itemEl = e.target.closest('li'); + const itemEl = (e.target).closest('li'); this.options.onItemClick.call(this, itemEl); } } @@ -273,7 +371,7 @@ export class Dropdown extends Component { } // Move dropdown after container or trigger - _moveDropdown(containerEl = null) { + _moveDropdown(containerEl: HTMLElement = null) { if (!!this.options.container) { this.options.container.append(this.dropdownEl); } @@ -315,7 +413,7 @@ export class Dropdown extends Component { } } - _getDropdownPosition(closestOverflowParent) { + _getDropdownPosition(closestOverflowParent: HTMLElement) { const offsetParentBRect = this.el.offsetParent.getBoundingClientRect(); const triggerBRect = this.el.getBoundingClientRect(); const dropdownBRect = this.dropdownEl.getBoundingClientRect(); @@ -449,20 +547,20 @@ export class Dropdown extends Component { }); } - private _getClosestAncestor(el: Element, condition: Function): Element { + private _getClosestAncestor(el: HTMLElement, condition: Function): HTMLElement { let ancestor = el.parentNode; while (ancestor !== null && ancestor !== document) { if (condition(ancestor)) { - return ancestor; + return ancestor; } - ancestor = ancestor.parentNode; + ancestor = ancestor.parentElement; } return null; }; _placeDropdown() { // Container here will be closest ancestor with overflow: hidden - let closestOverflowParent: HTMLElement = this._getClosestAncestor(this.dropdownEl, (ancestor: HTMLElement) => { + let closestOverflowParent: HTMLElement = this._getClosestAncestor(this.dropdownEl, (ancestor: HTMLElement) => { return !['HTML','BODY'].includes(ancestor.tagName) && getComputedStyle(ancestor).overflow !== 'visible'; }); // Fallback @@ -493,7 +591,10 @@ export class Dropdown extends Component { } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`; } - open() { + /** + * Open dropdown. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; // onOpenStart callback @@ -508,7 +609,10 @@ export class Dropdown extends Component { this._setupTemporaryEventHandlers(); } - close() { + /** + * Close dropdown. + */ + close = () => { if (!this.isOpen) return; this.isOpen = false; this.focusedIndex = -1; @@ -523,7 +627,10 @@ export class Dropdown extends Component { } } - recalculateDimensions() { + /** + * While dropdown is open, you can recalculate its dimensions if its contents have changed. + */ + recalculateDimensions = () => { if (this.isOpen) { this.dropdownEl.style.width = ''; this.dropdownEl.style.height = ''; diff --git a/src/global.ts b/src/global.ts index d9c942673d..9075ede485 100644 --- a/src/global.ts +++ b/src/global.ts @@ -159,46 +159,50 @@ export class M { } //--- - static AutoInit(context:Element = null) { - let root = !!context ? context : document.body; + /** + * Automatically initialize components. + * @param context Root element to initialize. Defaults to `document.body`. + */ + static AutoInit(context: HTMLElement = document.body) { + let root = context; let registry = { - Autocomplete: root.querySelectorAll('.autocomplete:not(.no-autoinit)'), - Carousel: root.querySelectorAll('.carousel:not(.no-autoinit)'), - Chips: root.querySelectorAll('.chips:not(.no-autoinit)'), - Collapsible: root.querySelectorAll('.collapsible:not(.no-autoinit)'), - Datepicker: root.querySelectorAll('.datepicker:not(.no-autoinit)'), - Dropdown: root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)'), - Materialbox: root.querySelectorAll('.materialboxed:not(.no-autoinit)'), - Modal: root.querySelectorAll('.modal:not(.no-autoinit)'), - Parallax: root.querySelectorAll('.parallax:not(.no-autoinit)'), - Pushpin: root.querySelectorAll('.pushpin:not(.no-autoinit)'), - ScrollSpy: root.querySelectorAll('.scrollspy:not(.no-autoinit)'), - FormSelect: root.querySelectorAll('select:not(.no-autoinit)'), - Sidenav: root.querySelectorAll('.sidenav:not(.no-autoinit)'), - Tabs: root.querySelectorAll('.tabs:not(.no-autoinit)'), - TapTarget: root.querySelectorAll('.tap-target:not(.no-autoinit)'), - Timepicker: root.querySelectorAll('.timepicker:not(.no-autoinit)'), - Tooltip: root.querySelectorAll('.tooltipped:not(.no-autoinit)'), - FloatingActionButton: root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)'), + Autocomplete: >root.querySelectorAll('.autocomplete:not(.no-autoinit)'), + Carousel: >root.querySelectorAll('.carousel:not(.no-autoinit)'), + Chips: >root.querySelectorAll('.chips:not(.no-autoinit)'), + Collapsible: >root.querySelectorAll('.collapsible:not(.no-autoinit)'), + Datepicker: >root.querySelectorAll('.datepicker:not(.no-autoinit)'), + Dropdown: >root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)'), + Materialbox: >root.querySelectorAll('.materialboxed:not(.no-autoinit)'), + Modal: >root.querySelectorAll('.modal:not(.no-autoinit)'), + Parallax: >root.querySelectorAll('.parallax:not(.no-autoinit)'), + Pushpin: >root.querySelectorAll('.pushpin:not(.no-autoinit)'), + ScrollSpy: >root.querySelectorAll('.scrollspy:not(.no-autoinit)'), + FormSelect: >root.querySelectorAll('select:not(.no-autoinit)'), + Sidenav: >root.querySelectorAll('.sidenav:not(.no-autoinit)'), + Tabs: >root.querySelectorAll('.tabs:not(.no-autoinit)'), + TapTarget: >root.querySelectorAll('.tap-target:not(.no-autoinit)'), + Timepicker: >root.querySelectorAll('.timepicker:not(.no-autoinit)'), + Tooltip: >root.querySelectorAll('.tooltipped:not(.no-autoinit)'), + FloatingActionButton: >root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)'), }; - M.Autocomplete.init(registry.Autocomplete, null); - M.Carousel.init(registry.Carousel, null); - M.Chips.init(registry.Chips, null); - M.Collapsible.init(registry.Collapsible, null); - M.Datepicker.init(registry.Datepicker, null); - M.Dropdown.init(registry.Dropdown, null); - M.Materialbox.init(registry.Materialbox, null); - M.Modal.init(registry.Modal, null); - M.Parallax.init(registry.Parallax, null); - M.Pushpin.init(registry.Pushpin, null); - M.ScrollSpy.init(registry.ScrollSpy, null); - M.FormSelect.init(registry.FormSelect, null); - M.Sidenav.init(registry.Sidenav, null); - M.Tabs.init(registry.Tabs, null); - M.TapTarget.init(registry.TapTarget, null); - M.Timepicker.init(registry.Timepicker, null); - M.Tooltip.init(registry.Tooltip, null); - M.FloatingActionButton.init(registry.FloatingActionButton, null); + M.Autocomplete.init(registry.Autocomplete, {}); + M.Carousel.init(registry.Carousel, {}); + M.Chips.init(registry.Chips, {}); + M.Collapsible.init(registry.Collapsible, {}); + M.Datepicker.init(registry.Datepicker, {}); + M.Dropdown.init(registry.Dropdown, {}); + M.Materialbox.init(registry.Materialbox, {}); + M.Modal.init(registry.Modal, {}); + M.Parallax.init(registry.Parallax, {}); + M.Pushpin.init(registry.Pushpin, {}); + M.ScrollSpy.init(registry.ScrollSpy, {}); + M.FormSelect.init(registry.FormSelect, {}); + M.Sidenav.init(registry.Sidenav, {}); + M.Tabs.init(registry.Tabs, {}); + M.TapTarget.init(registry.TapTarget, {}); + M.Timepicker.init(registry.Timepicker, {}); + M.Tooltip.init(registry.Tooltip, {}); + M.FloatingActionButton.init(registry.FloatingActionButton, {}); } static objectSelectorString(obj: any): string { @@ -324,7 +328,7 @@ export class M { return canAlign; } - static getOverflowParent(element) { + static getOverflowParent(element: HTMLElement) { if (element == null) { return null; } @@ -334,7 +338,7 @@ export class M { return this.getOverflowParent(element.parentElement); } - static getIdFromTrigger(trigger: Element): string { + static getIdFromTrigger(trigger: HTMLElement): string { let id = trigger.getAttribute('data-target'); if (!id) { id = trigger.getAttribute('href'); @@ -348,14 +352,14 @@ export class M { } static getDocumentScrollTop(): number { - return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0; }; static getDocumentScrollLeft(): number { - return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; + return window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft || 0; } - public static throttle(func, wait, options = null) { + public static throttle(func: Function, wait: number, options = null) { let context, args, result; let timeout = null; let previous = 0; diff --git a/src/materialbox.ts b/src/materialbox.ts index 02e088869d..9a41727f8f 100644 --- a/src/materialbox.ts +++ b/src/materialbox.ts @@ -1,8 +1,42 @@ -import { Component } from "./component"; import anim from "animejs"; + import { M } from "./global"; +import { BaseOptions, Component, InitElements } from "./component"; + +export interface MaterialboxOptions extends BaseOptions { + /** + * Transition in duration in milliseconds. + * @default 275 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 200 + */ + outDuration: number; + /** + * Callback function called before materialbox is opened. + * @default null + */ + onOpenStart: (el: Element) => void; + /** + * Callback function called after materialbox is opened. + * @default null + */ + onOpenEnd: (el: Element) => void; + /** + * Callback function called before materialbox is closed. + * @default null + */ + onCloseStart: (el: Element) => void; + /** + * Callback function called after materialbox is closed. + * @default null + */ + onCloseEnd: (el: Element) => void; +} -const _defaults = { +const _defaults: MaterialboxOptions = { inDuration: 275, outDuration: 200, onOpenStart: null, @@ -11,12 +45,16 @@ const _defaults = { onCloseEnd: null }; -export class Materialbox extends Component { - el: HTMLElement; +export class Materialbox extends Component { + /** If the materialbox overlay is showing. */ overlayActive: boolean; + /** If the materialbox is no longer being animated. */ doneAnimating: boolean; + /** Caption, if specified. */ caption: string; + /** Original width of image. */ originalWidth: number; + /** Original height of image. */ originalHeight: number; private originInlineStyles: string; private placeholder: HTMLElement; @@ -30,10 +68,15 @@ export class Materialbox extends Component { private _overlay: HTMLElement; private _photoCaption: HTMLElement; - constructor(el, options) { - super(Materialbox, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Materialbox); (this.el as any).M_Materialbox = this; - this.options = {...Materialbox.defaults, ...options}; + + this.options = { + ...Materialbox.defaults, + ...options + }; + this.overlayActive = false; this.doneAnimating = true; this.placeholder = document.createElement('div'); @@ -48,17 +91,33 @@ export class Materialbox extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): MaterialboxOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of MaterialBox. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Materialbox; + /** + * Initializes instances of MaterialBox. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Materialbox[]; + /** + * Initializes instances of MaterialBox. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Materialbox | Materialbox[]{ + return super.init(els, options, Materialbox); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Materialbox; + static getInstance(el: HTMLElement): Materialbox { + return (el as any).M_Materialbox; } destroy() { @@ -111,7 +170,7 @@ export class Materialbox extends Component { } } - private _offset(el) { + private _offset(el: HTMLElement) { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { @@ -199,7 +258,10 @@ export class Materialbox extends Component { this.caption = this.el.getAttribute('data-caption') || ''; } - open() { + /** + * Open materialbox. + */ + open = () => { this._updateVars(); this.originalWidth = this.el.getBoundingClientRect().width; this.originalHeight = this.el.getBoundingClientRect().height; @@ -298,7 +360,10 @@ export class Materialbox extends Component { window.addEventListener('keyup', this._handleWindowEscape); } - close() { + /** + * Close materialbox. + */ + close = () => { this._updateVars(); this.doneAnimating = false; // onCloseStart callback diff --git a/src/modal.ts b/src/modal.ts index 33fa07dde5..4b8101ccc2 100644 --- a/src/modal.ts +++ b/src/modal.ts @@ -1,6 +1,65 @@ -import { Component } from "./component"; import anim from "animejs"; + import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ModalOptions extends BaseOptions { + /** + * Opacity of the modal overlay. + * @default 0.5 + */ + opacity: number; + /** + * Transition in duration in milliseconds. + * @default 250 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 250 + */ + outDuration: number; + /** + * Prevent page from scrolling while modal is open. + * @default true + */ + preventScrolling: boolean; + /** + * Callback function called before modal is opened. + * @default null + */ + onOpenStart: (this: Modal, el: HTMLElement) => void; + /** + * Callback function called after modal is opened. + * @default null + */ + onOpenEnd: (this: Modal, el: HTMLElement) => void; + /** + * Callback function called before modal is closed. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is closed. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Allow modal to be dismissed by keyboard or overlay click. + * @default true + */ + dismissible: boolean; + /** + * Starting top offset. + * @default '4%' + */ + startingTop: string; + /** + * Ending top offset. + * @default '10%' + */ + endingTop: string; +} const _defaults = { opacity: 0.5, @@ -16,20 +75,33 @@ const _defaults = { endingTop: '10%' }; -export class Modal extends Component { - el: HTMLElement; +export class Modal extends Component { + static _modalsOpen: number; static _count: number; - isOpen: boolean; + + /** + * ID of the modal element. + */ id: string; + /** + * If the modal is open. + */ + isOpen: boolean; + private _openingTrigger: any; private _overlay: HTMLElement; private _nthModalOpened: number; - constructor(el, options) { - super(Modal, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Modal); (this.el as any).M_Modal = this; - this.options = {...Modal.defaults, ...options}; + + this.options = { + ...Modal.defaults, + ...options + }; + this.isOpen = false; this.id = this.el.id; this._openingTrigger = undefined; @@ -45,13 +117,29 @@ export class Modal extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Modal. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Modal; + /** + * Initializes instances of Modal. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Modal[]; + /** + * Initializes instances of Modal. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Modal | Modal[] { + return super.init(els, options, Modal); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Modal; + static getInstance(el: HTMLElement): Modal { + return (el as any).M_Modal; } destroy() { @@ -78,10 +166,10 @@ export class Modal extends Component { this.el.removeEventListener('click', this._handleModalCloseClick); } - _handleTriggerClick = (e) => { - const trigger = e.target.closest('.modal-trigger'); + _handleTriggerClick = (e: MouseEvent) => { + const trigger = (e.target as HTMLElement).closest('.modal-trigger'); if (!trigger) return; - const modalId = M.getIdFromTrigger(trigger); + const modalId = M.getIdFromTrigger(trigger as HTMLElement); const modalInstance = (document.getElementById(modalId) as any).M_Modal; if (modalInstance) modalInstance.open(trigger); e.preventDefault(); @@ -91,8 +179,8 @@ export class Modal extends Component { if (this.options.dismissible) this.close(); } - _handleModalCloseClick = (e) => { - const closeTrigger = e.target.closest('.modal-close'); + _handleModalCloseClick = (e: MouseEvent) => { + const closeTrigger = (e.target as HTMLElement).closest('.modal-close'); if (closeTrigger) this.close(); } @@ -100,9 +188,9 @@ export class Modal extends Component { if (M.keys.ESC.includes(e.key) && this.options.dismissible) this.close(); } - _handleFocus = (e) => { + _handleFocus = (e: FocusEvent) => { // Only trap focus if this modal is the last model opened (prevents loops in nested modals). - if (!this.el.contains(e.target) && this._nthModalOpened === Modal._modalsOpen) { + if (!this.el.contains(e.target as HTMLElement) && this._nthModalOpened === Modal._modalsOpen) { this.el.focus(); } } @@ -185,7 +273,10 @@ export class Modal extends Component { anim(exitAnimOptions); } - open(trigger: HTMLElement|undefined): Modal { + /** + * Open modal. + */ + open = (trigger?: HTMLElement): Modal => { if (this.isOpen) return; this.isOpen = true; Modal._modalsOpen++; @@ -216,7 +307,10 @@ export class Modal extends Component { return this; } - close() { + /** + * Close modal. + */ + close = () => { if (!this.isOpen) return; this.isOpen = false; Modal._modalsOpen--; diff --git a/src/parallax.ts b/src/parallax.ts index 412a2b83f6..01a6eba2a5 100644 --- a/src/parallax.ts +++ b/src/parallax.ts @@ -1,21 +1,34 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ParallaxOptions extends BaseOptions { + /** + * The minimum width of the screen, in pixels, where the parallax functionality starts working. + * @default 0 + */ + responsiveThreshold: number; +} -let _defaults = { +let _defaults: ParallaxOptions = { responsiveThreshold: 0 // breakpoint for swipeable }; -export class Parallax extends Component { +export class Parallax extends Component { private _enabled: boolean; private _img: HTMLImageElement; static _parallaxes: Parallax[] = []; static _handleScrollThrottled: () => any; static _handleWindowResizeThrottled: () => any; - constructor(el, options) { - super(Parallax, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Parallax); (this.el as any).M_Parallax = this; - this.options = {...Parallax.defaults, ...options}; + + this.options = { + ...Parallax.defaults, + ...options + }; + this._enabled = window.innerWidth > this.options.responsiveThreshold; this._img = this.el.querySelector('img'); this._updateParallax(); @@ -24,17 +37,33 @@ export class Parallax extends Component { Parallax._parallaxes.push(this); } - static get defaults() { + static get defaults(): ParallaxOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Parallax. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Parallax; + /** + * Initializes instances of Parallax. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Parallax[]; + /** + * Initializes instances of Parallax. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Parallax | Parallax[] { + return super.init(els, options, Parallax); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Parallax; + static getInstance(el: HTMLElement): Parallax { + return (el as any).M_Parallax; } destroy() { diff --git a/src/pushpin.ts b/src/pushpin.ts index 84165410cc..39b3e51747 100644 --- a/src/pushpin.ts +++ b/src/pushpin.ts @@ -1,5 +1,31 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface PushpinOptions extends BaseOptions { + /** + * The distance in pixels from the top of the page where + * the element becomes fixed. + * @default 0 + */ + top: number; + /** + * The distance in pixels from the top of the page where + * the elements stops being fixed. + * @default Infinity + */ + bottom: number; + /** + * The offset from the top the element will be fixed at. + * @default 0 + */ + offset: number; + /** + * Callback function called when pushpin position changes. + * You are provided with a position string. + * @default null + */ + onPositionChange: (position: "pinned" | "pin-top" | "pin-bottom") => void; +} let _defaults = { top: 0, @@ -8,31 +34,52 @@ let _defaults = { onPositionChange: null }; -export class Pushpin extends Component { +export class Pushpin extends Component { static _pushpins: any[]; originalOffset: any; - constructor(el, options) { - super(Pushpin, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Pushpin); (this.el as any).M_Pushpin = this; - this.options = {...Pushpin.defaults, ...options}; + + this.options = { + ...Pushpin.defaults, + ...options + }; + this.originalOffset = (this.el as HTMLElement).offsetTop; Pushpin._pushpins.push(this); this._setupEventHandlers(); this._updatePosition(); } - static get defaults() { + static get defaults(): PushpinOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Pushpin. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Pushpin; + /** + * Initializes instances of Pushpin. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Pushpin[]; + /** + * Initializes instances of Pushpin. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Pushpin | Pushpin[] { + return super.init(els, options, Pushpin); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Pushpin; + static getInstance(el: HTMLElement): Pushpin { + return (el as any).M_Pushpin; } destroy() { diff --git a/src/range.ts b/src/range.ts index 7da90015d5..420705fef3 100644 --- a/src/range.ts +++ b/src/range.ts @@ -1,36 +1,60 @@ -import { Component } from "./component"; import anim from "animejs"; -const _defaults = {}; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface RangeOptions extends BaseOptions {}; + +const _defaults: RangeOptions = {}; // TODO: !!!!! -export class Range extends Component { - el: HTMLInputElement; +export class Range extends Component { + declare el: HTMLInputElement; private _mousedown: boolean; value: HTMLElement; thumb: HTMLElement; - constructor(el, options) { - super(Range, el, options); + constructor(el: HTMLInputElement, options: Partial) { + super(el, options, Range); (this.el as any).M_Range = this; - this.options = {...Range.defaults, ...options}; + + this.options = { + ...Range.defaults, + ...options + }; + this._mousedown = false; this._setupThumb(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): RangeOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Range. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLInputElement, options: Partial): Range; + /** + * Initializes instances of Range. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Range[]; + /** + * Initializes instances of Range. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLInputElement | InitElements, options: Partial): Range | Range[] { + return super.init(els, options, Range); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Range; + static getInstance(el: HTMLInputElement): Range { + return (el as any).M_Range; } destroy() { @@ -77,7 +101,7 @@ export class Range extends Component { this.thumb.style.left = offsetLeft+'px'; } - _handleRangeMousedownTouchstart = (e) => { + _handleRangeMousedownTouchstart = (e: MouseEvent | TouchEvent) => { // Set indicator value this.value.innerHTML = this.el.value; this._mousedown = true; @@ -165,7 +189,10 @@ export class Range extends Component { return percent * width; } + /** + * Initializes every range input in the current document. + */ static Init(){ - Range.init(document.querySelectorAll('input[type=range]'), {}); + Range.init((document.querySelectorAll('input[type=range]')) as NodeListOf, {}); } } diff --git a/src/scrollspy.ts b/src/scrollspy.ts index 6192376449..aabb6fd450 100644 --- a/src/scrollspy.ts +++ b/src/scrollspy.ts @@ -1,16 +1,39 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -let _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface ScrollSpyOptions extends BaseOptions { + /** + * Throttle of scroll handler. + * @default 100 + */ + throttle: number; + /** + * Offset for centering element when scrolled to. + * @default 200 + */ + scrollOffset: number; + /** + * Class applied to active elements. + * @default 'active' + */ + activeClass: string; + /** + * Used to find active element. + * @default id => 'a[href="#' + id + '"]' + */ + getActiveElement: (id: string) => string; +}; + +let _defaults: ScrollSpyOptions = { throttle: 100, scrollOffset: 200, // offset - 200 allows elements near bottom of page to scroll activeClass: 'active', getActiveElement: (id: string): string => { return 'a[href="#'+id+'"]'; } }; -export class ScrollSpy extends Component { - el: HTMLElement; +export class ScrollSpy extends Component { static _elements: ScrollSpy[]; static _count: number; static _increment: number; @@ -20,10 +43,15 @@ export class ScrollSpy extends Component { static _visibleElements: any[]; static _ticks: number; - constructor(el, options) { - super(ScrollSpy, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, ScrollSpy); (this.el as any).M_ScrollSpy = this; - this.options = {...ScrollSpy.defaults, ...options}; + + this.options = { + ...ScrollSpy.defaults, + ...options + }; + ScrollSpy._elements.push(this); ScrollSpy._count++; ScrollSpy._increment++; @@ -33,17 +61,33 @@ export class ScrollSpy extends Component { this._handleWindowScroll(); } - static get defaults() { + static get defaults(): ScrollSpyOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of ScrollSpy. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): ScrollSpy; + /** + * Initializes instances of ScrollSpy. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): ScrollSpy[]; + /** + * Initializes instances of ScrollSpy. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): ScrollSpy | ScrollSpy[] { + return super.init(els, options, ScrollSpy); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_ScrollSpy; + static getInstance(el: HTMLElement): ScrollSpy { + return (el as any).M_ScrollSpy; } destroy() { @@ -75,7 +119,7 @@ export class ScrollSpy extends Component { _handleThrottledResize = (() => M.throttle(function(){ this._handleWindowScroll(); }, 200).bind(this))(); - _handleTriggerClick = (e) => { + _handleTriggerClick = (e: MouseEvent) => { const trigger = e.target; for (let i = ScrollSpy._elements.length - 1; i >= 0; i--) { const scrollspy = ScrollSpy._elements[i]; diff --git a/src/select.ts b/src/select.ts index 3829db54ca..445107d3f8 100644 --- a/src/select.ts +++ b/src/select.ts @@ -1,34 +1,60 @@ -import { Component } from "./component"; -import { Dropdown } from "./dropdown"; import { M } from "./global"; +import { Dropdown, DropdownOptions } from "./dropdown"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface FormSelectOptions extends BaseOptions { + /** + * Classes to be added to the select wrapper element. + * @default "" + */ + classes: string; + /** + * Pass options object to select dropdown initialization. + * @default {} + */ + dropdownOptions: Partial; +} -let _defaults = { +let _defaults: FormSelectOptions = { classes: '', dropdownOptions: {} }; type ValueStruct = { - el: any, - optionEl: HTMLOptionElement, + el: HTMLOptionElement, + optionEl: HTMLElement, } -export class FormSelect extends Component { - el: HTMLSelectElement; +export class FormSelect extends Component { + declare el: HTMLSelectElement; + /** If this is a multiple select. */ isMultiple: boolean; - private _values: ValueStruct[]; + /** + * Label associated with the current select element. + * Is "null", if not detected. + */ labelEl: HTMLLabelElement; - //private _labelFor: boolean; + /** Dropdown UL element. */ dropdownOptions: HTMLUListElement; + /** Text input that shows current selected option. */ input: HTMLInputElement; + /** Instance of the dropdown plugin for this select. */ dropdown: Dropdown; + /** The select wrapper element. */ wrapper: HTMLDivElement; - selectOptions: HTMLElement[]; + selectOptions: (HTMLOptionElement|HTMLOptGroupElement)[]; + private _values: ValueStruct[]; - constructor(el, options) { - super(FormSelect, el, options); + constructor(el: HTMLSelectElement, options: FormSelectOptions) { + super(el, options, FormSelect); if (this.el.classList.contains('browser-default')) return; (this.el as any).M_FormSelect = this; - this.options = {...FormSelect.defaults, ...options}; + + this.options = { + ...FormSelect.defaults, + ...options + }; + this.isMultiple = this.el.multiple; this.el.tabIndex = -1; this._values = []; @@ -38,17 +64,33 @@ export class FormSelect extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): FormSelectOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of FormSelect. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): FormSelect; + /** + * Initializes instances of FormSelect. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): FormSelect[]; + /** + * Initializes instances of FormSelect. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): FormSelect | FormSelect[] { + return super.init(els, options, FormSelect); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_FormSelect; + static getInstance(el: HTMLElement): FormSelect { + return (el as any).M_FormSelect; } destroy() { @@ -82,14 +124,14 @@ export class FormSelect extends Component { this._setValueToInput(); } - _handleOptionClick = (e) => { + _handleOptionClick = (e: MouseEvent | KeyboardEvent) => { e.preventDefault(); - const virtualOption = e.target.closest('li'); + const virtualOption = (e.target as HTMLLIElement).closest('li'); this._selectOptionElement(virtualOption); e.stopPropagation(); } - _arraysEqual(a, b) { + _arraysEqual(a: T[], b: (E|T)[]) { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; @@ -138,7 +180,7 @@ export class FormSelect extends Component { this.wrapper = document.createElement('div'); this.wrapper.classList.add('select-wrapper', 'input-field'); if (this.options.classes.length > 0) { - this.wrapper.classList.add(this.options.classes.split(' ')); + this.wrapper.classList.add(...this.options.classes.split(' ')); } this.el.before(this.wrapper); @@ -150,7 +192,7 @@ export class FormSelect extends Component { if (this.el.disabled) this.wrapper.classList.add('disabled'); - this.selectOptions = Array.from(this.el.children).filter(el => ['OPTION','OPTGROUP'].includes(el.tagName)); + this.selectOptions = <(HTMLOptGroupElement|HTMLOptionElement)[]>Array.from(this.el.children).filter(el => ['OPTION','OPTGROUP'].includes(el.tagName)); // Create dropdown this.dropdownOptions = document.createElement('ul'); @@ -166,7 +208,7 @@ export class FormSelect extends Component { if (realOption.tagName === 'OPTION') { // Option const virtualOption = this._createAndAppendOptionWithIcon(realOption, this.isMultiple ? 'multiple' : undefined); - this._addOptionToValues(realOption, virtualOption); + this._addOptionToValues(realOption as HTMLOptionElement, virtualOption); } else if (realOption.tagName === 'OPTGROUP') { // Optgroup @@ -318,7 +360,7 @@ export class FormSelect extends Component { if (this.labelEl) this.input.after(this.labelEl); } - _addOptionToValues(realOption, virtualOption) { + _addOptionToValues(realOption: HTMLOptionElement, virtualOption: HTMLElement) { this._values.push({ el: realOption, optionEl: virtualOption }); } @@ -362,19 +404,19 @@ export class FormSelect extends Component { return li; } - _selectValue(value) { + _selectValue(value: ValueStruct) { value.el.selected = true; value.optionEl.classList.add('selected'); value.optionEl.ariaSelected = 'true'; // setAttribute("aria-selected", true); - const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); + const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = true; } - _deselectValue(value) { + _deselectValue(value: ValueStruct) { value.el.selected = false; value.optionEl.classList.remove('selected'); value.optionEl.ariaSelected = 'false'; //setAttribute("aria-selected", false); - const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); + const checkbox = value.optionEl.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = false; } @@ -382,21 +424,21 @@ export class FormSelect extends Component { this._values.forEach(value => this._deselectValue(value)); } - _isValueSelected(value) { + _isValueSelected(value: ValueStruct) { const realValues = this.getSelectedValues(); return realValues.some((realValue) => realValue === value.el.value); } - _toggleEntryFromArray(value) { + _toggleEntryFromArray(value: ValueStruct) { if (this._isValueSelected(value)) this._deselectValue(value); else this._selectValue(value); } - _getSelectedOptions() { + _getSelectedOptions(): HTMLOptionElement[] { // remove null, false, ... values - return Array.prototype.filter.call(this.el.selectedOptions, (realOption) => realOption); + return Array.prototype.filter.call(this.el.selectedOptions, (realOption: HTMLOptionElement) => realOption); } _setValueToInput() { diff --git a/src/sidenav.ts b/src/sidenav.ts index c93183476d..91f2f75c4c 100644 --- a/src/sidenav.ts +++ b/src/sidenav.ts @@ -1,8 +1,58 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -const _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; + +export interface SidenavOptions extends BaseOptions { + /** + * Side of screen on which Sidenav appears. + * @default 'left' + */ + edge: 'left' | 'right'; + /** + * Allow swipe gestures to open/close Sidenav. + * @default true + */ + draggable: boolean; + /** + * Width of the area where you can start dragging. + * @default '10px' + */ + dragTargetWidth: string; + /** + * Length in ms of enter transition. + * @default 250 + */ + inDuration: number; + /** + * Length in ms of exit transition. + * @default 200 + */ + outDuration: number; + /** + * Prevent page from scrolling while sidenav is open. + * @default true + */ + preventScrolling: boolean; + /** + * Function called when sidenav starts entering. + */ + onOpenStart: (elem: HTMLElement) => void; + /** + * Function called when sidenav finishes entering. + */ + onOpenEnd: (elem: HTMLElement) => void; + /** + * Function called when sidenav starts exiting. + */ + onCloseStart: (elem: HTMLElement) => void; + /** + * Function called when sidenav finishes exiting. + */ + onCloseEnd: (elem: HTMLElement) => void; +} + +const _defaults: SidenavOptions = { edge: 'left', draggable: true, dragTargetWidth: '10px', @@ -15,14 +65,17 @@ const _defaults = { preventScrolling: true }; -export class Sidenav extends Component { - id: any; +export class Sidenav extends Component implements Openable { + id: string; + /** Describes open/close state of Sidenav. */ isOpen: boolean; + /** Describes if sidenav is fixed. */ isFixed: boolean; + /** Describes if Sidenav is being dragged. */ isDragged: boolean; lastWindowWidth: number; lastWindowHeight: number; - static _sidenavs: any; + static _sidenavs: Sidenav[]; private _overlay: HTMLElement; dragTarget: Element; private _startingXpos: number; @@ -35,11 +88,16 @@ export class Sidenav extends Component { private velocityX: number; private percentOpen: number; - constructor(el, options) { - super(Sidenav, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Sidenav); (this.el as any).M_Sidenav = this; + + this.options = { + ...Sidenav.defaults, + ...options + }; + this.id = this.el.id; - this.options = {...Sidenav.defaults, ...options}; this.isOpen = false; this.isFixed = this.el.classList.contains('sidenav-fixed'); this.isDragged = false; @@ -54,17 +112,33 @@ export class Sidenav extends Component { Sidenav._sidenavs.push(this); } - static get defaults() { + static get defaults(): SidenavOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Sidenav. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Sidenav; + /** + * Initializes instances of Sidenav. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Sidenav[]; + /** + * Initializes instances of Sidenav. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Sidenav | Sidenav[] { + return super.init(els, options, Sidenav); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Sidenav; + static getInstance(el: HTMLElement): Sidenav { + return (el as any).M_Sidenav; } destroy() { @@ -315,7 +389,10 @@ export class Sidenav extends Component { document.body.style.overflow = ''; } - open() { + /** + * Opens Sidenav. + */ + open = () => { if (this.isOpen === true) return; this.isOpen = true; // Run onOpenStart callback @@ -346,6 +423,9 @@ export class Sidenav extends Component { } } + /** + * Closes Sidenav. + */ close = () => { if (this.isOpen === false) return; this.isOpen = false; diff --git a/src/slider.ts b/src/slider.ts index 4924aba546..d52f3d191c 100644 --- a/src/slider.ts +++ b/src/slider.ts @@ -1,8 +1,50 @@ -import { Component } from "./component"; -import { M } from "./global"; import anim from "animejs"; -let _defaults = { +import { M } from "./global"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface SliderOptions extends BaseOptions { + /** + * Set to false to hide slide indicators. + * @default true + */ + indicators: boolean; + /** + * Set height of slider. + * @default 400 + */ + height: number; + /** + * Set the duration of the transition animation in ms. + * @default 500 + */ + duration: number; + /** + * Set the duration between transitions in ms. + * @default 6000 + */ + interval: number; + /** + * If slider should pause when keyboard focus is received. + * @default true + */ + pauseOnFocus: boolean; + /** + * If slider should pause when is hovered by a pointer. + * @default true + */ + pauseOnHover: boolean; + /** + * Optional function used to generate ARIA label to indicators (for accessibility purposes). + * @param index Current index, starting from "1". + * @param current A which indicates whether it is the current element or not + * @returns a string to be used as label indicator. + * @default null + */ + indicatorLabelFunc: (index: number, current: boolean) => string +} + +let _defaults: SliderOptions = { indicators: true, height: 400, duration: 500, @@ -12,24 +54,28 @@ let _defaults = { indicatorLabelFunc: null // Function which will generate a label for the indicators (ARIA) }; -export class Slider extends Component { - el: HTMLElement; +export class Slider extends Component { + /** Index of current slide. */ + activeIndex: number; + interval: string | number | NodeJS.Timeout; + eventPause: any; _slider: HTMLUListElement; _slides: HTMLLIElement[]; - activeIndex: number; _activeSlide: HTMLLIElement; _indicators: HTMLLIElement[]; - interval: string | number | NodeJS.Timeout; - eventPause: any; _hovered: boolean; _focused: boolean; _focusCurrent: boolean; _sliderId: string; - constructor(el, options) { - super(Slider, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Slider); (this.el as any).M_Slider = this; - this.options = {...Slider.defaults, ...options}; + + this.options = { + ...Slider.defaults, + ...options + }; // init props this.interval = null; @@ -128,13 +174,29 @@ export class Slider extends Component { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Slider. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Slider; + /** + * Initializes instances of Slider. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Slider[]; + /** + * Initializes instances of Slider. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Slider | Slider[] { + return super.init(els, options, Slider); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Slider; + static getInstance(el: HTMLElement): Slider { + return (el as any).M_Slider; } destroy() { @@ -176,7 +238,7 @@ export class Slider extends Component { } } - _handleIndicatorClick = (e) => { + _handleIndicatorClick = (e: MouseEvent) => { const el = (e.target).parentElement; const currIndex = [...el.parentNode.children].indexOf(el); this._focusCurrent = true; @@ -369,11 +431,17 @@ export class Slider extends Component { this.interval = null; } - pause() { + /** + * Pause slider autoslide. + */ + pause = () => { this._pause(false); } - start() { + /** + * Start slider autoslide. + */ + start = () => { clearInterval(this.interval); this.interval = setInterval( this._handleInterval, @@ -382,7 +450,10 @@ export class Slider extends Component { this.eventPause = false; } - next() { + /** + * Move to next slider. + */ + next = () => { let newIndex = this.activeIndex + 1; // Wrap around indices. if (newIndex >= this._slides.length) newIndex = 0; @@ -390,7 +461,10 @@ export class Slider extends Component { this.set(newIndex); } - prev() { + /** + * Move to prev slider. + */ + prev = () => { let newIndex = this.activeIndex - 1; // Wrap around indices. if (newIndex >= this._slides.length) newIndex = 0; diff --git a/src/tabs.ts b/src/tabs.ts index 4e5bbb4613..56c26316c1 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -1,17 +1,42 @@ -import { Component } from "./component"; -import { Carousel } from "./carousel"; import anim from "animejs"; -let _defaults = { +import { Carousel } from "./carousel"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface TabsOptions extends BaseOptions { + /** + * Transition duration in milliseconds. + * @default 300 + */ + duration: number; + /** + * Callback for when a new tab content is shown. + * @default null + */ + onShow: (newContent: Element) => void; + /** + * Set to true to enable swipeable tabs. + * This also uses the responsiveThreshold option. + * @default false + */ + swipeable: boolean; + /** + * The maximum width of the screen, in pixels, + * where the swipeable functionality initializes. + * @default infinity + */ + responsiveThreshold: number; +}; + +let _defaults: TabsOptions = { duration: 300, onShow: null, swipeable: false, - responsiveThreshold: Infinity, // breakpoint for swipeable + responsiveThreshold: Infinity // breakpoint for swipeable }; -export class Tabs extends Component { - el: HTMLElement; - _tabLinks: any; +export class Tabs extends Component { + _tabLinks: NodeListOf; _index: number; _indicator: any; _tabWidth: number; @@ -20,11 +45,15 @@ export class Tabs extends Component { _activeTabLink: any; _content: any; - constructor(el, options: any) { - super(Tabs, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Tabs); (this.el as any).M_Tabs = this; - this.options = {...Tabs.defaults, ...options}; + this.options = { + ...Tabs.defaults, + ...options + }; + this._tabLinks = this.el.querySelectorAll('li.tab > a'); this._index = 0; this._setupActiveTabLink(); @@ -39,17 +68,33 @@ export class Tabs extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TabsOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Tabs. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Tabs; + /** + * Initializes instances of Tabs. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Tabs[]; + /** + * Initializes instances of Tabs. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Tabs | Tabs[] { + return super.init(els, options, Tabs); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Tabs; + static getInstance(el: HTMLElement): Tabs { + return (el as any).M_Tabs; } destroy() { @@ -64,6 +109,11 @@ export class Tabs extends Component { (this.el as any).M_Tabs = undefined; } + /** + * The index of tab that is currently shown. + */ + get index(){ return this._index; } + _setupEventHandlers() { window.addEventListener('resize', this._handleWindowResize); this.el.addEventListener('click', this._handleTabClick); @@ -82,8 +132,8 @@ export class Tabs extends Component { } } - _handleTabClick = (e) => { - const tabLink = e.target; + _handleTabClick = (e: MouseEvent) => { + const tabLink = e.target as HTMLAnchorElement; const tab = tabLink.parentElement; // Handle click on tab link only if (!tabLink || !tab.classList.contains('tab')) return; @@ -231,9 +281,9 @@ export class Tabs extends Component { _teardownNormalTabs() { // show Tabs Content - this._tabLinks.forEach(a => { + this._tabLinks.forEach((a) => { if (a.hash) { - const currContent = document.querySelector(a.hash); + const currContent = document.querySelector(a.hash) as HTMLElement; if (currContent) currContent.style.display = ''; } }); @@ -252,6 +302,10 @@ export class Tabs extends Component { return Math.floor(el.offsetLeft); } + /** + * Recalculate tab indicator position. This is useful when + * the indicator position is not correct. + */ updateTabIndicator() { this._setTabsAndTabWidth(); this._animateIndicator(this._index); @@ -282,7 +336,11 @@ export class Tabs extends Component { anim(animOptions); } - select(tabId) { + /** + * Show tab content that corresponds to the tab with the id. + * @param tabId The id of the tab that you want to switch to. + */ + select(tabId: string) { const tab = Array.from(this._tabLinks).find((a: HTMLAnchorElement) => a.getAttribute('href') === '#'+tabId); if (tab) (tab).click(); } diff --git a/src/tapTarget.ts b/src/tapTarget.ts index 321a3d9c8b..0bede02be3 100644 --- a/src/tapTarget.ts +++ b/src/tapTarget.ts @@ -1,43 +1,80 @@ -import { Component } from "./component"; import { M } from "./global"; +import { Component, BaseOptions, InitElements, Openable } from "./component"; -let _defaults = { - onOpen: undefined, - onClose: undefined +export interface TapTargetOptions extends BaseOptions { + /** + * Callback function called when Tap Target is opened. + * @default null + */ + onOpen: (origin: HTMLElement) => void; + /** + * Callback function called when Tap Target is closed. + * @default null + */ + onClose: (origin: HTMLElement) => void; }; -export class TapTarget extends Component { - el: HTMLElement +let _defaults: TapTargetOptions = { + onOpen: null, + onClose: null +}; + +export class TapTarget extends Component implements Openable { + /** + * If the tap target is open. + */ isOpen: boolean; + private wrapper: HTMLElement; private _origin: HTMLElement; private originEl: HTMLElement; private waveEl: HTMLElement & Element & Node; private contentEl: HTMLElement; - constructor(el, options) { - super(TapTarget, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, TapTarget); (this.el as any).M_TapTarget = this; - this.options = {...TapTarget.defaults, ...options}; + + this.options = { + ...TapTarget.defaults, + ...options + }; + this.isOpen = false; // setup - this._origin = document.querySelector('#'+this.el.getAttribute('data-target')); + this._origin = document.querySelector(`#${el.dataset.target}`); this._setup(); this._calculatePositioning(); this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TapTargetOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of TapTarget. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): TapTarget; + /** + * Initializes instances of TapTarget. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): TapTarget[]; + /** + * Initializes instances of TapTarget. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): TapTarget | TapTarget[] { + return super.init(els, options, TapTarget); } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_TapTarget; + static getInstance(el: HTMLElement): TapTarget { + return (el as any).M_TapTarget; } destroy() { @@ -72,8 +109,8 @@ export class TapTarget extends Component { this._calculatePositioning(); } - _handleDocumentClick = (e) => { - if (!e.target.closest('.tap-target-wrapper')) { + _handleDocumentClick = (e: MouseEvent | TouchEvent) => { + if (!(e.target as HTMLElement).closest('.tap-target-wrapper')) { this.close(); e.preventDefault(); e.stopPropagation(); @@ -115,7 +152,7 @@ export class TapTarget extends Component { } } - private _offset(el) { + private _offset(el: HTMLElement) { const box = el.getBoundingClientRect(); const docElem = document.documentElement; return { @@ -204,7 +241,10 @@ export class TapTarget extends Component { this.waveEl.style.height = tapTargetWaveHeight+'px'; } - open() { + /** + * Open Tap Target. + */ + open = () => { if (this.isOpen) return; // onOpen callback if (typeof this.options.onOpen === 'function') { @@ -214,9 +254,12 @@ export class TapTarget extends Component { this.wrapper.classList.add('open'); document.body.addEventListener('click', this._handleDocumentClick, true); document.body.addEventListener('touchend', this._handleDocumentClick); - } + }; - close() { + /** + * Close Tap Target. + */ + close = () => { if (!this.isOpen) return; // onClose callback if (typeof this.options.onClose === 'function') { @@ -226,5 +269,5 @@ export class TapTarget extends Component { this.wrapper.classList.remove('open'); document.body.removeEventListener('click', this._handleDocumentClick, true); document.body.removeEventListener('touchend', this._handleDocumentClick); - } + }; } diff --git a/src/timepicker.ts b/src/timepicker.ts index 1f064c2e9f..27d5d09a96 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -1,8 +1,103 @@ -import { Component } from "./component"; import { M } from "./global"; import { Modal } from "./modal"; +import { Component, BaseOptions, InitElements, I18nOptions } from "./component"; + +export type Views = "hours" | "minutes"; + +export interface TimepickerOptions extends BaseOptions { + /** + * Dial radius. + * @default 135 + */ + dialRadius: number; + /** + * Outer radius. + * @default 105 + */ + outerRadius: number; + /** + * Inner radius. + * @default 70 + */ + innerRadius: number; + /** + * Tick radius. + * @default 20 + */ + tickRadius: number; + /** + * Duration of the transition from/to the hours/minutes view. + * @default 350 + */ + duration: number; + /** + * Specify a DOM element OR selector for a DOM element to render + * the time picker in, by default it will be placed before the input. + * @default null + */ + container: HTMLElement | string | null; + /** + * Show the clear button in the Timepicker. + * @default false + */ + showClearBtn: boolean; + /** + * Default time to set on the timepicker 'now' or '13:14'. + * @default 'now'; + */ + defaultTime: string; + /** + * Millisecond offset from the defaultTime. + * @default 0 + */ + fromNow: number; + /** + * Internationalization options. + */ + i18n: Partial; + /** + * Automatically close picker when minute is selected. + * @default false; + */ + autoClose: boolean; + /** + * Use 12 hour AM/PM clock instead of 24 hour clock. + * @default true + */ + twelveHour: boolean; + /** + * Vibrate device when dragging clock hand. + * @default true + */ + vibrate: boolean; + /** + * Callback function called before modal is opened. + * @default null + */ + onOpenStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is opened. + * @default null + */ + onOpenEnd: (el: HTMLElement) => void; + /** + * Callback function called before modal is closed. + * @default null + */ + onCloseStart: (el: HTMLElement) => void; + /** + * Callback function called after modal is closed. + * @default null + */ + onCloseEnd: (el: HTMLElement) => void; + /** + * Callback function when a time is selected. + * @default null + */ + onSelect: (hour: number, minute: number) => void; +} -let _defaults = { +let _defaults: TimepickerOptions = { dialRadius: 135, outerRadius: 105, innerRadius: 70, @@ -34,7 +129,7 @@ type Point = { y: number }; -export class Timepicker extends Component { +export class Timepicker extends Component { el: HTMLInputElement; id: string; modal: Modal; @@ -48,16 +143,27 @@ export class Timepicker extends Component { moved: boolean; dx: number; dy: number; - currentView: string; + /** + * Current view on the timepicker. + * @default 'hours' + */ + currentView: Views; hand: any; minutesView: HTMLElement; hours: any; minutes: any; + /** The selected time. */ time: string; - amOrPm: any; + /** + * If the time is AM or PM on twelve-hour clock. + * @default 'PM' + */ + amOrPm: "AM" | "PM"; static _template: any; + /** If the picker is open. */ isOpen: boolean; - vibrate: string; + /** Vibrate device when dragging clock hand. */ + vibrate: "vibrate" | "webkitVibrate" | null; _canvas: HTMLElement; hoursView: any; spanAmPm: HTMLSpanElement; @@ -71,10 +177,15 @@ export class Timepicker extends Component { canvas: any; vibrateTimer: any; - constructor(el, options) { - super(Timepicker, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Timepicker); (this.el as any).M_Timepicker = this; - this.options = {...Timepicker.defaults, ...options}; + + this.options = { + ...Timepicker.defaults, + ...options + }; + this.id = M.guid(); this._insertHTMLIntoDOM(); this._setupModal(); @@ -84,12 +195,29 @@ export class Timepicker extends Component { this._pickerSetup(); } - static get defaults() { + static get defaults(): TimepickerOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Timepicker. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Timepicker; + /** + * Initializes instances of Timepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Timepicker[]; + /** + * Initializes instances of Timepicker. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Timepicker | Timepicker[] { + return super.init(els, options, Timepicker); } static _addLeadingZero(num) { @@ -109,9 +237,8 @@ export class Timepicker extends Component { return { x: e.clientX, y: e.clientY }; } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Timepicker; + static getInstance(el: HTMLElement): Timepicker { + return (el as any).M_Timepicker; } destroy() { @@ -335,7 +462,7 @@ export class Timepicker extends Component { hand.setAttribute('y1', '0'); let bg = Timepicker._createSVGEl('circle'); bg.setAttribute('class', 'timepicker-canvas-bg'); - bg.setAttribute('r', tickRadius); + bg.setAttribute('r', tickRadius.toString()); g.appendChild(hand); g.appendChild(bg); g.appendChild(bearing); @@ -445,7 +572,11 @@ export class Timepicker extends Component { this._updateAmPmView(); } - showView = (view, delay: number = null) => { + /** + * Show hours or minutes view on timepicker. + * @param view The name of the view you want to switch to, 'hours' or 'minutes'. + */ + showView = (view: Views, delay: number = null) => { if (view === 'minutes' && getComputedStyle(this.hoursView).visibility === 'visible') { // raiseCallback(this.options.beforeHourSelect); } @@ -628,7 +759,10 @@ export class Timepicker extends Component { this.bg.setAttribute('cy', cy2.toString()); } - open() { + /** + * Open timepicker. + */ + open = () => { if (this.isOpen) return; this.isOpen = true; this._updateTimeFromInput(); @@ -636,6 +770,9 @@ export class Timepicker extends Component { this.modal.open(undefined); } + /** + * Close timepicker. + */ close = () => { if (!this.isOpen) return; this.isOpen = false; diff --git a/src/toasts.ts b/src/toasts.ts index 2bc2b0eab3..ae9dce4786 100644 --- a/src/toasts.ts +++ b/src/toasts.ts @@ -1,6 +1,47 @@ import anim from "animejs"; -let _defaults = { +import { BaseOptions, InitElements } from "./component"; + +export interface ToastOptions extends BaseOptions { + /** + * The content of the Toast. + * @default "" + */ + text: string; + /** + * Length in ms the Toast stays before dismissal. + * @default 4000 + */ + displayLength: number; + /** + * Transition in duration in milliseconds. + * @default 300 + */ + inDuration: number; + /** + * Transition out duration in milliseconds. + * @default 375 + */ + outDuration: number; + /** + * Classes to be added to the toast element. + * @default "" + */ + classes: string; + /** + * Callback function called when toast is dismissed. + * @default null + */ + completeCallback: () => void; + /** + * The percentage of the toast's width it takes fora drag + * to dismiss a Toast. + * @default 0.8 + */ + activationPercent: number; +} + +let _defaults: ToastOptions = { text: '', displayLength: 4000, inDuration: 300, @@ -11,34 +52,36 @@ let _defaults = { }; export class Toast { - static _toasts: Toast[]; - static _container: any; - static _draggedToast: Toast; - options: any; - message: string; - panning: boolean; - timeRemaining: number; + /** The toast element. */ el: HTMLDivElement; + /** + * The remaining amount of time in ms that the toast + * will stay before dismissal. + */ + timeRemaining: number; + /** + * Describes the current pan state of the Toast. + */ + panning: boolean; + options: ToastOptions; + message: string; counterInterval: NodeJS.Timeout; - wasSwiped: any; + wasSwiped: boolean; startingXPos: number; - xPos: any; + xPos: number; time: number; deltaX: number; velocityX: number; - constructor(options: any) { - this.options = {...Toast.defaults, ...options}; - //this.htmlMessage = this.options.html; - // Warn when using html - // if (!!this.options.html) - // console.warn( - // 'The html option is deprecated and will be removed in the future. See https://github.com/materializecss/materialize/pull/49' - // ); - // If the new unsafeHTML is used, prefer that - // if (!!this.options.unsafeHTML) { - // this.htmlMessage = this.options.unsafeHTML; - // } + static _toasts: Toast[]; + static _container: any; + static _draggedToast: Toast; + + constructor(options: Partial) { + this.options = { + ...Toast.defaults, + ...options + }; this.message = this.options.text; this.panning = false; this.timeRemaining = this.options.displayLength; @@ -54,13 +97,12 @@ export class Toast { this._setTimer(); } - static get defaults() { + static get defaults(): ToastOptions { return _defaults; } - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Toast; + static getInstance(el: HTMLElement): Toast { + return (el as any).M_Toast; } static _createContainer() { @@ -84,7 +126,7 @@ export class Toast { Toast._container = null; } - static _onDragStart(e) { + static _onDragStart(e: TouchEvent | MouseEvent) { if (e.target && (e.target).closest('.toast')) { const toastElem = (e.target).closest('.toast'); const toast: Toast = (toastElem as any).M_Toast; @@ -98,7 +140,7 @@ export class Toast { } } - static _onDragMove(e) { + static _onDragMove(e: TouchEvent | MouseEvent) { if (!!Toast._draggedToast) { e.preventDefault(); const toast = Toast._draggedToast; @@ -139,14 +181,17 @@ export class Toast { } } - static _xPos(e) { - if (e.targetTouches && e.targetTouches.length >= 1) { + static _xPos(e: TouchEvent | MouseEvent) { + if (e instanceof TouchEvent && e.targetTouches.length >= 1) { return e.targetTouches[0].clientX; } // mouse event - return e.clientX; + return (e as MouseEvent).clientX; } + /** + * dismiss all toasts. + */ static dismissAll() { for (let toastIndex in Toast._toasts) { Toast._toasts[toastIndex].dismiss(); @@ -165,29 +210,8 @@ export class Toast { toast.classList.add(...this.options.classes.split(' ')); } - // Set safe text content - toast.innerText = this.message; - - // if ( - // typeof HTMLElement === 'object' - // ? this.htmlMessage instanceof HTMLElement - // : this.htmlMessage && - // typeof this.htmlMessage === 'object' && - // this.htmlMessage !== null && - // this.htmlMessage.nodeType === 1 && - // typeof this.htmlMessage.nodeName === 'string' - // ) { - // //if the htmlMessage is an HTML node, append it directly - // toast.appendChild(this.htmlMessage); - // } - // else if (!!this.htmlMessage.jquery) { - // // Check if it is jQuery object, append the node - // $(toast).append(this.htmlMessage[0]); - // } - // else { - // // Append as unsanitized html; - // $(toast).append(this.htmlMessage); - // } + // Set text content + else toast.innerText = this.message; // Append toast Toast._container.appendChild(toast); @@ -207,7 +231,7 @@ export class Toast { /** * Create setInterval which automatically removes toast when timeRemaining >= 0 - * has been reached + * has been reached. */ _setTimer() { if (this.timeRemaining !== Infinity) { @@ -225,7 +249,7 @@ export class Toast { } /** - * Dismiss toast with animation + * Dismiss toast with animation. */ dismiss() { window.clearInterval(this.counterInterval); diff --git a/src/tooltip.ts b/src/tooltip.ts index e4745b34a6..6790a705dd 100644 --- a/src/tooltip.ts +++ b/src/tooltip.ts @@ -2,24 +2,81 @@ import anim from "animejs"; import { M } from "./global"; import { Bounding } from "./bounding"; -import { Component } from "./component"; +import { Component, BaseOptions, InitElements } from "./component"; + +export interface TooltipOptions extends BaseOptions { + /** + * Delay time before tooltip disappears. + * @default 200 + */ + exitDelay: number; + /** + * Delay time before tooltip appears. + * @default 0 + */ + enterDelay: number; + /** + * Text string for the tooltip. + * @default "" + */ + text: string; + /** + * Set distance tooltip appears away from its activator + * excluding transitionMovement. + * @default 5 + */ + margin: number; + /** + * Enter transition duration. + * @default 300 + */ + inDuration: number; + /** + * Opacity of the tooltip. + * @default 1 + */ + opacity: number; + /** + * Exit transition duration. + * @default 250 + */ + outDuration: number; + /** + * Set the direction of the tooltip. + * @default 'bottom' + */ + position: 'top' | 'right' | 'bottom' | 'left'; + /** + * Amount in px that the tooltip moves during its transition. + * @default 10 + */ + transitionMovement: number; +} -const _defaults = { +const _defaults: TooltipOptions = { exitDelay: 200, enterDelay: 0, - //html: null, text: '', - //unsafeHTML: null, margin: 5, inDuration: 250, outDuration: 200, position: 'bottom', - transitionMovement: 10 + transitionMovement: 10, + opacity: 1 }; -export class Tooltip extends Component { +export class Tooltip extends Component { + /** + * If tooltip is open. + */ isOpen: boolean; + /** + * If tooltip is hovered. + */ isHovered: boolean; + /** + * If tooltip is focused. + */ isFocused: boolean; tooltipEl: HTMLElement; private _exitDelayTimeout: string | number | NodeJS.Timeout; @@ -27,10 +84,15 @@ export class Tooltip extends Component { xMovement: number; yMovement: number; - constructor(el, options) { - super(Tooltip, el, options); + constructor(el: HTMLElement, options: Partial) { + super(el, options, Tooltip); (this.el as any).M_Tooltip = this; - this.options = {...Tooltip.defaults, ...options}; + + this.options = { + ...Tooltip.defaults, + ...options + }; + this.isOpen = false; this.isHovered = false; this.isFocused = false; @@ -38,17 +100,33 @@ export class Tooltip extends Component { this._setupEventHandlers(); } - static get defaults() { + static get defaults(): TooltipOptions { return _defaults; } - static init(els, options) { - return super.init(this, els, options); + /** + * Initializes instance of Tooltip. + * @param el HTML element. + * @param options Component options. + */ + static init(el: HTMLElement, options: Partial): Tooltip; + /** + * Initializes instances of Tooltip. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: InitElements, options: Partial): Tooltip[]; + /** + * Initializes instances of Tooltip. + * @param els HTML elements. + * @param options Component options. + */ + static init(els: HTMLElement | InitElements, options: Partial): Tooltip | Tooltip[] { + return super.init(els, options, Tooltip); } - static getInstance(el) { - const domElem = !!el.jquery ? el[0] : el; - return domElem.M_Tooltip; + static getInstance(el: HTMLElement): Tooltip { + return (el as any).M_Tooltip; } destroy() { @@ -90,7 +168,10 @@ export class Tooltip extends Component { this.el.removeEventListener('blur', this._handleBlur, true); } - open(isManual) { + /** + * Show tooltip. + */ + open = (isManual: boolean) => { if (this.isOpen) return; isManual = isManual === undefined ? true : undefined; // Default value true this.isOpen = true; @@ -99,8 +180,11 @@ export class Tooltip extends Component { this._updateTooltipContent(); this._setEnterDelayTimeout(isManual); } - - close() { + + /** + * Hide tooltip. + */ + close = () => { if (!this.isOpen) return; this.isHovered = false; this.isFocused = false; @@ -166,7 +250,7 @@ export class Tooltip extends Component { tooltip.style.left = newCoordinates.x+'px'; } - _repositionWithinScreen(x, y, width, height) { + _repositionWithinScreen(x: number, y: number, width: number, height: number) { const scrollLeft = M.getDocumentScrollLeft(); const scrollTop = M.getDocumentScrollTop(); let newX = x - scrollLeft; From c6cdb95ce26f9f652ba41ab3511e3bce302bf020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Wed, 21 Jun 2023 15:23:38 -0300 Subject: [PATCH 3/3] fix(types): overload initialization func types - New "MElement" type (HTMLElement | Element); - "init" overloads which supports nodelist must support "MElement" type; - Made "options" an optional attribute in "init" functions; - Update "init" types for input elements; - Removed unnecessary imports. --- src/autocomplete.ts | 10 +++++----- src/buttons.ts | 8 ++++---- src/carousel.ts | 8 ++++---- src/characterCounter.ts | 8 ++++---- src/chips.ts | 8 ++++---- src/collapsible.ts | 8 ++++---- src/component.ts | 15 ++++++++------- src/datepicker.ts | 16 ++++++++-------- src/dropdown.ts | 8 ++++---- src/materialbox.ts | 8 ++++---- src/modal.ts | 8 ++++---- src/parallax.ts | 8 ++++---- src/pushpin.ts | 8 ++++---- src/range.ts | 8 ++++---- src/scrollspy.ts | 8 ++++---- src/select.ts | 8 ++++---- src/sidenav.ts | 8 ++++---- src/slider.ts | 8 ++++---- src/tabs.ts | 8 ++++---- src/tapTarget.ts | 8 ++++---- src/timepicker.ts | 24 ++++++++++++------------ src/toasts.ts | 2 +- src/tooltip.ts | 8 ++++---- 23 files changed, 106 insertions(+), 105 deletions(-) diff --git a/src/autocomplete.ts b/src/autocomplete.ts index 119f6b9b00..cc11399e79 100644 --- a/src/autocomplete.ts +++ b/src/autocomplete.ts @@ -1,6 +1,6 @@ import { Utils } from "./utils"; import { Dropdown, DropdownOptions } from "./dropdown"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface AutocompleteData { /** @@ -111,7 +111,7 @@ export class Autocomplete extends Component { menuItems: AutocompleteData[]; - constructor(el: HTMLElement, options: Partial) { + constructor(el: HTMLInputElement, options: Partial) { super(el, options, Autocomplete); (this.el as any).M_Autocomplete = this; @@ -141,19 +141,19 @@ export class Autocomplete extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Autocomplete; + static init(el: HTMLInputElement, options?: Partial): Autocomplete; /** * Initializes instances of Autocomplete. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Autocomplete[]; + static init(els: InitElements, options?: Partial): Autocomplete[]; /** * Initializes instances of Autocomplete. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Autocomplete | Autocomplete[] { + static init(els: HTMLInputElement | InitElements, options: Partial = {}): Autocomplete | Autocomplete[] { return super.init(els, options, Autocomplete); } diff --git a/src/buttons.ts b/src/buttons.ts index 37d37e34db..5efbbdd7d0 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -1,6 +1,6 @@ import anim from "animejs"; -import { Component, BaseOptions, InitElements, Openable } from "./component"; +import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; export interface FloatingActionButtonOptions extends BaseOptions { /** @@ -81,19 +81,19 @@ export class FloatingActionButton extends Component * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): FloatingActionButton + static init(el: HTMLElement, options?: Partial): FloatingActionButton /** * Initializes instances of FloatingActionButton. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): FloatingActionButton[]; + static init(els: InitElements, options?: Partial): FloatingActionButton[]; /** * Initializes instances of FloatingActionButton. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): FloatingActionButton | FloatingActionButton[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): FloatingActionButton | FloatingActionButton[] { return super.init(els, options, FloatingActionButton); } diff --git a/src/carousel.ts b/src/carousel.ts index 898e78a05c..c3106e05c2 100644 --- a/src/carousel.ts +++ b/src/carousel.ts @@ -1,5 +1,5 @@ import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface CarouselOptions extends BaseOptions{ /** @@ -172,19 +172,19 @@ export class Carousel extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Carousel; + static init(el: HTMLElement, options?: Partial): Carousel; /** * Initializes instances of Carousel. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Carousel[]; + static init(els: InitElements, options?: Partial): Carousel[]; /** * Initializes instances of Carousel. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Carousel | Carousel[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Carousel | Carousel[] { return super.init(els, options, Carousel); } diff --git a/src/characterCounter.ts b/src/characterCounter.ts index cb3eb179c0..14cb982c1c 100644 --- a/src/characterCounter.ts +++ b/src/characterCounter.ts @@ -1,4 +1,4 @@ -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface CharacterCounterOptions extends BaseOptions {}; @@ -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); } diff --git a/src/chips.ts b/src/chips.ts index 84882c9060..59cecfe38e 100644 --- a/src/chips.ts +++ b/src/chips.ts @@ -1,6 +1,6 @@ import { Utils } from "./utils"; import { Autocomplete, AutocompleteOptions } from "./autocomplete"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface ChipData { /** @@ -140,19 +140,19 @@ export class Chips extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Chips; + static init(el: HTMLElement, options?: Partial): Chips; /** * Initializes instances of Chips. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Chips[]; + static init(els: InitElements, options?: Partial): Chips[]; /** * Initializes instances of Chips. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Chips | Chips[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Chips | Chips[] { return super.init(els, options, Chips); } diff --git a/src/collapsible.ts b/src/collapsible.ts index 15951dcb00..ce072b6507 100644 --- a/src/collapsible.ts +++ b/src/collapsible.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface CollapsibleOptions extends BaseOptions { /** @@ -85,19 +85,19 @@ export class Collapsible extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Collapsible; + static init(el: HTMLElement, options?: Partial): Collapsible; /** * Initializes instances of Collapsible. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Collapsible[]; + static init(els: InitElements, options?: Partial): Collapsible[]; /** * Initializes instances of Collapsible. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Collapsible | Collapsible[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Collapsible | Collapsible[] { return super.init(els, options, Collapsible); } diff --git a/src/component.ts b/src/component.ts index a94d35ad07..2abc34959c 100644 --- a/src/component.ts +++ b/src/component.ts @@ -3,7 +3,8 @@ */ export interface BaseOptions {}; -export type InitElements = NodeListOf | HTMLCollectionOf; +export type MElement = HTMLElement | Element; +export type InitElements = NodeListOf | HTMLCollectionOf; type ComponentConstructor, O extends BaseOptions> = { new (el: HTMLElement, options: Partial): T }; @@ -66,7 +67,7 @@ export class Component{ * @param classDef Class definition. */ protected static init< - I extends HTMLElement, O extends BaseOptions, C extends Component + I extends MElement, O extends BaseOptions, C extends Component >(els: InitElements, options: Partial, classDef: ComponentType): C[]; /** * Initializes component instances. @@ -75,7 +76,7 @@ export class Component{ * @param classDef Class definition. */ protected static init< - I extends HTMLElement, O extends BaseOptions, C extends Component + I extends MElement, O extends BaseOptions, C extends Component >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[]; /** * Initializes component instances. @@ -84,16 +85,16 @@ export class Component{ * @param classDef Class definition. */ protected static init< - I extends HTMLElement, O extends BaseOptions, C extends Component + I extends MElement, O extends BaseOptions, C extends Component >(els: I | InitElements, options: Partial, classDef: ComponentType): C | C[] { let instances = null; - if (els instanceof HTMLElement) { - instances = new classDef(els, options); + if (els instanceof Element) { + instances = new classDef(els, options); } else if (!!els && els.length) { instances = []; for (let i = 0; i < els.length; i++) { - instances.push(new classDef(els[i], options)); + instances.push(new classDef(els[i], options)); } } return instances; diff --git a/src/datepicker.ts b/src/datepicker.ts index 993db5e9f3..7e29ca5b27 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -1,7 +1,7 @@ import { Modal } from "./modal"; import { Utils } from "./utils"; import { FormSelect } from "./select"; -import { BaseOptions, Component, InitElements, I18nOptions } from "./component"; +import { BaseOptions, Component, InitElements, MElement, I18nOptions } from "./component"; export interface DateI18nOptions extends I18nOptions { previousMonth: string; @@ -236,7 +236,7 @@ let _defaults: DatepickerOptions = { }; export class Datepicker extends Component { - el: HTMLInputElement + declare el: HTMLInputElement id: string; /** If the picker is open. */ isOpen: boolean; @@ -258,7 +258,7 @@ export class Datepicker extends Component { private _m: any; static _template: string; - constructor(el: HTMLElement, options: Partial) { + constructor(el: HTMLInputElement, options: Partial) { super(el, options, Datepicker); (this.el as any).M_Datepicker = this; @@ -312,19 +312,19 @@ export class Datepicker extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Datepicker; + static init(el: HTMLInputElement, options?: Partial): Datepicker; /** * Initializes instances of Datepicker. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Datepicker[]; + static init(els: InitElements, options?: Partial): Datepicker[]; /** * Initializes instances of Datepicker. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Datepicker | Datepicker[] { + static init(els: HTMLInputElement | InitElements, options: Partial = {}): Datepicker | Datepicker[] { return super.init(els, options, Datepicker); } @@ -835,8 +835,8 @@ export class Datepicker extends Component { this.calendarEl.innerHTML = html; // Init Materialize Select - let yearSelect = this.calendarEl.querySelector('.orig-select-year') as HTMLElement; - let monthSelect = this.calendarEl.querySelector('.orig-select-month') as HTMLElement; + let yearSelect = this.calendarEl.querySelector('.orig-select-year') as HTMLSelectElement; + let monthSelect = this.calendarEl.querySelector('.orig-select-month') as HTMLSelectElement; FormSelect.init(yearSelect, { classes: 'select-year', dropdownOptions: { container: document.body, constrainWidth: false } diff --git a/src/dropdown.ts b/src/dropdown.ts index 6ba6ebb36b..0956f33634 100644 --- a/src/dropdown.ts +++ b/src/dropdown.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements, Openable } from "./component"; +import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; export interface DropdownOptions extends BaseOptions { /** @@ -143,19 +143,19 @@ export class Dropdown extends Component implements Openable { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Dropdown; + static init(el: HTMLElement, options?: Partial): Dropdown; /** * Initializes instances of Dropdown. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Dropdown[]; + static init(els: InitElements, options?: Partial): Dropdown[]; /** * Initializes instances of Dropdown. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Dropdown | Dropdown[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Dropdown | Dropdown[] { return super.init(els, options, Dropdown); } diff --git a/src/materialbox.ts b/src/materialbox.ts index 7a3dd72115..ecf8e96d2c 100644 --- a/src/materialbox.ts +++ b/src/materialbox.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { BaseOptions, Component, InitElements } from "./component"; +import { BaseOptions, Component, InitElements, MElement } from "./component"; export interface MaterialboxOptions extends BaseOptions { /** @@ -100,19 +100,19 @@ export class Materialbox extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Materialbox; + static init(el: HTMLElement, options?: Partial): Materialbox; /** * Initializes instances of MaterialBox. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Materialbox[]; + static init(els: InitElements, options?: Partial): Materialbox[]; /** * Initializes instances of MaterialBox. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Materialbox | Materialbox[]{ + static init(els: HTMLElement | InitElements, options: Partial = {}): Materialbox | Materialbox[]{ return super.init(els, options, Materialbox); } diff --git a/src/modal.ts b/src/modal.ts index 749af2b4d0..36b8bb5bb8 100644 --- a/src/modal.ts +++ b/src/modal.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface ModalOptions extends BaseOptions { /** @@ -122,19 +122,19 @@ export class Modal extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Modal; + static init(el: HTMLElement, options?: Partial): Modal; /** * Initializes instances of Modal. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Modal[]; + static init(els: InitElements, options?: Partial): Modal[]; /** * Initializes instances of Modal. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Modal | Modal[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Modal | Modal[] { return super.init(els, options, Modal); } diff --git a/src/parallax.ts b/src/parallax.ts index 2f7a574006..1662674aa3 100644 --- a/src/parallax.ts +++ b/src/parallax.ts @@ -1,5 +1,5 @@ import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface ParallaxOptions extends BaseOptions { /** @@ -46,19 +46,19 @@ export class Parallax extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Parallax; + static init(el: HTMLElement, options?: Partial): Parallax; /** * Initializes instances of Parallax. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Parallax[]; + static init(els: InitElements, options?: Partial): Parallax[]; /** * Initializes instances of Parallax. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Parallax | Parallax[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Parallax | Parallax[] { return super.init(els, options, Parallax); } diff --git a/src/pushpin.ts b/src/pushpin.ts index d71fbba3f4..5a17e16ea6 100644 --- a/src/pushpin.ts +++ b/src/pushpin.ts @@ -1,5 +1,5 @@ import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface PushpinOptions extends BaseOptions { /** @@ -62,19 +62,19 @@ export class Pushpin extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Pushpin; + static init(el: HTMLElement, options?: Partial): Pushpin; /** * Initializes instances of Pushpin. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Pushpin[]; + static init(els: InitElements, options?: Partial): Pushpin[]; /** * Initializes instances of Pushpin. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Pushpin | Pushpin[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Pushpin | Pushpin[] { return super.init(els, options, Pushpin); } diff --git a/src/range.ts b/src/range.ts index 420705fef3..9112d84861 100644 --- a/src/range.ts +++ b/src/range.ts @@ -1,6 +1,6 @@ import anim from "animejs"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface RangeOptions extends BaseOptions {}; @@ -37,19 +37,19 @@ export class Range extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLInputElement, options: Partial): Range; + static init(el: HTMLInputElement, options?: Partial): Range; /** * Initializes instances of Range. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Range[]; + static init(els: InitElements, options?: Partial): Range[]; /** * Initializes instances of Range. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLInputElement | InitElements, options: Partial): Range | Range[] { + static init(els: HTMLInputElement | InitElements, options: Partial = {}): Range | Range[] { return super.init(els, options, Range); } diff --git a/src/scrollspy.ts b/src/scrollspy.ts index 8180dcb556..6f8d1d55dc 100644 --- a/src/scrollspy.ts +++ b/src/scrollspy.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface ScrollSpyOptions extends BaseOptions { /** @@ -70,19 +70,19 @@ export class ScrollSpy extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): ScrollSpy; + static init(el: HTMLElement, options?: Partial): ScrollSpy; /** * Initializes instances of ScrollSpy. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): ScrollSpy[]; + static init(els: InitElements, options?: Partial): ScrollSpy[]; /** * Initializes instances of ScrollSpy. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): ScrollSpy | ScrollSpy[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): ScrollSpy | ScrollSpy[] { return super.init(els, options, ScrollSpy); } diff --git a/src/select.ts b/src/select.ts index ef0fa183a4..c460de9a36 100644 --- a/src/select.ts +++ b/src/select.ts @@ -1,6 +1,6 @@ import { Utils } from "./utils"; import { Dropdown, DropdownOptions } from "./dropdown"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface FormSelectOptions extends BaseOptions { /** @@ -73,19 +73,19 @@ export class FormSelect extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): FormSelect; + static init(el: HTMLSelectElement, options?: Partial): FormSelect; /** * Initializes instances of FormSelect. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): FormSelect[]; + static init(els: InitElements, options?: Partial): FormSelect[]; /** * Initializes instances of FormSelect. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): FormSelect | FormSelect[] { + static init(els: HTMLSelectElement | InitElements, options: Partial = {}): FormSelect | FormSelect[] { return super.init(els, options, FormSelect); } diff --git a/src/sidenav.ts b/src/sidenav.ts index bb766b12a1..58778569bc 100644 --- a/src/sidenav.ts +++ b/src/sidenav.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements, Openable } from "./component"; +import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; export interface SidenavOptions extends BaseOptions { /** @@ -121,19 +121,19 @@ export class Sidenav extends Component implements Openable { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Sidenav; + static init(el: HTMLElement, options?: Partial): Sidenav; /** * Initializes instances of Sidenav. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Sidenav[]; + static init(els: InitElements, options?: Partial): Sidenav[]; /** * Initializes instances of Sidenav. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Sidenav | Sidenav[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Sidenav | Sidenav[] { return super.init(els, options, Sidenav); } diff --git a/src/slider.ts b/src/slider.ts index ec0fd87cdc..0e6103b18b 100644 --- a/src/slider.ts +++ b/src/slider.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface SliderOptions extends BaseOptions { /** @@ -179,19 +179,19 @@ export class Slider extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Slider; + static init(el: HTMLElement, options?: Partial): Slider; /** * Initializes instances of Slider. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Slider[]; + static init(els: InitElements, options?: Partial): Slider[]; /** * Initializes instances of Slider. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Slider | Slider[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Slider | Slider[] { return super.init(els, options, Slider); } diff --git a/src/tabs.ts b/src/tabs.ts index 56c26316c1..875e421bc3 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -1,7 +1,7 @@ import anim from "animejs"; import { Carousel } from "./carousel"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface TabsOptions extends BaseOptions { /** @@ -77,19 +77,19 @@ export class Tabs extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Tabs; + static init(el: HTMLElement, options?: Partial): Tabs; /** * Initializes instances of Tabs. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Tabs[]; + static init(els: InitElements, options?: Partial): Tabs[]; /** * Initializes instances of Tabs. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Tabs | Tabs[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Tabs | Tabs[] { return super.init(els, options, Tabs); } diff --git a/src/tapTarget.ts b/src/tapTarget.ts index dfc70e5aa7..c938b8595a 100644 --- a/src/tapTarget.ts +++ b/src/tapTarget.ts @@ -1,5 +1,5 @@ import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements, Openable } from "./component"; +import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; export interface TapTargetOptions extends BaseOptions { /** @@ -57,19 +57,19 @@ export class TapTarget extends Component implements Openable { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): TapTarget; + static init(el: HTMLElement, options?: Partial): TapTarget; /** * Initializes instances of TapTarget. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): TapTarget[]; + static init(els: InitElements, options?: Partial): TapTarget[]; /** * Initializes instances of TapTarget. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): TapTarget | TapTarget[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): TapTarget | TapTarget[] { return super.init(els, options, TapTarget); } diff --git a/src/timepicker.ts b/src/timepicker.ts index 1ae8f73888..a4d64a3691 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -1,6 +1,6 @@ import { Modal } from "./modal"; import { Utils } from "./utils"; -import { Component, BaseOptions, InitElements, I18nOptions } from "./component"; +import { Component, BaseOptions, InitElements, MElement, I18nOptions } from "./component"; export type Views = "hours" | "minutes"; @@ -130,7 +130,7 @@ type Point = { }; export class Timepicker extends Component { - el: HTMLInputElement; + declare el: HTMLInputElement; id: string; modal: Modal; modalEl: HTMLElement; @@ -177,7 +177,7 @@ export class Timepicker extends Component { canvas: any; vibrateTimer: any; - constructor(el: HTMLElement, options: Partial) { + constructor(el: HTMLInputElement, options: Partial) { super(el, options, Timepicker); (this.el as any).M_Timepicker = this; @@ -204,37 +204,37 @@ export class Timepicker extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Timepicker; + static init(el: HTMLInputElement, options?: Partial): Timepicker; /** * Initializes instances of Timepicker. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Timepicker[]; + static init(els: InitElements, options?: Partial): Timepicker[]; /** * Initializes instances of Timepicker. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Timepicker | Timepicker[] { + static init(els: HTMLInputElement | InitElements, options: Partial = {}): Timepicker | Timepicker[] { return super.init(els, options, Timepicker); } - static _addLeadingZero(num) { + static _addLeadingZero(num: number) { return (num < 10 ? '0' : '') + num; } - static _createSVGEl(name) { + static _createSVGEl(name: string) { let svgNS = 'http://www.w3.org/2000/svg'; return document.createElementNS(svgNS, name); } - static _Pos(e): Point { - if (e.targetTouches && e.targetTouches.length >= 1) { - return { x: e.targetTouches[0].clientX, y: e.targetTouches[0].clientY }; + static _Pos(e: TouchEvent | MouseEvent): Point { + if (e.type.startsWith("touch") && (e as TouchEvent).targetTouches.length >= 1) { + return { x: (e as TouchEvent).targetTouches[0].clientX, y: (e as TouchEvent).targetTouches[0].clientY }; } // mouse event - return { x: e.clientX, y: e.clientY }; + return { x: (e as MouseEvent).clientX, y: (e as MouseEvent).clientY }; } static getInstance(el: HTMLElement): Timepicker { diff --git a/src/toasts.ts b/src/toasts.ts index 73df369cf8..c160f457f5 100644 --- a/src/toasts.ts +++ b/src/toasts.ts @@ -1,6 +1,6 @@ import anim from "animejs"; -import { BaseOptions, InitElements } from "./component"; +import { BaseOptions } from "./component"; export interface ToastOptions extends BaseOptions { /** diff --git a/src/tooltip.ts b/src/tooltip.ts index 26f026d494..41893f69ac 100644 --- a/src/tooltip.ts +++ b/src/tooltip.ts @@ -2,7 +2,7 @@ import anim from "animejs"; import { Utils } from "./utils"; import { Bounding } from "./bounding"; -import { Component, BaseOptions, InitElements } from "./component"; +import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface TooltipOptions extends BaseOptions { /** @@ -109,19 +109,19 @@ export class Tooltip extends Component { * @param el HTML element. * @param options Component options. */ - static init(el: HTMLElement, options: Partial): Tooltip; + static init(el: HTMLElement, options?: Partial): Tooltip; /** * Initializes instances of Tooltip. * @param els HTML elements. * @param options Component options. */ - static init(els: InitElements, options: Partial): Tooltip[]; + static init(els: InitElements, options?: Partial): Tooltip[]; /** * Initializes instances of Tooltip. * @param els HTML elements. * @param options Component options. */ - static init(els: HTMLElement | InitElements, options: Partial): Tooltip | Tooltip[] { + static init(els: HTMLElement | InitElements, options: Partial = {}): Tooltip | Tooltip[] { return super.init(els, options, Tooltip); }