zero-query 0.6.3 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +39 -29
  2. package/cli/commands/build.js +113 -4
  3. package/cli/commands/bundle.js +392 -29
  4. package/cli/commands/create.js +1 -1
  5. package/cli/commands/dev/devtools/index.js +56 -0
  6. package/cli/commands/dev/devtools/js/components.js +49 -0
  7. package/cli/commands/dev/devtools/js/core.js +409 -0
  8. package/cli/commands/dev/devtools/js/elements.js +413 -0
  9. package/cli/commands/dev/devtools/js/network.js +166 -0
  10. package/cli/commands/dev/devtools/js/performance.js +73 -0
  11. package/cli/commands/dev/devtools/js/router.js +105 -0
  12. package/cli/commands/dev/devtools/js/source.js +132 -0
  13. package/cli/commands/dev/devtools/js/stats.js +35 -0
  14. package/cli/commands/dev/devtools/js/tabs.js +79 -0
  15. package/cli/commands/dev/devtools/panel.html +95 -0
  16. package/cli/commands/dev/devtools/styles.css +244 -0
  17. package/cli/commands/dev/index.js +29 -4
  18. package/cli/commands/dev/logger.js +6 -1
  19. package/cli/commands/dev/overlay.js +428 -2
  20. package/cli/commands/dev/server.js +42 -5
  21. package/cli/commands/dev/watcher.js +59 -1
  22. package/cli/help.js +8 -5
  23. package/cli/scaffold/{scripts → app}/app.js +16 -23
  24. package/cli/scaffold/{scripts → app}/components/about.js +4 -4
  25. package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
  26. package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -7
  27. package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +3 -3
  28. package/cli/scaffold/app/components/home.js +137 -0
  29. package/cli/scaffold/{scripts → app}/routes.js +1 -1
  30. package/cli/scaffold/{scripts → app}/store.js +6 -6
  31. package/cli/scaffold/assets/.gitkeep +0 -0
  32. package/cli/scaffold/{styles/styles.css → global.css} +4 -2
  33. package/cli/scaffold/index.html +12 -11
  34. package/cli/utils.js +111 -6
  35. package/dist/zquery.dist.zip +0 -0
  36. package/dist/zquery.js +1122 -158
  37. package/dist/zquery.min.js +3 -16
  38. package/index.d.ts +129 -1290
  39. package/index.js +15 -10
  40. package/package.json +7 -6
  41. package/src/component.js +172 -49
  42. package/src/core.js +359 -18
  43. package/src/diff.js +256 -58
  44. package/src/expression.js +33 -3
  45. package/src/reactive.js +37 -5
  46. package/src/router.js +243 -7
  47. package/tests/component.test.js +886 -0
  48. package/tests/core.test.js +977 -0
  49. package/tests/diff.test.js +525 -0
  50. package/tests/errors.test.js +162 -0
  51. package/tests/expression.test.js +482 -0
  52. package/tests/http.test.js +289 -0
  53. package/tests/reactive.test.js +339 -0
  54. package/tests/router.test.js +649 -0
  55. package/tests/store.test.js +379 -0
  56. package/tests/utils.test.js +512 -0
  57. package/types/collection.d.ts +383 -0
  58. package/types/component.d.ts +217 -0
  59. package/types/errors.d.ts +103 -0
  60. package/types/http.d.ts +81 -0
  61. package/types/misc.d.ts +179 -0
  62. package/types/reactive.d.ts +76 -0
  63. package/types/router.d.ts +161 -0
  64. package/types/ssr.d.ts +49 -0
  65. package/types/store.d.ts +107 -0
  66. package/types/utils.d.ts +142 -0
  67. package/cli/commands/dev.old.js +0 -520
  68. package/cli/scaffold/scripts/components/home.js +0 -137
  69. /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
  70. /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
  71. /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
  72. /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
@@ -1,4 +1,4 @@
1
- // scripts/app.js — application entry point
1
+ // app/app.js — application entry point
2
2
  //
3
3
  // This file bootstraps the zQuery app: imports all components,
4
4
  // sets up routing, wires the responsive nav, and demonstrates
@@ -27,7 +27,7 @@ const router = $.router({
27
27
  // Highlight the active nav link on every route change
28
28
  router.onChange((to) => {
29
29
  $.all('.nav-link').removeClass('active');
30
- $.all(`.nav-link[z-link="${to.path}"]`).addClass('active');
30
+ $(`.nav-link[z-link="${to.path}"]`).addClass('active');
31
31
 
32
32
  // Close mobile menu on navigate
33
33
  closeMobileMenu();
@@ -36,26 +36,20 @@ router.onChange((to) => {
36
36
  // ---------------------------------------------------------------------------
37
37
  // Responsive sidebar toggle
38
38
  // ---------------------------------------------------------------------------
39
- const sidebar = $.id('sidebar');
40
- const overlay = $.id('overlay');
41
- const toggle = $.id('menu-toggle');
39
+ const $sidebar = $('#sidebar');
40
+ const $overlay = $('#overlay');
41
+ const $toggle = $('#menu-toggle');
42
42
 
43
- function openMobileMenu() {
44
- sidebar.classList.add('open');
45
- overlay.classList.add('visible');
46
- toggle.classList.add('active');
43
+ function toggleMobileMenu(open) {
44
+ $sidebar.toggleClass('open', open);
45
+ $overlay.toggleClass('visible', open);
46
+ $toggle.toggleClass('active', open);
47
47
  }
48
48
 
49
- function closeMobileMenu() {
50
- sidebar.classList.remove('open');
51
- overlay.classList.remove('visible');
52
- toggle.classList.remove('active');
53
- }
49
+ function closeMobileMenu() { toggleMobileMenu(false); }
54
50
 
55
51
  // $.on — global delegated event listeners
56
- $.on('click', '#menu-toggle', () => {
57
- sidebar.classList.contains('open') ? closeMobileMenu() : openMobileMenu();
58
- });
52
+ $.on('click', '#menu-toggle', () => toggleMobileMenu(!$sidebar.hasClass('open')));
59
53
  $.on('click', '#overlay', closeMobileMenu);
60
54
 
61
55
  // Close sidebar on Escape key — using $.on direct (no selector needed)
@@ -69,14 +63,13 @@ $.on('keydown', (e) => {
69
63
  // Any component can emit: $.bus.emit('toast', { message, type })
70
64
  // Types: 'success', 'error', 'info'
71
65
  $.bus.on('toast', ({ message, type = 'info' }) => {
72
- const container = $.id('toasts');
73
- const toast = $.create('div');
74
- toast.className = `toast toast-${type}`;
75
- toast.textContent = message;
76
- container.appendChild(toast);
66
+ const toast = $.create('div')
67
+ .addClass('toast', `toast-${type}`)
68
+ .text(message)
69
+ .appendTo('#toasts');
77
70
  // Auto-remove after 3 seconds
78
71
  setTimeout(() => {
79
- toast.classList.add('toast-exit');
72
+ toast.addClass('toast-exit');
80
73
  setTimeout(() => toast.remove(), 300);
81
74
  }, 3000);
82
75
  });
@@ -31,16 +31,16 @@ $.component('about-page', {
31
31
  </div>
32
32
 
33
33
  <div class="card">
34
- <h3>🎨 Theme</h3>
34
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42"/></svg> Theme</h3>
35
35
  <p>Toggle between dark and light mode. Persisted to <code>localStorage</code> via <code>$.storage</code>.</p>
36
36
  <div class="theme-toggle">
37
37
  <span>Current: <strong>${this.state.theme}</strong></span>
38
- <button class="btn btn-outline" @click="toggleTheme">${this.state.theme === 'dark' ? '☀️ Light Mode' : '🌙 Dark Mode'}</button>
38
+ <button class="btn btn-outline" @click="toggleTheme">${this.state.theme === 'dark' ? '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:16px;height:16px;vertical-align:-3px;margin-right:0.15rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/></svg> Light Mode' : '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:16px;height:16px;vertical-align:-3px;margin-right:0.15rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"/></svg> Dark Mode'}</button>
39
39
  </div>
40
40
  </div>
41
41
 
42
42
  <div class="card">
43
- <h3>🧰 Features Used in This App</h3>
43
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z"/></svg> Features Used in This App</h3>
44
44
  <div class="feature-grid">
45
45
  <div class="feature-item">
46
46
  <strong>$.component()</strong>
@@ -118,7 +118,7 @@ $.component('about-page', {
118
118
  </div>
119
119
 
120
120
  <div class="card card-muted">
121
- <h3>📚 Next Steps</h3>
121
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"/></svg> Next Steps</h3>
122
122
  <ul class="next-steps">
123
123
  <li>Read the <a href="https://z-query.com/docs" target="_blank" rel="noopener">full documentation</a></li>
124
124
  <li>Explore the <a href="https://github.com/tonywied17/zero-query" target="_blank" rel="noopener">source on GitHub</a></li>
@@ -58,7 +58,7 @@ $.component('api-demo', {
58
58
  <p class="subtitle">Fetching data with <code>$.get()</code>. Directives: <code>z-if</code>, <code>z-show</code>, <code>z-for</code>, <code>z-text</code>.</p>
59
59
  </div>
60
60
 
61
- <div class="card card-error" z-show="error"><p>⚠ <span z-text="error"></span></p></div>
61
+ <div class="card card-error" z-show="error"><p><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:16px;height:16px;vertical-align:-3px;margin-right:0.15rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/></svg> <span z-text="error"></span></p></div>
62
62
  <div class="loading-bar" z-show="loading"></div>
63
63
 
64
64
  <div z-if="!selectedUser">
@@ -216,13 +216,6 @@
216
216
  font-weight: 500;
217
217
  }
218
218
 
219
- @keyframes slide-in {
220
- from { opacity: 0; transform: translateY(-6px); }
221
- to { opacity: 1; transform: translateY(0); }
222
- }
223
- }
224
-
225
- /* -- Animation -- */
226
219
  @keyframes slide-in {
227
220
  from { opacity: 0; transform: translateY(-6px); }
228
221
  to { opacity: 1; transform: translateY(0); }
@@ -68,7 +68,7 @@
68
68
  </div>
69
69
 
70
70
  <!-- Contact count -->
71
- <div class="card" z-if="contacts.length > 0">
71
+ <div class="card" z-if="contacts.length > 0" z-key="contacts-list">
72
72
  <!-- Contacts list — z-for renders each item -->
73
73
  <ul class="contacts-list">
74
74
  <li
@@ -102,14 +102,14 @@
102
102
  </div>
103
103
 
104
104
  <!-- Empty state -->
105
- <div class="card" z-else>
105
+ <div class="card" z-else z-key="contacts-empty">
106
106
  <div class="empty-state">
107
107
  <p>No contacts yet — add one above!</p>
108
108
  </div>
109
109
  </div>
110
110
 
111
111
  <!-- Selected contact detail panel — z-if conditional rendering -->
112
- <div class="card contact-detail" z-if="selectedId !== null">
112
+ <div class="card contact-detail" z-if="selectedId !== null" z-key="contact-detail">
113
113
  <div class="detail-header">
114
114
  <div>
115
115
  <h3 z-text="selectedName"></h3>
@@ -0,0 +1,137 @@
1
+ // scripts/components/home.js — dashboard / landing page
2
+ //
3
+ // Demonstrates: $.component, state, render, mounted lifecycle,
4
+ // signal + computed + effect (reactive primitives),
5
+ // $.store integration, $.bus, template rendering
6
+
7
+ $.component('home-page', {
8
+ state: () => ({
9
+ greeting: '',
10
+ signalDemo: 0,
11
+ }),
12
+
13
+ mounted() {
14
+ // $.signal() — fine-grained reactive primitive
15
+ const count = $.signal(0);
16
+
17
+ // $.computed() — derived reactive value that auto-updates
18
+ const doubled = $.computed(() => count.value * 2);
19
+
20
+ // $.effect() — runs whenever its dependencies change
21
+ $.effect(() => {
22
+ this.state.signalDemo = doubled.value;
23
+ });
24
+
25
+ // Store the signal setter so the button can use it
26
+ this._signalCount = count;
27
+
28
+ // Greet based on time of day
29
+ const hour = new Date().getHours();
30
+ this.state.greeting = hour < 12 ? 'Good morning'
31
+ : hour < 18 ? 'Good afternoon'
32
+ : 'Good evening';
33
+
34
+ // Track page visit via the global store
35
+ const store = $.getStore('main');
36
+ store.dispatch('incrementVisits');
37
+ },
38
+
39
+ incrementSignal() {
40
+ if (this._signalCount) {
41
+ this._signalCount.value++;
42
+ }
43
+ },
44
+
45
+ render() {
46
+ const store = $.getStore('main');
47
+ return `
48
+ <div class="page-header">
49
+ <h1>${this.state.greeting}</h1>
50
+ <p class="subtitle">Welcome to your new <strong>zQuery</strong> app. Explore the pages to see different features in action.</p>
51
+ </div>
52
+
53
+ <div class="card-grid">
54
+ <div class="card card-accent">
55
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"/></svg> Reactive Signals</h3>
56
+ <p>Fine-grained reactivity with <code>signal()</code>, <code>computed()</code>, and <code>effect()</code>.</p>
57
+ <div class="signal-demo">
58
+ <span class="signal-value">Doubled: ${this.state.signalDemo}</span>
59
+ <button class="btn btn-sm" @click="incrementSignal">Increment Signal</button>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="card">
64
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5"/></svg> Counter</h3>
65
+ <p><code>computed</code> properties, <code>watch</code> callbacks, and <code>z-for</code> with <code>z-key</code> diffing.</p>
66
+ <a z-link="/counter" class="btn btn-outline">Try It →</a>
67
+ </div>
68
+
69
+ <div class="card">
70
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg> Todos</h3>
71
+ <p>Global store, <code>z-key</code> keyed lists, DOM diffing. <strong>${store.getters.todoCount}</strong> items, <strong>${store.getters.doneCount}</strong> done.</p>
72
+ <a z-link="/todos" class="btn btn-outline">Try It →</a>
73
+ </div>
74
+
75
+ <div class="card">
76
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/></svg> Contacts</h3>
77
+ <p>External templates, scoped styles, and <code>z-key</code> keyed lists. <strong>${store.getters.contactCount}</strong> contacts, <strong>${store.getters.favoriteCount}</strong> ★ favorited.</p>
78
+ <a z-link="/contacts" class="btn btn-outline">Try It →</a>
79
+ </div>
80
+
81
+ <div class="card">
82
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"/></svg> API Demo</h3>
83
+ <p>Fetch data with <code>$.get()</code>, loading states, and <code>$.escapeHtml()</code>.</p>
84
+ <a z-link="/api" class="btn btn-outline">Try It →</a>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="card card-muted">
89
+ <h3><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--accent)" style="width:20px;height:20px;vertical-align:-4px;margin-right:0.25rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/></svg> App Stats</h3>
90
+ <div class="stats-grid">
91
+ <div class="stat-group">
92
+ <span class="stat-group-title"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:14px;height:14px;vertical-align:-2px;margin-right:0.2rem;"><path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955a1.126 1.126 0 0 1 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/></svg> General</span>
93
+ <div class="stat-group-values">
94
+ <div class="stat">
95
+ <span class="stat-value">${store.state.visits}</span>
96
+ <span class="stat-label">Page Views</span>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="stat-group">
102
+ <span class="stat-group-title"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:14px;height:14px;vertical-align:-2px;margin-right:0.2rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg> Todos</span>
103
+ <div class="stat-group-values">
104
+ <div class="stat">
105
+ <span class="stat-value">${store.getters.todoCount}</span>
106
+ <span class="stat-label">Total</span>
107
+ </div>
108
+ <div class="stat">
109
+ <span class="stat-value">${store.getters.pendingCount}</span>
110
+ <span class="stat-label">Pending</span>
111
+ </div>
112
+ <div class="stat">
113
+ <span class="stat-value">${store.getters.doneCount}</span>
114
+ <span class="stat-label">Done</span>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="stat-group">
120
+ <span class="stat-group-title"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:14px;height:14px;vertical-align:-2px;margin-right:0.2rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/></svg> Contacts</span>
121
+ <div class="stat-group-values">
122
+ <div class="stat">
123
+ <span class="stat-value">${store.getters.contactCount}</span>
124
+ <span class="stat-label">Total</span>
125
+ </div>
126
+ <div class="stat">
127
+ <span class="stat-value">${store.getters.favoriteCount}</span>
128
+ <span class="stat-label">★ Favorited</span>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ <small class="muted">Stats powered by <code>$.store()</code> getters — visit count tracked globally.</small>
134
+ </div>
135
+ `;
136
+ }
137
+ });
@@ -1,4 +1,4 @@
1
- // scripts/routes.js — route definitions
1
+ // app/routes.js — route definitions
2
2
  //
3
3
  // Each route maps a URL path to a component tag name.
4
4
  // Supports: static paths, :params, wildcards, and lazy loading via `load`.
@@ -1,4 +1,4 @@
1
- // scripts/store.js — global state management
1
+ // app/store.js — global state management
2
2
  //
3
3
  // $.store() creates a centralized store with state, actions, and getters.
4
4
  // Components can dispatch actions and subscribe to changes.
@@ -11,11 +11,11 @@ export const store = $.store('main', {
11
11
 
12
12
  // Contacts
13
13
  contacts: [
14
- { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Designer', status: 'online', favorite: true },
15
- { id: 2, name: 'Bob Martinez', email: 'bob@example.com', role: 'Developer', status: 'offline', favorite: false },
16
- { id: 3, name: 'Carol White', email: 'carol@example.com', role: 'Manager', status: 'online', favorite: true },
17
- { id: 4, name: 'Dave Kim', email: 'dave@example.com', role: 'Designer', status: 'away', favorite: false },
18
- { id: 5, name: 'Eve Torres', email: 'eve@example.com', role: 'Developer', status: 'online', favorite: false },
14
+ { id: 1, name: 'Tony Wiedman', email: 'tony@z-query.com', role: 'Developer', status: 'online', favorite: true },
15
+ { id: 2, name: 'Robert Baratheon', email: 'robert@stormlands.io', role: 'Manager', status: 'offline', favorite: false },
16
+ { id: 3, name: 'Terry A. Davis', email: 'terry@templeos.net', role: 'Developer', status: 'online', favorite: true },
17
+ { id: 4, name: 'Trevor Moore', email: 'trevor@wkuk.tv', role: 'Designer', status: 'away', favorite: false },
18
+ { id: 5, name: 'Carlo Acutis', email: 'carlo@vatican.va', role: 'Developer', status: 'online', favorite: false },
19
19
  ],
20
20
  contactsAdded: 0,
21
21
  },
File without changes
@@ -1,4 +1,4 @@
1
- /* styles/styles.css — responsive scaffold styles
1
+ /* global.css — responsive scaffold styles
2
2
  *
3
3
  * Uses CSS custom properties for easy theming.
4
4
  * Dark theme by default, light theme via [data-theme="light"].
@@ -103,6 +103,7 @@ code { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-s
103
103
  transition: all 0.15s ease;
104
104
  border-left: 3px solid transparent;
105
105
  cursor: pointer;
106
+ user-select: none;
106
107
  }
107
108
 
108
109
  .nav-link:hover {
@@ -116,7 +117,8 @@ code { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-s
116
117
  border-left-color: var(--accent);
117
118
  }
118
119
 
119
- .nav-icon { font-size: 1.05rem; width: 1.4rem; text-align: center; }
120
+ .nav-icon { display: inline-flex; align-items: center; justify-content: center; width: 1.4rem; }
121
+ .nav-icon svg { width: 18px; height: 18px; flex-shrink: 0; }
120
122
 
121
123
  .sidebar-footer {
122
124
  padding: 0.75rem 1.25rem;
@@ -5,35 +5,36 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>{{NAME}}</title>
7
7
  <base href="/">
8
- <link rel="stylesheet" href="styles/styles.css">
9
- <script src="scripts/vendor/zquery.min.js"></script>
10
- <script type="module" src="scripts/app.js"></script>
8
+ <link rel="stylesheet" href="global.css">
9
+ <link rel="icon" type="image/png" href="favicon.ico">
10
+ <script src="zquery.min.js"></script>
11
+ <script type="module" src="app/app.js"></script>
11
12
  </head>
12
13
  <body>
13
14
 
14
15
  <!-- Sidebar Navigation -->
15
16
  <aside class="sidebar" id="sidebar">
16
17
  <div class="sidebar-header">
17
- <span class="brand">⚡ {{NAME}}</span>
18
+ <span class="brand"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--accent)" style="width:18px;height:18px;vertical-align:-3px;margin-right:0.2rem;"><path fill-rule="evenodd" d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" clip-rule="evenodd"/></svg> {{NAME}}</span>
18
19
  </div>
19
20
  <nav class="sidebar-nav">
20
21
  <a z-link="/" class="nav-link">
21
- <span class="nav-icon">🏠</span> Home
22
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955a1.126 1.126 0 0 1 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/></svg></span> Home
22
23
  </a>
23
24
  <a z-link="/counter" class="nav-link">
24
- <span class="nav-icon">🔢</span> Counter
25
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5"/></svg></span> Counter
25
26
  </a>
26
27
  <a z-link="/todos" class="nav-link">
27
- <span class="nav-icon">✅</span> Todos
28
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg></span> Todos
28
29
  </a>
29
30
  <a z-link="/contacts" class="nav-link">
30
- <span class="nav-icon">📇</span> Contacts
31
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/></svg></span> Contacts
31
32
  </a>
32
33
  <a z-link="/api" class="nav-link">
33
- <span class="nav-icon">🌐</span> API Demo
34
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"/></svg></span> API Demo
34
35
  </a>
35
36
  <a z-link="/about" class="nav-link">
36
- <span class="nav-icon">💡</span> About
37
+ <span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg></span> About
37
38
  </a>
38
39
  </nav>
39
40
  <div class="sidebar-footer">
@@ -46,7 +47,7 @@
46
47
  <button class="hamburger" id="menu-toggle" aria-label="Toggle menu">
47
48
  <span></span><span></span><span></span>
48
49
  </button>
49
- <span class="topbar-brand">⚡ {{NAME}}</span>
50
+ <span class="topbar-brand"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--accent)" style="width:16px;height:16px;vertical-align:-2px;margin-right:0.15rem;"><path fill-rule="evenodd" d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" clip-rule="evenodd"/></svg> {{NAME}}</span>
50
51
  </header>
51
52
 
52
53
  <!-- Overlay for mobile menu -->
package/cli/utils.js CHANGED
@@ -83,15 +83,120 @@ function stripComments(code) {
83
83
  }
84
84
 
85
85
  // ---------------------------------------------------------------------------
86
- // minify — quick minification (strips comments + collapses whitespace)
86
+ // minify — single-pass minification
87
+ // Strips comments, collapses whitespace to the minimum required,
88
+ // and preserves string / template-literal / regex content verbatim.
87
89
  // ---------------------------------------------------------------------------
88
90
 
89
91
  function minify(code, banner) {
90
- const body = stripComments(code.replace(banner, ''))
91
- .replace(/^\s*\n/gm, '')
92
- .replace(/\n\s+/g, '\n')
93
- .replace(/\s{2,}/g, ' ');
94
- return banner + '\n' + body;
92
+ return banner + '\n' + _minifyBody(code.replace(banner, ''));
93
+ }
94
+
95
+ /**
96
+ * Single-pass minifier: walks character-by-character, skips strings/regex,
97
+ * strips comments, and emits a space only when both neighbours are
98
+ * identifier-like characters (or when collapsing would create ++, --, // or /*).
99
+ */
100
+ function _minifyBody(code) {
101
+ let out = '';
102
+ let i = 0;
103
+
104
+ while (i < code.length) {
105
+ const ch = code[i];
106
+ const nx = code[i + 1];
107
+
108
+ // ── String / template literal: copy verbatim ────────────────
109
+ if (ch === '"' || ch === "'" || ch === '`') {
110
+ const q = ch;
111
+ out += ch; i++;
112
+ while (i < code.length) {
113
+ if (code[i] === '\\') { out += code[i] + (code[i + 1] || ''); i += 2; continue; }
114
+ out += code[i];
115
+ if (code[i] === q) { i++; break; }
116
+ i++;
117
+ }
118
+ continue;
119
+ }
120
+
121
+ // ── Block comment: skip ─────────────────────────────────────
122
+ if (ch === '/' && nx === '*') {
123
+ i += 2;
124
+ while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) i++;
125
+ i += 2;
126
+ continue;
127
+ }
128
+
129
+ // ── Line comment: skip ──────────────────────────────────────
130
+ if (ch === '/' && nx === '/') {
131
+ i += 2;
132
+ while (i < code.length && code[i] !== '\n') i++;
133
+ continue;
134
+ }
135
+
136
+ // ── Regex literal: copy verbatim ────────────────────────────
137
+ if (ch === '/') {
138
+ if (_isRegexCtx(out)) {
139
+ out += ch; i++;
140
+ let inCC = false;
141
+ while (i < code.length) {
142
+ const rc = code[i];
143
+ if (rc === '\\') { out += rc + (code[i + 1] || ''); i += 2; continue; }
144
+ if (rc === '[') inCC = true;
145
+ if (rc === ']') inCC = false;
146
+ out += rc; i++;
147
+ if (rc === '/' && !inCC) {
148
+ while (i < code.length && /[gimsuy]/.test(code[i])) { out += code[i]; i++; }
149
+ break;
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+ }
155
+
156
+ // ── Whitespace: collapse ────────────────────────────────────
157
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
158
+ while (i < code.length && (code[i] === ' ' || code[i] === '\t' || code[i] === '\n' || code[i] === '\r')) i++;
159
+ const before = out[out.length - 1];
160
+ const after = code[i];
161
+ if (_needsSpace(before, after)) out += ' ';
162
+ continue;
163
+ }
164
+
165
+ out += ch;
166
+ i++;
167
+ }
168
+
169
+ return out;
170
+ }
171
+
172
+ /** True when removing the space between a and b would change semantics. */
173
+ function _needsSpace(a, b) {
174
+ if (!a || !b) return false;
175
+ const idA = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || a === '_' || a === '$';
176
+ const idB = (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b === '_' || b === '$';
177
+ if (idA && idB) return true; // e.g. const x, return value
178
+ if (a === '+' && b === '+') return true; // prevent ++
179
+ if (a === '-' && b === '-') return true; // prevent --
180
+ if (a === '/' && (b === '/' || b === '*')) return true; // prevent // or /*
181
+ return false;
182
+ }
183
+
184
+ /** Heuristic: is the next '/' a regex start (vs division)? */
185
+ function _isRegexCtx(out) {
186
+ let end = out.length - 1;
187
+ while (end >= 0 && out[end] === ' ') end--;
188
+ if (end < 0) return true;
189
+ const last = out[end];
190
+ if ('=({[,;:!&|?~+-*/%^>'.includes(last)) return true;
191
+ const tail = out.substring(Math.max(0, end - 7), end + 1);
192
+ const kws = ['return', 'typeof', 'case', 'in', 'delete', 'void', 'throw', 'new'];
193
+ for (const kw of kws) {
194
+ if (tail.endsWith(kw)) {
195
+ const pos = end - kw.length;
196
+ if (pos < 0 || !((out[pos] >= 'a' && out[pos] <= 'z') || (out[pos] >= 'A' && out[pos] <= 'Z') || (out[pos] >= '0' && out[pos] <= '9') || out[pos] === '_' || out[pos] === '$')) return true;
197
+ }
198
+ }
199
+ return false;
95
200
  }
96
201
 
97
202
  // ---------------------------------------------------------------------------
Binary file