mirror of
https://github.com/JeremyLikness/vanillajs-deck
synced 2024-12-04 11:17:36 +00:00
Add JSDoc comments
This commit is contained in:
parent
5b0c9fbc15
commit
5daad9fcaa
@ -1,11 +1,19 @@
|
||||
@keyframes slide-left {
|
||||
from { margin-left: 0vw; }
|
||||
to { margin-left: -100vw; }
|
||||
from {
|
||||
margin-left: 0vw;
|
||||
}
|
||||
to {
|
||||
margin-left: -100vw;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes enter-right {
|
||||
from { margin-left: 100vw; }
|
||||
to { margin-left: 0vw; }
|
||||
from {
|
||||
margin-left: 100vw;
|
||||
}
|
||||
to {
|
||||
margin-left: 0vw;
|
||||
}
|
||||
}
|
||||
|
||||
.anim-slide-left-begin {
|
||||
@ -21,8 +29,12 @@
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg) }
|
||||
to { transform: rotate(360deg) }
|
||||
from {
|
||||
transform: rotate(0deg)
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
.anim-spin {
|
||||
|
@ -1,18 +1,43 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Handles animations and transitions
|
||||
* @property {boolean} _transitioning True when animation in effect
|
||||
* @property {string} _begin The beginning animation class
|
||||
* @property {string} _end The ending animation class
|
||||
*/
|
||||
export class Animator {
|
||||
/**
|
||||
* Create an instance of the animation helper
|
||||
*/
|
||||
constructor() {
|
||||
this._transitioning = false;
|
||||
this._begin = null;
|
||||
this._end = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when animation in effect
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get transitioning() {
|
||||
return this._transitioning;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when ready to complete second part of animation
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get animationReady() {
|
||||
return !!this._end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off a new animation (old slide)
|
||||
* @param {string} animationName Name of the animation
|
||||
* @param {HTMLDivElement} host The div to be animated
|
||||
* @param {Function} callback Function to call when the animation completes
|
||||
*/
|
||||
beginAnimation(animationName, host, callback) {
|
||||
this._transitioning = true;
|
||||
this._begin = `anim-${animationName}-begin`;
|
||||
@ -27,6 +52,10 @@ export class Animator {
|
||||
host.classList.add(this._begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off the final animation (new slide)
|
||||
* @param {HTMLDivElement} host The div to animate
|
||||
*/
|
||||
endAnimation(host) {
|
||||
this._transitioning = true;
|
||||
const animationEnd = () => {
|
||||
|
@ -2,11 +2,13 @@ import { registerDeck } from "./navigator.js"
|
||||
import { registerControls } from "./controls.js"
|
||||
import { registerKeyHandler } from "./keyhandler.js"
|
||||
|
||||
/**
|
||||
* Main application element, simply registers the web components
|
||||
*/
|
||||
const app = async () => {
|
||||
registerDeck();
|
||||
registerControls();
|
||||
registerKeyHandler();
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", app);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", app);
|
56
js/controls.js
vendored
56
js/controls.js
vendored
@ -1,11 +1,35 @@
|
||||
class Controls extends HTMLElement {
|
||||
// @ts-check
|
||||
|
||||
import { Navigator } from "./navigator.js"
|
||||
|
||||
/**
|
||||
* @typedef {object} CustomRef
|
||||
* @property {HTMLButtonElement} first The button to jump to the first slide
|
||||
* @property {HTMLButtonElement} prev The button to move to the previous slide
|
||||
* @property {HTMLButtonElement} next The button to advance to the next slide
|
||||
* @property {HTMLButtonElement} last The button to advance to the last slide
|
||||
* @property {HTMLSpanElement} pos The span for the positional information
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom element that renders controls to navigate the deck
|
||||
*/
|
||||
export class Controls extends HTMLElement {
|
||||
|
||||
/**
|
||||
* Create a new instance of controls
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {CustomRef} */
|
||||
this._controlRef = null;
|
||||
/** @type {Navigator} */
|
||||
this._deck = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the element is inserted into the DOM. Used to fetch the template and wire into the related Navigator instance.
|
||||
*/
|
||||
async connectedCallback() {
|
||||
const response = await fetch("/templates/controls.html");
|
||||
const template = await response.text();
|
||||
@ -14,11 +38,11 @@ class Controls extends HTMLElement {
|
||||
host.innerHTML = template;
|
||||
this.appendChild(host);
|
||||
this._controlRef = {
|
||||
first: document.getElementById("ctrlFirst"),
|
||||
prev: document.getElementById("ctrlPrevious"),
|
||||
next: document.getElementById("ctrlNext"),
|
||||
last: document.getElementById("ctrlLast"),
|
||||
pos: document.getElementById("position")
|
||||
first: /** @type {HTMLButtonElement} **/(document.getElementById("ctrlFirst")),
|
||||
prev: /** @type {HTMLButtonElement} **/(document.getElementById("ctrlPrevious")),
|
||||
next: /** @type {HTMLButtonElement} **/(document.getElementById("ctrlNext")),
|
||||
last: /** @type {HTMLButtonElement} **/(document.getElementById("ctrlLast")),
|
||||
pos: /** @type {HTMLSpanElement} **/(document.getElementById("position"))
|
||||
};
|
||||
this._controlRef.first.addEventListener("click", () => this._deck.jumpTo(0));
|
||||
this._controlRef.prev.addEventListener("click", () => this._deck.previous());
|
||||
@ -27,20 +51,33 @@ class Controls extends HTMLElement {
|
||||
this.refreshState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of attributes to watch
|
||||
* @returns {string[]}
|
||||
*/
|
||||
static get observedAttributes() {
|
||||
return ["deck"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the attribute is set
|
||||
* @param {string} attrName Name of the attribute that was set
|
||||
* @param {string} oldVal The old attribute value
|
||||
* @param {string} newVal The new attribute value
|
||||
*/
|
||||
async attributeChangedCallback(attrName, oldVal, newVal) {
|
||||
if (attrName === "deck") {
|
||||
if (oldVal !== newVal) {
|
||||
this._deck = document.getElementById(newVal);
|
||||
this._deck.addEventListener("slideschanged", () => this.refreshState());
|
||||
this._deck = /** @type {Navigator} */(document.getElementById(newVal));
|
||||
this._deck.addEventListener("slideschanged", () => this.refreshState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshState() {
|
||||
/**
|
||||
* Enables/disables buttons and updates position based on index in the deck
|
||||
*/
|
||||
refreshState() {
|
||||
const next = this._deck.hasNext;
|
||||
const prev = this._deck.hasPrevious;
|
||||
this._controlRef.first.disabled = !prev;
|
||||
@ -51,4 +88,5 @@ class Controls extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Register the custom slide-controls element */
|
||||
export const registerControls = () => customElements.define('slide-controls', Controls);
|
@ -1,4 +0,0 @@
|
||||
export async function getJson(path) {
|
||||
const response = await fetch(path);
|
||||
return await response.json();
|
||||
};
|
@ -1,18 +1,42 @@
|
||||
class KeyHandler extends HTMLElement {
|
||||
// @ts-check
|
||||
|
||||
import { Navigator } from "./navigator.js"
|
||||
|
||||
/**
|
||||
* Custom element to handle key press
|
||||
*/
|
||||
export class KeyHandler extends HTMLElement {
|
||||
|
||||
/**
|
||||
* Create a key handler instance
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
/**
|
||||
* The related Navigator element
|
||||
* @type {Navigator}
|
||||
*/
|
||||
this._deck = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the attributes being watched
|
||||
* @returns {string[]} The attributes to watch
|
||||
*/
|
||||
static get observedAttributes() {
|
||||
return ["deck"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when attributes change
|
||||
* @param {string} attrName The attribute that changed
|
||||
* @param {string} oldVal The old value
|
||||
* @param {string} newVal The new value
|
||||
*/
|
||||
async attributeChangedCallback(attrName, oldVal, newVal) {
|
||||
if (attrName === "deck") {
|
||||
if (oldVal !== newVal) {
|
||||
this._deck = document.getElementById(newVal);
|
||||
this._deck = /** @type {Navigator} */(document.getElementById(newVal));
|
||||
this._deck.parentElement.addEventListener("keydown", key => {
|
||||
if (key.keyCode == 39 || key.keyCode == 32) {
|
||||
this._deck.next();
|
||||
@ -26,4 +50,7 @@ class KeyHandler extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the custom key-handler element
|
||||
*/
|
||||
export const registerKeyHandler = () => customElements.define('key-handler', KeyHandler);
|
@ -1,9 +1,22 @@
|
||||
// @ts-check
|
||||
|
||||
import { loadSlides } from "./slideLoader.js"
|
||||
import { Slide } from "./slide.js"
|
||||
import { Router } from "./router.js"
|
||||
import { Animator } from "./animator.js"
|
||||
|
||||
class Navigator extends HTMLElement {
|
||||
/**
|
||||
* The main class that handles rendering the slide decks
|
||||
* @property {Animator} _animator Animation helper
|
||||
* @property {Router} _router Routing helper
|
||||
* @property {string} _route The current route
|
||||
* @property {CustomEvent} slidesChangedEvent Event fired when slide changes
|
||||
*/
|
||||
export class Navigator extends HTMLElement {
|
||||
|
||||
/**
|
||||
* Create an instance of the custom navigator element
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._animator = new Animator();
|
||||
@ -19,15 +32,25 @@ class Navigator extends HTMLElement {
|
||||
if (this._route) {
|
||||
var slide = parseInt(this._route) - 1;
|
||||
this.jumpTo(slide);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of observed attributes
|
||||
* @returns {string[]}
|
||||
*/
|
||||
static get observedAttributes() {
|
||||
return ["start"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an attribute changes
|
||||
* @param {string} attrName
|
||||
* @param {string} oldVal
|
||||
* @param {string} newVal
|
||||
*/
|
||||
async attributeChangedCallback(attrName, oldVal, newVal) {
|
||||
if (attrName === "start") {
|
||||
if (oldVal !== newVal) {
|
||||
@ -43,26 +66,50 @@ class Navigator extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current slide index
|
||||
* @returns {number}
|
||||
*/
|
||||
get currentIndex() {
|
||||
return this._currentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current slide
|
||||
* @returns {Slide}
|
||||
*/
|
||||
get currentSlide() {
|
||||
return this._slides ? this._slides[this._currentIndex] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of slides
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalSlides() {
|
||||
return this._slides ? this._slides.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a previous slide exists
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get hasPrevious() {
|
||||
return this._currentIndex > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a next slide exists
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get hasNext() {
|
||||
return this._currentIndex < (this.totalSlides - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main slide navigation: jump to specific slide
|
||||
* @param {number} slideIdx
|
||||
*/
|
||||
jumpTo(slideIdx) {
|
||||
if (this._animator.transitioning) {
|
||||
return;
|
||||
@ -71,9 +118,9 @@ class Navigator extends HTMLElement {
|
||||
this._currentIndex = slideIdx;
|
||||
this.innerHTML = '';
|
||||
this.appendChild(this.currentSlide.html);
|
||||
this._router.setRoute(slideIdx+1);
|
||||
this._router.setRoute((slideIdx + 1).toString());
|
||||
this._route = this._router.getRoute();
|
||||
document.title = `${this.currentIndex+1}/${this.totalSlides}: ${this.currentSlide.title}`;
|
||||
document.title = `${this.currentIndex + 1}/${this.totalSlides}: ${this.currentSlide.title}`;
|
||||
this.dispatchEvent(this.slidesChangedEvent);
|
||||
if (this._animator.animationReady) {
|
||||
this._animator.endAnimation(this.querySelector("div"));
|
||||
@ -81,13 +128,16 @@ class Navigator extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance to next slide, if it exists. Applies animation if transition is specified
|
||||
*/
|
||||
next() {
|
||||
if (this.hasNext) {
|
||||
if (this.currentSlide.transition !== null) {
|
||||
this._animator.beginAnimation(
|
||||
this.currentSlide.transition,
|
||||
this.currentSlide.transition,
|
||||
this.querySelector("div"),
|
||||
() => this.jumpTo(this.currentIndex+1));
|
||||
() => this.jumpTo(this.currentIndex + 1));
|
||||
}
|
||||
else {
|
||||
this.jumpTo(this.currentIndex + 1);
|
||||
@ -95,6 +145,9 @@ class Navigator extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to previous slide, if it exists
|
||||
*/
|
||||
previous() {
|
||||
if (this.hasPrevious) {
|
||||
this.jumpTo(this.currentIndex - 1);
|
||||
@ -102,4 +155,7 @@ class Navigator extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the custom slide-deck component
|
||||
*/
|
||||
export const registerDeck = () => customElements.define('slide-deck', Navigator);
|
28
js/router.js
28
js/router.js
@ -1,11 +1,25 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Handles routing for the app
|
||||
*/
|
||||
export class Router {
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* @property {HTMLDivElement} _eventSource Arbitrary DIV used to generate events
|
||||
*/
|
||||
this._eventSource = document.createElement("div");
|
||||
/**
|
||||
* @property {CustomEvent} _routeChanged Custom event raised when the route changes
|
||||
*/
|
||||
this._routeChanged = new CustomEvent("routechanged", {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
});
|
||||
/**
|
||||
* @property {string} _route The current route
|
||||
*/
|
||||
this._route = null;
|
||||
window.addEventListener("popstate", () => {
|
||||
if (this.getRoute() !== this._route) {
|
||||
@ -15,15 +29,27 @@ export class Router {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event source
|
||||
* @returns {HTMLDivElement} The event source div
|
||||
*/
|
||||
get eventSource() {
|
||||
return this._eventSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the current route
|
||||
* @param {string} route The route name
|
||||
*/
|
||||
setRoute(route) {
|
||||
window.location.hash = route;
|
||||
this._route = route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current route
|
||||
* @returns {string} The current route name
|
||||
*/
|
||||
getRoute() {
|
||||
return window.location.hash.substr(1).replace(/\//ig, "/");
|
||||
}
|
||||
|
33
js/slide.js
33
js/slide.js
@ -1,38 +1,65 @@
|
||||
// @ts-check
|
||||
/** Represents a slide */
|
||||
export class Slide {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {string} text - The content of the slide
|
||||
*/
|
||||
constructor(text) {
|
||||
/** @property {string} _text - internal text representation */
|
||||
this._text = text;
|
||||
/** @property {HTMLDivElement} _html - host div */
|
||||
this._html = document.createElement('div');
|
||||
this._html.innerHTML = text;
|
||||
/** @property {string} _title - title of the slide */
|
||||
this._title = this._html.querySelectorAll("title")[0].innerText;
|
||||
const transition = this._html.querySelectorAll("transition");
|
||||
/** @type{NodeListOf<HTMLElement>} */
|
||||
const transition = (this._html.querySelectorAll("transition"));
|
||||
if (transition.length) {
|
||||
this._transition = transition[0].innerText;
|
||||
}
|
||||
else {
|
||||
this._transition = null;
|
||||
}
|
||||
/** @type{NodeListOf<HTMLElement>} */
|
||||
const hasNext = this._html.querySelectorAll("nextslide");
|
||||
if (hasNext.length > 0) {
|
||||
this._nextSlideName = hasNext[0].innerText;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._nextSlideName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The slide transition
|
||||
* @return{string} The transition name
|
||||
*/
|
||||
get transition() {
|
||||
return this._transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* The slide title
|
||||
* @return{string} The slide title
|
||||
*/
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML DOM node for the slide
|
||||
* @return{HTMLDivElement} The HTML content
|
||||
*/
|
||||
get html() {
|
||||
return this._html;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the next slide (filename without the .html extension)
|
||||
* @return{string} The name of the next slide
|
||||
*/
|
||||
get nextSlide() {
|
||||
return this._nextSlideName;
|
||||
}
|
||||
|
@ -1,11 +1,22 @@
|
||||
//@ts-check
|
||||
import { Slide } from "./slide.js"
|
||||
|
||||
/**
|
||||
* Load a single slide
|
||||
* @param {string} slideName The name of the slide
|
||||
* @returns {Promise<Slide>} The slide
|
||||
*/
|
||||
async function loadSlide(slideName) {
|
||||
const response = await fetch(`./slides/${slideName}.html`);
|
||||
const slide = await response.text();
|
||||
return new Slide(slide);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} start The name of the slide to begin with
|
||||
* @returns {Promise<Slide[]>} The array of loaded slides
|
||||
*/
|
||||
export async function loadSlides(start) {
|
||||
var next = start;
|
||||
const slides = [];
|
||||
@ -20,6 +31,6 @@ export async function loadSlides(start) {
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return slides;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "Vanilla.js: Modern 1st Party JavaScript",
|
||||
"start": "001-title"
|
||||
}
|
Loading…
Reference in New Issue
Block a user