added embedded option
This commit is contained in:
@@ -546,107 +546,179 @@ class Modal {
|
||||
this.container = null;
|
||||
this.isVisible = false;
|
||||
this.currentScreen = null;
|
||||
this.floatingTab = null;
|
||||
this.isEmbedded = false;
|
||||
this.embedContainer = null;
|
||||
|
||||
// Initialize modal container and styles
|
||||
this._initModal();
|
||||
|
||||
// Initialize floating tab if enabled (only for floating modals)
|
||||
if (this.options?.floatingTab?.enabled && !this.isEmbedded) {
|
||||
this._initFloatingTab();
|
||||
}
|
||||
}
|
||||
|
||||
_initModal() {
|
||||
// Check if embedded mode is requested
|
||||
if (this.options?.embedded) {
|
||||
this.isEmbedded = true;
|
||||
this.embedContainer = typeof this.options.embedded === 'string'
|
||||
? document.querySelector(this.options.embedded)
|
||||
: this.options.embedded;
|
||||
|
||||
if (!this.embedContainer) {
|
||||
console.error('NOSTR_LOGIN_LITE: Embed container not found:', this.options.embedded);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create modal container
|
||||
this.container = document.createElement('div');
|
||||
this.container.id = 'nl-modal';
|
||||
this.container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
display: none;
|
||||
z-index: 10000;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
`;
|
||||
this.container.id = this.isEmbedded ? 'nl-embedded-modal' : 'nl-modal';
|
||||
|
||||
if (this.isEmbedded) {
|
||||
// Embedded mode styles
|
||||
this.container.style.cssText = `
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
width: 100%;
|
||||
`;
|
||||
} else {
|
||||
// Floating mode styles
|
||||
this.container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
display: none;
|
||||
z-index: 10000;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
`;
|
||||
}
|
||||
|
||||
// Create modal content
|
||||
const modalContent = document.createElement('div');
|
||||
modalContent.style.cssText = `
|
||||
position: relative;
|
||||
background: white;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
if (this.isEmbedded) {
|
||||
// Embedded content styles
|
||||
if (this.options?.seamless) {
|
||||
// Seamless mode - no borders, shadows, or background
|
||||
modalContent.style.cssText = `
|
||||
background: transparent;
|
||||
`;
|
||||
} else {
|
||||
// Standard embedded mode
|
||||
modalContent.style.cssText = `
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
border: 1px solid #e5e7eb;
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Floating content styles
|
||||
modalContent.style.cssText = `
|
||||
position: relative;
|
||||
background: white;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
}
|
||||
|
||||
// Header
|
||||
const modalHeader = document.createElement('div');
|
||||
modalHeader.style.cssText = `
|
||||
padding: 20px 24px 0 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
// Header (optional for embedded mode)
|
||||
if (!this.isEmbedded || this.options?.showHeader !== false) {
|
||||
const modalHeader = document.createElement('div');
|
||||
modalHeader.style.cssText = `
|
||||
padding: 20px 24px 0 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const modalTitle = document.createElement('h2');
|
||||
modalTitle.textContent = 'Nostr Login';
|
||||
modalTitle.style.cssText = `
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
`;
|
||||
const modalTitle = document.createElement('h2');
|
||||
modalTitle.textContent = this.options?.title || 'Nostr Login';
|
||||
modalTitle.style.cssText = `
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
`;
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.innerHTML = '×';
|
||||
closeButton.onclick = () => this.close();
|
||||
closeButton.style.cssText = `
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
`;
|
||||
closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6';
|
||||
closeButton.onmouseout = () => closeButton.style.background = 'none';
|
||||
modalHeader.appendChild(modalTitle);
|
||||
|
||||
modalHeader.appendChild(modalTitle);
|
||||
modalHeader.appendChild(closeButton);
|
||||
// Close button (only for floating modals)
|
||||
if (!this.isEmbedded) {
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.innerHTML = '×';
|
||||
closeButton.onclick = () => this.close();
|
||||
closeButton.style.cssText = `
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
`;
|
||||
closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6';
|
||||
closeButton.onmouseout = () => closeButton.style.background = 'none';
|
||||
modalHeader.appendChild(closeButton);
|
||||
}
|
||||
|
||||
modalContent.appendChild(modalHeader);
|
||||
}
|
||||
|
||||
// Body
|
||||
this.modalBody = document.createElement('div');
|
||||
this.modalBody.style.cssText = `
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
max-height: 500px;
|
||||
${this.isEmbedded ? '' : 'max-height: 500px;'}
|
||||
`;
|
||||
|
||||
modalContent.appendChild(modalHeader);
|
||||
modalContent.appendChild(this.modalBody);
|
||||
this.container.appendChild(modalContent);
|
||||
|
||||
// Add to body
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
// Click outside to close
|
||||
this.container.onclick = (e) => {
|
||||
if (e.target === this.container) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
// Add to appropriate container
|
||||
if (this.isEmbedded) {
|
||||
this.embedContainer.appendChild(this.container);
|
||||
} else {
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
// Click outside to close (floating mode only)
|
||||
this.container.onclick = (e) => {
|
||||
if (e.target === this.container) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update theme
|
||||
this.updateTheme();
|
||||
}
|
||||
|
||||
_initFloatingTab() {
|
||||
if (this.floatingTab) {
|
||||
this.floatingTab.destroy();
|
||||
}
|
||||
|
||||
this.floatingTab = new FloatingTab(this, this.options.floatingTab);
|
||||
this.floatingTab.show();
|
||||
|
||||
console.log('NOSTR_LOGIN_LITE: Floating tab initialized');
|
||||
}
|
||||
|
||||
updateTheme() {
|
||||
const isDark = this.options?.darkMode;
|
||||
@@ -665,7 +737,12 @@ class Modal {
|
||||
open(opts = {}) {
|
||||
this.currentScreen = opts.startScreen;
|
||||
this.isVisible = true;
|
||||
this.container.style.display = 'block';
|
||||
|
||||
if (this.isEmbedded) {
|
||||
this.container.style.display = 'block';
|
||||
} else {
|
||||
this.container.style.display = 'block';
|
||||
}
|
||||
|
||||
// Render login options
|
||||
this._renderLoginOptions();
|
||||
@@ -673,8 +750,14 @@ class Modal {
|
||||
|
||||
close() {
|
||||
this.isVisible = false;
|
||||
this.container.style.display = 'none';
|
||||
this.modalBody.innerHTML = '';
|
||||
|
||||
if (this.isEmbedded) {
|
||||
// For embedded mode, just clear content but keep visible
|
||||
this.modalBody.innerHTML = '';
|
||||
} else {
|
||||
this.container.style.display = 'none';
|
||||
this.modalBody.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
_renderLoginOptions() {
|
||||
@@ -2007,6 +2090,32 @@ class Modal {
|
||||
static getInstance() {
|
||||
return Modal.instance;
|
||||
}
|
||||
|
||||
// Floating tab methods
|
||||
showFloatingTab() {
|
||||
if (this.floatingTab) {
|
||||
this.floatingTab.show();
|
||||
}
|
||||
}
|
||||
|
||||
hideFloatingTab() {
|
||||
if (this.floatingTab) {
|
||||
this.floatingTab.hide();
|
||||
}
|
||||
}
|
||||
|
||||
updateFloatingTab(options) {
|
||||
if (this.floatingTab) {
|
||||
this.floatingTab.updateOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
destroyFloatingTab() {
|
||||
if (this.floatingTab) {
|
||||
this.floatingTab.destroy();
|
||||
this.floatingTab = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize global instance
|
||||
@@ -2017,6 +2126,456 @@ window.addEventListener('load', () => {
|
||||
});
|
||||
|
||||
|
||||
// ======================================
|
||||
// Floating Tab Component
|
||||
// ======================================
|
||||
|
||||
class FloatingTab {
|
||||
constructor(modal, options = {}) {
|
||||
this.modal = modal;
|
||||
this.options = {
|
||||
enabled: true,
|
||||
hPosition: 1.0, // 100% from left (right edge) - can be decimal 0.0-1.0 or percentage '95%'
|
||||
vPosition: 0.5, // 50% from top (center) - can be decimal 0.0-1.0 or percentage '50%'
|
||||
offset: { x: 0, y: 0 },
|
||||
appearance: {
|
||||
style: 'pill',
|
||||
theme: 'auto',
|
||||
icon: '🔐',
|
||||
text: 'Login',
|
||||
iconOnly: false
|
||||
},
|
||||
behavior: {
|
||||
hideWhenAuthenticated: true,
|
||||
showUserInfo: true,
|
||||
autoSlide: true,
|
||||
persistent: false
|
||||
},
|
||||
animation: {
|
||||
slideDistance: '80%',
|
||||
slideDirection: 'auto', // 'auto', 'left', 'right', 'up', 'down'
|
||||
duration: '300ms',
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
},
|
||||
...options
|
||||
};
|
||||
|
||||
this.container = null;
|
||||
this.isVisible = false;
|
||||
this.isAuthenticated = false;
|
||||
this.userInfo = null;
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
_init() {
|
||||
this._createContainer();
|
||||
this._attachEventListeners();
|
||||
this._updateAppearance();
|
||||
}
|
||||
|
||||
_createContainer() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = 'nl-floating-tab';
|
||||
this.container.id = 'nl-floating-tab';
|
||||
|
||||
// Set CSS custom properties for animations
|
||||
this.container.style.setProperty('--animation-duration', this.options.animation.duration);
|
||||
this.container.style.setProperty('--animation-easing', this.options.animation.easing);
|
||||
this.container.style.setProperty('--slide-distance', this.options.animation.slideDistance);
|
||||
|
||||
// Base positioning styles
|
||||
this.container.style.cssText += `
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
cursor: pointer;
|
||||
transition: transform var(--animation-duration) var(--animation-easing);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
`;
|
||||
|
||||
this._updatePosition();
|
||||
this._updateStyle();
|
||||
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
|
||||
_updatePosition() {
|
||||
const { hPosition, vPosition, offset } = this.options;
|
||||
|
||||
// Reset positioning
|
||||
this.container.style.left = '';
|
||||
this.container.style.right = '';
|
||||
this.container.style.top = '';
|
||||
this.container.style.bottom = '';
|
||||
this.container.style.transform = '';
|
||||
|
||||
// Parse position values (handle both decimal and percentage)
|
||||
const hPos = this._parsePositionValue(hPosition);
|
||||
const vPos = this._parsePositionValue(vPosition);
|
||||
|
||||
// Horizontal positioning
|
||||
this.container.style.left = `calc(${hPos * 100}% + ${offset.x}px)`;
|
||||
|
||||
// Vertical positioning
|
||||
this.container.style.top = `calc(${vPos * 100}% + ${offset.y}px)`;
|
||||
|
||||
// Center the element on its position
|
||||
this.container.style.transform = 'translate(-50%, -50%)';
|
||||
|
||||
// Update CSS classes for styling context
|
||||
if (hPos < 0.5) {
|
||||
this.container.classList.add('nl-floating-tab--left');
|
||||
this.container.classList.remove('nl-floating-tab--right');
|
||||
} else {
|
||||
this.container.classList.add('nl-floating-tab--right');
|
||||
this.container.classList.remove('nl-floating-tab--left');
|
||||
}
|
||||
|
||||
// Initial slide-out state
|
||||
this._updateSlideState(false);
|
||||
}
|
||||
|
||||
_parsePositionValue(value) {
|
||||
if (typeof value === 'string' && value.endsWith('%')) {
|
||||
return parseFloat(value) / 100;
|
||||
}
|
||||
return Math.max(0, Math.min(1, parseFloat(value) || 0));
|
||||
}
|
||||
|
||||
_updateStyle() {
|
||||
const { appearance } = this.options;
|
||||
const isDark = this._isDarkMode();
|
||||
|
||||
// Base styles
|
||||
let baseStyles = `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
`;
|
||||
|
||||
// Style-specific modifications
|
||||
switch (appearance.style) {
|
||||
case 'pill':
|
||||
if (this.options.position === 'left') {
|
||||
baseStyles += `border-radius: 0 25px 25px 0;`;
|
||||
} else {
|
||||
baseStyles += `border-radius: 25px 0 0 25px;`;
|
||||
}
|
||||
break;
|
||||
case 'square':
|
||||
if (this.options.position === 'left') {
|
||||
baseStyles += `border-radius: 0 8px 8px 0;`;
|
||||
} else {
|
||||
baseStyles += `border-radius: 8px 0 0 8px;`;
|
||||
}
|
||||
break;
|
||||
case 'circle':
|
||||
baseStyles += `
|
||||
border-radius: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 12px;
|
||||
justify-content: center;
|
||||
`;
|
||||
break;
|
||||
case 'minimal':
|
||||
baseStyles += `
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
`;
|
||||
break;
|
||||
}
|
||||
|
||||
// Theme colors
|
||||
if (isDark) {
|
||||
baseStyles += `
|
||||
background: rgba(31, 41, 55, 0.95);
|
||||
color: white;
|
||||
border: 1px solid rgba(75, 85, 99, 0.8);
|
||||
`;
|
||||
} else {
|
||||
baseStyles += `
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: #1f2937;
|
||||
border: 1px solid rgba(209, 213, 219, 0.8);
|
||||
`;
|
||||
}
|
||||
|
||||
this.container.style.cssText += baseStyles;
|
||||
}
|
||||
|
||||
_updateAppearance() {
|
||||
const { appearance } = this.options;
|
||||
|
||||
// Clear existing content
|
||||
this.container.innerHTML = '';
|
||||
|
||||
if (this.isAuthenticated && this.options.behavior.showUserInfo && this.userInfo) {
|
||||
this._renderAuthenticatedState();
|
||||
} else {
|
||||
this._renderUnauthenticatedState();
|
||||
}
|
||||
}
|
||||
|
||||
_renderUnauthenticatedState() {
|
||||
const { appearance } = this.options;
|
||||
|
||||
// Icon
|
||||
if (appearance.icon) {
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.textContent = appearance.icon;
|
||||
iconEl.style.cssText = `
|
||||
font-size: 18px;
|
||||
${appearance.iconOnly || appearance.style === 'circle' ? '' : 'margin-right: 8px;'}
|
||||
`;
|
||||
this.container.appendChild(iconEl);
|
||||
}
|
||||
|
||||
// Text (unless icon-only or circle style)
|
||||
if (!appearance.iconOnly && appearance.style !== 'circle' && appearance.text) {
|
||||
const textEl = document.createElement('span');
|
||||
textEl.textContent = appearance.text;
|
||||
textEl.style.cssText = `
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
this.container.appendChild(textEl);
|
||||
}
|
||||
}
|
||||
|
||||
_renderAuthenticatedState() {
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.textContent = '🚪';
|
||||
iconEl.style.cssText = `
|
||||
font-size: 18px;
|
||||
${this.options.appearance.style === 'circle' ? '' : 'margin-right: 8px;'}
|
||||
`;
|
||||
this.container.appendChild(iconEl);
|
||||
|
||||
if (this.options.appearance.style !== 'circle') {
|
||||
const textEl = document.createElement('span');
|
||||
if (this.userInfo) {
|
||||
const displayName = this.userInfo.name || this.userInfo.display_name || 'User';
|
||||
textEl.textContent = `Logout (${displayName.length > 8 ? displayName.substring(0, 8) + '...' : displayName})`;
|
||||
} else {
|
||||
textEl.textContent = 'Logout';
|
||||
}
|
||||
textEl.style.cssText = `
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
this.container.appendChild(textEl);
|
||||
}
|
||||
}
|
||||
|
||||
_attachEventListeners() {
|
||||
// Click handler
|
||||
this.container.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.isAuthenticated && this.options.behavior.showUserInfo) {
|
||||
// Logout when authenticated
|
||||
if (typeof window !== 'undefined' && window.NOSTR_LOGIN_LITE) {
|
||||
window.NOSTR_LOGIN_LITE.logout();
|
||||
}
|
||||
this._dispatchEvent('nlFloatingTabUserClick', { userInfo: this.userInfo });
|
||||
} else {
|
||||
// Open login modal when not authenticated
|
||||
this.modal.open();
|
||||
this._dispatchEvent('nlFloatingTabClick', {});
|
||||
}
|
||||
});
|
||||
|
||||
// Hover effects for auto-slide
|
||||
if (this.options.behavior.autoSlide) {
|
||||
this.container.addEventListener('mouseenter', () => {
|
||||
this._updateSlideState(true);
|
||||
});
|
||||
|
||||
this.container.addEventListener('mouseleave', () => {
|
||||
this._updateSlideState(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Authentication event listeners
|
||||
window.addEventListener('nlAuth', (event) => {
|
||||
this.updateAuthState(true, event.detail);
|
||||
});
|
||||
|
||||
window.addEventListener('nlLogout', () => {
|
||||
this.updateAuthState(false, null);
|
||||
});
|
||||
|
||||
// Responsive updates
|
||||
window.addEventListener('resize', () => {
|
||||
this._handleResize();
|
||||
});
|
||||
}
|
||||
|
||||
_updateSlideState(isHovered) {
|
||||
if (!this.options.behavior.autoSlide) return;
|
||||
|
||||
const { hPosition, vPosition, animation } = this.options;
|
||||
const { slideDistance, slideDirection } = animation;
|
||||
|
||||
// Parse positions
|
||||
const hPos = this._parsePositionValue(hPosition);
|
||||
const vPos = this._parsePositionValue(vPosition);
|
||||
|
||||
// Determine slide direction
|
||||
let direction = slideDirection;
|
||||
if (direction === 'auto') {
|
||||
// Auto-detect based on position
|
||||
if (hPos < 0.25) direction = 'left';
|
||||
else if (hPos > 0.75) direction = 'right';
|
||||
else if (vPos < 0.25) direction = 'up';
|
||||
else if (vPos > 0.75) direction = 'down';
|
||||
else direction = hPos < 0.5 ? 'left' : 'right'; // Default to horizontal
|
||||
}
|
||||
|
||||
// Base transform (centering)
|
||||
let transform = 'translate(-50%, -50%)';
|
||||
|
||||
if (!isHovered) {
|
||||
// Add slide offset based on direction
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
transform += ` translateX(calc(-1 * ${slideDistance}))`;
|
||||
break;
|
||||
case 'right':
|
||||
transform += ` translateX(${slideDistance})`;
|
||||
break;
|
||||
case 'up':
|
||||
transform += ` translateY(calc(-1 * ${slideDistance}))`;
|
||||
break;
|
||||
case 'down':
|
||||
transform += ` translateY(${slideDistance})`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.container.style.transform = transform.trim();
|
||||
}
|
||||
|
||||
_handleResize() {
|
||||
// Update positioning on window resize
|
||||
this._updatePosition();
|
||||
|
||||
// Handle responsive design
|
||||
const width = window.innerWidth;
|
||||
if (width < 768) {
|
||||
// Mobile: force icon-only mode
|
||||
this._setResponsiveMode('mobile');
|
||||
} else if (width < 1024) {
|
||||
// Tablet: abbreviated text
|
||||
this._setResponsiveMode('tablet');
|
||||
} else {
|
||||
// Desktop: full text
|
||||
this._setResponsiveMode('desktop');
|
||||
}
|
||||
}
|
||||
|
||||
_setResponsiveMode(mode) {
|
||||
const originalIconOnly = this.options.appearance.iconOnly;
|
||||
|
||||
switch (mode) {
|
||||
case 'mobile':
|
||||
this.options.appearance.iconOnly = true;
|
||||
break;
|
||||
case 'tablet':
|
||||
this.options.appearance.iconOnly = originalIconOnly;
|
||||
if (this.options.appearance.text && this.options.appearance.text.length > 8) {
|
||||
// Abbreviate text on tablet
|
||||
this.options.appearance.text = this.options.appearance.text.substring(0, 6) + '...';
|
||||
}
|
||||
break;
|
||||
case 'desktop':
|
||||
// Restore original settings
|
||||
break;
|
||||
}
|
||||
|
||||
this._updateAppearance();
|
||||
}
|
||||
|
||||
_isDarkMode() {
|
||||
const { theme } = this.options.appearance;
|
||||
|
||||
if (theme === 'dark') return true;
|
||||
if (theme === 'light') return false;
|
||||
|
||||
// Auto-detect
|
||||
if (this.modal && this.modal.options && this.modal.options.darkMode) {
|
||||
return this.modal.options.darkMode;
|
||||
}
|
||||
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
_dispatchEvent(eventName, detail) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent(eventName, { detail }));
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
show() {
|
||||
if (this.container && !this.isVisible) {
|
||||
this.container.style.display = 'flex';
|
||||
this.isVisible = true;
|
||||
|
||||
// Trigger initial slide state
|
||||
if (this.options.behavior.autoSlide) {
|
||||
setTimeout(() => this._updateSlideState(false), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.container && this.isVisible) {
|
||||
this.container.style.display = 'none';
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateOptions(newOptions) {
|
||||
this.options = { ...this.options, ...newOptions };
|
||||
this._updatePosition();
|
||||
this._updateStyle();
|
||||
this._updateAppearance();
|
||||
}
|
||||
|
||||
updateAuthState(isAuthenticated, userInfo = null) {
|
||||
this.isAuthenticated = isAuthenticated;
|
||||
this.userInfo = userInfo;
|
||||
|
||||
if (isAuthenticated && this.options.behavior.hideWhenAuthenticated) {
|
||||
this.hide();
|
||||
} else {
|
||||
this._updateAppearance();
|
||||
if (!this.isVisible) {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.container) {
|
||||
this.container.remove();
|
||||
this.container = null;
|
||||
}
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// Main NOSTR_LOGIN_LITE Library
|
||||
// ======================================
|
||||
@@ -2080,7 +2639,7 @@ class NostrLite {
|
||||
async init(options = {}) {
|
||||
console.log('NOSTR_LOGIN_LITE: Initializing with options:', options);
|
||||
|
||||
this.options = {
|
||||
this.options = this._deepMerge({
|
||||
theme: 'light',
|
||||
darkMode: false,
|
||||
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
|
||||
@@ -2091,8 +2650,32 @@ class NostrLite {
|
||||
connect: false,
|
||||
otp: false
|
||||
},
|
||||
...options
|
||||
};
|
||||
floatingTab: {
|
||||
enabled: false,
|
||||
hPosition: 1.0, // 100% from left (right edge)
|
||||
vPosition: 0.5, // 50% from top (center)
|
||||
offset: { x: 0, y: 0 },
|
||||
appearance: {
|
||||
style: 'pill',
|
||||
theme: 'auto',
|
||||
icon: '🔐',
|
||||
text: 'Login',
|
||||
iconOnly: false
|
||||
},
|
||||
behavior: {
|
||||
hideWhenAuthenticated: true,
|
||||
showUserInfo: true,
|
||||
autoSlide: true,
|
||||
persistent: false
|
||||
},
|
||||
animation: {
|
||||
slideDistance: '80%',
|
||||
slideDirection: 'auto', // 'auto', 'left', 'right', 'up', 'down'
|
||||
duration: '300ms',
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}
|
||||
}
|
||||
}, options);
|
||||
|
||||
// Set up window.nostr facade if no extension detected
|
||||
if (this.extensionBridge.getExtensionCount() === 0) {
|
||||
@@ -2105,9 +2688,28 @@ class NostrLite {
|
||||
// Set up event listeners for authentication flow
|
||||
this._setupAuthEventHandlers();
|
||||
|
||||
// Initialize modal with floating tab support
|
||||
this.modal = new Modal(this.options);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_deepMerge(target, source) {
|
||||
const result = { ...target };
|
||||
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
result[key] = this._deepMerge(target[key] || {}, source[key]);
|
||||
} else {
|
||||
result[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_setupWindowNostrFacade() {
|
||||
if (typeof window !== 'undefined' && !window.nostr) {
|
||||
window.nostr = new WindowNostr(this);
|
||||
@@ -2289,9 +2891,11 @@ class NostrLite {
|
||||
launch(startScreen = 'login') {
|
||||
console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen);
|
||||
|
||||
if (typeof Modal !== 'undefined') {
|
||||
const modal = new Modal(this.options);
|
||||
modal.open({ startScreen });
|
||||
if (this.modal) {
|
||||
this.modal.open({ startScreen });
|
||||
} else if (typeof Modal !== 'undefined') {
|
||||
this.modal = new Modal(this.options);
|
||||
this.modal.open({ startScreen });
|
||||
} else {
|
||||
console.error('NOSTR_LOGIN_LITE: Modal component not available');
|
||||
}
|
||||
@@ -2312,6 +2916,45 @@ class NostrLite {
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Floating tab methods
|
||||
showFloatingTab() {
|
||||
if (this.modal) {
|
||||
this.modal.showFloatingTab();
|
||||
}
|
||||
}
|
||||
|
||||
hideFloatingTab() {
|
||||
if (this.modal) {
|
||||
this.modal.hideFloatingTab();
|
||||
}
|
||||
}
|
||||
|
||||
updateFloatingTab(options) {
|
||||
if (this.modal) {
|
||||
this.modal.updateFloatingTab(options);
|
||||
}
|
||||
}
|
||||
|
||||
destroyFloatingTab() {
|
||||
if (this.modal) {
|
||||
this.modal.destroyFloatingTab();
|
||||
}
|
||||
}
|
||||
|
||||
embed(container, options = {}) {
|
||||
const embedOptions = {
|
||||
...this.options,
|
||||
...options,
|
||||
embedded: container
|
||||
};
|
||||
|
||||
// Create new modal instance for embedding
|
||||
const embeddedModal = new Modal(embedOptions);
|
||||
embeddedModal.open();
|
||||
|
||||
return embeddedModal;
|
||||
}
|
||||
}
|
||||
|
||||
// Window.nostr facade for when no extension is available
|
||||
@@ -2365,6 +3008,15 @@ if (typeof window !== 'undefined') {
|
||||
launch: (startScreen) => nostrLite.launch(startScreen),
|
||||
logout: () => nostrLite.logout(),
|
||||
|
||||
// Embedded modal method
|
||||
embed: (container, options) => nostrLite.embed(container, options),
|
||||
|
||||
// Floating tab methods
|
||||
showFloatingTab: () => nostrLite.showFloatingTab(),
|
||||
hideFloatingTab: () => nostrLite.hideFloatingTab(),
|
||||
updateFloatingTab: (options) => nostrLite.updateFloatingTab(options),
|
||||
destroyFloatingTab: () => nostrLite.destroyFloatingTab(),
|
||||
|
||||
// Expose for debugging
|
||||
_extensionBridge: nostrLite.extensionBridge,
|
||||
_instance: nostrLite
|
||||
|
||||
Reference in New Issue
Block a user