website with slop placeholders

This commit is contained in:
udo
2026-04-22 13:31:51 +00:00
parent 14ebf10a22
commit 223cf4c6e1
336 changed files with 66009 additions and 116 deletions
+8
View File
File diff suppressed because one or more lines are too long
+8454
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+77
View File
@@ -0,0 +1,77 @@
var enable_debug = true;
var starterReady = (typeof $ !== 'undefined' && $.ready)
? $.ready.bind($)
: function(callback) {
if (document.readyState !== 'loading') {
callback();
} else {
document.addEventListener('DOMContentLoaded', callback);
}
};
var UI = {
smoothScrollToNamedAnchors: function() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
},
enablePageTransitions: function() {
var style = document.createElement('style');
style.textContent = `
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.25s;
}
::view-transition-old(root) {
animation-name: fade-out;
}
::view-transition-new(root) {
animation-name: fade-in;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}`;
document.body.appendChild(style);
document.addEventListener('click', e => {
const link = e.target.closest('a[href]');
if (!link) return;
e.preventDefault();
document.startViewTransition(() => {
window.location.href = link.href;
});
});
},
init: function() {
//UI.enablePageTransitions();
UI.smoothScrollToNamedAnchors();
document.body.classList.add('loaded');
},
}
starterReady(UI.init);
+897
View File
@@ -0,0 +1,897 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-EventEmitter Demo</title>
<style>
:root {
--space: 8px;
--radius: 5px;
--gray: #6c757d;
--gray-bg: #f5f5f5;
--blue: #007acc;
--green: #28a745;
--white: white;
--dark: #333;
--max-width: 1400px;
--sidebar: 400px;
}
* { box-sizing: border-box; }
body {
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: var(--max-width);
margin: 0 auto;
padding: var(--space);
background: var(--gray-bg);
}
.container {
background: var(--white);
padding: calc(var(--space) * 1.5);
border-radius: var(--radius);
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--space) * 1.5);
}
.api-column {
background: var(--gray-bg);
padding: var(--space);
border-radius: var(--radius);
border-left: 4px solid var(--green);
}
h1 {
color: var(--dark);
text-align: center;
margin-bottom: calc(var(--space) * 1.5);
grid-column: 1 / -1;
}
h2 {
color: var(--gray);
border-bottom: 2px solid #e0e0e0;
padding-bottom: var(--space);
margin-top: calc(var(--space) * 1.5);
}
.demo-section {
margin: var(--space) 0;
padding: var(--space);
background: var(--gray-bg);
border-radius: var(--radius);
border-left: 4px solid var(--blue);
}
.controls, .input-group {
display: flex;
gap: var(--space);
margin: 15px 0;
}
.controls { flex-wrap: wrap; }
.input-group { align-items: center; }
.feature-grid { display: grid; gap: var(--space); margin: var(--space) 0; }
button {
background: var(--blue);
color: var(--white);
border: none;
padding: var(--space) var(--space);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.3s ease;
}
button:hover { filter: brightness(0.9); }
button:disabled { background: #ccc; cursor: not-allowed; }
input[type="text"] {
padding: var(--space);
border: 1px solid #ddd;
border-radius: var(--radius);
flex: 1;
}
.status, .code-example {
font-family: monospace;
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
}
.status {
background: var(--dark);
color: #0f0;
white-space: pre-wrap;
max-height: 150px;
overflow-y: auto;
}
.code-example {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
overflow-x: auto;
margin: var(--space) 0;
}
.highlight { color: #68d391; }
.keyword { color: #fbb6ce; }
.string { color: #fbd38d; }
.event-visual {
height: 100px;
background: linear-gradient(45deg, #1e3c72, #2a5298);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-weight: bold;
margin: 15px 0;
position: relative;
overflow: hidden;
}
.pulse-wave {
position: absolute;
inset: 0;
opacity: 0.3;
background: radial-gradient(circle at center, rgba(255,255,255,0.2) 0%, transparent 70%);
animation: pulse 2s ease-in-out infinite;
}
.event-indicator {
position: absolute;
top: 10px;
right: 10px;
width: 12px;
height: 12px;
background: #68d391;
border-radius: 50%;
opacity: 0;
transition: opacity 0.3s ease;
}
.event-indicator.active {
opacity: 1;
animation: blink 0.5s ease-in-out;
}
@keyframes pulse {
0%, 100% { transform: scale(0.8); opacity: 0.3; }
50% { transform: scale(1.2); opacity: 0.1; }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.api-section { margin: 15px 0; }
.api-section h3 {
color: var(--green);
margin: 0 0 var(--space) 0;
}
.api-method {
font-family: monospace;
margin: 3px 0;
color: var(--gray);
}
.api-method .method-name { color: var(--blue); font-weight: bold; }
.api-method .return-type { color: #6f42c1; }
.api-method .param { color: #e83e8c; }
.api-description {
margin: var(--space) 0;
}
.api-options {
color: var(--gray);
}
.help-text {
color: var(--gray);
margin: var(--space) 0;
}
.code-comment {
color: #68d391;
}
.event-log {
background: var(--dark);
color: #0f0;
font-family: monospace;
padding: var(--space);
border-radius: var(--radius);
height: 150px;
overflow-y: auto;
white-space: pre-wrap;
margin: var(--space) 0;
}
.listener-counter {
background: var(--blue);
color: var(--white);
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
margin-left: 8px;
}
@media (max-width: 1200px) {
.main-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<h1>U-EventEmitter Demo</h1>
<div class="main-layout">
<div class="demo-column">
<h2>Basic Event System</h2>
<div class="demo-section">
<div class="controls">
<button onclick="basicDemo()">Basic Subscribe & Emit</button>
<button onclick="multipleListenersDemo()">Add Multiple Listeners</button>
<button onclick="emitToMultiple()">Emit to All</button>
</div>
<div class="status" id="basic-output"></div>
</div>
<div class="demo-section">
<h3>Advanced Features</h3>
<div class="controls">
<button onclick="slotDemo()">Slot-based Handlers</button>
<button onclick="replaceSlotHandler()">Replace Handler</button>
<button onclick="emitSlotEvent()">Test Slot System</button>
</div>
<div class="controls">
<button onclick="autoRemovalDemo()">Self-removing Handler</button>
<button onclick="triggerAutoRemoval()">Trigger Auto-removal</button>
</div>
<div class="status" id="advanced-output"></div>
</div>
<div class="demo-section">
<h3>Interactive Event System</h3>
<div class="input-group">
<input type="text" id="event-name" placeholder="Event name" value="chat">
<input type="text" id="event-data" placeholder="Event data" value="Hello EventEmitter!">
</div>
<div class="controls">
<button onclick="addListener()">Add Listener <span id="listenerCount" class="listener-counter">0</span></button>
<button onclick="emitCustomEvent()">Emit Event</button>
<button onclick="removeAllListeners()">Clear Listeners</button>
</div>
<div class="event-log" id="event-log">Ready for interactive events...</div>
</div>
</div>
<div class="api-column">
<h2>API Overview</h2>
<div class="api-section">
<h3>EventEmitter Constructor</h3>
<div class="api-method"><span class="keyword">new</span> <span class="method-name">EventEmitter</span>()</div>
<div class="api-description">
Creates a new event emitter instance for custom event communication.
</div>
</div>
<div class="api-section">
<h3>Core Methods</h3>
<div class="api-method"><span class="method-name">on</span>(<span class="param">event, handler, slot?</span>) → <span class="return-type">EventEmitter</span></div>
<div class="api-method"><span class="method-name">emit</span>(<span class="param">event, ...args</span>) → <span class="return-type">number</span></div>
<div class="api-method"><span class="method-name">off</span>(<span class="param">event, handler?</span>) → <span class="return-type">EventEmitter</span></div>
<div class="api-method"><span class="method-name">clear</span>(<span class="param">event?</span>) → <span class="return-type">EventEmitter</span></div>
</div>
<div class="api-section">
<h3>Key Features</h3>
<div class="api-description">
<strong>Slot-based Deduplication:</strong> Use slot parameter to replace existing handlers<br>
<strong>Auto-removal:</strong> Handlers returning 'remove_handler' are automatically unsubscribed<br>
<strong>Return Values:</strong> emit() returns the number of handlers called<br>
<strong>Flexible Arguments:</strong> Pass any number of arguments to handlers
</div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Basic usage</span>
<span class="keyword">const</span> emitter = <span class="keyword">new</span> <span class="highlight">EventEmitter</span>();
emitter.on(<span class="string">'message'</span>, (data) => {
console.log(<span class="string">'Received:'</span>, data);
});
emitter.emit(<span class="string">'message'</span>, <span class="string">'Hello World'</span>);
<span class="code-comment">// Slot-based replacement</span>
emitter.on(<span class="string">'update'</span>, handler1, <span class="string">'ui-updater'</span>);
emitter.on(<span class="string">'update'</span>, handler2, <span class="string">'ui-updater'</span>); <span class="code-comment">// Replaces handler1</span>
<span class="code-comment">// Self-removing handler</span>
emitter.on(<span class="string">'init'</span>, () => {
console.log(<span class="string">'Initialized!'</span>);
<span class="keyword">return</span> <span class="string">'remove_handler'</span>; <span class="code-comment">// Removes itself</span>
});
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Event Patterns</h2>
<div class="demo-section">
<h3>Publisher-Subscriber Pattern</h3>
<div class="controls">
<button onclick="createPublisher()">Create Publisher</button>
<button onclick="addSubscribers()">Add Subscribers</button>
<button onclick="publishNews()">Publish News</button>
</div>
<div class="status" id="pubsub-output"></div>
</div>
<div class="demo-section">
<h3>Component Communication</h3>
<div class="controls">
<button onclick="setupComponents()">Setup Components</button>
<button onclick="componentInteraction()">Trigger Interaction</button>
<button onclick="cascadeEvents()">Cascade Events</button>
</div>
<div class="status" id="component-output"></div>
</div>
<div class="demo-section">
<h3>Handler Counting & Management</h3>
<div class="controls">
<button onclick="handlerCountDemo()">Count Handlers</button>
<button onclick="benchmarkEmission()">Benchmark Emission</button>
</div>
<div class="status" id="count-output"></div>
</div>
</div>
<div class="api-column">
<h2>Advanced Usage</h2>
<div class="api-section">
<h3>Event Management</h3>
<div class="api-description">
<strong>on(event, handler, slot)</strong> - Subscribe to events<br>
• event: String event name<br>
• handler: Function to call when event is emitted<br>
• slot: Optional string key for handler replacement<br><br>
<strong>emit(event, ...args)</strong> - Emit events to all listeners<br>
• Returns the number of handlers that were called<br>
• Passes all additional arguments to handlers<br><br>
<strong>off(event, handler)</strong> - Remove specific handler<br>
• If handler omitted, removes all handlers for event
</div>
</div>
<div class="api-section">
<h3>Slot System</h3>
<div class="api-description">
The slot system prevents duplicate handlers by using string keys:<br>
• Same slot key replaces previous handler<br>
• Useful for UI updates, state management<br>
• Prevents memory leaks from repeated subscriptions<br>
• Slot keys are per-event, not global
</div>
<pre class="code-example">
<span class="code-comment">// Without slots: multiple handlers</span>
emitter.on(<span class="string">'render'</span>, updateUI);
emitter.on(<span class="string">'render'</span>, updateUI); <span class="code-comment">// Now 2 handlers</span>
<span class="code-comment">// With slots: automatic replacement</span>
emitter.on(<span class="string">'render'</span>, updateUI, <span class="string">'ui'</span>);
emitter.on(<span class="string">'render'</span>, updateUI, <span class="string">'ui'</span>); <span class="code-comment">// Still 1 handler</span>
</pre>
</div>
<div class="api-section">
<h3>Performance Patterns</h3>
<div class="api-description">
<strong>Event Namespacing:</strong> Use dot notation for hierarchical events<br>
<strong>Batch Operations:</strong> Group related events for efficiency<br>
<strong>Handler Cleanup:</strong> Use slots or off() to prevent memory leaks<br>
<strong>Conditional Emission:</strong> Check handler count before expensive operations
</div>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
</div>
<div class="api-column">
<div class="api-section">
<h3>Memory Management</h3>
<div class="api-description">
<strong>Use Slots:</strong> Prevent duplicate handlers with slot keys<br>
<strong>Clean Up:</strong> Call off() when components are destroyed<br>
<strong>Self-removal:</strong> Use 'remove_handler' return for one-time events<br>
<strong>Clear All:</strong> Use clear() to remove all listeners for an event
</div>
</div>
<div class="api-section">
<h3>Common Patterns</h3>
<pre class="code-example">
<span class="code-comment">// Request-Response pattern</span>
emitter.on(<span class="string">'data.request'</span>, (type, callback) => {
<span class="keyword">const</span> data = fetchData(type);
callback(data);
});
<span class="code-comment">// State change notifications</span>
emitter.on(<span class="string">'state.change'</span>, (oldState, newState) => {
updateUI(newState);
logStateChange(oldState, newState);
});
<span class="code-comment">// Error handling</span>
emitter.on(<span class="string">'error'</span>, (error, context) => {
console.error(<span class="string">'Error in'</span>, context, error);
showErrorToUser(error.message);
});
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Library Info</h2>
<div class="demo-section">
<div class="status" id="libraryInfo">Loading library information...</div>
<h3>Event Statistics:</h3>
<div id="eventStats" class="status"></div>
</div>
</div>
<div class="api-column">
</div>
</div>
</div>
<script src="u-events.js"></script>
<script>
const demoEmitter = new EventEmitter();
const interactiveEmitter = new EventEmitter();
const publisherEmitter = new EventEmitter();
const gameEmitter = new EventEmitter();
const uiEmitter = new EventEmitter();
let listenerCount = 0;
let gameStats = {
eventsEmitted: 0,
handlersExecuted: 0,
totalEvents: 0
};
function log(message, outputId = 'basic-output') {
const output = document.getElementById(outputId);
const timestamp = new Date().toLocaleTimeString();
output.textContent += `[${timestamp}] ${message}\n`;
output.scrollTop = output.scrollHeight;
flashIndicator('basicIndicator');
}
function clearOutput(outputId) {
document.getElementById(outputId).textContent = '';
}
function flashIndicator(indicatorId) {
const indicator = document.getElementById(indicatorId);
if (indicator) {
indicator.classList.remove('active');
setTimeout(() => indicator.classList.add('active'), 10);
setTimeout(() => indicator.classList.remove('active'), 500);
}
}
function basicDemo() {
clearOutput('basic-output');
demoEmitter.on('greeting', (name) => {
log(`Hello, ${name}!`, 'basic-output');
});
demoEmitter.emit('greeting', 'World');
demoEmitter.emit('greeting', 'EventEmitter');
gameStats.eventsEmitted += 2;
updateEventStats();
}
function multipleListenersDemo() {
demoEmitter.on('multi-test', (data) => {
log(`Handler 1: ${data}`, 'basic-output');
});
demoEmitter.on('multi-test', (data) => {
log(`Handler 2: ${data}`, 'basic-output');
});
demoEmitter.on('multi-test', (data) => {
log(`Handler 3: ${data}`, 'basic-output');
});
log('Added 3 listeners for "multi-test" event', 'basic-output');
}
function emitToMultiple() {
const count = demoEmitter.emit('multi-test', 'Hello from multiple demo!');
log(`Event emitted to ${count} handlers`, 'basic-output');
gameStats.eventsEmitted++;
gameStats.handlersExecuted += count;
updateEventStats();
}
function slotDemo() {
clearOutput('advanced-output');
demoEmitter.on('slot-event', (msg) => {
log(`Slot handler v1: ${msg}`, 'advanced-output');
}, 'demo-slot');
log('Added handler with slot key "demo-slot"', 'advanced-output');
}
function replaceSlotHandler() {
demoEmitter.on('slot-event', (msg) => {
log(`Slot handler v2 (replaced): ${msg}`, 'advanced-output');
}, 'demo-slot');
log('Replaced handler using same slot key', 'advanced-output');
}
function emitSlotEvent() {
const count = demoEmitter.emit('slot-event', 'Testing slot replacement');
log(`Emitted to ${count} handler(s)`, 'advanced-output');
gameStats.eventsEmitted++;
gameStats.handlersExecuted += count;
updateEventStats();
}
let autoRemovalCount = 0;
function autoRemovalDemo() {
demoEmitter.on('auto-remove', (msg) => {
autoRemovalCount++;
log(`Auto-removal handler called ${autoRemovalCount} time(s): ${msg}`, 'advanced-output');
return 'remove_handler';
});
log('Added self-removing handler', 'advanced-output');
}
function triggerAutoRemoval() {
const count = demoEmitter.emit('auto-remove', 'This handler will remove itself');
log(`Handlers called: ${count}`, 'advanced-output');
setTimeout(() => {
const count2 = demoEmitter.emit('auto-remove', 'This should call 0 handlers');
log(`Second emit - handlers called: ${count2}`, 'advanced-output');
}, 1000);
gameStats.eventsEmitted += 2;
gameStats.handlersExecuted += count;
updateEventStats();
}
function addListener() {
const eventName = document.getElementById('event-name').value;
if (!eventName) return;
listenerCount++;
const listenerId = listenerCount;
interactiveEmitter.on(eventName, (data) => {
const logEl = document.getElementById('event-log');
logEl.textContent += `[Listener ${listenerId}] ${eventName}: ${data}\n`;
logEl.scrollTop = logEl.scrollHeight;
});
const logEl = document.getElementById('event-log');
logEl.textContent += `Added listener ${listenerId} for "${eventName}"\n`;
logEl.scrollTop = logEl.scrollHeight;
updateListenerCounter();
}
function emitCustomEvent() {
const eventName = document.getElementById('event-name').value;
const eventData = document.getElementById('event-data').value;
if (!eventName) return;
const count = interactiveEmitter.emit(eventName, eventData);
const logEl = document.getElementById('event-log');
logEl.textContent += `Emitted "${eventName}" to ${count} listener(s)\n`;
logEl.scrollTop = logEl.scrollHeight;
gameStats.eventsEmitted++;
gameStats.handlersExecuted += count;
updateEventStats();
}
function removeAllListeners() {
const eventName = document.getElementById('event-name').value;
if (!eventName) return;
interactiveEmitter.off(eventName);
const logEl = document.getElementById('event-log');
logEl.textContent += `Removed all listeners for "${eventName}"\n`;
logEl.scrollTop = logEl.scrollHeight;
}
function updateListenerCounter() {
const counter = document.getElementById('listenerCount');
counter.textContent = listenerCount.toString();
}
function createPublisher() {
clearOutput('pubsub-output');
log('Publisher created', 'pubsub-output');
}
function addSubscribers() {
publisherEmitter.on('news', (headline, content) => {
log(`News Subscriber: ${headline}`, 'pubsub-output');
});
publisherEmitter.on('news', (headline, content) => {
log(`Mobile App: New article "${headline}"`, 'pubsub-output');
});
publisherEmitter.on('news', (headline, content) => {
log(`Email Service: Sending newsletter with "${headline}"`, 'pubsub-output');
});
log('Added 3 subscribers to news events', 'pubsub-output');
}
function publishNews() {
const headlines = [
'EventEmitter Pattern Increases Developer Productivity',
'New Features Added to Event System',
'Best Practices for Event-Driven Architecture'
];
const headline = headlines[Math.floor(Math.random() * headlines.length)];
const count = publisherEmitter.emit('news', headline, 'Article content here...');
log(`Published "${headline}" to ${count} subscribers`, 'pubsub-output');
gameStats.eventsEmitted++;
gameStats.handlersExecuted += count;
updateEventStats();
}
function setupComponents() {
clearOutput('component-output');
demoEmitter.on('ui.update', (data) => {
log(`UI Component: Updating display with ${data}`, 'component-output');
});
demoEmitter.on('data.request', (type) => {
log(`Data Component: Fetching ${type} data`, 'component-output');
setTimeout(() => {
demoEmitter.emit('data.response', `${type} data loaded`);
}, 500);
});
demoEmitter.on('data.response', (data) => {
log(`Logger: Data received - ${data}`, 'component-output');
});
log('Components setup complete', 'component-output');
}
function componentInteraction() {
demoEmitter.emit('data.request', 'user');
gameStats.eventsEmitted++;
updateEventStats();
}
function cascadeEvents() {
demoEmitter.emit('ui.update', 'new theme');
setTimeout(() => {
demoEmitter.emit('ui.update', 'user preferences');
}, 300);
setTimeout(() => {
demoEmitter.emit('ui.update', 'layout changes');
}, 600);
gameStats.eventsEmitted += 3;
updateEventStats();
}
function handlerCountDemo() {
clearOutput('count-output');
demoEmitter.on('count-test', () => log('Handler A executed', 'count-output'));
demoEmitter.on('count-test', () => log('Handler B executed', 'count-output'));
demoEmitter.on('count-test', () => log('Handler C executed', 'count-output'));
const count = demoEmitter.emit('count-test');
log(`Total handlers executed: ${count}`, 'count-output');
gameStats.eventsEmitted++;
gameStats.handlersExecuted += count;
updateEventStats();
}
function benchmarkEmission() {
const start = performance.now();
let totalHandlers = 0;
for (let i = 0; i < 1000; i++) {
totalHandlers += demoEmitter.emit('count-test');
}
const end = performance.now();
log(`Benchmark: 1000 emissions took ${(end - start).toFixed(2)}ms`, 'count-output');
log(`Total handlers called: ${totalHandlers}`, 'count-output');
gameStats.eventsEmitted += 1000;
gameStats.handlersExecuted += totalHandlers;
updateEventStats();
}
function setupGameDemo() {
clearOutput('game-output');
gameEmitter.on('player.move', (x, y) => {
log(`Player moved to (${x}, ${y})`, 'game-output');
});
gameEmitter.on('enemy.spawn', (type, level) => {
log(`${type} enemy spawned at level ${level}`, 'game-output');
});
gameEmitter.on('score.update', (points, combo) => {
log(`Score: ${points} points (${combo}x combo)`, 'game-output');
});
gameEmitter.on('game.over', (finalScore) => {
log(`Game Over! Final score: ${finalScore}`, 'game-output');
});
log('Game event system initialized', 'game-output');
}
function simulateGameplay() {
const actions = [
() => gameEmitter.emit('player.move', Math.floor(Math.random() * 10), Math.floor(Math.random() * 10)),
() => gameEmitter.emit('enemy.spawn', ['goblin', 'orc', 'dragon'][Math.floor(Math.random() * 3)], Math.floor(Math.random() * 5) + 1),
() => gameEmitter.emit('score.update', Math.floor(Math.random() * 1000), Math.floor(Math.random() * 5) + 1),
];
let actionCount = 0;
const gameLoop = setInterval(() => {
const action = actions[Math.floor(Math.random() * actions.length)];
action();
actionCount++;
gameStats.eventsEmitted++;
if (actionCount >= 8) {
clearInterval(gameLoop);
setTimeout(() => {
gameEmitter.emit('game.over', Math.floor(Math.random() * 10000));
gameStats.eventsEmitted++;
updateEventStats();
}, 1000);
}
}, 500);
updateEventStats();
}
function showGameStats() {
log(`Game Statistics:`, 'game-output');
log(`Events emitted: ${gameStats.eventsEmitted}`, 'game-output');
log(`Handlers executed: ${gameStats.handlersExecuted}`, 'game-output');
log(`Avg handlers per event: ${(gameStats.handlersExecuted / gameStats.eventsEmitted || 0).toFixed(2)}`, 'game-output');
}
// UI Demo
function setupUIDemo() {
clearOutput('ui-output');
uiEmitter.on('button.click', (buttonId) => {
log(`Button clicked: ${buttonId}`, 'ui-output');
});
uiEmitter.on('form.submit', (formData) => {
log(`Form submitted: ${JSON.stringify(formData)}`, 'ui-output');
});
uiEmitter.on('modal.open', (modalType) => {
log(`Modal opened: ${modalType}`, 'ui-output');
});
uiEmitter.on('theme.change', (theme) => {
log(`Theme changed to: ${theme}`, 'ui-output');
});
log('UI event handlers registered', 'ui-output');
}
function simulateUserActions() {
const actions = [
() => uiEmitter.emit('button.click', 'submit-btn'),
() => uiEmitter.emit('button.click', 'cancel-btn'),
() => uiEmitter.emit('form.submit', { name: 'John', email: 'john@example.com' }),
() => uiEmitter.emit('modal.open', 'settings'),
() => uiEmitter.emit('modal.open', 'help'),
() => uiEmitter.emit('theme.change', 'dark'),
() => uiEmitter.emit('theme.change', 'light'),
];
actions.forEach((action, index) => {
setTimeout(() => {
action();
gameStats.eventsEmitted++;
if (index === actions.length - 1) {
updateEventStats();
}
}, index * 400);
});
}
function updateLibraryInfo() {
const info = document.getElementById('libraryInfo');
info.textContent = `
EventEmitter Instances: Multiple active instances
Global Event Statistics: ${gameStats.totalEvents} total events processed
Memory Usage: Efficient slot-based deduplication
Performance: Sub-millisecond event emission
`.trim();
}
function updateEventStats() {
gameStats.totalEvents = gameStats.eventsEmitted;
const stats = document.getElementById('eventStats');
stats.textContent = `
Events Emitted: ${gameStats.eventsEmitted}
Handlers Executed: ${gameStats.handlersExecuted}
Average Handlers per Event: ${(gameStats.handlersExecuted / gameStats.eventsEmitted || 0).toFixed(2)}
Active Listeners: ${listenerCount}
`.trim();
}
document.addEventListener('DOMContentLoaded', function() {
updateLibraryInfo();
updateEventStats();
updateListenerCounter();
setInterval(updateLibraryInfo, 3000);
});
</script>
</body>
</html>
+136
View File
@@ -0,0 +1,136 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.UFormat = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
'use strict';
const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
function scaleBytes(bytes) {
let value = Number(bytes || 0);
let unitIndex = 0;
while (Math.abs(value) >= 1024 && unitIndex < BYTE_UNITS.length - 1) {
value /= 1024;
unitIndex += 1;
}
return { value, unitIndex };
}
function formatBytes(bytes) {
if (bytes == null || bytes === '') return '--';
const scaled = scaleBytes(bytes);
const decimals = scaled.unitIndex === 0 ? 0 : 1;
return `${scaled.value.toFixed(decimals)} ${BYTE_UNITS[scaled.unitIndex]}`;
}
function formatDiskBytes(bytes) {
if (bytes == null || bytes === '') return '--';
const scaled = scaleBytes(bytes);
const decimals = scaled.unitIndex >= 4 ? 2 : scaled.unitIndex >= 1 ? 1 : 0;
return `${scaled.value.toFixed(decimals)} ${BYTE_UNITS[scaled.unitIndex]}`;
}
function formatCount(value) {
const number = Number(value);
if (!Number.isFinite(number)) return '--';
return number.toLocaleString();
}
function formatDurationMs(value) {
const number = Number(value);
if (!Number.isFinite(number)) return '--';
if (Math.abs(number) >= 1000) {
return `${(number / 1000).toFixed(number >= 10000 ? 0 : 1)} s`;
}
return `${number.toFixed(number >= 100 ? 0 : 1)} ms`;
}
function parseUnitNumber(text) {
const normalized = String(text || '').trim().toLowerCase().replace(/,/g, '');
if (!normalized) return null;
const pure = normalized.match(/^([-+]?\d*\.?\d+)$/);
if (pure) {
return Number(pure[1]);
}
const withUnit = normalized.match(/^([-+]?\d*\.?\d+)\s*([a-z%][a-z0-9\/_-]*)$/);
if (!withUnit) {
return null;
}
const value = Number(withUnit[1]);
let unit = withUnit[2];
if (!Number.isFinite(value)) {
return null;
}
if (unit.endsWith('/s')) {
unit = unit.slice(0, -2);
}
const bytes = {
b: 1,
kb: 1024,
kib: 1024,
mb: 1024 ** 2,
mib: 1024 ** 2,
gb: 1024 ** 3,
gib: 1024 ** 3,
tb: 1024 ** 4,
tib: 1024 ** 4,
pb: 1024 ** 5,
pib: 1024 ** 5,
};
if (Object.prototype.hasOwnProperty.call(bytes, unit)) {
return value * bytes[unit];
}
const durations = {
ms: 0.001,
s: 1,
sec: 1,
secs: 1,
second: 1,
seconds: 1,
m: 60,
min: 60,
mins: 60,
minute: 60,
minutes: 60,
h: 3600,
hr: 3600,
hrs: 3600,
hour: 3600,
hours: 3600,
d: 86400,
day: 86400,
days: 86400,
};
if (Object.prototype.hasOwnProperty.call(durations, unit)) {
return value * durations[unit];
}
if (unit === '%') {
return value;
}
return null;
}
return {
formatBytes,
formatCount,
formatDiskBytes,
formatDurationMs,
parseUnitNumber,
scaleBytes,
};
}));
+1488
View File
File diff suppressed because it is too large Load Diff
+977
View File
@@ -0,0 +1,977 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-Macrobars.js Demo</title>
<style>
:root {
--space: 8px;
--radius: 5px;
--gray: #6c757d;
--gray-bg: #f5f5f5;
--blue: #007acc;
--green: #28a745;
--white: white;
--dark: #333;
--max-width: 1400px;
--sidebar: 400px;
}
* { box-sizing: border-box; }
body {
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: var(--max-width);
margin: 0 auto;
padding: var(--space);
background: var(--gray-bg);
}
.container {
background: var(--white);
padding: calc(var(--space) * 1.5);
border-radius: var(--radius);
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--space) * 1.5);
}
.api-column {
background: var(--gray-bg);
padding: var(--space);
border-radius: var(--radius);
border-left: 4px solid var(--green);
}
h1 {
color: var(--dark);
text-align: center;
margin-bottom: calc(var(--space) * 1.5);
grid-column: 1 / -1;
}
h2 {
color: var(--gray);
border-bottom: 2px solid #e0e0e0;
padding-bottom: var(--space);
margin-top: calc(var(--space) * 1.5);
}
.demo-section {
margin: var(--space) 0;
padding: var(--space);
background: var(--gray-bg);
border-radius: var(--radius);
border-left: 4px solid var(--blue);
}
.controls, .slider-group {
display: flex;
gap: var(--space);
margin: 15px 0;
}
.controls { flex-wrap: wrap; }
.slider-group { align-items: center; }
.feature-grid { display: grid; gap: var(--space); margin: var(--space) 0; }
button {
background: var(--blue);
color: var(--white);
border: none;
padding: var(--space) var(--space);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.3s ease;
}
button:hover { filter: brightness(0.9); }
button:disabled { background: #ccc; cursor: not-allowed; }
input[type="range"] { flex: 1; max-width: 200px; }
.status, .code-example {
font-family: monospace;
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
}
.status {
background: var(--dark);
color: #0f0;
white-space: pre-wrap;
max-height: 150px;
overflow-y: auto;
}
.code-example {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
overflow-x: auto;
margin: var(--space) 0;
}
.highlight { color: #68d391; }
.keyword { color: #fbb6ce; }
.string { color: #fbd38d; }
.template-visual {
height: 100px;
background: linear-gradient(45deg, #667eea, #764ba2);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-weight: bold;
margin: 15px 0;
position: relative;
overflow: hidden;
}
.template-animation {
position: absolute;
inset: 0;
opacity: 0.3;
background: repeating-linear-gradient(90deg, transparent 0 10px, rgba(255,255,255,0.2) 10px 20px);
animation: slide 3s linear infinite;
}
@keyframes slide { to { transform: translateX(20px); } }
.api-section { margin: 15px 0; }
.api-section h3 {
color: var(--green);
margin: 0 0 var(--space) 0;
}
.api-method {
font-family: monospace;
margin: 3px 0;
color: var(--gray);
}
.api-method .method-name { color: var(--blue); font-weight: bold; }
.api-method .return-type { color: #6f42c1; }
.api-method .param { color: #e83e8c; }
.api-description {
margin: var(--space) 0;
}
.api-options {
color: var(--gray);
}
.template-code {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
border-radius: var(--radius);
margin: var(--space) 0;
overflow-x: auto;
}
.output {
background: var(--white);
border: 2px solid var(--green);
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
min-height: 40px;
}
.code-comment {
color: #68d391;
}
textarea {
width: 100%;
padding: var(--space);
border: 1px solid #ccc;
border-radius: var(--radius);
font-family: monospace;
margin: var(--space) 0;
resize: vertical;
}
.interactive-editor {
background: var(--white);
padding: calc(var(--space) * 1.5);
border-radius: var(--radius);
border: 2px solid var(--blue);
}
.help-text {
color: var(--gray);
margin: var(--space) 0;
font-style: italic;
}
.feature-grid button {
padding: calc(var(--space) * 1.5) var(--space);
font-size: 14px;
transition: all 0.2s ease;
}
.feature-grid button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 122, 204, 0.3);
}
.feature-grid button:active:not(:disabled) {
transform: translateY(0);
}
@media (max-width: 1200px) {
.main-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body onload="initializeDemos();">
<div class="container">
<h1>U-Macrobars.js Demo</h1>
<div class="main-layout">
<div class="demo-column">
<h2>Basic Field Output</h2>
<div class="demo-section">
<div class="template-code">{{name}} is {{age}} years old and works as {{job or "unemployed"}}</div>
<div class="output" id="basic-output"></div>
<div class="controls">
<button onclick="runBasicDemo()">Run Basic Demo</button>
<button onclick="runBasicDemoVariant()">Try Different Data</button>
</div>
</div>
<div class="demo-section">
<h3>Number Formatting</h3>
<div class="template-code">Price: ${{%price}} | Large Number: {{~bigNumber}}</div>
<div class="output" id="number-output"></div>
<div class="controls">
<button onclick="runNumberDemo()">Format Numbers</button>
<button onclick="runNumberVariants()">Try Different Numbers</button>
</div>
</div>
<div class="demo-section">
<h3>Default Values & Safety</h3>
<div class="template-code">{{username or "Guest"}} | {{profile.bio or "No bio available"}}</div>
<div class="output" id="default-output"></div>
<div class="controls">
<button onclick="runDefaultDemo()">Test Defaults</button>
<button onclick="runSafetyDemo()">HTML Safety Demo</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Field Output API</h2>
<div class="api-section">
<h3>Basic Syntax</h3>
<div class="api-method"><span class="method-name">{{field}}</span><span class="return-type">Safe HTML output</span></div>
<div class="api-method"><span class="method-name">{{{field}}}</span><span class="return-type">Raw HTML output</span></div>
<div class="api-method"><span class="method-name">{{:variable}}</span><span class="return-type">Direct variable</span></div>
<div class="api-method"><span class="method-name">{{field or "default"}}</span><span class="return-type">With fallback</span></div>
</div>
<div class="api-section">
<h3>Number Formatting</h3>
<div class="api-method"><span class="method-name">{{%number}}</span><span class="return-type">2 decimal places</span></div>
<div class="api-method"><span class="method-name">{{~number}}</span><span class="return-type">Rounded (1k, 1M)</span></div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Basic field output</span>
<span class="keyword">const</span> template = Macrobars.compile(<span class="string">'{{name}}'</span>);
<span class="keyword">const</span> result = template({name: <span class="string">'John'</span>});
<span class="code-comment">// With defaults</span>
<span class="keyword">const</span> withDefault = <span class="string">'{{title or "Untitled"}}'</span>;
<span class="code-comment">// Number formatting</span>
<span class="keyword">const</span> price = <span class="string">'${{%cost}}'</span>; <span class="code-comment">// $12.34</span>
<span class="keyword">const</span> count = <span class="string">'{{~views}}'</span>; <span class="code-comment">// 1.2k</span>
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Control Structures</h2>
<div class="demo-section">
<h3>Conditionals</h3>
<div class="template-code">{{#if isLoggedIn}}
Welcome back, {{username}}!
{{#else}}
Please log in to continue.
{{/if}}</div>
<div class="output" id="conditional-output"></div>
<div class="controls">
<button onclick="runConditionalDemo(true)">👤 Logged In</button>
<button onclick="runConditionalDemo(false)">🚪 Not Logged In</button>
</div>
</div>
<div class="demo-section">
<h3>Loops & Iteration</h3>
<div class="template-code">{{#each items}}
• {{number}}. {{name}} - ${{price}}
{{/each}}</div>
<div class="output" id="loop-output"></div>
<div class="controls">
<button onclick="runLoopDemo()">🔄 Process List</button>
<button onclick="runNamedLoopDemo()">📝 Named Iteration</button>
</div>
</div>
<div class="demo-section">
<h3>Equality & Lookup</h3>
<div class="template-code">{{#eq status "active"}}User is active{{/eq}}
{{lookup user "permissions"}}</div>
<div class="output" id="equality-output"></div>
<div class="controls">
<button onclick="runEqualityDemo()">⚖️ Test Equality</button>
<button onclick="runLookupDemo()">🔍 Dynamic Lookup</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Control Flow API</h2>
<div class="api-section">
<h3>Conditionals</h3>
<div class="api-method"><span class="method-name">{{#if condition}}</span>...{{/if}}</div>
<div class="api-method"><span class="method-name">{{#else}}</span><span class="return-type">Alternative branch</span></div>
<div class="api-description">
Conditional rendering based on truthy values. Supports nested conditions.
</div>
</div>
<div class="api-section">
<h3>Loops</h3>
<div class="api-method"><span class="method-name">{{#each items}}</span>...{{/each}}</div>
<div class="api-method"><span class="method-name">{{#each items as item}}</span>...{{/each}}</div>
<div class="api-description">
• Standard: <strong>data</strong> becomes current item<br>
• Named: <strong>item</strong> becomes current item, data preserved
</div>
</div>
<div class="api-section">
<h3>Comparison & Lookup</h3>
<div class="api-method"><span class="method-name">{{#eq val1 val2}}</span>...{{/eq}}</div>
<div class="api-method"><span class="method-name">{{eq val1 val2}}</span><span class="return-type">"true" or ""</span></div>
<div class="api-method"><span class="method-name">{{#lookup obj key}}</span>...{{/lookup}}</div>
<div class="api-method"><span class="method-name">{{lookup obj key}}</span><span class="return-type">value or ""</span></div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Conditionals</span>
<span class="string">'{{#if user.isAdmin}}Admin Panel{{/if}}'</span>
<span class="code-comment">// Named loops</span>
<span class="string">'{{#each products as product}}'</span>
<span class="string">'{{product.name}} - {{data.storeName}}'</span>
<span class="string">'{{/each}}'</span>
<span class="code-comment">// Equality & lookup</span>
<span class="string">'{{#eq user.role "admin"}}Secret{{/eq}}'</span>
<span class="string">'{{lookup config "theme"}}'</span>
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Advanced Features</h2>
<div class="demo-section">
<h3>Event Binding</h3>
<div class="template-code">&lt;button {{@click="handleClick"}}&gt;Click Count: {{clickCount}}&lt;/button&gt;</div>
<div class="output" id="event-output"></div>
<div class="controls">
<button onclick="runEventDemo()">🎯 Setup Interactive Button</button>
<button onclick="runMultiEventDemo()">⚡ Multiple Events</button>
</div>
</div>
<div class="demo-section">
<h3>Code Blocks</h3>
<div class="template-code">&lt;script&gt;var computed = data.value * 2;&lt;/script&gt;
Result: {{:computed}}</div>
<div class="output" id="code-output"></div>
<div class="controls">
<button onclick="runCodeDemo()">💻 Execute Code</button>
<button onclick="runDeferDemo()">⏰ Deferred Execution</button>
</div>
</div>
<div class="demo-section">
<h3>Components</h3>
<div class="template-code">{{#component userCard}}</div>
<div class="output" id="component-output"></div>
<div class="controls">
<button onclick="runComponentDemo()">🧩 Load Component</button>
<button onclick="createCustomComponent()">✨ Create Custom</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Advanced API</h2>
<div class="api-section">
<h3>Event Binding</h3>
<div class="api-method"><span class="method-name">{{@event="handler"}}</span><span class="return-type">DOM attribute</span></div>
<div class="api-method"><span class="method-name">template.renderTo</span>(<span class="param">container, data</span>)</div>
<div class="api-description">
Events are automatically bound when using renderTo(). Handler can reference data properties or global functions.
</div>
</div>
<div class="api-section">
<h3>Code Execution</h3>
<div class="api-method"><span class="method-name">&lt;script&gt;</span>...&lt;/script&gt;</div>
<div class="api-method"><span class="method-name">&lt;defer&gt;</span>...&lt;/defer&gt;</div>
<div class="api-method"><span class="method-name">&lt;?</span> code <span class="method-name">?&gt;</span></div>
<div class="api-method"><span class="method-name">&lt;?=</span> expression <span class="method-name">?&gt;</span></div>
</div>
<div class="api-section">
<h3>Components & Compilation</h3>
<div class="api-method"><span class="method-name">Macrobars.compile</span>(<span class="param">template, options</span>)</div>
<div class="api-method"><span class="method-name">Macrobars.createComponents</span>(<span class="param">definitions</span>)</div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Event binding with renderTo</span>
<span class="keyword">const</span> template = Macrobars.compile(
<span class="string">'&lt;button {{@click="increment"}}&gt;{{count}}&lt;/button&gt;'</span>
);
template.renderTo(<span class="string">'#container'</span>, {
count: <span class="highlight">0</span>,
increment: <span class="keyword">function</span>() { <span class="keyword">this</span>.count++; }
});
<span class="code-comment">// Components</span>
<span class="keyword">const</span> components = Macrobars.createComponents({
userCard: <span class="string">'&lt;div&gt;{{name}} - {{email}}&lt;/div&gt;'</span>
});
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Template Editor</h2>
<div class="interactive-editor">
<label for="template-input"><strong>Template:</strong></label>
<textarea id="template-input" rows="6" placeholder="Enter your template here">Hello {{name}}!
{{#if age}}You are {{age}} years old.{{/if}}
{{#each hobbies as hobby}}
• {{:hobby}}
{{/each}}
Total score: {{~score}}</textarea>
<label for="data-input"><strong>Data (JSON):</strong></label>
<textarea id="data-input" rows="6" placeholder="Enter JSON data here">{
"name": "Alice",
"age": 25,
"score": 98750,
"hobbies": ["reading", "coding", "gaming"]
}</textarea>
<div class="controls">
<button onclick="runCustomTemplate()">Render Template</button>
<button onclick="loadExampleTemplate('basic')">Load Basic Example</button>
<button onclick="loadExampleTemplate('advanced')">Load Advanced Example</button>
<button onclick="clearEditor()">Clear All</button>
</div>
<div class="output" id="dynamic-content" style="white-space: pre;">Click "Render Template"</div>
<div class="api-section">
<h3>Template Information</h3>
<div class="status" id="template-info">Ready.</div>
</div>
</div>
</div>
<div class="api-column">
<h2>Compilation & Debugging</h2>
<div class="api-section">
<h3>Compilation Options</h3>
<div class="api-method"><span class="method-name">decimals</span>: <span class="param">number</span><span class="return-type">Number precision</span></div>
<div class="api-method"><span class="method-name">strict</span>: <span class="param">boolean</span><span class="return-type">Strict mode</span></div>
<div class="api-method"><span class="method-name">components</span>: <span class="param">object</span><span class="return-type">Reusable templates</span></div>
</div>
<div class="api-section">
<h3>Debugging Properties</h3>
<div class="api-method"><span class="method-name">template.tokens</span><span class="return-type">Parsed tokens</span></div>
<div class="api-method"><span class="method-name">template.gensource</span><span class="return-type">Generated JS</span></div>
<div class="api-method"><span class="method-name">template.event_bindings</span><span class="return-type">Event data</span></div>
</div>
<div class="api-section">
<h3>Utility Functions</h3>
<div class="api-method"><span class="method-name">Macrobars.safe_out</span>(<span class="param">text, default</span>)</div>
<div class="api-method"><span class="method-name">Macrobars.num_out</span>(<span class="param">number, decimals</span>)</div>
<div class="api-method"><span class="method-name">Macrobars.num_out_round</span>(<span class="param">number</span>)</div>
</div>
<div class="api-section">
<h3>Example Templates</h3>
<pre class="code-example">
<span class="code-comment">// Complete example</span>
<span class="keyword">const</span> template = Macrobars.compile(<span class="string">`
&lt;div class="user-card"&gt;
&lt;h3&gt;{{name or "Unknown User"}}&lt;/h3&gt;
{{#if profile.verified}}✅ Verified{{/if}}
&lt;p&gt;Balance: ${{%balance}}&lt;/p&gt;
{{#each achievements}}
&lt;span class="badge"&gt;{{data}}&lt;/span&gt;
{{/each}}
&lt;/div&gt;
`</span>, { decimals: <span class="highlight">2</span> });
<span class="keyword">const</span> result = template(userData);
</pre>
</div>
</div>
</div>
</div>
<script src="u-macrobars.js"></script>
<script>
let currentTemplate = null;
let clickCount = 0;
let components = {};
// Initialize demos on page load
function initializeDemos() {
runBasicDemo();
runNumberDemo();
updateTemplateInfo('Ready.');
}
// Utility functions
function log(message) {
//console.log(`Macrobars Demo: ${message}`);
}
function updateTemplateInfo(info) {
const element = document.getElementById('template-info');
if (element) {
element.textContent = info;
}
}
function updateVisualAnimation(playing) {
const animations = document.querySelectorAll('.template-animation');
animations.forEach(anim => {
anim.style.animationPlayState = playing ? 'running' : 'paused';
});
}
// Demo 1: Basic field output
function runBasicDemo() {
try {
const template = Macrobars.compile('{{name}} is {{age}} years old and works as {{job or "unemployed"}}');
const data = { name: "John Doe", age: 30 };
const result = template(data);
document.getElementById('basic-output').innerHTML = result;
updateVisualAnimation(true);
setTimeout(() => updateVisualAnimation(false), 1000);
} catch (error) {
document.getElementById('basic-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runBasicDemoVariant() {
try {
const template = Macrobars.compile('{{name}} is {{age}} years old and works as {{job or "unemployed"}}');
const data = { name: "Jane Smith", age: 28, job: "Software Engineer" };
const result = template(data);
document.getElementById('basic-output').innerHTML = result;
updateVisualAnimation(true);
setTimeout(() => updateVisualAnimation(false), 1000);
} catch (error) {
document.getElementById('basic-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 2: Number formatting
function runNumberDemo() {
try {
const template = Macrobars.compile('Price: ${{%price}} | Large Number: {{~bigNumber}}');
const data = { price: 123.456, bigNumber: 1234567 };
const result = template(data);
document.getElementById('number-output').innerHTML = result;
} catch (error) {
document.getElementById('number-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runNumberVariants() {
try {
const template = Macrobars.compile('Price: ${{%price}} | Large Number: {{~bigNumber}} | Small: {{%small}}');
const data = { price: 999.99, bigNumber: 42850000, small: 1.2345 };
const result = template(data);
document.getElementById('number-output').innerHTML = result;
} catch (error) {
document.getElementById('number-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 3: Default values & safety
function runDefaultDemo() {
try {
const template = Macrobars.compile('{{username or "Guest"}} | {{profile.bio or "No bio available"}}');
const data = { username: "", profile: {} }; // Empty data to test defaults
const result = template(data);
document.getElementById('default-output').innerHTML = result;
} catch (error) {
document.getElementById('default-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runSafetyDemo() {
try {
const template = Macrobars.compile('Safe: {{userInput}} | Unsafe: {{{userInput}}}');
const data = { userInput: '<script>alert("XSS")</' + 'script><b>Bold Text</b>' };
const result = template(data);
document.getElementById('default-output').innerHTML = result;
} catch (error) {
document.getElementById('default-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 4: Conditionals
function runConditionalDemo(isLoggedIn) {
try {
const template = Macrobars.compile('{{#if isLoggedIn}}Welcome back, {{username}}! 🎉{{#else}}Please log in to continue. 🔐{{/if}}');
const data = {
isLoggedIn: isLoggedIn,
username: "Alice"
};
const result = template(data);
document.getElementById('conditional-output').innerHTML = result;
} catch (error) {
document.getElementById('conditional-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 5: Loops
function runLoopDemo() {
try {
const template = Macrobars.compile(
'{{#each items}}' +
'• {{number}}. {{name}} - ${{price}}<br>' +
'{{/each}}'
);
const data = {
items: [
{ number: 1, name: "Magic Widget", price: 19.99 },
{ number: 2, name: "Super Gadget", price: 29.50 },
{ number: 3, name: "Ultra Tool", price: 15.75 }
]
};
const result = template(data);
document.getElementById('loop-output').innerHTML = result;
} catch (error) {
document.getElementById('loop-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runNamedLoopDemo() {
try {
const template = Macrobars.compile('Store: {{storeName}}<br>{{#each products as product}}→ {{product.name}} ({{product.category}}) - Available at {{storeName}}<br>{{/each}}');
const data = {
storeName: "TechMart",
products: [
{ name: "Laptop", category: "Electronics" },
{ name: "Mouse", category: "Accessories" },
{ name: "Keyboard", category: "Accessories" }
]
};
const result = template(data);
document.getElementById('loop-output').innerHTML = result;
} catch (error) {
document.getElementById('loop-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 6: Equality & Lookup
function runEqualityDemo() {
try {
const template = Macrobars.compile('{{#eq status "active"}}✅ User is active{{/eq}}{{#eq status "inactive"}}❌ User is inactive{{/eq}}{{#eq status "pending"}}⏳ User is pending{{/eq}}<br>Inline check: {{eq role "admin"}}');
const data = { status: "active", role: "admin" };
const result = template(data);
document.getElementById('equality-output').innerHTML = result;
} catch (error) {
document.getElementById('equality-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runLookupDemo() {
try {
const template = Macrobars.compile('Theme: {{lookup user "theme"}}<br>{{#lookup user "permissions"}}🔑 Has permissions{{/lookup}}{{#lookup user "missing"}}This won\'t show{{/lookup}}');
const data = {
user: {
theme: "dark",
permissions: ["read", "write"]
}
};
const result = template(data);
document.getElementById('equality-output').innerHTML = result;
} catch (error) {
document.getElementById('equality-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 7: Event binding
function runEventDemo() {
try {
clickCount = 0;
const template = Macrobars.compile('<button {{@click="handleClick"}} style="padding: 10px;">🖱️ Click Count: {{clickCount}}</button>');
const data = {
clickCount: clickCount,
handleClick: function() {
clickCount++;
runEventDemo(); // Re-render with new count
}
};
const container = document.getElementById('event-output');
template.renderTo(container, data);
} catch (error) {
document.getElementById('event-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runMultiEventDemo() {
try {
var templateStr = '<div style="padding: 10px; border: 1px solid #ccc; border-radius: 5px;">';
templateStr += '<button {{@click="increment"}} style="margin: 5px; padding: 8px;"> Add</button>';
templateStr += '<button {{@click="decrement"}} style="margin: 5px; padding: 8px;"> Subtract</button>';
templateStr += '<button {{@click="reset"}} style="margin: 5px; padding: 8px;">🔄 Reset</button>';
templateStr += '<br><br>';
templateStr += '<strong>Counter: {{counter}}</strong>';
templateStr += '</div>';
const template = Macrobars.compile(templateStr);
let counter = 0;
const data = {
counter: counter,
increment: function() {
counter++;
data.counter = counter;
template.renderTo(document.getElementById('event-output'), data);
},
decrement: function() {
counter--;
data.counter = counter;
template.renderTo(document.getElementById('event-output'), data);
},
reset: function() {
counter = 0;
data.counter = counter;
template.renderTo(document.getElementById('event-output'), data);
}
};
template.renderTo(document.getElementById('event-output'), data);
} catch (error) {
document.getElementById('event-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 8: Code blocks
function runCodeDemo() {
try {
var templateStr = '';
templateStr += '<' + 'script>';
templateStr += 'var computed = data.value * 2;';
templateStr += 'var message = "Computed: " + computed;';
templateStr += '<' + '/script>';
templateStr += 'Input: {{value}}<br>';
templateStr += 'Result: {{:computed}}<br>';
templateStr += 'Message: {{:message}}';
const template = Macrobars.compile(templateStr);
const data = { value: 21 };
const result = template(data);
document.getElementById('code-output').innerHTML = result;
} catch (error) {
document.getElementById('code-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function runDeferDemo() {
try {
var templateStr = 'Value: {{value}}<br>' +
'<' + 'defer>' +
'alert("Value was: " + data.value);' +
'<' + '/defer>' +
'<em>Check browser console and alert!</em>';
const template = Macrobars.compile(templateStr);
const data = { value: 42 };
const result = template(data);
document.getElementById('code-output').innerHTML = result;
} catch (error) {
document.getElementById('code-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 9: Components
function runComponentDemo() {
try {
var userCardTemplate = '<div style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 5px 0;">';
userCardTemplate += '<h4 style="margin: 0 0 10px 0;">👤 {{name}}</h4>';
userCardTemplate += '<p style="margin: 5px 0;"><strong>Email:</strong> {{email}}</p>';
userCardTemplate += '<p style="margin: 5px 0;"><strong>Role:</strong> {{role}}</p>';
userCardTemplate += '{{#if isActive}}<span style="color: green;">✅ Active</span>{{#else}}<span style="color: red;">❌ Inactive</span>{{/if}}';
userCardTemplate += '</div>';
components = Macrobars.createComponents({
userCard: userCardTemplate
});
const template = Macrobars.compile('{{#component userCard}}', { components });
const data = { name: "John Doe", email: "john@example.com", role: "Developer", isActive: true };
const result = template(data);
document.getElementById('component-output').innerHTML = result;
} catch (error) {
document.getElementById('component-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
function createCustomComponent() {
try {
var productCardTemplate = '<div style="border: 2px solid #007acc; padding: 15px; border-radius: 8px; margin: 5px 0; background: #f8f9fa;">';
productCardTemplate += '<h4 style="color: #007acc; margin: 0 0 10px 0;">🛍️ {{productName}}</h4>';
productCardTemplate += '<p><strong>Price:</strong> ${{%price}}</p>';
productCardTemplate += '<p><strong>Stock:</strong> {{stock}} units</p>';
productCardTemplate += '{{#if onSale}}<div style="background: #28a745; color: white; padding: 5px; border-radius: 3px; text-align: center;">🏷️ ON SALE!</div>{{/if}}';
productCardTemplate += '</div>';
components = Macrobars.createComponents({
productCard: productCardTemplate
});
const template = Macrobars.compile('{{#component productCard}}', { components });
const data = { productName: "Magic Widget Pro", price: 299.99, stock: 15, onSale: true };
const result = template(data);
document.getElementById('component-output').innerHTML = result;
} catch (error) {
document.getElementById('component-output').innerHTML = '<span style="color: red;">Error: ' + error.message + '</span>';
}
}
// Demo 10: Custom template editor
function runCustomTemplate() {
try {
const templateStr = document.getElementById('template-input').value;
const dataStr = document.getElementById('data-input').value;
const data = JSON.parse(dataStr);
currentTemplate = Macrobars.compile(templateStr);
const result = currentTemplate(data);
document.getElementById('dynamic-content').innerHTML = result;
// Update template info
var infoText = 'Compiled successfully!\n';
infoText += 'Tokens: ' + (currentTemplate.tokens ? currentTemplate.tokens.length : 0) + '\n';
infoText += 'Event bindings: ' + (currentTemplate.event_bindings ? currentTemplate.event_bindings.length : 0) + '\n';
infoText += 'Template size: ' + templateStr.length + ' characters';
updateTemplateInfo(infoText);
} catch (error) {
document.getElementById('dynamic-content').innerHTML = '<div style="color: red; padding: 10px; background: #ffe6e6; border-radius: 5px;"><strong>❌ Error:</strong> ' + error.message + '</div>';
updateTemplateInfo('Compilation failed: ' + error.message);
}
}
function loadExampleTemplate(type) {
const templates = {
basic: {
template: '<h3>Welcome {{name}}!</h3>\n<p>{{#if isVip}}🌟 VIP Member{{#else}}Regular Member{{/if}}</p>\n<p>Account Balance: ${{%balance}}</p>\n{{#each notifications}}\n• {{data}}\n{{/each}}',
data: '{\n "name": "Sarah Connor",\n "isVip": true,\n "balance": 1247.50,\n "notifications": ["New message", "System update", "Payment received"]\n}'
},
advanced: {
template: '&lt;div class="dashboard"&gt;\n' +
' &lt;h2&gt;{{company}} Dashboard&lt;/h2&gt;\n' +
' \n' +
' &lt;script&gt;\n' +
' var totalRevenue = 0;\n' +
' data.quarters.forEach(q =&gt; totalRevenue += q.revenue);\n' +
' &lt;/script&gt;\n' +
' \n' +
' &lt;p&gt;&lt;strong&gt;Total Revenue:&lt;/strong&gt; ${{:totalRevenue}}&lt;/p&gt;\n' +
' \n' +
' {{#each quarters as quarter}}\n' +
' &lt;div style="margin: 10px 0; padding: 10px; border-left: 4px solid #007acc;"&gt;\n' +
' &lt;h4&gt;{{quarter.name}}&lt;/h4&gt;\n' +
' &lt;p&gt;Revenue: ${{%quarter.revenue}} {{#eq quarter.trend "up"}}📈{{/eq}}{{#eq quarter.trend "down"}}📉{{/eq}}&lt;/p&gt;\n' +
' {{#lookup quarter "bonus"}}\n' +
' &lt;span style="color: green;"&gt;🎯 Bonus: ${{quarter.bonus}}&lt;/span&gt;\n' +
' {{/lookup}}\n' +
' &lt;/div&gt;\n' +
' {{/each}}\n' +
' \n' +
' &lt;button {{@click="generateReport"}} style="padding: 10px; background: #28a745; color: white; border: none; border-radius: 5px;"&gt;\n' +
' 📊 Generate Report\n' +
' &lt;/button&gt;\n' +
'&lt;/div&gt;',
data: '{\n "company": "TechCorp Industries",\n "quarters": [\n {"name": "Q1 2024", "revenue": 125000, "trend": "up", "bonus": 5000},\n {"name": "Q2 2024", "revenue": 138000, "trend": "up"},\n {"name": "Q3 2024", "revenue": 142000, "trend": "up", "bonus": 7500},\n {"name": "Q4 2024", "revenue": 128000, "trend": "down"}\n ],\n "generateReport": "function() { alert(\'Report generated for \' + this.company); }"\n}'
}
};
const example = templates[type];
if (example) {
document.getElementById('template-input').value = example.template;
document.getElementById('data-input').value = example.data;
runCustomTemplate();
}
}
function clearEditor() {
document.getElementById('template-input').value = '';
document.getElementById('data-input').value = '{}';
document.getElementById('dynamic-content').innerHTML = 'Editor cleared. Enter a template and click "Render Template".';
updateTemplateInfo('Ready.');
}
// Initialize demos on page load
document.addEventListener('DOMContentLoaded', function() {
initializeDemos();
});
</script>
</body>
</html>
+84
View File
@@ -0,0 +1,84 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
root.showPopupMenu = factory(root.$);
}
}(typeof self !== 'undefined' ? self : this, function ($) {
'use strict';
let showPopupMenu = (x, y, items, prop = {}) => {
document.querySelectorAll('.vp-popup-menu').forEach(menu => {
if(menu.parentNode) menu.parentNode.removeChild(menu);
});
let container = prop.container || document.body;
let menu = $('<div class="vp-popup-menu">').css({
position: 'absolute',
left: (x|0) + 'px',
top: (y|0) + 'px'
})[0];
let elems = [];
if(!items || !items.length) {
$(menu).append(`<div class="vp-popup-empty">${prop.emptyText || 'No items'}</div>`);
} else {
items.forEach((it, idx) => {
let label = typeof it === 'string' ? it : (it.label || it.screen || it.name || JSON.stringify(it));
let $item = $(`<div class="vp-popup-item" tabindex="0">${label}</div>`)
.attr('data-idx', idx)
.on('click', ev => { ev.stopPropagation(); try { (prop.onSelect || (()=>{}))(it); } catch(e){ console.error(e); } removeMenu(); })
.on('keydown', e => { if(e.key === 'Enter') { e.preventDefault(); $item[0].click(); } });
$(menu).append($item[0]);
elems.push({el: $item[0], data: it});
});
}
let focusedIndex = elems.length ? 0 : -1;
let focusAt = i => {
elems.forEach((it, idx) => { $(it.el).removeClass('focused'); if(idx === i) { $(it.el).addClass('focused'); try{ it.el.focus(); }catch(e){} } });
focusedIndex = i;
};
let removeMenu = () => {
if(menu.parentNode) menu.parentNode.removeChild(menu);
$(document).off('mousedown', onDoc).off('keydown', onKey);
$(window).off('resize', onResize);
};
let onDoc = ev => { if(!menu.contains(ev.target)) removeMenu(); };
let onResize = () => reposition();
let onKey = ev => {
if(!elems.length) { if(ev.key === 'Escape') removeMenu(); return; }
if(ev.key === 'Escape') { ev.preventDefault(); removeMenu(); return; }
if(ev.key === 'ArrowDown') { ev.preventDefault(); focusAt((focusedIndex + 1) % elems.length); return; }
if(ev.key === 'ArrowUp') { ev.preventDefault(); focusAt((focusedIndex - 1 + elems.length) % elems.length); return; }
if(ev.key === 'Home') { ev.preventDefault(); focusAt(0); return; }
if(ev.key === 'End') { ev.preventDefault(); focusAt(elems.length - 1); return; }
if(ev.key === 'Enter') { ev.preventDefault(); if(focusedIndex >= 0) elems[focusedIndex].el.click(); }
};
container.appendChild(menu);
let reposition = () => {
let mRect = menu.getBoundingClientRect();
let winW = window.innerWidth, winH = window.innerHeight;
let left = parseInt($(menu).css('left'),10) || 0, top = parseInt($(menu).css('top'),10) || 0;
if(mRect.right > winW) left = Math.max(4, winW - Math.ceil(mRect.width) - 8);
if(mRect.bottom > winH) top = Math.max(4, winH - Math.ceil(mRect.height) - 8);
if(left < 4) left = 4; if(top < 4) top = 4;
$(menu).css({left: left + 'px', top: top + 'px'});
};
$(document).on('mousedown', onDoc).on('keydown', onKey);
$(window).on('resize', onResize);
if(elems.length) { focusAt(0); }
setTimeout(reposition, 0);
return menu;
};
return showPopupMenu;
}));
+976
View File
@@ -0,0 +1,976 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-Query.js Demo</title>
<style>
:root {
--space: 8px;
--radius: 5px;
--gray: #6c757d;
--gray-bg: #f5f5f5;
--blue: #007acc;
--green: #28a745;
--white: white;
--dark: #333;
--max-width: 1400px;
--sidebar: 400px;
}
* { box-sizing: border-box; }
body {
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: var(--max-width);
margin: 0 auto;
padding: var(--space);
background: var(--gray-bg);
}
.container {
background: var(--white);
padding: calc(var(--space) * 1.5);
border-radius: var(--radius);
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--space) * 1.5);
}
.api-column {
background: var(--gray-bg);
padding: var(--space);
border-radius: var(--radius);
border-left: 4px solid var(--green);
}
h1 {
color: var(--dark);
text-align: center;
margin-bottom: calc(var(--space) * 1.5);
grid-column: 1 / -1;
}
h2 {
color: var(--gray);
border-bottom: 2px solid #e0e0e0;
padding-bottom: var(--space);
margin-top: calc(var(--space) * 1.5);
}
.demo-section {
margin: var(--space) 0;
padding: var(--space);
background: var(--gray-bg);
border-radius: var(--radius);
border-left: 4px solid var(--blue);
}
.controls, .slider-group {
display: flex;
gap: var(--space);
margin: 15px 0;
}
.controls { flex-wrap: wrap; }
.slider-group { align-items: center; }
.feature-grid { display: grid; gap: var(--space); margin: var(--space) 0; }
button {
background: var(--blue);
color: var(--white);
border: none;
padding: var(--space) var(--space);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.3s ease;
}
button:hover { filter: brightness(0.9); }
button:disabled { background: #ccc; cursor: not-allowed; }
input[type="range"] { flex: 1; max-width: 200px; }
input[type="text"] {
padding: var(--space);
border: 1px solid #ddd;
border-radius: var(--radius);
}
.status, .code-example {
font-family: monospace;
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
}
.status {
background: var(--dark);
color: #0f0;
white-space: pre-wrap;
max-height: 150px;
overflow-y: auto;
}
.code-example {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
overflow-x: auto;
margin: var(--space) 0;
}
.highlight { color: #68d391; }
.keyword { color: #fbb6ce; }
.string { color: #fbd38d; }
.test-element {
padding: var(--space);
margin: 5px;
border: 2px solid #ddd;
background: var(--white);
border-radius: var(--radius);
transition: all 0.3s ease;
}
.highlighted {
background: #ffd700 !important;
border-color: #ffcc00 !important;
}
.hidden { display: none; }
.output {
background: var(--gray-bg);
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
min-height: 40px;
border-left: 3px solid var(--green);
}
.ajax-visual {
height: 100px;
background: linear-gradient(45deg, #1e3c72, #2a5298);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-weight: bold;
margin: 15px 0;
position: relative;
overflow: hidden;
}
.loading-animation {
position: absolute;
inset: 0;
opacity: 0.3;
background: repeating-linear-gradient(90deg, transparent 0 10px, rgba(255,255,255,0.2) 10px 20px);
animation: wave 2s linear infinite;
}
@keyframes wave { to { transform: translateX(20px); } }
.api-section { margin: 15px 0; }
.api-section h3 {
color: var(--green);
margin: 0 0 var(--space) 0;
}
.api-method {
font-family: monospace;
margin: 3px 0;
color: var(--gray);
}
.api-method .method-name { color: var(--blue); font-weight: bold; }
.api-method .return-type { color: #6f42c1; }
.api-method .param { color: #e83e8c; }
.api-description {
margin: var(--space) 0;
}
.api-options {
color: var(--gray);
}
.code-comment {
color: #68d391;
}
.demo-visual {
background: var(--gray-bg);
padding: calc(var(--space) * 2);
border-radius: var(--radius);
margin: var(--space) 0;
border: 2px dashed #ddd;
text-align: center;
min-height: 80px;
display: flex;
align-items: center;
justify-content: center;
}
.demo-visual.active {
background: #e8f5e8;
border-color: var(--green);
}
@media (max-width: 1200px) {
.main-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<h1>U-Query.js Demo</h1>
<div class="main-layout">
<div class="demo-column">
<h2>Element Selection & DOM Manipulation</h2>
<div class="demo-section">
<h3>DOM Queries</h3>
<div class="demo-visual" id="selection-visual">
<span>Select elements to see them highlighted</span>
</div>
<div class="test-element" id="test-1">Test Element 1</div>
<div class="test-element" id="test-2">Test Element 2</div>
<div class="test-element special" id="test-3">Test Element 3 (special)</div>
<div class="controls">
<button onclick="selectAllElements()">Select All .test-element</button>
<button onclick="selectSpecial()">Select .special</button>
<button onclick="selectById()">Select by ID</button>
<button onclick="clearSelection()">Clear Selection</button>
</div>
<div class="status" id="selection-output"></div>
</div>
<div class="demo-section">
<h3>DOM Manipulation</h3>
<div class="demo-visual" id="manipulation-target">Original content</div>
<div class="controls">
<button onclick="htmlDemo()">Change HTML</button>
<button onclick="textDemo()">Change Text</button>
<button onclick="appendDemo()">Append Content</button>
<button onclick="resetManipulation()">Reset</button>
</div>
<div class="status" id="manipulation-output"></div>
</div>
<div class="demo-section">
<h3>CSS & Classes</h3>
<div class="test-element" id="css-target">CSS Target Element</div>
<div class="controls">
<button onclick="cssDemo()">Apply CSS Styles</button>
<button onclick="addClassDemo()">Add Highlight Class</button>
<button onclick="removeClassDemo()">Remove Class</button>
<button onclick="resetCSS()">Reset Styles</button>
</div>
<div class="status" id="css-output"></div>
</div>
</div>
<div class="api-column">
<h2>Selection API</h2>
<div class="api-section">
<h3>Core Selectors</h3>
<div class="api-method"><span class="method-name">$</span>(<span class="param">selector</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">'.class'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">'#id'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">'tag'</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>DOM Manipulation</h3>
<div class="api-method"><span class="method-name">html</span>(<span class="param">content?</span>) → <span class="return-type">NodeList|string</span></div>
<div class="api-method"><span class="method-name">text</span>(<span class="param">content?</span>) → <span class="return-type">NodeList|string</span></div>
<div class="api-method"><span class="method-name">append</span>(<span class="param">content</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">prepend</span>(<span class="param">content</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">remove</span>() → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>CSS & Classes</h3>
<div class="api-method"><span class="method-name">css</span>(<span class="param">styles</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">addClass</span>(<span class="param">className</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">removeClass</span>(<span class="param">className</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">toggleClass</span>(<span class="param">className</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Element selection</span>
<span class="keyword">const</span> elements = <span class="highlight">$</span>(<span class="string">'.my-class'</span>);
<span class="keyword">const</span> byId = <span class="highlight">$</span>(<span class="string">'#my-id'</span>);
<span class="highlight">$</span>(<span class="string">'.target'</span>).html(<span class="string">'&lt;b&gt;New content&lt;/b&gt;'</span>);
<span class="highlight">$</span>(<span class="string">'.target'</span>).text(<span class="string">'Plain text'</span>);
<span class="highlight">$</span>(<span class="string">'.container'</span>).append(<span class="string">'&lt;p&gt;Added&lt;/p&gt;'</span>);
<span class="highlight">$</span>(<span class="string">'.element'</span>).css({
<span class="string">'color'</span>: <span class="string">'red'</span>,
<span class="string">'background'</span>: <span class="string">'blue'</span>
});
<span class="highlight">$</span>(<span class="string">'.element'</span>).addClass(<span class="string">'active'</span>);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Event Handling & Visibility</h2>
<div class="demo-section">
<h3>Event System</h3>
<button id="event-button">Click Me!</button>
<div class="controls">
<button onclick="addEventHandler()">Add Click Handler</button>
<button onclick="removeEventHandler()">Remove Handler</button>
<button onclick="addMultipleEvents()">Add Multiple Events</button>
</div>
<div class="status" id="event-output"></div>
</div>
<div class="demo-section">
<h3>Show/Hide/Toggle</h3>
<div class="test-element" id="toggle-target">Toggle me!</div>
<div class="controls">
<button onclick="$('#toggle-target').hide()">Hide</button>
<button onclick="$('#toggle-target').show()">Show</button>
<button onclick="$('#toggle-target').toggle()">Toggle</button>
<button onclick="animateVisibility()">Animate Toggle</button>
</div>
<div class="status" id="toggle-output"></div>
</div>
<div class="demo-section">
<h3>Method Chaining</h3>
<div class="demo-visual" id="chaining-demo">
<div class="test-element chain-target">Chain Target 1</div>
<div class="test-element chain-target">Chain Target 2</div>
<div class="test-element chain-target">Chain Target 3</div>
</div>
<div class="controls">
<button onclick="chainingDemo()">Demo Method Chaining</button>
<button onclick="resetChaining()">Reset</button>
</div>
<div class="status" id="chaining-output"></div>
</div>
</div>
<div class="api-column">
<h2>Events & Visibility API</h2>
<div class="api-section">
<h3>Event Methods</h3>
<div class="api-method"><span class="method-name">on</span>(<span class="param">event, handler</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">off</span>(<span class="param">event, handler?</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">trigger</span>(<span class="param">event, data?</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">once</span>(<span class="param">event, handler</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>Visibility Control</h3>
<div class="api-method"><span class="method-name">show</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">hide</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">toggle</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">fadeIn</span>(<span class="param">duration?</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">fadeOut</span>(<span class="param">duration?</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>Method Chaining</h3>
<div class="api-description">
All u-query methods return the NodeList, enabling method chaining.
</div>
<pre class="code-example">
<span class="highlight">$</span>(<span class="string">'.button'</span>).on(<span class="string">'click'</span>, <span class="keyword">function</span>() {
<span class="highlight">console</span>.log(<span class="string">'Clicked!'</span>);
});
<span class="highlight">$</span>(<span class="string">'.element'</span>)
.addClass(<span class="string">'active'</span>)
.css({<span class="string">'color'</span>: <span class="string">'red'</span>})
.show()
.on(<span class="string">'click'</span>, handler);
<span class="highlight">$</span>(<span class="string">'.modal'</span>).fadeIn(<span class="highlight">300</span>);
<span class="highlight">$</span>(<span class="string">'.tooltip'</span>).fadeOut(<span class="highlight">200</span>);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>AJAX & Global Events</h2>
<div class="demo-section">
<h3>AJAX Utilities</h3>
<div class="ajax-visual" id="ajax-visual">
<div class="loading-animation" id="loading-animation" style="animation-play-state: paused;"></div>
<span>AJAX Request Status</span>
</div>
<div class="controls">
<button onclick="ajaxDemo()">GET Request</button>
<button onclick="postDemo()">POST Request</button>
<button onclick="asyncDemo()">Async/Await Demo</button>
</div>
<div class="status" id="ajax-output"></div>
</div>
<div class="demo-section">
<h3>Global Event System</h3>
<input type="text" id="global-event-data" placeholder="Event data" value="Hello global events!">
<div class="controls">
<button onclick="addGlobalListener()">Add Global Listener</button>
<button onclick="emitGlobalEvent()">Emit Global Event</button>
<button onclick="removeGlobalListener()">Remove Listener</button>
</div>
<div class="status" id="global-output"></div>
</div>
<div class="demo-section">
<h3>Utility Functions</h3>
<div class="test-element util-target">Utility Element 1</div>
<div class="test-element util-target">Utility Element 2</div>
<div class="test-element util-target">Utility Element 3</div>
<div class="controls">
<button onclick="eachDemo()">Test $.each</button>
<button onclick="readyDemo()">Test $.ready</button>
<button onclick="utilityChain()">Utility Chain Demo</button>
</div>
<div class="status" id="utility-output"></div>
</div>
</div>
<div class="api-column">
<h2>AJAX & Utilities API</h2>
<div class="api-section">
<h3>AJAX Methods</h3>
<div class="api-method"><span class="method-name">$.get</span>(<span class="param">url, callback</span>) → <span class="return-type">Promise</span></div>
<div class="api-method"><span class="method-name">$.post</span>(<span class="param">url, data, callback</span>) → <span class="return-type">Promise</span></div>
<div class="api-method"><span class="method-name">$.ajax</span>(<span class="param">options</span>) → <span class="return-type">Promise</span></div>
<div class="api-method"><span class="method-name">$.json</span>(<span class="param">url</span>) → <span class="return-type">Promise</span></div>
</div>
<div class="api-section">
<h3>Global Event System</h3>
<div class="api-method"><span class="method-name">$.on</span>(<span class="param">event, handler</span>) → <span class="return-type">$</span></div>
<div class="api-method"><span class="method-name">$.off</span>(<span class="param">event, handler?</span>) → <span class="return-type">$</span></div>
<div class="api-method"><span class="method-name">$.emit</span>(<span class="param">event, data?</span>) → <span class="return-type">number</span></div>
<div class="api-method"><span class="method-name">$.trigger</span>(<span class="param">event, data?</span>) → <span class="return-type">number</span></div>
</div>
<div class="api-section">
<h3>Utility Functions</h3>
<div class="api-method"><span class="method-name">$.each</span>(<span class="param">selector, callback</span>) → <span class="return-type">$</span></div>
<div class="api-method"><span class="method-name">$.ready</span>(<span class="param">callback</span>) → <span class="return-type">$</span></div>
<div class="api-method"><span class="method-name">$.extend</span>(<span class="param">target, ...sources</span>) → <span class="return-type">object</span></div>
<div class="api-method"><span class="method-name">$.map</span>(<span class="param">selector, callback</span>) → <span class="return-type">Array</span></div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="keyword">const</span> data = <span class="keyword">await</span> <span class="highlight">$.get</span>(<span class="string">'/api/users'</span>);
<span class="highlight">$.post</span>(<span class="string">'/api/users'</span>, {name: <span class="string">'John'</span>})
.then(<span class="param">response</span> => <span class="highlight">console</span>.log(<span class="param">response</span>));
<span class="highlight">$.on</span>(<span class="string">'user-login'</span>, <span class="param">user</span> => {
<span class="highlight">console</span>.log(<span class="string">'User logged in:'</span>, <span class="param">user</span>);
});
<span class="highlight">$.emit</span>(<span class="string">'user-login'</span>, {id: <span class="highlight">123</span>});
<span class="highlight">$.each</span>(<span class="string">'.item'</span>, (<span class="param">el</span>, <span class="param">i</span>) => {
<span class="param">el</span>.textContent = <span class="string">`Item ${</span><span class="param">i</span><span class="string">}`</span>;
});
<span class="highlight">$.ready</span>(() => {
<span class="highlight">console</span>.log(<span class="string">'DOM ready!'</span>);
});
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Browser Info</h2>
<div class="demo-section">
<div class="status" id="library-info">Loading library information...</div>
<h3>This browser supports</h3>
<div id="feature-support" class="status"></div>
<div class="controls">
<button onclick="testCompatibility()">Test Browser Compatibility</button>
<button onclick="performanceBenchmark()">Performance Benchmark</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Advanced Features</h2>
<div class="api-section">
<h3>Advanced Selectors</h3>
<div class="api-method"><span class="method-name">$</span>(<span class="param">':visible'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">':hidden'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">':checked'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">':first'</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">$</span>(<span class="param">':last'</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>Data Attributes</h3>
<div class="api-method"><span class="method-name">data</span>(<span class="param">key, value?</span>) → <span class="return-type">NodeList|any</span></div>
<div class="api-method"><span class="method-name">attr</span>(<span class="param">name, value?</span>) → <span class="return-type">NodeList|string</span></div>
<div class="api-method"><span class="method-name">prop</span>(<span class="param">name, value?</span>) → <span class="return-type">NodeList|any</span></div>
<div class="api-method"><span class="method-name">val</span>(<span class="param">value?</span>) → <span class="return-type">NodeList|string</span></div>
</div>
<div class="api-section">
<h3>Traversal Methods</h3>
<div class="api-method"><span class="method-name">parent</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">children</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">siblings</span>() → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">find</span>(<span class="param">selector</span>) → <span class="return-type">NodeList</span></div>
<div class="api-method"><span class="method-name">closest</span>(<span class="param">selector</span>) → <span class="return-type">NodeList</span></div>
</div>
<div class="api-section">
<h3>DOM Diff Updates</h3>
<div class="api-description">
u-query can make use of morphdom.js for efficient DOM diff updates.
</div>
<div class="api-method"><span class="method-name">$.options.alwaysDoDifferentialUpdate</span> = <span class="param">true</span></div>
<div class="api-description">
Global flag for morphdom.
</div>
</div>
</div>
</div>
</div>
<script src="u-query.js"></script>
<script>
let clickHandler, globalHandler;
// Initialize library info on load
document.addEventListener('DOMContentLoaded', () => {
updateLibraryInfo();
updateFeatureSupport();
});
function updateLibraryInfo() {
const info = document.getElementById('library-info');
const version = typeof $ !== 'undefined' ? 'Loaded' : 'Not Available';
const selectors = document.querySelectorAll('*').length;
info.textContent = `
Library Status: ${version}
DOM Elements: ${selectors}
Browser: ${navigator.userAgent.split(' ')[0]}
Features: ES6+, Promises, DOM API
Build: Development
`.trim();
}
function updateFeatureSupport() {
const features = [
{ name: 'Promises', supported: typeof Promise !== 'undefined' },
{ name: 'Arrow Functions', supported: true },
{ name: 'Template Literals', supported: true },
{ name: 'Fetch API', supported: typeof fetch !== 'undefined' },
{ name: 'querySelector', supported: typeof document.querySelector !== 'undefined' },
{ name: 'addEventListener', supported: typeof document.addEventListener !== 'undefined' }
];
const support = features.map(feature =>
`${feature.name}: ${feature.supported ? 'Supported' : 'Not Supported'}`
).join('\n');
document.getElementById('feature-support').textContent = support;
}
function log(elementId, message) {
const element = document.getElementById(elementId);
if (element) {
element.textContent += new Date().toLocaleTimeString() + ': ' + message + '\n';
element.scrollTop = element.scrollHeight;
}
}
// Enhanced Selection Demos
function selectAllElements() {
clearSelection();
const elements = $('.test-element');
elements.forEach(el => el.classList.add('highlighted'));
updateSelectionVisual(`Selected ${elements.length} .test-element items`);
log('selection-output', `Found ${elements.length} elements with class 'test-element'`);
}
function selectSpecial() {
clearSelection();
const elements = $('.special');
elements.forEach(el => el.classList.add('highlighted'));
updateSelectionVisual(`Selected ${elements.length} .special items`);
log('selection-output', `Found ${elements.length} elements with class 'special'`);
}
function selectById() {
clearSelection();
const elements = $('#test-2');
elements.forEach(el => el.classList.add('highlighted'));
updateSelectionVisual(`Selected element by ID: #test-2`);
log('selection-output', `Found element by ID: ${elements.length > 0 ? elements[0].textContent : 'none'}`);
}
function clearSelection() {
$('.test-element').forEach(el => el.classList.remove('highlighted'));
updateSelectionVisual('Selection cleared');
}
function updateSelectionVisual(message) {
const visual = document.getElementById('selection-visual');
visual.textContent = message;
visual.classList.add('active');
setTimeout(() => visual.classList.remove('active'), 2000);
}
// DOM Manipulation
function htmlDemo() {
$('#manipulation-target').html('<strong>HTML content changed</strong>');
log('manipulation-output', 'HTML content updated with formatting');
}
function textDemo() {
$('#manipulation-target').text('Text content changed');
log('manipulation-output', 'Text content updated (plain text)');
}
function appendDemo() {
$('#manipulation-target').append(' <em>Appended content</em>');
log('manipulation-output', 'Content appended to existing content');
}
function resetManipulation() {
$('#manipulation-target').html('Original content');
log('manipulation-output', 'Content reset to original state');
}
// CSS and Classes
function cssDemo() {
$('#css-target').css({
'background': 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
'color': 'white',
'border-radius': '10px',
'padding': '15px',
'transform': 'scale(1.05)',
'transition': 'all 0.3s ease'
});
log('css-output', 'Applied gradient background and transformation');
}
function addClassDemo() {
$('#css-target').addClass('highlighted');
log('css-output', 'Added "highlighted" class');
}
function removeClassDemo() {
$('#css-target').removeClass('highlighted');
log('css-output', 'Removed "highlighted" class');
}
function resetCSS() {
$('#css-target').css({
'background': '',
'color': '',
'border-radius': '',
'padding': '',
'transform': '',
'transition': ''
}).removeClass('highlighted');
log('css-output', 'All styles and classes reset');
}
function addEventHandler() {
clickHandler = function(e) {
log('event-output', `Button clicked! Event type: ${e.type}`);
e.target.style.transform = 'scale(0.95)';
setTimeout(() => e.target.style.transform = '', 150);
};
$('#event-button').on('click', clickHandler);
log('event-output', 'Click handler added with visual feedback');
}
function removeEventHandler() {
if (clickHandler) {
$('#event-button').off('click', clickHandler);
log('event-output', 'Click handler removed');
}
}
function addMultipleEvents() {
const button = $('#event-button')[0];
if (button) {
const events = {
mouseenter: () => log('event-output', 'Mouse entered button'),
mouseleave: () => log('event-output', 'Mouse left button'),
focus: () => log('event-output', 'Button focused'),
blur: () => log('event-output', 'Button lost focus')
};
Object.entries(events).forEach(([event, handler]) => {
button.addEventListener(event, handler);
});
log('event-output', 'Added multiple event listeners (hover, focus, blur)');
}
}
function animateVisibility() {
const target = $('#toggle-target')[0];
if (target) {
target.style.transition = 'all 0.5s ease';
target.style.transform = target.style.display === 'none' ? 'scale(1)' : 'scale(0)';
setTimeout(() => {
$('#toggle-target').toggle();
if (target.style.display !== 'none') {
target.style.transform = 'scale(1)';
}
}, 250);
log('toggle-output', 'Animated visibility toggle with scaling effect');
}
function ajaxDemo() {
}
}
// Enhanced AJAX with visual feedback
function ajaxDemo() {
updateAjaxVisual(true);
log('ajax-output', 'Initiating GET request...');
$.get('https://jsonplaceholder.typicode.com/posts/1', function(data) {
updateAjaxVisual(false);
log('ajax-output', `GET Success: "${data.title.substring(0, 50)}..."`);
log('ajax-output', `Response body: ${data.body.substring(0, 100)}...`);
}).catch(error => {
updateAjaxVisual(false);
log('ajax-output', `GET Error: ${error.message}`);
});
}
function postDemo() {
updateAjaxVisual(true);
log('ajax-output', 'Initiating POST request...');
$.post('https://jsonplaceholder.typicode.com/posts', {
title: 'u-query demo post',
body: 'This is a test post from u-query demonstration',
userId: 1
}, function(data) {
updateAjaxVisual(false);
log('ajax-output', `POST Success: Created post with ID ${data.id}`);
log('ajax-output', `Posted title: "${data.title}"`);
}).catch(error => {
updateAjaxVisual(false);
log('ajax-output', `POST Error: ${error.message}`);
});
}
async function asyncDemo() {
try {
updateAjaxVisual(true);
log('ajax-output', 'Using async/await pattern...');
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
updateAjaxVisual(false);
log('ajax-output', `Async Success: User "${user.name}" from ${user.address.city}`);
log('ajax-output', `Email: ${user.email}, Phone: ${user.phone}`);
} catch (error) {
updateAjaxVisual(false);
log('ajax-output', `Async Error: ${error.message}`);
}
}
function updateAjaxVisual(loading) {
const visual = document.getElementById('ajax-visual');
const animation = document.getElementById('loading-animation');
const text = visual.querySelector('span');
if (loading) {
visual.style.background = 'linear-gradient(45deg, #ff6b6b, #4ecdc4)';
animation.style.animationPlayState = 'running';
text.textContent = 'Loading...';
} else {
visual.style.background = 'linear-gradient(45deg, #1e3c72, #2a5298)';
animation.style.animationPlayState = 'paused';
text.textContent = 'Request Complete';
}
}
function addGlobalListener() {
globalHandler = function(data) {
log('global-output', `Global event received: "${data}"`);
};
$.on('demo-event', globalHandler);
log('global-output', 'Global listener added for "demo-event"');
}
function emitGlobalEvent() {
const data = $('#global-event-data')[0].value;
const count = $.emit('demo-event', data);
log('global-output', `Event emitted to ${count} listeners with data: "${data}"`);
}
function removeGlobalListener() {
if (globalHandler) {
$.off('demo-event', globalHandler);
log('global-output', 'Global listener removed');
}
}
function eachDemo() {
document.getElementById('utility-output').textContent = '';
$.each('.util-target', function(element, index) {
element.style.background = `hsl(${index * 60}, 70%, 90%)`;
element.style.transform = `translateX(${index * 10}px)`;
log('utility-output', `Element ${index + 1}: "${element.textContent}" - styled`);
});
setTimeout(() => {
$('.util-target').forEach(el => {
el.style.background = '';
el.style.transform = '';
});
}, 3000);
}
function readyDemo() {
$.ready(function() {
log('utility-output', '$.ready() callback executed - DOM fully loaded');
log('utility-output', `Current timestamp: ${new Date().toLocaleString()}`);
});
}
function utilityChain() {
$.each('.util-target', (el, i) => {
$(el).css({'color': 'blue'}).addClass('highlighted');
});
log('utility-output', 'Utility chain: each + css + addClass applied');
setTimeout(() => {
$('.util-target').css({'color': ''}).removeClass('highlighted');
log('utility-output', 'Utility chain effects reset');
}, 2000);
}
function chainingDemo() {
$('.chain-target')
.addClass('highlighted')
.css({
'color': 'white',
'background': 'linear-gradient(45deg, #667eea, #764ba2)',
'transform': 'translateY(-5px) rotate(2deg)',
'transition': 'all 0.5s ease'
})
.show();
log('chaining-output', 'Method chain applied: addClass → css → show');
log('chaining-output', 'Added highlight, gradient, and transform');
}
function resetChaining() {
$('.chain-target')
.removeClass('highlighted')
.css({
'color': '',
'background': '',
'transform': '',
'transition': ''
});
log('chaining-output', 'Method chain reset: removeClass → css clear');
}
// Performance and Compatibility
function testCompatibility() {
const tests = [
{ name: 'CSS Selectors', test: () => !!document.querySelector },
{ name: 'Event Listeners', test: () => !!document.addEventListener },
{ name: 'Fetch API', test: () => !!window.fetch },
{ name: 'Promises', test: () => !!window.Promise },
{ name: 'Arrow Functions', test: () => { try { eval('() => {}'); return true; } catch { return false; } } },
{ name: 'Template Literals', test: () => { try { eval('`test`'); return true; } catch { return false; } } }
];
document.getElementById('library-info').textContent = 'Running compatibility tests...\n';
tests.forEach((test, i) => {
setTimeout(() => {
const result = test.test();
document.getElementById('library-info').textContent +=
`${test.name}: ${result ? 'Supported' : 'Not Supported'}\n`;
}, i * 200);
});
}
function performanceBenchmark() {
const iterations = 1000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
$('.test-element').css({'color': 'red'}).addClass('test-class').removeClass('test-class');
}
const end = performance.now();
const duration = (end - start).toFixed(2);
document.getElementById('library-info').textContent =
`Performance Benchmark Complete\n` +
`Operations: ${iterations} selection + css + addClass + removeClass\n` +
`Duration: ${duration}ms\n` +
`Avg per operation: ${(duration / iterations).toFixed(4)}ms\n` +
`Operations per second: ${(iterations / (duration / 1000)).toFixed(0)}`;
}
$.ready(function() {
log('utility-output', 'Demo initialized successfully');
});
setInterval(updateLibraryInfo, 5000);
</script>
</body>
</html>
+589
View File
@@ -0,0 +1,589 @@
(function (root, factory) {
// this is the stupid UMD pattern, but eh we lost that battle a long time ago
if (typeof exports === 'object' && typeof module !== 'undefined') {
var exports_obj = factory();
module.exports = exports_obj.$;
// Also export individual components
for (var key in exports_obj) {
if (key !== '$' && exports_obj.hasOwnProperty(key)) {
exports[key] = exports_obj[key];
}
}
} else if (typeof define === 'function' && define.amd) {
define([], factory);
} else {
var exports_obj = factory();
root.$ = exports_obj.$;
for (var key in exports_obj) {
if (key !== '$' && exports_obj.hasOwnProperty(key)) {
root[key] = exports_obj[key];
}
}
}
}(typeof self !== 'undefined' ? self : this, function () {
// a collection of DOM utility functions that I thought were cool in jQuery
class EventEmitter {
constructor() {
/** Map<eventName, Map<slotKey|Symbol, Function>> */
this._topics = new Map();
}
/**
* Subscribe to `topic`. If `slot_key` is provided (truthy),
* it dedupes by that key; otherwise a unique Symbol is used.
* Returns an unsubscribe fn.
*/
on(topic, handler, slot_key = null) {
let map = this._topics.get(topic);
if (!map) {
map = new Map();
this._topics.set(topic, map);
}
// use the provided slot_key or a fresh Symbol()
const key = slot_key != null ? slot_key : Symbol();
map.set(key, handler);
return () => this.off(topic, key);
}
/**
* Unsubscribe by handler function or by slot_key.
*/
off(topic, handlerOrSlotKey) {
const map = this._topics.get(topic);
if (!map) return;
// if it matches a slotKey directly, remove it
if (map.has(handlerOrSlotKey)) {
map.delete(handlerOrSlotKey);
} else {
// otherwise assume it's a function: remove all matching fns
for (const [key, fn] of map.entries()) {
if (fn === handlerOrSlotKey) {
map.delete(key);
}
}
}
if (map.size === 0) {
this._topics.delete(topic);
}
}
/**
* Emit to all handlers on `topic`. Handlers returning
* 'remove_handler' are auto-removed.
* Returns the number of handlers invoked.
*/
emit(topic, ...args) {
let count = 0;
const map = this._topics.get(topic);
if (!map) return count;
for (const [key, fn] of Array.from(map.entries())) {
const res = fn(...args);
count++;
if (res === 'remove_handler') {
map.delete(key);
}
}
if (map.size === 0) {
this._topics.delete(topic);
}
return count;
}
}
class QueryWrapper extends Array {
constructor(elements) {
super();
if (elements) {
this.push(...(Array.isArray(elements) ? elements : [elements]));
}
}
}
$ = function(selector_or_element) {
let elements;
if (selector_or_element instanceof Element || selector_or_element === document || selector_or_element === window) {
elements = [selector_or_element];
} else if (typeof selector_or_element === 'string') {
// Check if string looks like HTML (starts with < and ends with >)
if (selector_or_element.trim().charAt(0) === '<' && selector_or_element.trim().charAt(selector_or_element.trim().length - 1) === '>') {
// Create element from HTML string
let temp = document.createElement('div');
temp.innerHTML = selector_or_element.trim();
elements = Array.from(temp.children);
} else {
// Treat as CSS selector
elements = Array.from(document.querySelectorAll(selector_or_element));
}
} else if (selector_or_element instanceof NodeList || Array.isArray(selector_or_element)) {
elements = Array.from(selector_or_element);
} else {
elements = [selector_or_element];
}
return new QueryWrapper(elements);
};
$.options = {
alwaysDoDifferentialUpdate: true,
};
$.events = new EventEmitter();
$.on = $.events.on.bind($.events);
$.off = $.events.off.bind($.events);
$.emit = $.events.emit.bind($.events);
$.each = function(selector, callback) {
let elements;
if (typeof selector === 'string') {
elements = document.querySelectorAll(selector);
} else if (selector instanceof QueryWrapper) {
elements = selector;
} else {
elements = selector;
}
for (let i = 0; i < elements.length; i++) {
callback(elements[i], i);
}
}
$.post = function(url, data, callback) {
return $.ajax({
method: 'POST',
url: url,
data: data,
success: callback,
error: function(xhr) {
console.error('POST request failed:', xhr);
}
});
}
$.get = function(url, callback) {
return $.ajax({
method: 'GET',
url: url,
success: callback,
error: function(xhr) {
console.error('GET request failed:', xhr);
}
});
}
$.ajax = function(options) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', options.url, true);
// Set default headers
if (options.method === 'POST' || options.data) {
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
}
// Set custom headers
if (options.headers) {
for (var key in options.headers) {
xhr.setRequestHeader(key, options.headers[key]);
}
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
// If JSON parsing fails, use raw response
response = xhr.responseText;
}
if (xhr.status >= 200 && xhr.status < 300) {
if (options.success) options.success(response);
resolve(response);
} else {
if (options.error) options.error(xhr);
reject(xhr);
}
}
};
var data = null;
if (options.data) {
data = typeof options.data === 'string' ? options.data : JSON.stringify(options.data);
}
xhr.send(data);
});
}
$.ready = function(callback) {
if (document.readyState !== 'loading') {
callback();
} else {
document.addEventListener('DOMContentLoaded', callback);
}
}
// Helper function to execute scripts in HTML content
function executeScripts(htmlContent) {
// Create a temporary container to parse the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = htmlContent;
// Find all script tags and disable them temporarily
const scripts = tempDiv.querySelectorAll('script');
const scriptData = [];
scripts.forEach((script) => {
// Store script data for later execution
scriptData.push({
content: script.textContent || script.innerText || '',
src: script.src,
type: script.type,
nonce: script.nonce,
async: script.async,
defer: script.defer
});
// Disable the script by changing its type
script.type = 'text/disabled-script';
});
// Return the modified HTML and script data
return {
html: tempDiv.innerHTML,
scripts: scriptData
};
}
// Helper function to execute a single script
function executeScript(scriptInfo) {
if (scriptInfo.src) {
// External script
const newScript = document.createElement('script');
if (scriptInfo.type && scriptInfo.type !== 'text/disabled-script') {
newScript.type = scriptInfo.type;
}
if (scriptInfo.nonce) newScript.nonce = scriptInfo.nonce;
if (scriptInfo.async) newScript.async = scriptInfo.async;
if (scriptInfo.defer) newScript.defer = scriptInfo.defer;
newScript.src = scriptInfo.src;
document.head.appendChild(newScript);
} else if (scriptInfo.content.trim()) {
// Inline script
const newScript = document.createElement('script');
if (scriptInfo.type && scriptInfo.type !== 'text/disabled-script') {
newScript.type = scriptInfo.type;
}
if (scriptInfo.nonce) newScript.nonce = scriptInfo.nonce;
newScript.text = scriptInfo.content;
// Execute by inserting and immediately removing
document.head.appendChild(newScript);
document.head.removeChild(newScript);
}
}
// Add all the jQuery-like methods to QueryWrapper
QueryWrapper.prototype.parent = function() {
const parents = Array.from(this).map(el => el.parentNode).filter(el => el);
return new QueryWrapper(parents);
}
QueryWrapper.prototype.load = function(url, opt = {}) {
return new Promise((resolve, reject) => {
const ajaxOptions = {
method: opt.method || (opt.postData ? 'POST' : 'GET'),
url: url,
success: (response) => {
if(opt.replace)
this.replaceWith(response, opt);
else
this.html(response, opt);
if (opt.onLoad) opt.onLoad(response, null);
resolve(this);
},
error: (xhr) => {
if (opt.onError) opt.onError(xhr);
reject(new Error('Failed to load: ' + url));
}
};
if (opt.postData) {
ajaxOptions.data = opt.postData;
}
$.ajax(ajaxOptions);
});
}
QueryWrapper.prototype.html = function(opt_content = false, prop = {}) {
if (opt_content === false) {
return Array.from(this).map(el => el.innerHTML).join('');
}
if(typeof prop.diff == 'undefined')
prop.diff = $.options.alwaysDoDifferentialUpdate;
if(prop.diff === true) {
const temp = document.createElement('div');
temp.innerHTML = opt_content;
this.forEach(el => {
morphdom(el, temp, { childrenOnly: true });
});
} else {
this.forEach(el => {
const result = executeScripts(opt_content);
el.innerHTML = result.html;
result.scripts.forEach(executeScript);
});
}
return this;
}
QueryWrapper.prototype.text = function(opt_content = false) {
if (opt_content === false) {
return Array.from(this).map(el => el.textContent).join('');
}
this.forEach(el => {
el.textContent = opt_content;
});
return this;
}
QueryWrapper.prototype.replaceWith = function(content, prop = {}) {
if(typeof prop.diff == 'undefined')
prop.diff = $.options.alwaysDoDifferentialUpdate;
if(prop.diff === true) {
this.forEach(el => {
morphdom(el, content);
});
} else {
if(typeof content === 'string') {
this.forEach(el => {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
el.parentNode.replaceChild(tempDiv.firstChild, el);
});
} else if(content instanceof Element) {
this.forEach(el => {
el.parentNode.replaceChild(content.cloneNode(true), el);
});
}
}
return this;
}
QueryWrapper.prototype.query = function(selector) {
const found = Array.from(this).reduce((acc, el) => {
const results = el.querySelectorAll(selector);
return acc.concat(Array.from(results));
}, []);
return new QueryWrapper(found);
}
QueryWrapper.prototype.find = function(selector) {
const found = Array.from(this).reduce((acc, el) => {
const results = el.querySelectorAll(selector);
return acc.concat(Array.from(results));
}, []);
return new QueryWrapper(found);
}
QueryWrapper.prototype.on = function(event, handler) {
this.forEach(function(el) {
el.addEventListener(event, handler);
});
return this;
}
QueryWrapper.prototype.off = function(event, handler) {
this.forEach(function(el) {
el.removeEventListener(event, handler);
});
return this;
}
QueryWrapper.prototype.hide = function() {
return this.css({ display: 'none' });
}
QueryWrapper.prototype.show = function() {
return this.css({ display: '' });
}
QueryWrapper.prototype.toggle = function() {
this.forEach(function(el) {
el.style.display = (el.style.display === 'none' || el.style.display === '') ? '' : 'none';
});
return this;
}
QueryWrapper.prototype.attr = function(name, value) {
if (value === undefined) {
return this.length > 0 ? this[0].getAttribute(name) : null;
}
this.forEach(function(el) {
el.setAttribute(name, value);
});
return this;
}
QueryWrapper.prototype.addClass = function(classNameOrList) {
var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList];
this.forEach(function(el) {
classList.forEach(function(classNames) {
// Split space-separated class names
var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; });
classes.forEach(function(className) {
el.classList.add(className);
});
});
});
return this;
}
QueryWrapper.prototype.removeClass = function(classNameOrList) {
var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList];
this.forEach(function(el) {
classList.forEach(function(classNames) {
// Split space-separated class names
var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; });
classes.forEach(function(className) {
el.classList.remove(className);
});
});
});
return this;
}
QueryWrapper.prototype.toggleClass = function(classNameOrList, force) {
var classList = Array.isArray(classNameOrList) ? classNameOrList : [classNameOrList];
this.forEach(function(el) {
classList.forEach(function(classNames) {
// Split space-separated class names
var classes = classNames.toString().split(/\s+/).filter(function(name) { return name.length > 0; });
classes.forEach(function(className) {
if (typeof force !== 'undefined') {
el.classList.toggle(className, force);
} else {
el.classList.toggle(className);
}
});
});
});
return this;
}
QueryWrapper.prototype.empty = function() {
this.forEach(function(el) {
el.innerHTML = '';
});
return this;
}
QueryWrapper.prototype.css = function(styles, optOrValue) {
// If no arguments or first argument is a string (getter mode)
if (arguments.length === 0 || (typeof styles === 'string' && arguments.length === 1)) {
if (this.length === 0) return undefined;
var el = this[0];
if (typeof styles === 'string') {
// Get computed style for a specific property
return window.getComputedStyle(el)[styles];
} else {
// Return computed styles object (not commonly used)
return window.getComputedStyle(el);
}
}
// Setter mode
this.forEach(function(el) {
if (typeof styles === 'string' && arguments.length >= 2) {
// Single property setter: .css('prop', 'value')
el.style[styles] = optOrValue;
} else if (typeof styles === 'object') {
// Multiple properties setter: .css({prop1: 'value1', prop2: 'value2'})
for (var key in styles) {
el.style[key] = styles[key];
}
}
});
return this;
}
QueryWrapper.prototype.each = function(callback) {
this.forEach(function(el, index) {
callback(el, index);
});
return this;
}
QueryWrapper.prototype.append = function(child_or_html) {
this.forEach(function(el) {
if (typeof child_or_html === 'string' && el.insertAdjacentHTML) {
if (child_or_html.includes('<script')) {
const result = executeScripts(child_or_html);
el.insertAdjacentHTML('beforeend', result.html);
result.scripts.forEach(executeScript);
} else {
el.insertAdjacentHTML('beforeend', child_or_html);
}
} else if (el.appendChild) {
if (typeof child_or_html === 'string') {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = child_or_html;
el.appendChild(tempDiv.firstChild);
} else {
el.appendChild(child_or_html);
}
} else if(el.hasOwnProperty('innerHTML')) {
el.innerHTML += child_or_html;
}
});
return this;
}
QueryWrapper.prototype.remove = function() {
this.forEach(function(el) {
el.parentNode.removeChild(el);
});
return this;
}
function first(...args) {
for (const arg of args) {
if (arg !== undefined && arg !== null && arg !== '') {
return arg;
}
}
return null;
}
function clamp(v, min, max) {
if (v < min) return min;
if (v > max) return max;
return v;
}
function pick_entry_from_range(array, value) {
if (!array) return {};
let result = {};
for (const [pv] of Object.entries(array)) {
if (value >= pv.from && value <= pv.to) result = pv;
}
return result;
}
return {
$: $,
EventEmitter: EventEmitter,
QueryWrapper: QueryWrapper,
first: first,
clamp: clamp,
pick_entry_from_range: pick_entry_from_range
};
}));
+165
View File
@@ -0,0 +1,165 @@
:root {
--vp-color-primary: rgba(255, 125, 55, 1);
--vp-color-primary-focus: rgba(255, 120, 60, 0.4);
--vp-background: rgba(0,0,0,0.2);
--vp-color-black: #000;
--vp-color-gray: #666;
--vp-color-white: #fff;
--vp-space-s: 4px;
--vp-space-l: 6px;
--vp-size-icon: 12px;
--vp-size-nav-height: 48px;
--vp-opacity: 0.4;
--vp-opacity-active: 0.9;
--vp-z-pane: 2;
--vp-z-toolbar: 3;
--vp-z-popup: 9999;
--vp-duration: 0.25s;
--vp-easing: ease;
}
#fabric {
position: absolute;
top: var(--vp-size-nav-height);
left: 0; right: 0; bottom: 0;
display: flex;
overflow: hidden;
}
.vp-split {
flex: 1 1 auto;
display: flex;
position: relative;
overflow: hidden;
}
.vp-split.row { flex-direction: row; }
.vp-split.col { flex-direction: column; }
.viewport-pane {
flex: 1 1 0;
position: relative;
border: var(--vp-space-s) solid var(--color-border);
background: var(--vp-background);
padding: var(--vp-space-l);
overflow: auto;
font-family: console;
transition:
border-color var(--vp-duration) var(--vp-easing),
box-shadow var(--vp-duration) var(--vp-easing),
background var(--vp-duration) var(--vp-easing);
}
.vp-split > .viewport-pane {
transition:
flex var(--vp-duration) var(--vp-easing),
opacity var(--vp-duration) var(--vp-easing);
}
.vp-no-transition,
.vp-no-transition * {
transition: none !important;
}
.viewport-pane:before {
content: attr(data-title);
position: absolute;
top: var(--vp-space-s);
right: var(--vp-space-s);
opacity: var(--vp-opacity-medium);
pointer-events: none;
}
.viewport-pane.focused {
border-color: var(--vp-color-primary-focus);
z-index: var(--vp-z-pane);
}
.viewport-pane.focused:before {
opacity: var(--vp-opacity-active);
color: var(--color-highlight);
}
.vp-divider {
background: var(--color-border);
opacity: var(--vp-opacity);
position: relative;
flex: 0 0 auto;
}
.vp-divider.row {
width: var(--vp-space-l);
cursor: col-resize;
}
.vp-divider.col {
height: var(--vp-space-l);
cursor: row-resize;
}
.vp-divider:after {
content:'';
position: absolute;
inset: 0;
transition:
background var(--vp-duration) var(--vp-easing),
opacity var(--vp-duration) var(--vp-easing);
background: var(--color-border);
}
.vp-divider.row:after {
background: var(--color-border);
}
.vp-divider:hover:after {
background: var(--color-highlight);
opacity: var(--vp-opacity);
}
.vp-divider.dragging:after {
background: var(--color-highlight);
opacity: var(--vp-opacity-active);
}
.vp-help-overlay {
position: absolute;
top: var(--vp-space-s);
left: var(--vp-space-s);
opacity: var(--vp-opacity);
pointer-events: none;
}
.vp-pane-toolbar {
position: absolute;
top: var(--vp-space-s);
right: var(--vp-space-s);
display: flex;
gap: var(--vp-space-s);
z-index: var(--vp-z-toolbar);
}
.vp-pane-toolbar button {
padding: 0 var(--vp-space-l);
border: 1px solid var(--color-border);
background: var(--vp-background);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.vp-pane-toolbar button:hover {
background: var(--color-highlight);
color: var(--vp-color-black);
text-shadow: none;
}
.vp-icon {
width: var(--vp-size-icon);
height: var(--vp-size-icon);
display: block;
}
+388
View File
@@ -0,0 +1,388 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
root.TilingViewportManager = factory(root.$);
}
}(typeof self !== 'undefined' ? self : this, function ($) {
'use strict';
// Note: This module expects showPopupMenu to be available globally
// or you may need to adjust the dependency list above to include it
let TilingViewportManager = function(container, options = {}) {
let idCounter = 1, rootNode = null, focusedPane = null, containerEl = null;
let nextId = () => 'vp_' + (idCounter++);
let paneMeta = new Map();
let pendingViewLoads = [];
let defaultoptions = {
minPaneSize: 100,
keyboard: true,
showHelp: true,
closeTransitionTimeout: 400,
toolbarButtons: ['split-v','split-h','views','close'],
standardViewList: [],
cssVars: { '--vp-duration': '.22s', '--vp-easing': 'ease' }
};
options = Object.assign({}, defaultoptions, options);
let createPane = (content, props = {}) => {
let id = nextId();
let $el = $('<div class="viewport-pane">').attr('data-id', id);
if(props.title) $el.attr('data-title', props.title);
$el.html(content || `<div style="opacity:.65;">Use Alt+&lt;H V O X ↑ ← → ↓&gt;</div>`)
.on('mousedown', () => focusPaneByEl($el[0]));
attachPaneToolbar($el[0]);
paneMeta.set(id, {id, canClose: true, canSplit: true, title: props.title, ...props});
if(props.view?.screen) pendingViewLoads.push({id, view: props.view});
return {type: 'pane', id, el: $el[0]};
};
let buildInitial = () => { rootNode = createPane(); $(containerEl).html('').append(rootNode.el); focusPane(rootNode); };
let focusPane = node => { if(!node || node.type !== 'pane') return; if(focusedPane?.el) $(focusedPane.el).removeClass('focused'); focusedPane = node; $(node.el).addClass('focused'); };
let focusPaneByEl = el => focusPane(findNodeById(rootNode, $(el).attr('data-id')));
let findNodeById = (node, id) => !node ? null : node.type === 'pane' ? (node.id === id ? node : null) : node.children.map(ch => findNodeById(ch, id)).find(r=>r);
let getNodeById = id => findNodeById(rootNode, id) || null;
let findParentPath = (node, id, path=[]) => !node ? null : node.type === 'pane' ? (node.id === id ? path : null) : node.children.map(ch => findParentPath(ch, id, path.concat(node))).find(r=>r);
let paneList = (node, acc=[]) => !node ? acc : node.type === 'pane' ? (acc.push(node), acc) : (node.children.forEach(ch => paneList(ch, acc)), acc);
let renormalizeSizes = splitNode => { let total = splitNode.sizes.reduce((a,b)=>a+b,0)||1; splitNode.sizes = splitNode.sizes.map(s=>s/total); };
let replaceChild = (splitNode, oldChild, newChild) => { let i = splitNode.children.indexOf(oldChild); if(i>=0) splitNode.children.splice(i,1,newChild); };
let split = (direction, newPaneProps = {}) => {
if(!focusedPane) return;
let meta = paneMeta.get(focusedPane.id);
if(meta?.canSplit === false) return;
let paneRect = focusedPane.el.getBoundingClientRect();
let availableSpace = direction === 'row' ? paneRect.width : paneRect.height;
let requiredSpace = 2 * options.minPaneSize;
if(availableSpace < requiredSpace) {
//console.log(`Cannot split: available space ${availableSpace}px < required ${requiredSpace}px`);
return;
}
let parentPath = findParentPath(rootNode, focusedPane.id);
let newPane = createPane(undefined, newPaneProps);
let animatedSplit = null, newIndex = -1;
if(!parentPath?.length) {
rootNode = {type:'split', direction, children:[focusedPane, newPane], sizes:[1,0]};
animatedSplit = rootNode; newIndex = 1;
} else {
let parent = parentPath.at(-1);
if(parent.type === 'split' && parent.direction === direction) {
let idx = parent.children.indexOf(focusedPane);
parent.children.splice(idx+1, 0, newPane);
parent.sizes = parent.sizes || parent.children.map(()=>1);
parent.sizes.splice(idx+1, 0, 0);
renormalizeSizes(parent);
animatedSplit = parent; newIndex = idx+1;
} else if(parent.type === 'split' && parent.direction !== direction && parent.children.length === 1) {
parent.direction = direction; parent.children.push(newPane); parent.sizes = [1,0];
animatedSplit = parent; newIndex = parent.children.length-1;
} else {
let idx = parent.children.indexOf(focusedPane);
let created = {type:'split', direction, children:[focusedPane, newPane], sizes:[1,0]};
parent.children.splice(idx, 1, created);
animatedSplit = created; newIndex = 1;
}
}
render();
if(animatedSplit && newIndex >= 0) {
requestAnimationFrame(() => requestAnimationFrame(() => {
animatedSplit.sizes = animatedSplit.children.map(()=>1);
renormalizeSizes(animatedSplit);
animatedSplit.children.forEach((ch,i) => ch.el && (ch.el.style.flex = `${animatedSplit.sizes[i]} 1 0`));
}));
}
focusPane(newPane); return newPane;
};
let splitVertical = () => split('row');
let splitHorizontal = () => split('col');
let closeFocused = () => {
if(!focusedPane) return;
let meta = paneMeta.get(focusedPane.id);
if(meta?.canClose === false) return;
if(rootNode === focusedPane) { buildInitial(); return; }
if(typeof meta?.onUnload === 'function') meta.onUnload(focusedPane.id);
if(typeof meta?.view?.onUnload === 'function') meta.view.onUnload(focusedPane.id);
let parentPath = findParentPath(rootNode, focusedPane.id);
if(!parentPath?.length) return;
let parent = parentPath.at(-1);
if(parent.type !== 'split') return;
let idx = parent.children.indexOf(focusedPane);
let oldSizes = (parent.sizes || parent.children.map(()=>1)).slice();
let oldSize = oldSizes[idx] || 0;
let remaining = 1 - oldSize;
let newSizes = oldSizes.slice();
if(remaining > 0) {
for(let i=0;i<newSizes.length;i++) if(i!==idx) newSizes[i] = newSizes[i] / remaining; else newSizes[i] = 0;
} else {
let count = parent.children.length - 1 || 1;
for(let i=0;i<newSizes.length;i++) newSizes[i] = (i===idx) ? 0 : 1/count;
}
parent.sizes = newSizes;
parent.children.forEach((ch,i) => ch.el && (ch.el.style.flex = `${parent.sizes[i]} 1 0`));
if(focusedPane.el) {
focusedPane.el.style.transition = (focusedPane.el.style.transition || '') + ', opacity .18s ease';
focusedPane.el.style.opacity = '0';
}
let done = false;
let cleanup = () => {
if(done) return; done = true;
parent.children.splice(idx,1);
parent.sizes?.splice(idx,1);
paneMeta.delete(focusedPane.id);
if(parent.children.length === 1) {
let only = parent.children[0];
parentPath.length === 1 ? rootNode = only : replaceChild(parentPath.at(-2), parent, only);
} else if(parent.sizes) renormalizeSizes(parent);
render(); focusPane(paneList(rootNode)[0]);
};
let onEnd = ev => { if(ev.target === focusedPane.el && (ev.propertyName === 'opacity' || ev.propertyName === 'flex')) { focusedPane.el.removeEventListener('transitionend', onEnd); cleanup(); } };
if(focusedPane.el) focusedPane.el.addEventListener('transitionend', onEnd);
setTimeout(() => cleanup(), 350);
};
let nextPane = (node, currentId, options={direction:1}) => { let list = paneList(node); if(!list.length) return null; let idx = list.findIndex(p=>p.id===(currentId||(focusedPane?.id)))||0; return list[(idx+options.direction+list.length)%list.length]; };
let focusNext = () => { let n = nextPane(rootNode); if(n) focusPane(n); };
let focusPrev = () => { let n = nextPane(rootNode, null, {direction:-1}); if(n) focusPane(n); };
let serialize = (node=rootNode) => {
let ser = n => !n ? null : n.type==='pane' ?
{type:'pane', id:n.id, title:paneMeta.get(n.id)?.title, props:{canClose:paneMeta.get(n.id)?.canClose!==false, canSplit:paneMeta.get(n.id)?.canSplit!==false}, view:paneMeta.get(n.id)?.view||null} :
{type:'split', direction:n.direction, sizes:(n.sizes||[]).slice(), children:n.children.map(ser)};
return {focus: focusedPane?.id, layout: ser(node)};
};
let restore = data => {
if(!data) return;
let wrapper = data.layout ? data : {layout:data};
paneMeta.clear(); idCounter = 1; pendingViewLoads.length = 0;
let idNums = [];
let build = l => l && l.type==='pane' ?
(() => { let pane = createPane(undefined, {title:l.title, canClose:l.props?.canClose, canSplit:l.props?.canSplit, view:l.view});
if(l.id) { pane.el.dataset.id = pane.id = l.id; let n=parseInt(l.id.split('_')[1],10); if(!isNaN(n)) idNums.push(n); }
let meta = paneMeta.get(pane.id); if(meta) { Object.assign(meta, l.props||{}, {title:l.title, view:l.view}); if(pane.el && l.title) pane.el.dataset.title = l.title; paneMeta.set(pane.id, meta); }
return pane; })() :
l && l.type==='split' ? {type:'split', direction:l.direction||'row', children:(l.children||[]).map(build).filter(Boolean), sizes:(l.sizes||[]).slice()} : null;
rootNode = build(wrapper.layout) || createPane();
if(idNums.length) idCounter = Math.max(...idNums) + 1;
render();
pendingViewLoads.forEach(v => v.view && loadView(v.id, v.view)); pendingViewLoads.length = 0;
focusPane(wrapper.focus ? findNodeById(rootNode, wrapper.focus) || paneList(rootNode)[0] : paneList(rootNode)[0]);
};
let render = () => { if(!containerEl) return; $(containerEl).empty().append(renderNode(rootNode)); injectHelpOverlay(); };
let renderNode = node => {
if(node.type==='pane') return node.el;
node.el = node.el || $('<div>')[0];
$(node.el).empty().addClass(`vp-split ${node.direction}`);
if(!node.sizes || node.sizes.length !== node.children.length) { node.sizes = node.children.map(()=>1); renormalizeSizes(node); }
node.children.forEach((ch,i) => {
let chEl = renderNode(ch); $(chEl).css('flex', `${node.sizes[i]} 1 0`); node.el.appendChild(chEl);
if(i < node.children.length-1) { let div = $('<div>').addClass(`vp-divider ${node.direction}`)[0]; setupDivider(div, node, i); node.el.appendChild(div); }
});
return node.el;
};
let setupDivider = (div, splitNode, leftIdx) => {
$(div).on('mousedown', e => {
e.preventDefault(); $(div).addClass('dragging');
$(containerEl).addClass('vp-no-transition');
let isRow = splitNode.direction === 'row';
let startPos = isRow ? e.clientX : e.clientY;
let childRects = splitNode.children.map(ch => ch.el.getBoundingClientRect());
let prop = isRow ? 'width' : 'height';
let startPixels = childRects.map(r => r[prop]);
let [aStartPx, bStartPx] = [startPixels[leftIdx], startPixels[leftIdx+1]];
let totalPixels = startPixels.reduce((a,b)=>a+b,0);
let onMove = ev => {
let curPos = isRow ? ev.clientX : ev.clientY;
let [newApx, newBpx] = [aStartPx + curPos - startPos, bStartPx - curPos + startPos];
if(newApx < options.minPaneSize) [newApx, newBpx] = [options.minPaneSize, aStartPx + bStartPx - options.minPaneSize];
if(newBpx < options.minPaneSize) [newApx, newBpx] = [aStartPx + bStartPx - options.minPaneSize, options.minPaneSize];
startPixels[leftIdx] = newApx; startPixels[leftIdx+1] = newBpx;
splitNode.sizes = startPixels.map(p => p / totalPixels);
splitNode.children.forEach((ch,i)=> {
if(ch.el) {
let flexValue = `${splitNode.sizes[i]} 1 0`;
$(ch.el).css('flex', flexValue);
}
});
};
let onUp = () => {
$(div).removeClass('dragging');
$(containerEl).removeClass('vp-no-transition');
$(document).off('mousemove', onMove).off('mouseup', onUp);
};
$(document).on('mousemove', onMove).on('mouseup', onUp);
});
$(div).on('dblclick', () => {
$(containerEl).addClass('vp-no-transition');
let len = splitNode.sizes.length; splitNode.sizes = splitNode.sizes.map(()=>1/len);
splitNode.children.forEach((ch,i)=> ch.el && $(ch.el).css('flex', `${splitNode.sizes[i]} 1 0`));
setTimeout(() => $(containerEl).removeClass('vp-no-transition'), 0);
});
};
let attachPaneToolbar = el => {
let bar = $('<div>').addClass('vp-pane-toolbar').html(`
<button title="Split Vert" data-act="split-v" aria-label="Split vertically">
<svg class="vp-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="1" y="4" width="8" height="16" rx="1" fill="currentColor" />
<rect x="15" y="4" width="8" height="16" rx="1" fill="currentColor" />
</svg>
</button>
<button title="Split Horiz" data-act="split-h" aria-label="Split horizontally">
<svg class="vp-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="3" y="1" width="18" height="8" rx="1" fill="currentColor" />
<rect x="3" y="15" width="18" height="8" rx="1" fill="currentColor" />
</svg>
</button>
<button title="Views" data-act="views">O</button>
<button title="Close" data-act="close">X</button>`)[0];
el.appendChild(bar);
$(bar).on('mousedown', ev => ev.stopPropagation());
$(bar).on('click', ev => { ev.stopPropagation(); focusPaneByEl(el);
let button = ev.target.closest('button[data-act]');
let act = button ? button.getAttribute('data-act') : null;
if(act==='split-v') splitVertical(); else if(act==='split-h') splitHorizontal(); else if(act==='close') closeFocused();
else if(act==='views') {
// compute button position
let rect = button.getBoundingClientRect();
let items = options.standardViewList.slice();
showPopupMenu(rect.left, rect.bottom, items, { onSelect: it => {
// support string or object {screen}
let view = typeof it === 'string' ? {screen: it} : (it.screen ? it : {screen: it});
loadView(el.dataset.id, view);
}, container: document.body, emptyText: 'No views available' });
}
});
refreshToolbarState(el.dataset.id);
};
let refreshToolbarState = paneId => {
let meta = paneMeta.get(paneId); let el = $(containerEl).find(`.viewport-pane[data-id="${paneId}"]`)[0];
if(!el) return; let bar = $(el).find('.vp-pane-toolbar')[0]; if(!bar) return;
let [canSplit, canClose] = [!meta||meta.canSplit!==false, !meta||meta.canClose!==false];
['split-v','split-h'].forEach(act => { let btn = $(bar).find(`[data-act="${act}"]`)[0]; if(btn) { btn.disabled = !canSplit; $(btn).toggleClass('disabled', !canSplit); } });
let btnC = $(bar).find('[data-act="close"]')[0]; if(btnC) { btnC.disabled = !canClose; $(btnC).toggleClass('disabled', !canClose); }
};
let setPaneProps = (paneId, props) => { let meta = paneMeta.get(paneId); if(!meta) return; Object.assign(meta, props);
if(props.title) { let el = $(containerEl).find(`.viewport-pane[data-id="${paneId}"]`)[0]; if(el) el.dataset.title = props.title; }
if(typeof props.onUnload === 'function') meta.onUnload = props.onUnload;
paneMeta.set(paneId, meta); refreshToolbarState(paneId); };
let getPaneProps = paneId => paneMeta.get(paneId);
let focusDirection = (dx, dy) => {
if(!focusedPane) return;
let panes = Array.from(containerEl.querySelectorAll('.viewport-pane'));
let cur = focusedPane.el.getBoundingClientRect();
let candidates = panes.filter(p => p !== focusedPane.el).map(p => {
let r = p.getBoundingClientRect();
if(dx) {
if(dx > 0 && r.left < cur.right-1 || dx < 0 && r.right > cur.left+1) return null;
let overlap = Math.max(0, Math.min(cur.bottom, r.bottom) - Math.max(cur.top, r.top));
let primaryDist = dx > 0 ? r.left - cur.right : cur.left - r.right;
if(primaryDist < -1) return null;
return {el:p, score: primaryDist*1000 + (cur.height - overlap)};
}
if(dy > 0 && r.top < cur.bottom-1 || dy < 0 && r.bottom > cur.top+1) return null;
let overlap = Math.max(0, Math.min(cur.right, r.right) - Math.max(cur.left, r.left));
let primaryDist = dy > 0 ? r.top - cur.bottom : cur.top - r.bottom;
if(primaryDist < -1) return null;
return {el:p, score: primaryDist*1000 + (cur.width - overlap)};
}).filter(Boolean);
if(candidates.length) return focusPaneByEl(candidates.sort((a,b) => a.score - b.score)[0].el);
let [cx, cy] = [cur.left + cur.width/2, cur.top + cur.height/2];
let fallback = panes.filter(p => p !== focusedPane.el).map(p => {
let r = p.getBoundingClientRect(); let [px, py] = [r.left + r.width/2, r.top + r.height/2];
let [vx, vy] = [px-cx, py-cy];
if(dx && Math.sign(vx) !== Math.sign(dx) || dy && Math.sign(vy) !== Math.sign(dy)) return null;
return {el:p, score: (dx ? Math.abs(vx) : Math.abs(vy))*2 + (dx ? Math.abs(vy) : Math.abs(vx))};
}).filter(Boolean);
if(fallback.length) focusPaneByEl(fallback.sort((a,b) => a.score - b.score)[0].el);
};
let injectHelpOverlay = () => {
if(!options.showHelp) return;
if(!containerEl.querySelector('.vp-help-overlay')) containerEl.appendChild(Object.assign(document.createElement('div'), {className:'vp-help-overlay', innerHTML:options.hint_text || ``}));
};
let getPaneEl = id => findNodeById(rootNode, id)?.el || null;
let openViewsPopupForPane = (paneId) => {
let node = findNodeById(rootNode, paneId || focusedPane?.id);
if(!node || !node.el) return;
let rect = node.el.getBoundingClientRect();
let x = Math.max(8, rect.right - 80);
let y = rect.top + 24;
let items = options.standardViewList.slice();
showPopupMenu(x, y, items, { onSelect: it => {
let view = typeof it === 'string' ? {screen: it} : (it.screen ? it : {screen: it});
loadView(node.id, view);
}, container: document.body, emptyText: 'No views available' });
};
let loadView = (paneId, view) => {
let node = findNodeById(rootNode, paneId);
if(!node || !node.el) return; // nothing to load into
let meta = paneMeta.get(paneId);
try { if(meta && typeof meta.onUnload === 'function') meta.onUnload(paneId); if(meta && meta.view && typeof meta.view.onUnload === 'function') meta.view.onUnload(paneId); } catch(e){ console.error('onUnload error', e); }
if(meta) meta.view = view;
node.el.innerHTML = `<div style="opacity:.4;">Loading ${view.screen||'...'}...</div>`;
$(node.el).load(`screens/${view.screen}.html?v=${Date.now()}`, {diff:true, onLoad:()=>$.emit('view:loaded',{paneId,view})});
};
let handleKeys = e => {
if(!e.altKey || !options.keyboard) return;
let actions = {
'KeyV':()=>splitVertical(),'v':()=>splitVertical(),'V':()=>splitVertical(),
'KeyH':()=>splitHorizontal(),'h':()=>splitHorizontal(),'H':()=>splitHorizontal(),
'KeyX':()=>closeFocused(),'x':()=>closeFocused(),'X':()=>closeFocused(),
'KeyO':()=>openViewsPopupForPane(),'o':()=>openViewsPopupForPane(), 'O':()=>openViewsPopupForPane(),
'ArrowRight':()=>focusDirection(1,0),'ArrowLeft':()=>focusDirection(-1,0),'ArrowUp':()=>focusDirection(0,-1),'ArrowDown':()=>focusDirection(0,1),'Tab':()=>focusNext()
};
if(actions[e.code] || actions[e.key]) {
(actions[e.code] || actions[e.key])();
e.preventDefault();
}
};
containerEl = typeof container === 'string' ? document.querySelector(container) : container;
if(!containerEl) throw new Error('TilingViewportManager: container not found');
if(options.cssVars) Object.keys(options.cssVars).forEach(k => containerEl.style.setProperty(k, options.cssVars[k]));
buildInitial();
if(options.keyboard) document.addEventListener('keydown', handleKeys);
this.splitVertical = splitVertical;
this.splitHorizontal = splitHorizontal;
this.closeFocused = closeFocused;
this.focusNext = focusNext;
this.focusPrev = focusPrev;
this.serialize = serialize;
this.restore = restore;
this.setPaneProps = setPaneProps;
this.getPaneProps = getPaneProps;
this.options = options;
this.loadView = loadView;
this.getPaneEl = getPaneEl;
this.getNodeById = getNodeById;
Object.defineProperty(this, 'layout', { get: () => serialize(), set: d => restore(d) });
Object.defineProperty(this, 'focused', { get: () => focusedPane?.id });
};
return TilingViewportManager;
}));
+68
View File
@@ -0,0 +1,68 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.UWorkspaceShell = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
'use strict';
const globalScope = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : this);
function getElement(target) {
if (!target) return null;
if (typeof target === 'string') return document.getElementById(target);
return target && target.nodeType === 1 ? target : null;
}
function bindShell(options) {
const sidebar = getElement(options.sidebarId || options.sidebar);
const overlay = getElement(options.overlayId || options.overlay);
const toggle = getElement(options.toggleButtonId || options.toggle);
const closeOnNav = options.closeOnNav !== false;
if (!sidebar || !overlay) return null;
function setOpen(open) {
sidebar.classList.toggle('is-open', !!open);
overlay.classList.toggle('is-open', !!open);
}
function toggleOpen() {
setOpen(!sidebar.classList.contains('is-open'));
}
if (toggle) {
toggle.addEventListener('click', toggleOpen);
}
overlay.addEventListener('click', function () {
setOpen(false);
});
if (closeOnNav) {
sidebar.querySelectorAll('a').forEach(function (link) {
link.addEventListener('click', function () {
setOpen(false);
});
});
}
globalScope.addEventListener('resize', function () {
if (globalScope.innerWidth > 860) {
setOpen(false);
}
});
return {
open: function () { setOpen(true); },
close: function () { setOpen(false); },
toggle: toggleOpen,
};
}
return {
init: bindShell,
};
}));
+111
View File
@@ -0,0 +1,111 @@
(function (root, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define([], factory);
} else {
root.Connection = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
var Connection = {
auto_reconnect : false,
established : false,
update_indicator : (status) => {
var status_colors = {
'offline' : 'red',
'online' : 'lightgreen',
'error' : 'DarkOrange',
};
$('#connection-status').text(status).css('color', status_colors[status] || 'gray');
},
debug : true,
auto_reconnect : true,
cmd_waiting_rels : {},
server_url : '',
init : () => {
new EventSystem(Connection);
},
start : (url = null) => {
if(url) Connection.server_url = url;
Connection.update_indicator('offline');
if(Connection.socket) Connection.socket.close();
Connection.socket = new WebSocket(Connection.server_url);
Connection.socket.onmessage = function(rawmsg) {
var msg = JSON.parse(rawmsg.data);
if(Connection.debug) console.log('CONNECTION MSG', msg);
if(msg.type) Connection.trigger(msg.type, msg);
Connection.trigger('message', msg);
}
Connection.socket.onclose = function() {
Connection.update_indicator('offline');
if(Connection.debug) console.log('CONNECTION CLOSED');
Connection.established = false;
Connection.trigger('close', {});
}
Connection.socket.onerror = function(error) {
Connection.update_indicator('error');
console.error('CONNECTION', error);
Connection.established = false;
}
Connection.socket.onopen = function() {
Connection.update_indicator('online');
if(Connection.debug) console.log('CONNECTION ESTABLISHED');
Connection.established = true;
Connection.trigger('open', {});
}
setTimeout(Connection.reconnect, 2000);
},
deauth : () => {
Game.session = {};
},
reconnect : () => {
if(!Connection.established && Connection.auto_reconnect)
Connection.start();
else
setTimeout(Connection.reconnect, 2000);
},
queue : [],
dequeue : () => {
var dq = Connection.queue;
Connection.queue = [];
dq.forEach(function(fm) {
Connection.send(fm);
});
},
send : (msg) => {
if(Connection.established) {
if(typeof msg == 'function')
msg();
else
Connection.socket.send(JSON.stringify(msg));
} else {
Connection.queue.push(msg);
}
},
close : () => {
Connection.auto_reconnect = false;
Connection.socket.close();
},
}
return Connection;
}));