From a1493691b487ff7ff58e59d7eec67d1cc6923e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Artmu=CC=88ller?= Date: Thu, 20 Aug 2020 23:28:15 +0200 Subject: [PATCH] cleanup --- .vscode/extensions.json | 9 - examples/index.html | 21 +- rollup.config.js | 2 +- src/components/base-component.ts | 9 - src/components/clone/clone.component.ts | 171 ---------- .../controller/controller.component.ts | 107 ------- src/components/drag/drag.component.ts | 190 ------------ .../layout/directions/horizontal-layout.ts | 96 ------ src/components/layout/index.ts | 108 ------- src/components/pagination/index.ts | 3 - .../pagination/pagination.component.ts | 236 -------------- src/components/track/directions/horizontal.ts | 91 ------ src/components/track/index.ts | 3 - src/components/track/track.component.ts | 175 ----------- src/components/virtual/index.ts | 2 - src/components/virtual/slide.component.ts | 169 ---------- src/components/virtual/virtual.component.ts | 257 --------------- src/constants/directions.ts | 9 - src/constants/states.ts | 24 -- src/{constants/classes.ts => contants.ts} | 0 src/core/index.ts | 1 - src/{components2 => }/drag.ts | 22 +- src/{components2 => }/slide.ts | 24 +- src/transitions/index.ts | 1 - src/transitions/slide/index.ts | 62 ---- src/utils/dom.ts | 79 ----- src/utils/error.ts | 14 - src/{core => utils}/event.ts | 2 +- src/utils/object.ts | 47 --- src/utils/utils.ts | 76 ----- src/virchual.ts | 292 ++++++++++-------- src/virchual2.ts | 227 -------------- 32 files changed, 222 insertions(+), 2307 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 src/components/base-component.ts delete mode 100644 src/components/clone/clone.component.ts delete mode 100644 src/components/controller/controller.component.ts delete mode 100644 src/components/drag/drag.component.ts delete mode 100644 src/components/layout/directions/horizontal-layout.ts delete mode 100644 src/components/layout/index.ts delete mode 100644 src/components/pagination/index.ts delete mode 100644 src/components/pagination/pagination.component.ts delete mode 100644 src/components/track/directions/horizontal.ts delete mode 100644 src/components/track/index.ts delete mode 100644 src/components/track/track.component.ts delete mode 100644 src/components/virtual/index.ts delete mode 100644 src/components/virtual/slide.component.ts delete mode 100644 src/components/virtual/virtual.component.ts delete mode 100644 src/constants/directions.ts delete mode 100644 src/constants/states.ts rename src/{constants/classes.ts => contants.ts} (100%) delete mode 100644 src/core/index.ts rename src/{components2 => }/drag.ts (88%) rename src/{components2 => }/slide.ts (75%) delete mode 100644 src/transitions/index.ts delete mode 100644 src/transitions/slide/index.ts rename src/{core => utils}/event.ts (98%) delete mode 100644 src/virchual2.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index e5e8c0c..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "esbenp.prettier-vscod", - "steoates.autoimport", - "mike-co.import-sorter", - "mikestead.dotenv", - "kisstkondoros.vscode-codemetrics" - ] -} diff --git a/examples/index.html b/examples/index.html index af8e79e..7cdf7d5 100644 --- a/examples/index.html +++ b/examples/index.html @@ -10,18 +10,25 @@ margin: 0 auto; margin-top: 50px; } + + .image-slide { + height: 100%; + display: block; + } -
- -
+ +
+ +
- -
-
-
+ +
+
+
+
diff --git a/rollup.config.js b/rollup.config.js index 4d4ea07..b594493 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -8,7 +8,7 @@ import postcss_copy from 'postcss-copy'; import visualizer from 'rollup-plugin-visualizer'; export default { - input: 'src/virchual2.ts', // our source file + input: 'src/virchual.ts', // our source file output: [ { file: pkg.main, diff --git a/src/components/base-component.ts b/src/components/base-component.ts deleted file mode 100644 index f61b512..0000000 --- a/src/components/base-component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Virchual, { VirchualOptions, VirchualComponents } from './../virchual'; - -export interface BaseComponentConstructor { - new (options: VirchualOptions): BaseComponent; -} - -export interface BaseComponent { - mount(instance: Virchual, components: VirchualComponents): void; -} diff --git a/src/components/clone/clone.component.ts b/src/components/clone/clone.component.ts deleted file mode 100644 index 16a13e1..0000000 --- a/src/components/clone/clone.component.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { addClass, append, before, domify, remove } from '../../utils/dom'; -import TrackComponent from '../track/track.component'; -import VirtualComponent from '../virtual/virtual.component'; -import Virchual, { VirchualComponents, VirchualOptions } from './../../virchual'; -import { BaseComponent } from './../base-component'; -import { SlideComponent } from './../virtual/slide.component'; -import { ELEMENT_CLASSES } from '../../constants/classes'; - -export default class CloneComponent implements BaseComponent { - /** - * Store information of clones. - */ - private _clonesBefore: SlideComponent[] = []; - private _clonesAfter: SlideComponent[] = []; - - private instance: Virchual; - private virtual: VirtualComponent; - private track: TrackComponent; - - constructor(private options: VirchualOptions) {} - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.virtual = components.Virtual as VirtualComponent; - this.track = components.Track as TrackComponent; - - this.generateClones(); - - this.instance.on('refresh', () => { - this.destroy(); - this.generateClones(); - }); - - this.instance.on('moved', () => { - this.generateClones(); - }); - } - - /** - * Destroy. - */ - destroy() { - remove(this.clones.map(clone => clone.slide)); - - this._clonesBefore = []; - this._clonesAfter = []; - } - - /** - * Return all clones. - * - * @return Cloned elements. - */ - get clones(): SlideComponent[] { - return [...this._clonesBefore, ...this._clonesAfter]; - } - - /** - * Return clone length. - * - * @return A length of clones. - */ - get length(): number { - return this.clones.length; - } - - /** - * Return before clone length. - * - * @return A length of before clones. - */ - get lengthBefore(): number { - return this._clonesBefore.length; - } - - /** - * Return after clone length. - * - * @return A length of after clones. - */ - get lengthAfter(): number { - return this._clonesAfter.length; - } - - private generateClones() { - const length = this.virtual.length; - - if (!length) { - return; - } - - const count = this.getCloneCount(); - const slides = this.virtual.getSlides(); - const firstSlide = slides[0]; - - const currentIndex = this.instance.index; - const virtualSlidesLength = this.virtual.getSlides(false).length; - - // no before and after clones needed -> remove - if (currentIndex > 0 && currentIndex < virtualSlidesLength) { - this.cleanClones(); - - return; - } - - if (currentIndex > 0 && currentIndex >= virtualSlidesLength - 1 && this.lengthAfter === 0) { - this.virtual.slides.slice(0, count).forEach((slide, index) => { - const node = domify(`
${slide.html}
`) as HTMLElement; - const clone = this.cloneDeeply(node); - - append(this.track.list, clone); - - const slideClone = this.virtual.register(clone, index + length, index); - - this._clonesAfter.push(slideClone); - - this.instance.emit('cloned'); - }); - } - - if (this.lengthBefore === 0) { - this.virtual.slides.slice(-count).forEach((slide, index) => { - const node = domify(`
${slide.html}
`) as HTMLElement; - const clone = this.cloneDeeply(node); - - before(clone, firstSlide.slide); - - const slideClone = this.virtual.register(clone, index - count, index); - - this._clonesBefore.push(slideClone); - - this.instance.emit('cloned'); - }); - } - } - - /** - * Remove and clean unused clones from HTML. - */ - private cleanClones() { - console.log('cleaned clones'); - - this.clones.forEach(clone => { - clone.slide.parentNode.removeChild(clone.slide); - - this.virtual.unregister(clone.index); - }); - } - - private getCloneCount(): number { - return this.options.cloneCount; - } - - /** - * Clone deeply the given element. - * - * @param slide - An element being duplicated. - * - * @return A cloned node(element). - */ - private cloneDeeply(element: HTMLElement): HTMLElement { - const clone = element.cloneNode(true) as HTMLElement; - - addClass(clone, ELEMENT_CLASSES.clone); - - // ID should not be duplicated. - clone.removeAttribute('id'); - - return clone as HTMLElement; - } -} diff --git a/src/components/controller/controller.component.ts b/src/components/controller/controller.component.ts deleted file mode 100644 index 8508c56..0000000 --- a/src/components/controller/controller.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { between } from '../../utils/utils'; -import TrackComponent from '../track/track.component'; -import Virchual, { VirchualComponents, VirchualOptions } from './../../virchual'; -import { BaseComponent } from './../base-component'; - -export default class ControllerComponent implements BaseComponent { - /** - * True if the slide is LOOP mode. - */ - private isLoop: boolean = true; - - private instance: Virchual; - private track: TrackComponent; - - constructor(private options: VirchualOptions) {} - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.track = components.Track as TrackComponent; - - this.bind(); - } - - /** - * Go to next slide. - * - * @param silently - Go to the destination without event emission. - */ - next(silently: boolean = false) { - const nextIndex = this.instance.index + 1; - - this.track.go(nextIndex, this.rewind(nextIndex), silently); - } - - /** - * Go to previous slide. - * - * @param silently - Go to the destination without event emission. - */ - previous(silently: boolean = false) { - const nextIndex = this.instance.index - 1; - - this.track.go(nextIndex, this.rewind(nextIndex), silently); - } - - /** - * Rewind the given index if it's out of range. - * - * @param index - An index. - * - * @return A rewound index. - */ - rewind(index: number): number { - const edge = this.edgeIndex; - - if (index > edge) { - index = 0; - } else if (index < 0) { - index = edge; - } - - return index; - } - - /** - * Return the edge index. - * - * @return Edge index. - */ - get edgeIndex(): number { - const length = this.instance.length; - - if (!length) { - return 0; - } - - if (this.hasFocus() || this.options.isNavigation || this.isLoop) { - return length - 1; - } - - return length - 1; - } - - /** - * Listen some events. - */ - private bind() { - this.instance.on('move', newIndex => { - this.instance.index = newIndex; - }); - - this.instance.on('updated refresh', newOptions => { - this.options = newOptions || this.options; - - this.instance.index = between(this.instance.index, 0, this.edgeIndex); - }); - } - - /** - * Verify if the focus option is available or not. - * - * @return True if a slider has the focus option. - */ - private hasFocus(): boolean { - return this.options.focus !== false; - } -} diff --git a/src/components/drag/drag.component.ts b/src/components/drag/drag.component.ts deleted file mode 100644 index 4924c10..0000000 --- a/src/components/drag/drag.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -import ControllerComponent from '../controller/controller.component'; -import { BaseLayout } from '../layout/index'; -import TrackComponent from '../track/track.component'; -import Virchual, { VirchualComponents, VirchualOptions } from './../../virchual'; -import { BaseComponent } from './../base-component'; - -export default class DragComponent implements BaseComponent { - // Coordinate of the track on starting drag. - private startCoord: { x: number; y: number }; - - // Analyzed info on starting drag. - private startInfo; - - // Analyzed info being updated while dragging/swiping. - private currentInfo; - - // Determine whether slides are being dragged or not. - private isDragging = false; - - // Whether the slider direction is vertical or not. - private isVertical = false; - - // Axis for the direction. - private axis = this.isVertical ? 'y' : 'x'; - - private isDisabled = false; - - private track: TrackComponent; - private layout: BaseLayout; - private controller: ControllerComponent; - private instance: Virchual; - - constructor(private options: VirchualOptions) {} - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.track = components.Track as TrackComponent; - this.layout = components.Layout as BaseLayout; - this.controller = components.Controller as ControllerComponent; - - this.instance.on('touchstart mousedown', this.onStart.bind(this), this.track.list); - this.instance.on('touchmove mousemove', this.onMove.bind(this), this.track.list, { passive: false }); - this.instance.on('touchend touchcancel mouseleave mouseup dragend', this.onEnd.bind(this), this.track.list); - } - - /** - * Called when the track starts to be dragged. - */ - private onStart(event: MouseEvent & TouchEvent) { - if (!this.isDisabled && !this.isDragging) { - this.startCoord = this.track.toCoord(this.track.position); - this.startInfo = this.analyze(event, {}); - - this.currentInfo = this.startInfo; - } - } - - private onMove(event: MouseEvent & TouchEvent) { - if (!this.startInfo) { - return; - } - - this.currentInfo = this.analyze(event, this.startInfo); - - if (this.isDragging) { - event.cancelable && event.preventDefault(); - - const position = this.startCoord[this.axis] + this.currentInfo.offset[this.axis]; - - this.track.translate(position); - } else { - if (this.shouldMove(this.currentInfo)) { - this.instance.emit('drag', this.startInfo); - - this.isDragging = true; - } - } - } - - /** - * Determine whether to start moving the track or not by drag angle. - * - * @param info - An information object. - * - * @return True if the track should be moved or false if not. - */ - private shouldMove({ offset }) { - // if (Splide.State.is(IDLE)) { - if (true) { - let angle = (Math.atan(Math.abs(offset.y) / Math.abs(offset.x)) * 180) / Math.PI; - - if (this.isVertical) { - angle = 90 - angle; - } - - const dragAngleThreshold = 45; - - return angle < dragAngleThreshold; - } - - return false; - } - - /** - * Called when dragging ends. - */ - private onEnd() { - this.startInfo = null; - - if (this.isDragging) { - this.instance.emit('dragged', this.currentInfo); - console.log(this.currentInfo); - this.go(this.currentInfo); - - this.isDragging = false; - } - } - - /** - * Go to the slide determined by the analyzed data. - * - * @param info - An info object. - */ - private go(info) { - const velocity = info.velocity[this.axis]; - const absV = Math.abs(velocity); - - if (absV > 0) { - const options = this.options; - const sign = velocity < 0 ? -1 : 1; - - let destination = this.track.position; - - if (absV > options.flickVelocityThreshold && Math.abs(info.offset[this.axis]) < options.swipeDistanceThreshold) { - destination += sign * Math.min(absV * options.flickPower, this.layout.width * (options.flickMaxPages || 1)); - } - - let index = this.track.direction.toIndex(destination); - - // Do not allow the track to go to a previous position. - if (index === this.instance.index) { - index += sign * this.track.direction.sign; - } - console.log('index:', index); - - // next slide - if (index > this.instance.index) { - this.controller.next(options.isNavigation); - - // previous slide - } else { - this.controller.previous(options.isNavigation); - } - } - } - - /** - * Analyze the given event object and return important information for handling swipe behavior. - * - * @param event - Touch or Mouse event object. - * @param startInfo - Information analyzed on start for calculating difference from the current one. - * - * @return - An object containing analyzed information, such as offset, velocity, etc. - */ - private analyze( - event: MouseEvent & TouchEvent, - startInfo, - ): { - to: { x: number; y: number }; - offset: { x: number; y: number }; - velocity: { x: number; y: number }; - time: number; - } { - const { timeStamp, touches } = event; - const { clientX, clientY } = touches ? touches[0] : event; - const { x: fromX = clientX, y: fromY = clientY } = startInfo.to || {}; - - const startTime = startInfo.time || 0; - const offset = { x: clientX - fromX, y: clientY - fromY }; - const duration = timeStamp - startTime; - const velocity = { x: offset.x / duration, y: offset.y / duration }; - - return { - offset, - velocity, - to: { x: clientX, y: clientY }, - time: timeStamp, - }; - } -} diff --git a/src/components/layout/directions/horizontal-layout.ts b/src/components/layout/directions/horizontal-layout.ts deleted file mode 100644 index 7adb0e3..0000000 --- a/src/components/layout/directions/horizontal-layout.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { BaseLayout } from './../index'; -import { toPixel, unit } from '../../../utils/utils'; -import { applyStyle } from '../../../utils/dom'; - -export class HorizontalLayout extends BaseLayout { - private padding: { left: number; right: number }; - private _gap: number; - - initLayout() { - this._gap = toPixel(this.instance.root, this.options.gap); - - const padding = this.options.padding || { left: 0, right: 0 }; - const { left, right } = padding; - - this.padding = { - left: toPixel(this.instance.root, left), - right: toPixel(this.instance.root, right), - }; - - applyStyle(this.track.list, { - paddingLeft: unit(left), - paddingRight: unit(right), - }); - } - - /** - * Accumulate slide width including the gap to the designated index. - * - * @param index If undefined, width of all slides will be accumulated. - * - * @return Accumulated width. - */ - totalWidth(index: number): number { - return this.virtual - .getSlides(true) - .filter(slide => slide.index <= index) - .reduce((accumulator, slide) => { - return accumulator + this.slideWidth(slide.index) + this.gap; - }, 0); - } - - get width(): number { - return this.track.track.clientWidth - this.padding.left - this.padding.right; - } - - /** - * Return list width. - * - * @return Current list width. - */ - get listWidth() { - const total = this.virtual.total; - - return this.totalWidth(total); - } - - get listHeight(): number { - return 0; - } - - /** - * Return the slide height in px. - * - * @return The slide height. - */ - get slideHeight() { - const height = this.options.height || this.options.fixedHeight || this.width * this.options.heightRatio; - - return toPixel(this.instance.root, height); - } - - /** - * Return the slide width in px. - * - * @param index - Slide index. - * - * @return The slide width. - */ - slideWidth(index?: number): number { - const width: string | number = this.width + this.gap - this.gap; - - return toPixel(this.instance.root, width); - } - - get height(): number { - return 0; - } - - get margin(): string { - return 'margin' + (this.options.direction === 'rtl' ? 'Left' : 'Right'); - } - - get gap(): number { - return this._gap; - } -} diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts deleted file mode 100644 index 0ac050c..0000000 --- a/src/components/layout/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { applyStyle } from '../../utils/dom'; -import { throttle } from '../../utils/throttle'; -import { unit } from '../../utils/utils'; -import Virchual, { VirchualComponents, VirchualOptions } from '../../virchual'; -import { BaseComponent } from '../base-component'; -import TrackComponent from '../track/track.component'; -import VirtualComponent from '../virtual/virtual.component'; - -/** - * Interval time for throttle. - */ -const THROTTLE = 100; - -export abstract class BaseLayout implements BaseComponent { - protected instance: Virchual; - protected track: TrackComponent; - protected virtual: VirtualComponent; - - constructor(protected options: VirchualOptions) {} - - abstract initLayout(); - abstract get listWidth(): number; - abstract get listHeight(): number; - abstract get slideHeight(): number; - abstract slideWidth(index?: number): number; - abstract get height(): number; - abstract get margin(): string; - abstract get gap(): number; - abstract totalWidth(index: number): number; - abstract get width(): number; - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.virtual = components.Virtual as VirtualComponent; - this.track = components.Track as TrackComponent; - - this.bind(); - this.init(); - } - - private init() { - this.initLayout(); - - applyStyle(this.instance.root, { maxWidth: unit(this.options.width) }); - - this.virtual.each(slide => { - slide.slide.style[this.margin] = unit(this.gap); - }); - - this.onResize(); - } - - /** - * Listen the resize native event with throttle. - * Initialize when the component is mounted or options are updated. - */ - private bind() { - this.instance.on( - 'resize load', - throttle(() => { - this.instance.emit('resize'); - }, THROTTLE), - window, - ); - this.instance.on('resize', this.onResize.bind(this)); - this.instance.on('updated refresh cloned moved', this.init.bind(this)); - this.instance.on('add', this.onResizeSlide.bind(this)); - } - - /** - * Resize the list and slides including clones. - */ - private onResize() { - applyStyle(this.track.list, { width: unit(this.listWidth), height: unit(this.listHeight) }); - // applyStyle(this.track.list, { height: unit(this.height) }); - - const slideHeight = unit(this.slideHeight); - - this.virtual.each(slide => { - applyStyle(slide.container, { height: slideHeight }); - - applyStyle(slide.slide, { - width: unit(this.slideWidth(slide.index)), - height: slide.container ? null : slideHeight, - }); - }); - } - - /** - * Resize slide - */ - private onResizeSlide(index: number) { - const slide = this.virtual.getSlide(index, false); - - if (slide == null) { - return; - } - - applyStyle(this.track.list, { width: unit(this.listWidth), height: unit(this.listHeight) }); - - const slideHeight = unit(this.slideHeight); - - applyStyle(slide.slide, { - width: unit(this.slideWidth(slide.index)), - height: slide.container ? null : slideHeight, - }); - } -} diff --git a/src/components/pagination/index.ts b/src/components/pagination/index.ts deleted file mode 100644 index 83f32b8..0000000 --- a/src/components/pagination/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import PaginationComponent from './pagination.component'; - -export default PaginationComponent; diff --git a/src/components/pagination/pagination.component.ts b/src/components/pagination/pagination.component.ts deleted file mode 100644 index 4fb1159..0000000 --- a/src/components/pagination/pagination.component.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { STATUS_CLASSES } from '../../constants/classes'; -import { addClass, append, applyStyle, create, remove, removeClass } from '../../utils/dom'; -import { unit } from '../../utils/utils'; -import Virchual, { VirchualComponents, VirchualOptions } from '../../virchual'; -import ControllerComponent from '../controller/controller.component'; -import VirtualComponent from '../virtual/virtual.component'; -import { BaseComponent } from './../base-component'; - -/** - * The event name for updating some attributes of pagination nodes. - */ -const ATTRIBUTES_UPDATE_EVENT = 'move.page'; - -/** - * The event name for recreating pagination. - */ -const UPDATE_EVENT = 'updated.page refresh.page'; - -/** - * The component for handling pagination - */ -export default class TrackComponent implements BaseComponent { - /** - * Store all data for pagination. - * - list: A list element. - * - items: An array that contains objects(li, button, index, page). - */ - private _data: any = {}; - - private virtual: VirtualComponent; - private controller: ControllerComponent; - private instance: Virchual; - private currentPosition: number; - private components: VirchualComponents; - - constructor(private options: VirchualOptions) {} - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.components = components; - this.virtual = components.Virtual as VirtualComponent; - this.controller = components.Controller as ControllerComponent; - this.currentPosition = 0; - - this._data = this.createPagination(); - - const parent = this.options.pagination ? this.instance.root : undefined; - - append(parent, this._data.list); - - this.bind(); - } - - /** - * Called after all components are mounted. - */ - mounted() { - const index = this.instance.index; - - this.instance.emit(`${name}:mounted`, this._data, this.getItem(index)); - - this.update(index, -1); - } - - /** - * Destroy the pagination. - * Be aware that node.remove() is not supported by IE. - */ - destroy() { - remove(this._data.list); - - if (this._data.items) { - this._data.items.forEach(item => { - this.instance.off('click', item.button); - }); - } - - this.instance.off(ATTRIBUTES_UPDATE_EVENT); - this.instance.off(UPDATE_EVENT); - - this._data = {}; - } - - /** - * Return an item by index. - * - * @param index - A slide index. - * - * @return An item object on success or undefined on failure. - */ - getItem(index: number) { - return this._data.items[this.controller.toPage(index)]; - } - - /** - * Return object containing pagination data. - * - * @return Pagination data including list and items. - */ - get data(): any { - return this._data; - } - - /** - * Listen some events. - */ - private bind() { - this.instance.on(ATTRIBUTES_UPDATE_EVENT, this.update.bind(this)); - this.instance.on(UPDATE_EVENT, () => { - this.destroy(); - - if (this.options.pagination) { - this.mount(this.instance, this.components); - this.mounted(); - } - }); - } - - /** - * Update attributes. - * - * @param index - Active index. - * @param prevIndex - Prev index. - */ - private update(index: number, prevIndex: number) { - const prev = this.getItem(prevIndex); - const curr = this.getItem(index); - const next = this.getItem(index + 1); - const trackElement = this.data.track as HTMLElement; - - const active = STATUS_CLASSES.active; - - if (index > 4) { - this.currentPosition = (index - 4) * 16 * -1; - - // insert bullet if there are more slides to come - if (index < this.virtual.slides.length - 1) { - const button = this.createBullet(index, 'active-next'); - - append(trackElement, button); - - this._data.items.push({ button }); - - // remove bullet from opposite end - const firstButton = this.getItem(Math.abs(4 - index - 1)); - (firstButton.button as HTMLElement).parentNode.removeChild(firstButton.button); - // this._data.items.shift(); - } - } - - if (prev) { - removeClass(prev.button, 'active-next'); - removeClass(prev.button, active); - addClass(prev.button, 'active-prev'); - } - - if (curr) { - removeClass(curr.button, 'active-prev'); - removeClass(curr.button, 'active-next'); - addClass(curr.button, active); - } - - if (next) { - removeClass(next.button, 'active-prev'); - removeClass(next.button, active); - addClass(next.button, 'active-next'); - } - - if (index > 4) { - applyStyle(trackElement, { transform: `translateX(${this.currentPosition}px)` }); - this.offsetPositionSlides(); - } - - this.instance.emit(`${name}:updated`, this.data, prev, curr); - } - - /** - * Create a wrapper and button elements. - * - * @return An object contains all data. - */ - private createPagination() { - const classes = this.options.classes; - const wrapper = create('div', { class: classes.pagination }); - const track = create('div', { class: classes.paginationTrack }); - - const count = Math.min(this.virtual.slides.length, 6); - - append(wrapper, track); - - const data = { - track, - list: wrapper, - items: [], - }; - - for (let i = 0; i < count; i++) { - const button = this.createBullet(i); - - append(track, button); - - data.items.push({ button }); - } - - return data; - } - - private createBullet(index: number, cssClass: string = '') { - const button = create('button', { class: `${this.options.classes.page} ${cssClass}`, type: 'button' }); - - this.instance.on( - 'click', - () => { - this.controller.go(`>${index}`); - }, - button, - ); - - return button; - } - - /** - * Position slides with offset to counteract removed pagination bullets - */ - private offsetPositionSlides() { - const offsetProp: string = 'left'; - const offset = 16 * Math.max(this.instance.index - 4, 0); - - const styles = {}; - styles[offsetProp] = unit(offset); - - this.data.items.forEach(bullet => { - applyStyle(bullet.button, styles); - }); - } -} diff --git a/src/components/track/directions/horizontal.ts b/src/components/track/directions/horizontal.ts deleted file mode 100644 index 7384282..0000000 --- a/src/components/track/directions/horizontal.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { between } from '../../../utils/utils'; -import Virchual, { VirchualComponents, VirchualOptions } from '../../../virchual'; -import VirtualComponent from '../../virtual/virtual.component'; -import { BaseLayout } from '../../layout/index'; - -export class HorizontalDirection { - /** - * Axis of translate. - */ - public axis: 'X' | 'Y' = 'X'; - - /** - * Sign for the direction. - */ - public sign: number = -1; - - private virtual: VirtualComponent; - private layout: BaseLayout; - - constructor(private options: VirchualOptions, private instance: Virchual, private components: VirchualComponents) { - this.virtual = components.Virtual as VirtualComponent; - this.layout = components.Layout as BaseLayout; - } - - /** - * Calculate position by index. - * - * @param index - Slide index. - * - * @return Calculated position. - */ - toPosition(index: number): number { - return this.sign * (this.layout.totalWidth(index - 1) + this.offset(index)); - } - - /** - * Calculate the closest slide index from the given position. - * - * @return The closest slide position. - */ - toIndex(position: number): number { - position *= this.sign; - - // if (this.instance.is(SLIDE)) { - if (true) { - position = between(position, this.layout.totalWidth(this.virtual.total), 0); - } - - const slides = this.virtual.getSlides(true); - console.log('toIndex', position); - for (const slide of slides) { - const slideIndex = slide.index; - const slidePosition = this.sign * this.toPosition(slideIndex); - - if (slidePosition < position && position <= slidePosition + this.layout.slideWidth(slideIndex) + this.layout.gap && !slide.isClone) { - return slideIndex; - } - } - - return 0; - } - - /** - * Trim redundant spaces on the left or right edge if necessary. - * - * @param position - Position value to be trimmed. - * - * @return Trimmed position. - */ - trim(position: number): number { - const edge = this.sign * (this.layout.totalWidth(this.virtual.total) - (this.layout.width + this.layout.gap)); - - return between(position, edge, 0); - } - - /** - * Return current offset value, considering direction. - * - * @return Offset amount. - */ - offset(index: number): number { - const { focus } = this.options; - const slideWidth = this.layout.slideWidth(index); - - if (focus === 'center') { - return -(this.layout.width - slideWidth) / 2; - } - - return -(parseInt(`${focus}`) || 0) * (slideWidth + this.layout.gap); - } -} diff --git a/src/components/track/index.ts b/src/components/track/index.ts deleted file mode 100644 index 4a61353..0000000 --- a/src/components/track/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import TrackComponent from './track.component'; - -export default TrackComponent; diff --git a/src/components/track/track.component.ts b/src/components/track/track.component.ts deleted file mode 100644 index c7da860..0000000 --- a/src/components/track/track.component.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { applyStyle } from '../../utils/dom'; -import ControllerComponent from '../controller/controller.component'; -import { SlideTransition } from './../../transitions/slide/index'; -import Virchual, { VirchualComponents, VirchualOptions } from './../../virchual'; -import { BaseComponent } from './../base-component'; -import { HorizontalDirection } from './directions/horizontal'; - -export default class TrackComponent implements BaseComponent { - // Store the current position. - private currPosition: number = 0; - - // Whether the current direction is vertical or not. - private isVertical: boolean = false; - - private instance: Virchual; - private controller: ControllerComponent; - private _direction: HorizontalDirection; - private _list: HTMLElement; - private _track: HTMLElement; - private transition: SlideTransition; - - constructor(private options: VirchualOptions) {} - - get direction() { - return this._direction; - } - - get list() { - return this._list; - } - - get track() { - return this._track; - } - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.controller = components.Controller as ControllerComponent; - this.transition = components.Transition as SlideTransition; - this._direction = new HorizontalDirection(this.options, instance, components); - - this._track = this.instance.root.querySelector('.virchual__track'); - this._list = this.instance.root.querySelector('.virchual__list'); - } - - /** - * Called after the component is mounted. - * The resize event must be registered after the Layout's one is done. - */ - mounted() { - this.instance.on('mounted resize updated', () => { - this.jump(this.instance.index); - }); - } - - /** - * Go to the given destination index. - * After arriving there, the track is jump to the new index without animation, mainly for loop mode. - * - * @param destIndex - A destination index. - * This can be negative or greater than slides length for reaching clones. - * @param newIndex - An actual new index. They are always same in Slide and Rewind mode. - * @param silently - If true, suppress emitting events. - */ - go(destIndex: number, newIndex: number, silently: boolean = false) { - const prevIndex = this.instance.index; - - if (!silently) { - this.instance.emit('move', newIndex, prevIndex, destIndex); - } - - const newPosition = this.getTrimmedPosition(destIndex); - - if (Math.abs(newPosition - this.currPosition) >= 1) { - console.log('transition', newPosition, destIndex); - this.transition.start(100, () => { - this.end(destIndex, newIndex, prevIndex, silently); - }); - } - - // else { - // if (destIndex !== prevIndex && this.instance.options.trimSpace === 'move') { - // this.controller.go(destIndex + destIndex - prevIndex, silently); - // } else { - // this.end(destIndex, newIndex, prevIndex, silently); - // } - // } - } - - /** - * Called whenever slides arrive at a destination. - * - * @param destIndex - A destination index. - * @param newIndex - A new index. - * @param prevIndex - A previous index. - * @param silently - If true, suppress emitting events. - */ - end(destIndex: number, newIndex: number, prevIndex: number, silently?: boolean) { - applyStyle(this.list, { transition: '' }); - - this.jump(newIndex); - - if (!silently) { - this.instance.emit('moved', newIndex, prevIndex, destIndex); - } - } - - /** - * Move the track to the specified index. - * - * @param index - A destination index where the track jumps. - */ - jump(index: number) { - this.translate(this.getTrimmedPosition(index)); - } - - /** - * Set position. - * - * @param position - A new position value. - */ - translate(position: number) { - this.currPosition = position; - - applyStyle(this.list, { transform: `translate${this._direction.axis}(${position}px)` }); - } - - /** - * Trim redundant spaces on the left or right edge if necessary. - * - * @param position - Position value to be trimmed. - * - * @return Trimmed position. - */ - trim(position: number): number { - // if ( ! Splide.options.trimSpace || Splide.is( LOOP ) ) { - if (true) { - return position; - } - - // return this._s.trim( position ); - } - - /** - * Return coordinates object by the given position. - * - * @param position - A position value. - * - * @return - A coordinates object. - */ - toCoord(position: number) { - return { - x: this.isVertical ? 0 : position, - y: this.isVertical ? position : 0, - }; - } - - /** - * Return current position. - * - * @return Current position. - */ - get position(): number { - return this.currPosition; - } - - /** - * Convert index to the trimmed position. - * - * @return Trimmed position. - */ - getTrimmedPosition(index: number): number { - return this.trim(this._direction.toPosition(index)); - } -} diff --git a/src/components/virtual/index.ts b/src/components/virtual/index.ts deleted file mode 100644 index e7e1eb4..0000000 --- a/src/components/virtual/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './slide.component'; -export * from './virtual.component'; diff --git a/src/components/virtual/slide.component.ts b/src/components/virtual/slide.component.ts deleted file mode 100644 index a84cb13..0000000 --- a/src/components/virtual/slide.component.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { addClass, find, removeClass } from '../../utils/dom'; -import { values } from '../../utils/object'; -import { pad } from '../../utils/utils'; -import Virchual, { VirchualComponents, VirchualOptions } from '../../virchual'; -import TrackComponent from '../track/track.component'; -import { STATUS_CLASSES } from './../../constants/classes'; -import { BaseComponent } from './../base-component'; - -/** - * Events for restoring original styles. - */ -const STYLE_RESTORE_EVENTS = 'update.slide'; - -export class SlideComponent implements BaseComponent { - /** - * Container element if available. - */ - container: HTMLElement; - - /** - * Whether this is a cloned slide or not. - */ - isClone: boolean; - - /** - * Hold the original styles. - */ - private styles: string; - - private instance: Virchual; - private track: TrackComponent; - - /** - * Events when the slide status is updated. - * Append a namespace to remove listeners later. - */ - private statusUpdateEvents: string; - - constructor( - private options: VirchualOptions, - public index: number, - public realIndex: number, - public slide: HTMLElement, - public key?: string, - ) { - this.container = find(this.slide, `.virchual__list`); - this.isClone = realIndex > -1; - this.styles = this.slide.getAttribute('style') || ''; - this.statusUpdateEvents = 'ready.slide updated.slide resize.slide ' + (this.options.updateOnMove ? 'move.slide' : 'moved.slide'); - } - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.track = components.Track as TrackComponent; - - if (!this.isClone) { - this.slide.id = `${this.instance.root.id}-slide${pad(this.index + 1)}`; - } - - if (this.key != null) { - this.slide.dataset.key = this.key; - } - - this.instance.on(this.statusUpdateEvents, () => this.update()); - this.instance.on(STYLE_RESTORE_EVENTS, this.restoreStyles); - } - - /** - * Destroy. - */ - destroy() { - this.instance.off(this.statusUpdateEvents); - this.instance.off(STYLE_RESTORE_EVENTS); - - removeClass(this.slide, values(STATUS_CLASSES)); - - this.restoreStyles(); - } - - /** - * Update active and visible status. - */ - update() { - this.updateClasses(this.isActive(), false); - this.updateClasses(this.isVisible(), true); - } - - /** - * Check whether this slide is active or not. - * - * @return True if the slide is active or false if not. - */ - isActive(): boolean { - return this.instance.index === this.index; - } - - /** - * Check whether this slide is visible in the viewport or not. - * - * @return True if the slide is visible or false if not. - */ - isVisible() { - const active = this.isActive(); - - if (active) { - return active; - } - - const { floor } = Math; - const prop = 'clientWidth'; - const position = floor( - (this.track.direction.toPosition(this.index) + this.track.direction.offset(this.index) - this.track.position) * - this.track.direction.sign, - ); - const edge = floor(position + this.slide[prop]); - const size = this.track.list[prop]; - - return 0 <= position && position <= size && 0 <= edge && edge <= size; - } - - /** - * Calculate how far this slide is from another slide and - * return true if the distance is within the given number. - * - * @param from - Index of a target slide. - * @param within - True if the slide is within this number. - * - * @return True if the slide is within the number or false otherwise. - */ - isWithin(from: number, within: number): boolean { - let diff = Math.abs(from - this.index); - - if (!this.isClone) { - diff = Math.min(diff, this.instance.length - diff); - } - - return diff < within; - } - - /** - * Update classes for activity or visibility. - * - * @param active - Is active/visible or not. - * @param forVisibility - Toggle classes for activity or visibility. - */ - private updateClasses(active, forVisibility) { - const type = forVisibility ? 'visible' : 'active'; - const className = STATUS_CLASSES[type]; - - if (active) { - addClass(this.slide, className); - - this.instance.emit(`${type}`, this.slide); - } else { - if (this.slide.classList.contains(className)) { - removeClass(this.slide, className); - - this.instance.emit(`${forVisibility ? 'hidden' : 'inactive'}`, this.slide); - } - } - } - - /** - * Restore the original styles. - */ - private restoreStyles() { - this.slide.setAttribute('style', this.styles); - } -} diff --git a/src/components/virtual/virtual.component.ts b/src/components/virtual/virtual.component.ts deleted file mode 100644 index 768c1fe..0000000 --- a/src/components/virtual/virtual.component.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { SlideComponent } from './slide.component'; -import Virchual, { VirchualOptions, VirchualComponents, VirchualSlide } from './../../virchual'; -import { BaseComponent } from './../base-component'; -import { domify, append, applyStyle, before } from '../../utils/dom'; -import TrackComponent from '../track/track.component'; -import { pad, unit } from '../../utils/utils'; -import { Event } from './../../core/event'; -import { exist } from '../../utils/error'; -import { values } from '../../utils/object'; -import { BaseLayout } from '../layout/index'; - -type VirtualSlide = { - index: number; - key?: string; - html: string; -}; - -/** - * The property name for UID stored in a window object. - */ -const UID_NAME: string = 'uid'; - -export default class VirtualComponent implements BaseComponent { - private hydratedSlides: HTMLElement[]; - private _slides: VirtualSlide[]; - private virtualSlides: SlideComponent[]; - private track: TrackComponent; - private layout: BaseLayout; - private instance: Virchual; - private previousFrom: number; - private previousTo: number; - - constructor(private options: VirchualOptions) { - this.previousFrom = 0; - this.previousTo = 0; - } - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.track = components.Track as TrackComponent; - this.layout = components.Layout as BaseLayout; - - /* - * Assign unique ID to the root element if it doesn't have the one. - * Note that IE doesn't support padStart() to fill the uid by 0. - */ - if (!this.instance.root.id) { - window['virchual'] = window['virchual'] || {}; - - const uid = window['virchual'][UID_NAME] || 0; - - window['virchual'][UID_NAME] = uid + 1; - - this.instance.root.id = `virchual-${pad(uid)}`; - } - - this.collect(); - this.init(); - this.bind(); - } - - get total() { - return this.virtualSlides.length; - } - - /** - * Return slides length without clones. - * - * @return Slide length. - */ - get length() { - return this.getSlides().length; - } - - /** - * Get all slides. - */ - get slides() { - return this._slides || []; - } - - getSlide(index: number, includeClones: boolean = true) { - return this.virtualSlides - .filter(slide => slide.index === index) - .filter(slide => includeClones === true || (includeClones === false && !slide.isClone))[0]; - } - - /** - * Return all Slide objects. - * - * @param includeClones - Whether to include cloned slides or not. - * @return Slide objects. - */ - getSlides(includeClones: boolean = false): SlideComponent[] { - return includeClones ? this.virtualSlides : this.virtualSlides.filter(slide => !slide.isClone); - } - - each(callback: (slide: SlideComponent) => void) { - this.virtualSlides.forEach(callback); - } - - /** - * Register a slide to create a Slide object and handle its behavior. - * - * @param slide - A slide element. - * @param index - A unique index. This can be negative. - * @param realIndex - A real index for clones. Set -1 for real slides. - */ - register(slide: HTMLElement, index: number, realIndex: number, key?: string) { - const slideInstance = new SlideComponent(this.options, index, realIndex, slide, key); - - slideInstance.mount(this.instance, { Track: this.track }); - - this.virtualSlides.push(slideInstance); - - return slideInstance; - } - - /** - * Unregister a slide. - * - * @param index - Unique slide index. - */ - unregister(index: number) { - this.virtualSlides = this.virtualSlides.filter(vSlide => vSlide.index !== index); - } - - private init() { - let slides: VirchualSlide[] = []; - this.virtualSlides = []; - - if (typeof this.options.slides === 'function') { - slides = this.options.slides(); - } else { - slides = this.options.slides; - } - - slides = slides || []; - - this._slides = slides.map((slide, index) => { - if (typeof slide === 'string') { - return { - index, - html: slide, - }; - } - - return { - index, - key: slide.key, - html: slide.html, - }; - }); - - const firstSlide = this._slides[0]; - const firstSlideHydrated = this.hydratedSlides.find(hydratedSlide => hydratedSlide.dataset.key === firstSlide.key); - - this._slides.slice(-1).forEach((slide, index) => { - let element = domify(`
${slide.html}
`); - console.log('asdfasdf', slide.index, firstSlideHydrated, slide.html); - before(element, firstSlideHydrated); - - this.register(element, -1, -1, slide.key); - }); - - this._slides.slice(0, 2).forEach((slide, index) => { - const hydratedSlide = this.hydratedSlides.find(hydratedSlide => hydratedSlide.dataset.key === slide.key); - let element: HTMLElement; - - // use already existig and hydrated DOM node - if (hydratedSlide) { - element = hydratedSlide; - - // create new DOM node - } else { - element = domify(`
${slide.html}
`); - - append(this.track.list, element); - } - - this.register(element, index, -1, slide.key); - }); - } - - /** - * Collect elements. - */ - private collect() { - exist(this.track.track && this.track.list, 'Track or list was not found.'); - - this.hydratedSlides = values(this.track.list.children); - } - - private bind() { - this.instance.on('move', (newIndex, _prevIndex) => { - console.log('virtual movee', newIndex, _prevIndex); - newIndex = newIndex > 0 ? newIndex + 1 : newIndex; - - const slide = this._slides[newIndex]; - - if (slide == null) { - return; - } - - const virtualSlide = this.getSlide(slide.index); - console.log('virtual slide move', slide.index, virtualSlide); - // slide already injected in DOM - if (virtualSlide) { - return; - } - - const node = domify(`
${slide.html}
`); - - append(this.track.list, node); - - this.register(node, newIndex, -1, slide.key); - - this.instance.emit('add', newIndex); - }); - - this.instance.on('moved', () => { - const slidesBefore = 2; - const slidesAfter = 2; - - const from = Math.max((this.instance.index || 0) - slidesBefore, 0); - const to = Math.min((this.instance.index || 0) + slidesAfter, this.length - 1); - - for (let i = this.previousFrom; i <= this.previousTo; i += 1) { - if (i < from || i > to) { - const slide = this.getSlide(i, false); - - slide.slide.parentNode.removeChild(slide.slide); - } - } - - this.previousFrom = from; - this.previousTo = to; - - this.offsetPositionSlides(); - }); - } - - /** - * Position slides with offset to counteract removed slides - */ - private offsetPositionSlides() { - const offsetProp: string = 'left'; - const offset = this.layout.slideWidth() * this.previousFrom; - - const styles = {}; - styles[offsetProp] = unit(offset); - - this.each(slide => { - applyStyle(slide.slide, styles); - }); - } -} diff --git a/src/constants/directions.ts b/src/constants/directions.ts deleted file mode 100644 index 03ee3fe..0000000 --- a/src/constants/directions.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Enumerate slides from left to right. - */ -export const LTR = 'ltr'; - -/** - * Enumerate slides from right to left. - */ -export const RTL = 'rtl'; diff --git a/src/constants/states.ts b/src/constants/states.ts deleted file mode 100644 index d2f3c51..0000000 --- a/src/constants/states.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * virchual has been just created. - */ -export const CREATED = 1; - -/** - * All components have been mounted and initialized. - */ -export const MOUNTED = 2; - -/** - * virchual is ready for transition. - */ -export const IDLE = 3; - -/** - * virchual is moving. - */ -export const MOVING = 4; - -/** - * virchual is moving. - */ -export const DESTROYED = 5; diff --git a/src/constants/classes.ts b/src/contants.ts similarity index 100% rename from src/constants/classes.ts rename to src/contants.ts diff --git a/src/core/index.ts b/src/core/index.ts deleted file mode 100644 index 0ef9384..0000000 --- a/src/core/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './event'; diff --git a/src/components2/drag.ts b/src/drag.ts similarity index 88% rename from src/components2/drag.ts rename to src/drag.ts index c81fbcf..ec03a45 100644 --- a/src/components2/drag.ts +++ b/src/drag.ts @@ -1,6 +1,6 @@ -import { Event } from './../core/event'; -import { VirchualOptions } from './../virchual2'; -import { debounce } from '../utils/debouncer'; +import { debounce } from './utils/debouncer'; +import { Event } from './utils/event'; +import { VirchualOptions } from './virchual'; export class Drag { // Coordinate of the track on starting drag. @@ -40,6 +40,18 @@ export class Drag { this.event.on('touchstart mousedown', this.eventBindings.onStart, this.frame); this.event.on('touchmove mousemove', debounce(this.eventBindings.onMove, 1), this.frame, { passive: false }); this.event.on('touchend touchcancel mouseleave mouseup dragend', this.eventBindings.onEnd, this.frame); + + // Prevent dragging an image or anchor itself. + [].forEach.call(this.frame.querySelectorAll('img, a'), (element: HTMLElement, index: number) => { + this.event.on( + 'dragstart touchstart', + e => { + e.preventDefault(); + }, + element, + { passive: false }, + ); + }); } /** @@ -51,6 +63,8 @@ export class Drag { this.startInfo = this.analyze(event, {}); this.currentInfo = this.startInfo; + + this.event.emit('dragstart', this.currentInfo); } } @@ -126,7 +140,7 @@ export class Drag { console.log('index:', destinationIndex, info); - this.event.emit('dragged', this.currentInfo); + this.event.emit('dragend', this.currentInfo); } } diff --git a/src/components2/slide.ts b/src/slide.ts similarity index 75% rename from src/components2/slide.ts rename to src/slide.ts index ef4a6b7..d671c97 100644 --- a/src/components2/slide.ts +++ b/src/slide.ts @@ -1,4 +1,5 @@ -import { prepend as prependFn, domify, append, remove } from '../utils/dom'; +import { VirchualOptions } from './virchual'; +import { prepend as prependFn, domify, append, remove } from './utils/dom'; /** * Virtual slide component. @@ -9,10 +10,11 @@ export class Slide { private html: string; private ref: HTMLElement; + private transitionEndCallback: Function; private _isActive: boolean = false; private _position: number; - constructor(public content: string, private frame: HTMLElement) {} + constructor(public content: string, private frame: HTMLElement, private options: VirchualOptions) {} get isActive() { return this._isActive; @@ -76,6 +78,12 @@ export class Slide { this.ref = domify(this.html) as HTMLElement; + this.ref.addEventListener('transitionend', e => { + if (e.target === this.ref && this.transitionEndCallback) { + this.transitionEndCallback(); + } + }); + if (prepend) { prependFn(this.frame, this.ref); @@ -95,10 +103,18 @@ export class Slide { remove(this.ref); } - translate(value: number) { + /** + * Start transition. + * + * @param value + * @param done + */ + translate(value: number, done?: Function) { value = Math.round(value); - this.ref.style.transitionDuration = 'unset'; + this.transitionEndCallback = done; + + this.ref.style.transition = `transform ${this.options.speed}ms ${this.options.easing}`; this.ref.style.transform = `translate3d(calc(${this.position}% + ${value}px), 0, 0)`; } } diff --git a/src/transitions/index.ts b/src/transitions/index.ts deleted file mode 100644 index 75b8442..0000000 --- a/src/transitions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SlideTransition } from './slide'; diff --git a/src/transitions/slide/index.ts b/src/transitions/slide/index.ts deleted file mode 100644 index b129ab6..0000000 --- a/src/transitions/slide/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import ControllerComponent from '../../components/controller/controller.component'; -import TrackComponent from '../../components/track/track.component'; -import { applyStyle } from '../../utils/dom'; -import Virchual, { VirchualComponents, VirchualOptions } from '../../virchual'; -import { BaseComponent } from './../../components/base-component'; -import VirtualComponent from '../../components/virtual/virtual.component'; - -export class SlideTransition implements BaseComponent { - /** - * Hold the list element. - */ - private list: HTMLElement; - - /** - * Hold the onEnd callback function. - */ - private endCallback: Function; - - private track: TrackComponent; - private instance: Virchual; - private virtual: VirtualComponent; - - constructor(private options: VirchualOptions) {} - - mount(instance: Virchual, components: VirchualComponents) { - this.instance = instance; - this.track = components.Track as TrackComponent; - this.virtual = components.Virtual as VirtualComponent; - - this.list = this.track.list; - - this.instance.on( - 'transitionend', - e => { - if (e.target === this.list && this.endCallback) { - this.endCallback(); - } - }, - this.list, - ); - } - - /** - * Start transition. - * - * @param value - * @param done - Callback function must be invoked when transition is completed. - */ - start(value: number, done: Function) { - const options = this.options; - let speed = options.speed; - - this.endCallback = done; - - this.virtual.getSlides().forEach(slide => { - applyStyle(slide.slide, { - transition: `transform ${speed}ms ${options.easing}`, - transform: `translate3d(${value}%,0px,0px)`, - }); - }); - } -} diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 1d2338e..fa837fa 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -1,19 +1,6 @@ import { each } from './object'; import { toArray } from './utils'; -/** - * Find the first element matching the given selector. - * Be aware that all selectors after a space are ignored. - * - * @param elm - An ancestor element. - * @param selector - DOMString. - * - * @return A found element or null. - */ -export function find(elm: HTMLElement | ParentNode, selector: string): HTMLElement { - return elm ? elm.querySelector(selector.split(' ')[0]) : null; -} - /** * Create an element with some optional attributes. * @@ -68,18 +55,6 @@ export function append(parent: HTMLElement, child: HTMLElement) { } } -/** - * Insert an element before the reference element. - * - * @param element- An element to be inserted. - * @param ref - A reference element. - */ -export function before(element: HTMLElement, ref: HTMLElement) { - if (element && ref && ref.parentElement) { - ref.parentElement.insertBefore(element, ref); - } -} - /** * Prepend an element to parent. * @@ -91,57 +66,3 @@ export function prepend(parent: HTMLElement, element: HTMLElement) { parent.insertBefore(element, parent.firstChild); } } - -/** - * Apply styles to the given element. - * - * @param elm - An element where styles are applied. - * @param styles - Object containing styles. - */ -export function applyStyle(elm: HTMLElement, styles: any) { - if (elm) { - each(styles, (value, prop) => { - if (value !== null) { - elm.style[prop] = value; - } - }); - } -} - -/** - * Add or remove classes to/from the element. - * This function is for internal usage. - * - * @param elm - An element where classes are added. - * @param classes - Class names being added. - * @param remove - Whether to remove or add classes. - */ -function addOrRemoveClasses(elm: HTMLElement, classes: string | string[], remove: boolean) { - if (elm) { - toArray(classes).forEach(name => { - if (name) { - elm.classList[remove ? 'remove' : 'add'](name); - } - }); - } -} - -/** - * Add classes to the element. - * - * @param elm - An element where classes are added. - * @param classes - Class names being added. - */ -export function addClass(elm: HTMLElement, classes: string | string[]) { - addOrRemoveClasses(elm, classes, false); -} - -/** - * Remove a class from the element. - * - * @param elm - An element where classes are removed. - * @param classes - A class name being removed. - */ -export function removeClass(elm: HTMLElement, classes: string | string[]) { - addOrRemoveClasses(elm, classes, true); -} diff --git a/src/utils/error.ts b/src/utils/error.ts index 7c94d29..8982438 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,17 +1,3 @@ -/** - * Prefix of an error massage. - */ -const MESSAGE_PREFIX: string = '[VIRCHUAL]'; - -/** - * Display an error message on the browser console. - * - * @param message - An error message. - */ -export function error(message: string) { - console.error(`${MESSAGE_PREFIX} ${message}`); -} - /** * Check existence of the given object and throw an error if it doesn't. * diff --git a/src/core/event.ts b/src/utils/event.ts similarity index 98% rename from src/core/event.ts rename to src/utils/event.ts index 616bf9a..932de7b 100644 --- a/src/core/event.ts +++ b/src/utils/event.ts @@ -15,7 +15,7 @@ export class Event { */ on( events: string, - handler: () => void, + handler: (event: any) => void, element?: (Window & typeof globalThis) | Element, options: boolean | AddEventListenerOptions = {}, ) { diff --git a/src/utils/object.ts b/src/utils/object.ts index 497446d..0d231c3 100644 --- a/src/utils/object.ts +++ b/src/utils/object.ts @@ -10,50 +10,3 @@ export function each(obj: object, callback: Function) { return callback(obj[key], key, index); }); } - -/** - * Return values of the given object as an array. - * IE doesn't support Object.values. - * - * @param obj - An object. - * - * @return An array containing all values of the given object. - */ -export function values(obj: object): Array { - return Object.keys(obj).map(key => obj[key]); -} - -/** - * Check if the given subject is object or not. - * - * @param subject - A subject to be verified. - * - * @return True if object, false otherwise. - */ -export function isObject(subject: any): boolean { - return typeof subject === 'object'; -} - -/** - * Merge two objects deeply. - * - * @param to - An object where "from" is merged. - * @param from - An object merged to "to". - * - * @return A merged object. - */ -export function merge({ ...to }: object, from: object): object { - each(from, (value, key) => { - if (isObject(value)) { - if (!isObject(to[key])) { - to[key] = {}; - } - - to[key] = merge(to[key], value); - } else { - to[key] = value; - } - }); - - return to; -} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e053ae3..4e0e112 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,3 @@ -import { create, append, remove, applyStyle } from './dom'; - /** * Convert the given value to array. * @@ -11,80 +9,6 @@ export function toArray(value: any): any[] { return Array.isArray(value) ? value : [value]; } -/** - * Check if the given value is between min and max. - * Min will be returned when the value is less than min or max will do when greater than max. - * - * @param value - A number to be checked. - * @param m1 - Minimum or maximum number. - * @param m2 - Maximum or minimum number. - * - * @return A value itself, min or max. - */ -export function between(value: number, m1: number, m2: number): number { - return Math.min(Math.max(value, m1 > m2 ? m2 : m1), m1 > m2 ? m1 : m2); -} - -/** - * Append px unit to the given subject if necessary. - * - * @param value - A value that may not include an unit. - * - * @return If the value is string, return itself. - * If number, do value + "px". An empty string, otherwise. - */ -export function unit(value: number | string): string { - const type = typeof value; - - if (type === 'number' && value > 0) { - return parseFloat(`${value}`) + 'px'; - } - - return type === 'string' ? `${value}` : ''; -} - -/** - * Pad start with 0. - * - * @param number - A number to be filled with 0. - * - * @return Padded number. - */ -export function pad(number: number): string | number { - return number < 10 ? '0' + number : number; -} - -/** - * Convert the given value to pixel. - * - * @param root - Root element where a dummy div is appended. - * @param value - CSS value to be converted, such as 10rem. - * - * @return Pixel. - */ -export function toPixel(root: HTMLElement, value: string | number): number { - let pixelValue: number; - - if (typeof value === 'string') { - const div = create('div', {}); - - applyStyle(div, { - position: 'absolute', - width: value, - }); - - append(root, div); - - pixelValue = div.clientWidth; - - remove(div); - } else { - pixelValue = value; - } - - return pixelValue; -} - export function range(start: number, end: number): number[] { return Array(end - start + 1) .fill(0) diff --git a/src/virchual.ts b/src/virchual.ts index 0f737c9..61f9cc1 100644 --- a/src/virchual.ts +++ b/src/virchual.ts @@ -1,124 +1,84 @@ -import { BaseComponent } from './components/base-component'; -import ControllerComponent from './components/controller/controller.component'; -import DragComponent from './components/drag/drag.component'; -import { HorizontalLayout } from './components/layout/directions/horizontal-layout'; -import TrackComponent from './components/track/track.component'; -import VirtualComponent from './components/virtual/virtual.component'; -import { ELEMENT_CLASSES as classes } from './constants/classes'; -import { Event } from './core/event'; import './css/styles.css'; -import { SlideTransition } from './transitions/slide/index'; -import { applyStyle, find } from './utils/dom'; -import { error, exist } from './utils/error'; -import { each } from './utils/object'; +import { Drag } from './drag'; +import { Slide } from './slide'; +import { exist } from './utils/error'; +import { Event } from './utils/event'; +import { slidingWindow } from './utils/sliding-window'; +import { range, rewind } from './utils/utils'; export type VirchualOptions = { - type?: 'slide' | 'loop'; - slides?: VirchualSlides; - rewindSpeed?: number; + slides?: string[] | (() => string[]); speed?: number; - rewind?: boolean; - focus?: boolean | string | number; - isNavigation?: boolean; easing?: string; - gap?: number | string; - padding?: { left: number | string; right: number | string }; - width?: number | string; - trimSpace?: boolean | string; - direction?: string; - height?: number; - fixedHeight?: number | string; - heightRatio?: number; - updateOnMove?: boolean; swipeDistanceThreshold?: number; flickVelocityThreshold?: number; flickPower?: number; - flickMaxPages?: number; - classes?: any; - cloneCount?: number; pagination?: boolean; + window?: number; }; -export type VirchualSlide = string | { key: string; html: string }; -export type VirchualSlides = VirchualSlide[] | (() => VirchualSlide[]); +export class Virchual { + container: HTMLElement; + frame: HTMLElement; + frameWidth: number; + currentIndex: number = 0; + slides: Slide[] = []; -export type VirchualComponents = { [key: string]: BaseComponent }; - -export default class Virchual { - root: HTMLElement; - - private _index: number; private event: Event; + private isBusy: boolean = false; + + // bound event handlers (to keep `this` context) + private eventBindings: { + onClick: () => {}; + onDrag: () => {}; + onDragEnd: () => {}; + onKeyUp: () => {}; + }; - constructor(public selector: HTMLElement | string, public options: VirchualOptions = {}, private components: VirchualComponents = {}) { - this.root = selector instanceof Element ? selector : find(document, selector); + constructor(public selector: HTMLElement | string, public options: VirchualOptions = {}) { + this.container = selector instanceof Element ? selector : document.querySelector(selector); + this.frame = this.container.querySelector('.virchual__frame'); - exist(this.root, 'Invalid element/selector'); + exist(this.frame, 'Invalid element/selector'); - this._index = 0; + this.currentIndex = 0; this.options = { slides: [], - type: 'loop', - speed: 400, - rewind: false, - focus: false, - isNavigation: false, - trimSpace: false, - padding: undefined, - width: 0, - gap: 0, - direction: 'ltr', - height: 0, - fixedHeight: 0, - heightRatio: 0, - updateOnMove: false, + speed: 200, swipeDistanceThreshold: 150, flickVelocityThreshold: 0.6, flickPower: 600, - flickMaxPages: 1, - easing: 'cubic-bezier(.42,.65,.27,.99)', - cloneCount: 1, + easing: 'ease-out', pagination: true, - classes, + window: 1, ...options, }; - const defaultComponents: VirchualComponents = { - Controller: new ControllerComponent(this.options), - Track: new TrackComponent(this.options), - Virtual: new VirtualComponent(this.options), - Transition: new SlideTransition(this.options), - Drag: new DragComponent(this.options), - Layout: new HorizontalLayout(this.options), - // Clone: new CloneComponent(this.options), - // Pagination: new PaginationComponent(this.options), - }; + this.event = new Event(); - this.components = { - ...defaultComponents, - ...this.components, + this.eventBindings = { + onClick: this.onClick.bind(this), + onDrag: this.onDrag.bind(this), + onKeyUp: this.onKeyUp.bind(this), + onDragEnd: this.onDragEnd.bind(this), }; - this.event = new Event(); + let rawSlides; - this.mount(); - } + if (typeof this.options.slides === 'function') { + rawSlides = this.options.slides(); + } else { + rawSlides = this.options.slides; + } - /** - * Get current slide index. - */ - get index() { - return this._index; - } + this.slides = (rawSlides || []).map((slide, index) => new Slide(slide, this.frame, this.options)); - set index(index: number) { - this._index = parseInt(`${index}`, 10); - } + this.resize(); + this.mount(); - get length() { - const virtual = this.components.Virtual as VirtualComponent; + new Drag(this.frame, this.options, { event: this.event }).start(); - return virtual.length; + this.bindEvents(); } /** @@ -129,13 +89,9 @@ export default class Virchual { * @param handler - A callback function. * @param elm - Optional. Native event will be listened to when this arg is provided. * @param options - Optional. Options for addEventListener. - * - * @return - This instance. */ - on(events: string, handler: any, elm: (Window & typeof globalThis) | Element = null, options: object = {}): Virchual { + on(events: string, handler: any, elm: (Window & typeof globalThis) | Element = null, options: object = {}) { this.event.on(events, handler, elm, options); - - return this; } /** @@ -143,60 +99,148 @@ export default class Virchual { * * @param events - A event name. * @param elm - Optional. removeEventListener() will be called when this arg is provided. - * - * @return This instance. */ - off(events: string, elm: (Window & typeof globalThis) | Element = null): Virchual { + off(events: string, elm: (Window & typeof globalThis) | Element = null) { this.event.off(events, elm); + } + + previous() {} + + next() {} + + private mount() { + this.event.emit('mounted'); + + this.runSlidesLifecycle(); + } - return this; + private resize() { + this.frameWidth = this.frame.getBoundingClientRect().width; } /** - * Emit an event. - * - * @param event - An event name. - * @param args - Any number of arguments passed to handlers. + * Mount and unmount slides. */ - emit(event: string, ...args: any) { - this.event.emit(event, ...args); + private runSlidesLifecycle({ direction }: { direction?: 'prev' | 'next' } = {}) { + const currentSlide = this.slides[this.currentIndex]; + + const mountableSlideIndices = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window); + const mountableSlideIndicesWithOffset = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window + 1); + + mountableSlideIndicesWithOffset.forEach(index => { + const slide = this.slides[index]; + + if (index === this.currentIndex) { + currentSlide.isActive = true; + } else { + this.slides[this.currentIndex].isActive = false; + } + + let realIndex = mountableSlideIndices.indexOf(index); + + // unmount + if (realIndex < 0) { + return slide.unmount(); + } + + slide.position = (this.options.window - realIndex) * -100; - return this; + const prepend = direction === 'prev'; + + slide.mount(prepend); + }); } - private mount() { - try { - each(this.components, (component: BaseComponent, key: string) => { - component.mount(this, this.components); - }); - } catch (e) { - error(e.message); - - return; + private bindEvents() { + this.event.on('keyup', this.eventBindings.onKeyUp, window); + this.event.on('drag', this.eventBindings.onDrag); + this.event.on('dragend', this.eventBindings.onDragEnd); + + // Disable clicks on slides + this.frame.addEventListener('click', this.eventBindings.onClick, { capture: true }); + } + + private unbindEvents() { + window.removeEventListener('keyup', this.eventBindings.onKeyUp); + } + + /** + * Called when frame is clicked. + * + * @param event A click event. + */ + private onClick(event: MouseEvent) { + if (this.isBusy) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); } + } + + /** + * Handle drag event. + * + * @param event + */ + private onDrag(event: { offset: { x: number; y: number }; direction: 'prev' | 'next' }) { + this.isBusy = true; + + const mountableSlideIndices = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window); - each(this.components, component => { - component.mounted && component.mounted(); + const sign = event.direction === 'prev' ? +1 : -1; + + mountableSlideIndices.forEach(index => { + const slide = this.slides[index]; + + const x = sign * Math.abs(event.offset.x); + + slide.translate(x); + }); + } + + /** + * Handle dragged event. + * + * @param event + */ + private onDragEnd(event: { direction: 'prev' | 'next' }) { + console.debug('[Drag] Drag end', event); + + const slide = this.slides[this.currentIndex]; + + slide.translate(-100, () => { + console.log('translation END'); + this.isBusy = false; }); - this.emit('mounted'); - // this.State.set( STATES.IDLE ); todo - this.emit('ready'); + const sign = event.direction === 'prev' ? -1 : +1; + + this.currentIndex = rewind(this.currentIndex + sign * 1, this.slides.length - 1); - applyStyle(this.root, { visibility: 'visible' }); + this.runSlidesLifecycle({ direction: event.direction }); + } + + private onKeyUp(event: KeyboardEvent) { + switch (event.which) { + // arrow left + case 37: + this.previous(); + break; + + // arrow right + case 39: + this.next(); + } } } [].forEach.call(document.querySelectorAll('.image-swiper'), (slider: HTMLElement) => { new Virchual(slider, { slides: () => { - const slides: { key: string; html: string }[] = []; + const slides: string[] = []; for (let i = 0; i < 10; i++) { - slides.push({ - key: i + '', - html: `Slide ${i + 1}`, - }); + slides.push(`Slide ${i + 1}`); } return slides; diff --git a/src/virchual2.ts b/src/virchual2.ts deleted file mode 100644 index 3baa896..0000000 --- a/src/virchual2.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { Slide } from './components2/slide'; -import { Event } from './core/event'; -import { exist } from './utils/error'; -import { find } from './utils/dom'; -import { Drag } from './components2/drag'; -import { rewind, range } from './utils/utils'; -import { slidingWindow } from './utils/sliding-window'; -import './css/styles.css'; - -export type VirchualOptions = { - slides?: string[] | (() => string[]); - speed?: number; - easing?: string; - swipeDistanceThreshold?: number; - flickVelocityThreshold?: number; - flickPower?: number; - pagination?: boolean; - window?: number; -}; - -export class Virchual { - container: HTMLElement; - frame: HTMLElement; - frameWidth: number; - currentIndex: number = 0; - slides: Slide[] = []; - - private event: Event; - - // bound event handlers (to keep `this` context) - private eventBindings: { - onDrag: () => {}; - onDragged: () => {}; - onKeyUp: () => {}; - }; - - constructor(public selector: HTMLElement | string, public options: VirchualOptions = {}) { - this.container = selector instanceof Element ? selector : find(document, selector); - this.frame = this.container.querySelector('.virchual__frame'); - - exist(this.frame, 'Invalid element/selector'); - - this.currentIndex = 0; - this.options = { - slides: [], - speed: 400, - swipeDistanceThreshold: 150, - flickVelocityThreshold: 0.6, - flickPower: 600, - easing: 'cubic-bezier(.42,.65,.27,.99)', - pagination: true, - window: 1, - ...options, - }; - - this.event = new Event(); - - this.eventBindings = { - onDrag: this.onDrag.bind(this), - onKeyUp: this.onKeyUp.bind(this), - onDragged: this.onDragged.bind(this), - }; - - let rawSlides; - - if (typeof this.options.slides === 'function') { - rawSlides = this.options.slides(); - } else { - rawSlides = this.options.slides; - } - - this.slides = (rawSlides || []).map((slide, index) => new Slide(slide, this.frame)); - - this.resize(); - this.mount(); - - new Drag(this.frame, this.options, { event: this.event }).start(); - - this.bindEvents(); - } - - /** - * Register callback fired on the given event(s). - * - * @param events - An event name. Use space to separate multiple events. - * Also, namespace is accepted by dot, such as 'resize.{namespace}'. - * @param handler - A callback function. - * @param elm - Optional. Native event will be listened to when this arg is provided. - * @param options - Optional. Options for addEventListener. - */ - on(events: string, handler: any, elm: (Window & typeof globalThis) | Element = null, options: object = {}) { - this.event.on(events, handler, elm, options); - } - - /** - * Unsubscribe the given event. - * - * @param events - A event name. - * @param elm - Optional. removeEventListener() will be called when this arg is provided. - */ - off(events: string, elm: (Window & typeof globalThis) | Element = null) { - this.event.off(events, elm); - } - - previous() {} - - next() {} - - private mount() { - this.event.emit('mounted'); - - this.runSlidesLifecycle(); - } - - private resize() { - this.frameWidth = this.frame.getBoundingClientRect().width; - } - - /** - * Mount and unmount slides. - */ - private runSlidesLifecycle({ direction }: { direction?: 'prev' | 'next' } = {}) { - const currentSlide = this.slides[this.currentIndex]; - - const mountableSlideIndices = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window); - const mountableSlideIndicesWithOffset = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window + 1); - - mountableSlideIndicesWithOffset.forEach(index => { - const slide = this.slides[index]; - - if (index === this.currentIndex) { - currentSlide.isActive = true; - } else { - this.slides[this.currentIndex].isActive = false; - } - - let realIndex = mountableSlideIndices.indexOf(index); - - // unmount - if (realIndex < 0) { - return slide.unmount(); - } - - slide.position = (this.options.window - realIndex) * -100; - console.log('slide position', slide.position, 'realIndex', realIndex, this.currentIndex); - - const prepend = direction === 'prev'; - - slide.mount(prepend); - }); - } - - private bindEvents() { - this.event.on('keyup', this.eventBindings.onKeyUp, window); - this.event.on('drag', this.eventBindings.onDrag); - this.event.on('dragged', this.eventBindings.onDragged); - } - - private unbindEvents() { - window.removeEventListener('keyup', this.eventBindings.onKeyUp); - } - - /** - * Handle drag event. - * - * @param event - */ - private onDrag(event: { offset: { x: number; y: number }; direction: 'prev' | 'next' }) { - const mountableSlideIndices = slidingWindow(range(0, this.slides.length - 1), this.currentIndex, this.options.window); - - const sign = event.direction === 'prev' ? +1 : -1; - - mountableSlideIndices.forEach(index => { - const slide = this.slides[index]; - - const x = sign * Math.abs(event.offset.x); - - slide.translate(x); - }); - } - - /** - * Handle dragged event. - * - * @param event - */ - private onDragged(event: { direction: 'prev' | 'next' }) { - console.debug('[Drag] Drag end', event); - - const slide = this.slides[this.currentIndex]; - - slide.translate(-100); - - const sign = event.direction === 'prev' ? -1 : +1; - - this.currentIndex = rewind(this.currentIndex + sign * 1, this.slides.length - 1); - - this.runSlidesLifecycle({ direction: event.direction }); - } - - private onKeyUp(event: KeyboardEvent) { - switch (event.which) { - // arrow left - case 37: - this.previous(); - break; - - // arrow right - case 39: - this.next(); - } - } -} - -[].forEach.call(document.querySelectorAll('.image-swiper'), (slider: HTMLElement) => { - new Virchual(slider, { - slides: () => { - const slides: string[] = []; - - for (let i = 0; i < 10; i++) { - slides.push(`Slide ${i + 1}`); - } - - return slides; - }, - }); -});