diff --git a/README.md b/README.md index da38d36..cba1350 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,12 @@ Initialize Allonsh by importing the module and providing the required options. ```js import Allonsh from './allonsh.js'; +import { CSS_CLASSES, EVENTS } from './constants.js'; // Initialize Allonsh with options const allonsh = new Allonsh({ - draggableSelector: 'allonsh-draggable', // Required: class for draggable elements - dropzoneSelector: 'allonsh-dropzone', // Optional: class for dropzones + draggableSelector: CSS_CLASSES.DRAGGABLE, // Required: class for draggable elements + dropzoneSelector: CSS_CLASSES.DROPZONE, // Optional: class for dropzones playAreaSelector: 'play-area', // Optional: class for the container area restrictToDropzones: false, // Optional: restrict dragging to dropzones enableStacking: true, // Optional: enable stacking inside dropzones @@ -47,6 +48,14 @@ const allonsh = new Allonsh({ stackSpacing: 8, // Optional: spacing between stacked items (px) useGhostEffect: true, // Optional: enable ghost effect (shows transparent clone while dragging) }); + +// Listen for custom events +document.addEventListener(EVENTS.DRAG_START, (e) => { + // handle drag start +}); +document.addEventListener(EVENTS.DROP, (e) => { + // handle drop +}); ``` ## Documentation diff --git a/docs/allonsh.md b/docs/allonsh.md index bb28958..1d1737b 100644 --- a/docs/allonsh.md +++ b/docs/allonsh.md @@ -82,10 +82,11 @@ Allonsh dispatches the following custom events on relevant elements: ```js import Allonsh from 'allonsh'; +import { CSS_CLASSES, EVENTS } from 'allonsh/constants'; const dragDrop = new Allonsh({ - draggableSelector: 'draggable', - dropzoneSelector: 'dropzone', + draggableSelector: CSS_CLASSES.DRAGGABLE, + dropzoneSelector: CSS_CLASSES.DROPZONE, playAreaSelector: 'play-area', restrictToDropzones: true, enableStacking: true, @@ -93,4 +94,12 @@ const dragDrop = new Allonsh({ stackSpacing: 10, useGhostEffect: true, }); + +// Listen for custom events +document.addEventListener(EVENTS.DRAG_START, (e) => { + // handle drag start +}); +document.addEventListener(EVENTS.DROP, (e) => { + // handle drop +}); ``` diff --git a/docs/constants.md b/docs/constants.md new file mode 100644 index 0000000..5f57187 --- /dev/null +++ b/docs/constants.md @@ -0,0 +1,163 @@ +# Allonsh.js Constants Documentation + +This document provides a detailed reference for the constants defined in `src/constants.js` for the Allonsh.js project. These constants centralize configuration and hardcoded values used throughout the library, ensuring consistency and maintainability. + +## Z_INDEX + +Defines z-index values for various UI elements to control stacking order: + +- `GHOST`: 999 — Used for ghost elements during drag operations. +- `DRAGGING`: 1000 — Active dragging element. +- `DROPPED`: 9999 — Element after being dropped. +- `HEADER`: 10 — Header bar. +- `THEME_TOGGLE`: 9 — Theme toggle button. +- `CONTROL_PANEL`: 5 — Control panel UI. +- `FREEMODE_TITLE`: 5 — Title in freemode. + +## DEFAULTS + +Default configuration values for stack layout and direction: + +- `STACK_SPACING`: 5 — Default spacing between stack items. +- `STACK_SPACING_DEMO`: 10 — Spacing for demo mode. +- `STACK_DIRECTION`: 'horizontal' — Default stack direction. +- `STACK_DIRECTION_VERTICAL`: 'vertical' — Vertical stack direction. +- `STACK_DIRECTION_HORIZONTAL`: 'horizontal' — Horizontal stack direction. + +## CSS_CLASSES + +CSS class names used for styling and DOM selection: + +- `DROPZONE`: 'allonsh-dropzone' — Dropzone area. +- `HIGHLIGHT`: 'allonsh-highlight' — Highlighted element. +- `DRAGGABLE`: 'allonsh-draggable' — Draggable element. +- `RESTRICTED`: 'restricted' — Restricted area or element. + +## CSS_POSITIONS + +CSS position values: + +- `RELATIVE`: 'relative' +- `ABSOLUTE`: 'absolute' + +## CSS_CURSORS + +Cursor styles for drag-and-drop interactions: + +- `GRAB`: 'grab' +- `GRABBING`: 'grabbing' + +## OPACITY + +Opacity values for UI elements: + +- `GHOST`: '0.3' — Semi-transparent ghost element. +- `FULL`: '1' — Fully opaque element. + +## EVENTS + +Custom DOM event names for drag-and-drop lifecycle: + +- `DRAG_START`: 'allonsh-dragstart' +- `DROP`: 'allonsh-drop' +- `DRAG_ENTER`: 'allonsh-dragenter' +- `DRAG_LEAVE`: 'allonsh-dragleave' + +## FLEX_DIRECTIONS + +Flexbox direction values: + +- `ROW`: 'row' +- `COLUMN`: 'column' + +## FLEX_WRAP + +Flexbox wrap mode: + +- `'wrap'` + +## DISPLAY_MODES + +Display property values: + +- `FLEX`: 'flex' +- `NONE`: 'none' + +## POINTER_EVENTS + +Pointer event values: + +- `NONE`: 'none' — Disables pointer events. +- `AUTO`: 'auto' — Enables pointer events. + +## Usage + +Below are examples of how constants from `src/constants.js` are used throughout the Allonsh.js project: + +### Importing Constants + +In the main source file (`src/allonsh.js`): + +```js +import { + Z_INDEX, + DEFAULTS, + CSS_CLASSES, + CSS_POSITIONS, + CSS_CURSORS, + OPACITY, + EVENTS, + FLEX_DIRECTIONS, + FLEX_WRAP, + DISPLAY_MODES, + POINTER_EVENTS, +} from './constants.js'; +``` + +### Using Constants in the Library + +- **Default values:** + ```js + stackDirection = DEFAULTS.STACK_DIRECTION; + stackSpacing = DEFAULTS.STACK_SPACING; + ``` +- **Styling elements:** + ```js + element.style.cursor = CSS_CURSORS.GRAB; + element.style.position = CSS_POSITIONS.RELATIVE; + element.style.zIndex = Z_INDEX.DRAGGING; + element.style.opacity = OPACITY.GHOST; + dropzone.classList.add(CSS_CLASSES.DROPZONE); + dropzone.classList.remove(CSS_CLASSES.HIGHLIGHT); + ``` +- **Flexbox and display:** + ```js + dropzone.style.display = DISPLAY_MODES.FLEX; + dropzone.style.flexDirection = FLEX_DIRECTIONS.ROW; + dropzone.style.flexWrap = FLEX_WRAP; + ``` +- **Pointer events:** + ```js + element.style.pointerEvents = POINTER_EVENTS.NONE; + ``` +- **Custom events:** + ```js + element.dispatchEvent(new CustomEvent(EVENTS.DRAG_START, { detail: { ... } })); + dropzone.dispatchEvent(new CustomEvent(EVENTS.DROP, { detail: { ... } })); + ``` + +### Usage in Tests + +In `test/allonsh.test.js`: + +```js +import { EVENTS } from '../src/constants.js'; + +// Example: Listening for custom drop event +const dropHandler = vi.fn(); +dropzone.addEventListener(EVENTS.DROP, dropHandler); +``` + +### Usage in Demo + +In `demo/js/script.js`, constants are used indirectly via the Allonsh class, which applies them for styling, event handling, and configuration. diff --git a/src/allonsh.js b/src/allonsh.js index db9994e..20fde8a 100644 --- a/src/allonsh.js +++ b/src/allonsh.js @@ -1,3 +1,17 @@ +import { + Z_INDEX, + DEFAULTS, + CSS_CLASSES, + CSS_POSITIONS, + CSS_CURSORS, + OPACITY, + EVENTS, + FLEX_DIRECTIONS, + FLEX_WRAP, + DISPLAY_MODES, + POINTER_EVENTS, +} from './constants.js'; + class Allonsh { constructor(options = {}) { const { @@ -6,8 +20,8 @@ class Allonsh { playAreaSelector = null, restrictToDropzones = false, enableStacking = false, - stackDirection = 'horizontal', - stackSpacing = 5, + stackDirection = DEFAULTS.STACK_DIRECTION, + stackSpacing = DEFAULTS.STACK_SPACING, useGhostEffect = false, } = options; @@ -103,7 +117,7 @@ class Allonsh { const newPlayArea = document.querySelector(`.${playAreaSelector}`); if (newPlayArea) { this.playAreaElement = newPlayArea; - this.playAreaElement.style.position = 'relative'; + this.playAreaElement.style.position = CSS_POSITIONS.RELATIVE; } else { console.warn( `Allonsh Warning: Play area element with class '${playAreaSelector}' not found.` @@ -117,7 +131,7 @@ class Allonsh { ); this.draggableElements.forEach((element) => { - element.style.cursor = 'grab'; + element.style.cursor = CSS_CURSORS.GRAB; element.addEventListener('mousedown', this._boundMouseDown); element.addEventListener('touchstart', this._boundTouchStart, { passive: false, @@ -152,7 +166,7 @@ class Allonsh { 'Allonsh Initialization Error: Play area element is not defined.' ); } - this.playAreaElement.style.position = 'relative'; + this.playAreaElement.style.position = CSS_POSITIONS.RELATIVE; if (!this.draggableElements || this.draggableElements.length === 0) { console.warn('Allonsh Warning: No draggable elements to initialize.'); @@ -161,7 +175,7 @@ class Allonsh { this.draggableElements.forEach((element) => { try { - element.style.cursor = 'grab'; + element.style.cursor = CSS_CURSORS.GRAB; element.addEventListener('mousedown', this._onMouseDown.bind(this)); element.addEventListener('touchstart', this._onTouchStart.bind(this), { passive: false, @@ -177,7 +191,7 @@ class Allonsh { if (this.dropzoneElements) { this.dropzoneElements.forEach((dropzone) => { try { - dropzone.classList.add('allonsh-dropzone'); + dropzone.classList.add(CSS_CLASSES.DROPZONE); if (this.enableStacking) { this._applyStackingStyles(dropzone); } @@ -199,11 +213,13 @@ class Allonsh { _applyStackingStyles(dropzone) { try { - dropzone.style.display = 'flex'; + dropzone.style.display = DISPLAY_MODES.FLEX; dropzone.style.flexDirection = - this.stackDirection === 'vertical' ? 'column' : 'row'; + this.stackDirection === DEFAULTS.STACK_DIRECTION_VERTICAL + ? FLEX_DIRECTIONS.COLUMN + : FLEX_DIRECTIONS.ROW; dropzone.style.gap = `${this.stackSpacing}px`; - dropzone.style.flexWrap = 'wrap'; + dropzone.style.flexWrap = FLEX_WRAP; } catch (err) { console.error('Allonsh Error applying stacking styles:', err); } @@ -288,14 +304,14 @@ class Allonsh { } _onPointerUp(clientX, clientY, event) { - this.currentDraggedElement.style.opacity = '1'; + this.currentDraggedElement.style.opacity = OPACITY.FULL; this._handleDrop(clientX, clientY, event); } _startDrag(event, clientX, clientY) { try { this.currentDraggedElement = event.currentTarget; - this.currentDraggedElement.style.cursor = 'grabbing'; + this.currentDraggedElement.style.cursor = CSS_CURSORS.GRABBING; this.originalParent = this.currentDraggedElement.parentElement; this.originalDropzone = this._findClosestDropzone( @@ -316,12 +332,12 @@ class Allonsh { } this.ghostElement = this.currentDraggedElement.cloneNode(true); - this.ghostElement.style.pointerEvents = 'none'; - this.ghostElement.style.position = 'absolute'; + this.ghostElement.style.pointerEvents = POINTER_EVENTS.NONE; + this.ghostElement.style.position = CSS_POSITIONS.ABSOLUTE; - this.ghostElement.style.zIndex = '999'; + this.ghostElement.style.zIndex = Z_INDEX.GHOST; - this.currentDraggedElement.style.opacity = '0.3'; + this.currentDraggedElement.style.opacity = OPACITY.GHOST; this.playAreaElement.appendChild(this.ghostElement); this.ghostElement.style.left = `${rect.left - playAreaRect.left}px`; @@ -333,11 +349,11 @@ class Allonsh { this.dragOffsetX = clientX - rect.left; this.dragOffsetY = clientY - rect.top; - this.currentDraggedElement.style.cursor = 'grabbing'; - this.currentDraggedElement.style.zIndex = 1000; + this.currentDraggedElement.style.cursor = CSS_CURSORS.GRABBING; + this.currentDraggedElement.style.zIndex = Z_INDEX.DRAGGING; this.currentDraggedElement.dispatchEvent( - new CustomEvent('allonsh-dragstart', { + new CustomEvent(EVENTS.DRAG_START, { detail: { originalEvent: event }, }) ); @@ -357,13 +373,13 @@ class Allonsh { const style = element.style; if (isGhost) { - style.pointerEvents = 'none'; - style.position = 'absolute'; - style.zIndex = '999'; - style.opacity = '0.3'; + style.pointerEvents = POINTER_EVENTS.NONE; + style.position = CSS_POSITIONS.ABSOLUTE; + style.zIndex = Z_INDEX.GHOST; + style.opacity = OPACITY.GHOST; } else { - style.cursor = 'grabbing'; - style.zIndex = 1000; + style.cursor = CSS_CURSORS.GRABBING; + style.zIndex = Z_INDEX.DRAGGING; } if (isGhost && element !== this.ghostElement) { @@ -402,7 +418,7 @@ class Allonsh { this.ghostElement.style.left = `${newLeft}px`; this.ghostElement.style.top = `${newTop}px`; } else { - this.currentDraggedElement.style.position = 'absolute'; + this.currentDraggedElement.style.position = CSS_POSITIONS.ABSOLUTE; const playAreaRect = this.playAreaElement.getBoundingClientRect(); const draggedRect = this.currentDraggedElement.getBoundingClientRect(); @@ -444,9 +460,9 @@ class Allonsh { playAreaRect.bottom - 1 ); - this.currentDraggedElement.style.pointerEvents = 'none'; + this.currentDraggedElement.style.pointerEvents = POINTER_EVENTS.NONE; let elementBelow = document.elementFromPoint(clampedX, clampedY); - this.currentDraggedElement.style.pointerEvents = 'auto'; + this.currentDraggedElement.style.pointerEvents = POINTER_EVENTS.AUTO; if (!elementBelow) { if (this.restrictToDropzones) { @@ -491,7 +507,7 @@ class Allonsh { } this.currentDropzone.dispatchEvent( - new CustomEvent('allonsh-drop', { + new CustomEvent(EVENTS.DROP, { detail: { draggedElement: this.currentDraggedElement, originalEvent: event, @@ -500,17 +516,17 @@ class Allonsh { ); this.currentDropzone.dispatchEvent( - new CustomEvent('allonsh-dragenter', { + new CustomEvent(EVENTS.DRAG_ENTER, { detail: { draggedElement: this.currentDraggedElement }, }) ); this.currentDropzone.dispatchEvent( - new CustomEvent('allonsh-dragleave', { + new CustomEvent(EVENTS.DRAG_LEAVE, { detail: { draggedElement: this.currentDraggedElement }, }) ); - this.currentDropzone.classList.remove('allonsh-highlight'); + this.currentDropzone.classList.remove(CSS_CLASSES.HIGHLIGHT); this.currentDropzone = null; } else { if (this.ghostElement && this.ghostElement.parentElement) { @@ -534,10 +550,11 @@ class Allonsh { const offsetX = clampedX - playAreaRect.left - this.dragOffsetX; const offsetY = clampedY - playAreaRect.top - this.dragOffsetY; - this.currentDraggedElement.style.position = 'absolute'; + this.currentDraggedElement.style.position = + CSS_POSITIONS.ABSOLUTE; this.currentDraggedElement.style.left = `${offsetX}px`; this.currentDraggedElement.style.top = `${offsetY}px`; - this.currentDraggedElement.style.zIndex = '9999'; + this.currentDraggedElement.style.zIndex = Z_INDEX.DROPPED; } catch (e) { console.warn( 'Allonsh Warning: Could not append dragged element back to play area.', @@ -579,14 +596,14 @@ class Allonsh { } _isDropzoneRestricted(dropzone) { - return dropzone && dropzone.classList.contains('restricted'); + return dropzone && dropzone.classList.contains(CSS_CLASSES.RESTRICTED); } _resetDraggedElementState() { if (!this.currentDraggedElement) return; try { - this.currentDraggedElement.style.cursor = 'grab'; + this.currentDraggedElement.style.cursor = CSS_CURSORS.GRAB; this.currentDraggedElement.style.zIndex = ''; this._toggleDropzoneHighlight(false); @@ -622,7 +639,7 @@ class Allonsh { try { element.style.left = ''; element.style.top = ''; - element.style.position = 'relative'; + element.style.position = CSS_POSITIONS.RELATIVE; } catch (err) { console.error('Allonsh Error resetting element position:', err); } @@ -631,7 +648,7 @@ class Allonsh { _toggleDropzoneHighlight(enable) { try { this.dropzoneElements.forEach((dropzone) => { - dropzone.classList.toggle('allonsh-highlight', enable); + dropzone.classList.toggle(CSS_CLASSES.HIGHLIGHT, enable); }); } catch (err) { console.error('Allonsh Error toggling dropzone highlight:', err); @@ -653,17 +670,18 @@ class Allonsh { addDraggable(element) { try { - if (!element.classList.contains('allonsh-draggable')) { - element.classList.add('allonsh-draggable'); + if (!element.classList.contains(CSS_CLASSES.DRAGGABLE)) { + element.classList.add(CSS_CLASSES.DRAGGABLE); } - element.style.cursor = 'grab'; + element.style.cursor = CSS_CURSORS.GRAB; element.addEventListener('mousedown', this._boundMouseDown); element.addEventListener('touchstart', this._boundTouchStart, { passive: false, }); - this.draggableElements = - this.playAreaElement.querySelectorAll('.allonsh-draggable'); + this.draggableElements = this.playAreaElement.querySelectorAll( + `.${CSS_CLASSES.DRAGGABLE}` + ); } catch (err) { console.error('Allonsh Error adding draggable element:', err); } @@ -674,12 +692,13 @@ class Allonsh { element.removeEventListener('mousedown', this._boundMouseDown); element.removeEventListener('touchstart', this._boundTouchStart); - if (element.classList.contains('allonsh-draggable')) { - element.classList.remove('allonsh-draggable'); + if (element.classList.contains(CSS_CLASSES.DRAGGABLE)) { + element.classList.remove(CSS_CLASSES.DRAGGABLE); } - this.draggableElements = - this.playAreaElement.querySelectorAll('.allonsh-draggable'); + this.draggableElements = this.playAreaElement.querySelectorAll( + `.${CSS_CLASSES.DRAGGABLE}` + ); } catch (err) { console.error('Allonsh Error removing draggable element:', err); } diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..6183e28 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,68 @@ +/** + * Allonsh Constants + * Centralized configuration for all hardcoded values used throughout the library + */ + +export const Z_INDEX = { + GHOST: 999, + DRAGGING: 1000, + DROPPED: 9999, + HEADER: 10, + THEME_TOGGLE: 9, + CONTROL_PANEL: 5, + FREEMODE_TITLE: 5, +}; + +export const DEFAULTS = { + STACK_SPACING: 5, + STACK_SPACING_DEMO: 10, + STACK_DIRECTION: 'horizontal', + STACK_DIRECTION_VERTICAL: 'vertical', + STACK_DIRECTION_HORIZONTAL: 'horizontal', +}; + +export const CSS_CLASSES = { + DROPZONE: 'allonsh-dropzone', + HIGHLIGHT: 'allonsh-highlight', + DRAGGABLE: 'allonsh-draggable', + RESTRICTED: 'restricted', +}; + +export const CSS_POSITIONS = { + RELATIVE: 'relative', + ABSOLUTE: 'absolute', +}; + +export const CSS_CURSORS = { + GRAB: 'grab', + GRABBING: 'grabbing', +}; + +export const OPACITY = { + GHOST: '0.3', + FULL: '1', +}; + +export const EVENTS = { + DRAG_START: 'allonsh-dragstart', + DROP: 'allonsh-drop', + DRAG_ENTER: 'allonsh-dragenter', + DRAG_LEAVE: 'allonsh-dragleave', +}; + +export const FLEX_DIRECTIONS = { + ROW: 'row', + COLUMN: 'column', +}; + +export const FLEX_WRAP = 'wrap'; + +export const DISPLAY_MODES = { + FLEX: 'flex', + NONE: 'none', +}; + +export const POINTER_EVENTS = { + NONE: 'none', + AUTO: 'auto', +}; diff --git a/test/allonsh.test.js b/test/allonsh.test.js index 58e9b6c..204c383 100644 --- a/test/allonsh.test.js +++ b/test/allonsh.test.js @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import Allonsh from '../src/allonsh.js'; +import { EVENTS } from '../src/constants.js'; function createDOM() { document.body.innerHTML = ` @@ -154,7 +155,7 @@ describe('Allonsh', () => { const el = allonsh.draggableElements[0]; const dropzone = allonsh.dropzoneElements[0]; const dropHandler = vi.fn(); - dropzone.addEventListener('allonsh-drop', dropHandler); + dropzone.addEventListener(EVENTS.DROP, dropHandler); document.elementFromPoint = vi.fn(() => dropzone);