neumorphic-peripheral
Version:
A lightweight, framework-agnostic JavaScript/TypeScript library for beautiful neumorphic styling
196 lines (195 loc) • 7.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseComponent = void 0;
const themes_1 = require("../themes");
const utils_1 = require("../utils");
class BaseComponent {
constructor(element, config = {}) {
this._listeners = [];
this._destroyed = false;
if (!(0, utils_1.isElement)(element)) {
throw new Error('Invalid element provided to component');
}
this._element = element;
this._config = { ...config };
this._theme = typeof config.theme === 'string'
? (0, themes_1.getCurrentTheme)()
: config.theme || (0, themes_1.getCurrentTheme)();
this.init();
this.bindEvents();
}
get element() {
return this._element;
}
get config() {
return { ...this._config };
}
bindEvents() {
// Listen for theme changes
this.addEventListener(window, 'np:theme-change', () => {
if (typeof this._config.theme === 'string') {
this._theme = (0, themes_1.getCurrentTheme)();
this.onThemeChange();
}
});
// Handle disabled state
if (this._config.disabled) {
this.setDisabled(true);
}
}
addEventListener(element, event, handler, options) {
element.addEventListener(event, handler, options);
this._listeners.push({ element, event, handler });
}
applyBaseStyles() {
// Add base component class
(0, utils_1.addClassName)(this._element, 'component');
// Apply custom className if provided
if (this._config.className) {
this._element.classList.add(this._config.className);
}
// Apply basic neumorphic styling
(0, utils_1.applyNeumorphicStyles)(this._element, {
backgroundColor: this._theme.colors.surface,
color: this._theme.colors.text,
borderRadius: this._theme.borderRadius,
transition: `all ${this._theme.animation.duration} ${this._theme.animation.easing}`,
outline: 'none'
}, this._theme);
}
onThemeChange() {
// Re-apply styles with new theme
this.applyBaseStyles();
}
setDisabled(disabled) {
this._config.disabled = disabled;
if (disabled) {
this._element.setAttribute('disabled', '');
this._element.setAttribute('aria-disabled', 'true');
(0, utils_1.addClassName)(this._element, 'disabled');
}
else {
this._element.removeAttribute('disabled');
this._element.removeAttribute('aria-disabled');
this._element.classList.remove('np-disabled');
}
}
emit(eventName, detail) {
const event = new CustomEvent(`np:${eventName}`, {
detail: {
instance: this,
...detail
},
bubbles: true,
cancelable: true
});
this._element.dispatchEvent(event);
}
update(newConfig) {
if (this._destroyed) {
console.warn('Cannot update destroyed component');
return;
}
const oldConfig = { ...this._config };
this._config = { ...this._config, ...newConfig };
// Update theme if changed
if (newConfig.theme && newConfig.theme !== oldConfig.theme) {
this._theme = typeof newConfig.theme === 'string'
? (0, themes_1.getCurrentTheme)()
: newConfig.theme;
this.onThemeChange();
}
// Update disabled state if changed
if (newConfig.disabled !== undefined && newConfig.disabled !== oldConfig.disabled) {
this.setDisabled(newConfig.disabled);
}
// Update className if changed
if (newConfig.className !== oldConfig.className) {
if (oldConfig.className) {
this._element.classList.remove(oldConfig.className);
}
if (newConfig.className) {
this._element.classList.add(newConfig.className);
}
}
this.onUpdate(newConfig, oldConfig);
}
onUpdate(newConfig, oldConfig) {
// Override in subclasses to handle specific config updates
}
destroy() {
if (this._destroyed)
return;
// Remove all event listeners
this._listeners.forEach(({ element, event, handler }) => {
element.removeEventListener(event, handler);
});
this._listeners = [];
// Remove classes
this._element.classList.remove('np-component');
if (this._config.className) {
this._element.classList.remove(this._config.className);
}
// Emit destroy event
this.emit('destroy');
// Perform component-specific cleanup
this.onDestroy();
this._destroyed = true;
}
onDestroy() {
// Override in subclasses for custom cleanup
}
// Utility methods for subclasses
createShadowStyle(variant = 'raised') {
const intensity = this._theme.shadowIntensity;
const { light, dark } = this._theme.colors.shadow;
const offset = Math.round(8 * intensity);
const blur = Math.round(16 * intensity);
const spread = Math.round(-2 * intensity);
switch (variant) {
case 'raised':
return `${offset}px ${offset}px ${blur}px ${spread}px ${dark}, -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'inset':
return `inset ${offset}px ${offset}px ${blur}px ${spread}px ${dark}, inset -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'flat':
return 'none';
default:
return 'none';
}
}
createHoverShadowStyle(variant = 'raised') {
const intensity = this._theme.shadowIntensity * 1.2; // Slightly more intense on hover
const { light, dark } = this._theme.colors.shadow;
const offset = Math.round(10 * intensity);
const blur = Math.round(20 * intensity);
const spread = Math.round(-1 * intensity);
switch (variant) {
case 'raised':
return `${offset}px ${offset}px ${blur}px ${spread}px ${dark}, -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'inset':
return `inset ${offset}px ${offset}px ${blur}px ${spread}px ${dark}, inset -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'flat':
return 'none';
default:
return 'none';
}
}
createActiveShadowStyle(variant = 'raised') {
const intensity = this._theme.shadowIntensity * 0.5; // Less intense when active
const { light, dark } = this._theme.colors.shadow;
const offset = Math.round(4 * intensity);
const blur = Math.round(8 * intensity);
const spread = Math.round(-1 * intensity);
switch (variant) {
case 'raised':
return `inset ${offset}px ${offset}px ${blur}px ${spread}px ${dark}, inset -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'inset':
return `${offset}px ${offset}px ${blur}px ${spread}px ${dark}, -${offset}px -${offset}px ${blur}px ${spread}px ${light}`;
case 'flat':
return 'none';
default:
return 'none';
}
}
}
exports.BaseComponent = BaseComponent;