From 959a7830da7e2523a7e7650f321caf7c1331c5ad Mon Sep 17 00:00:00 2001 From: Sebastian Wachter Date: Mon, 11 Dec 2023 15:16:16 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=96=20Add=20JSDoc=20documentation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scratch/scratch.ts | 104 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/scratch/scratch.ts b/src/scratch/scratch.ts index 050ad4c..1cbbf12 100644 --- a/src/scratch/scratch.ts +++ b/src/scratch/scratch.ts @@ -19,7 +19,17 @@ export interface Vector { y: number } +/** + * A custom Scratch Card component extending HTMLElement. + * @class Scratch + * @extends {HTMLElement} + */ export class Scratch extends HTMLElement { + /** + * Observed attributes for the custom element. + * @static + * @returns {Array} An array of observed attribute names. + */ static get observedAttributes (): ScratchAttributes[] { return [ScratchAttributes.BRUSH_SIZE, ScratchAttributes.BRUSH_SHAPE, @@ -27,16 +37,39 @@ export class Scratch extends HTMLElement { ScratchAttributes.PERCENTAGE_UPDATE] } + /** + * Local reference of the component's canvas element. + * @private {HTMLCanvasElement} + */ canvas: HTMLCanvasElement + /** + * Local reference of the component's content that should be hidden by the canvas. + * @private {HTMLSlotElement} + */ slotRef: HTMLSlotElement + /** + * The 2D context of the component's canvas. Can be null when it is not yet initialized. + * @private {CanvasRenderingContext2D|null} + */ context: CanvasRenderingContext2D | null + /** + * A Vector defining the x and y coordinates of the last input - used for drawing a line between these values and the new input values. + * @private {Vector} + */ lastPosition: Vector + /** + * Flag that determines if a drawing process is currently happening. + * @private {boolean} + */ isDrawing: boolean = false + /** + * Creates an instance of Scratch and initializes the local references as well as registers the mouse and touch event handlers. + */ constructor () { super() @@ -61,6 +94,10 @@ export class Scratch extends HTMLElement { window.addEventListener('touchend', () => this.handleEnd(), false) } + /** + * Lifecycle callback that gets invoked when the element is appended to the DOM. The slot shoudl be populated at this point. + * Uses the slot's width and height vlaues to set the canvas' size to hide the slot. Also fills the canvas initially with the defined color. + */ connectedCallback (): void { const slotChildNodeBoundingClientRect = this.slotRef.assignedElements()[0].getBoundingClientRect() const newWidth = Math.floor(slotChildNodeBoundingClientRect.width) @@ -73,40 +110,76 @@ export class Scratch extends HTMLElement { this.fillArea() } + /** + * Lifecycle callback that gets invoked when the attribute of the element changes. + * Reinitializes the canvas' context when ScratchAttributes.PERCENTAGE_UPDATE is toggled. + * @param {string} name - Name of the attribute that changed. + */ attributeChangedCallback (name: string): void { if (name === ScratchAttributes.PERCENTAGE_UPDATE) this.context = this.canvas.getContext('2d', { willReadFrequently: this.percentageUpdate }) } + /** + * Gets the value of the ScratchAttributes.BRUSH_SIZE attribute and parses it as a number. + * @returns {number} + */ get brushSize (): number { return parseInt(this.getAttribute(ScratchAttributes.BRUSH_SIZE) ?? '10', 10) } + /** + * Gets the value of the ScratchAttributes.BRUSH_SHAPE attribute and parses it as a valid CanvasLineJoin. + * @returns {CanvasLineJoin} + */ get brushShape (): CanvasLineJoin { let shape = this.getAttribute(ScratchAttributes.BRUSH_SHAPE) ?? 'round' if (shape !== 'bevel' && shape !== 'miter' && shape !== 'round') shape = 'round' return shape as CanvasLineJoin } + /** + * Gets the value of the ScratchAttributes.SCRATCH_COLOR attribute and parses it as a valid CSS color. + * @returns {string} + */ get scratchColor (): string { let color = this.getAttribute(ScratchAttributes.SCRATCH_COLOR) ?? '#000000' if (!CSS.supports('color', color)) color = '#000000' return color } + /** + * Gets whether ScratchAttributes.PERCENTAGE_UPDATE is present on the element or not. + * @returns {boolean} + */ get percentageUpdate (): boolean { return this.hasAttribute(ScratchAttributes.PERCENTAGE_UPDATE) } + /** + * Gets the offset of the canvas element from the top. + * @private + * @returns {number} + */ get offsetTop (): number { const { top } = this.canvas.getBoundingClientRect() return top + document.body.scrollTop } + /** + * Gets the offset of the canvas element from the left. + * @private + * @returns {number} + */ get offsetLeft (): number { const { left } = this.canvas.getBoundingClientRect() return left + document.body.scrollLeft } + /** + * Handles an event type agnostic input start event. + * @param {Vector} { x, y } The x and y coordinates where the start event is triggered. + * @returns {void} + */ handleStart ({ x, y }: Vector): void { this.lastPosition = { x: x - this.offsetLeft, y: y - this.offsetTop } this.scratch({ x: x - this.offsetLeft, y: y - this.offsetTop }) @@ -114,16 +187,29 @@ export class Scratch extends HTMLElement { if (this.percentageUpdate) this.calcAreaCleared() } + /** + * Handles an event type agnostic input move event. + * @param {Vector} { x, y } The x and y coordinates where the move event is triggered. + * @returns {void} + */ handleMove ({ x, y }: Vector): void { if (!this.isDrawing) return this.scratch({ x: x - this.offsetLeft, y: y - this.offsetTop }) if (this.percentageUpdate) this.calcAreaCleared() } + /** + * Handles an event type agnostic input end or cancelled event. + * @returns {void} + */ handleEnd (): void { this.isDrawing = false } + /** + * Fills the canvas with the defined color. + * @returns {void} + */ fillArea (): void { if (this.context === null) return const { width, height } = this.context.canvas @@ -133,6 +219,10 @@ export class Scratch extends HTMLElement { if (this.percentageUpdate) this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, 0) } + /** + * Clears the canvas. + * @returns {void} + */ clearArea (): void { if (this.context === null) return const { width, height } = this.context.canvas @@ -141,6 +231,11 @@ export class Scratch extends HTMLElement { if (this.percentageUpdate) this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, 100) } + /** + * Clears the color off the canvas at the given coordinates. + * @param {Vector} { x, y } The coordinates at which color should be cleared off. + * @returns {void} + */ scratch ({ x, y }: Vector): void { if (this.context === null) return this.context.beginPath() @@ -155,6 +250,10 @@ export class Scratch extends HTMLElement { this.lastPosition = { x, y } } + /** + * Calculates the current amount of cleared pixels with an inaccuracy of 150 from the canvas' ImageData. + * @returns {void} + */ calcAreaCleared (): void { if (this.context === null) return const { width, height } = this.context.canvas @@ -169,6 +268,11 @@ export class Scratch extends HTMLElement { this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, percentage) } + /** + * Emits given event with given payload. + * @param {ScratchEvents} eventType The name of the event that should be emitted. + * @param {number} payload The payload for the emitted event. + */ emitEvent (eventType: ScratchEvents, payload: number): void { const event = new CustomEvent(eventType, { detail: payload }) this.dispatchEvent(event)