zero-query 0.9.7 → 0.9.8

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 (56) hide show
  1. package/README.md +31 -3
  2. package/cli/commands/build.js +50 -3
  3. package/cli/commands/create.js +22 -9
  4. package/cli/help.js +2 -0
  5. package/cli/scaffold/default/app/app.js +211 -0
  6. package/cli/scaffold/default/app/components/about.js +201 -0
  7. package/cli/scaffold/default/app/components/api-demo.js +143 -0
  8. package/cli/scaffold/default/app/components/contact-card.js +231 -0
  9. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -0
  10. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -0
  11. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -0
  12. package/cli/scaffold/default/app/components/counter.js +127 -0
  13. package/cli/scaffold/default/app/components/home.js +249 -0
  14. package/cli/scaffold/{app → default/app}/components/not-found.js +2 -2
  15. package/cli/scaffold/default/app/components/playground/playground.css +116 -0
  16. package/cli/scaffold/default/app/components/playground/playground.html +162 -0
  17. package/cli/scaffold/default/app/components/playground/playground.js +117 -0
  18. package/cli/scaffold/default/app/components/todos.js +225 -0
  19. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -0
  20. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -0
  21. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -0
  22. package/cli/scaffold/default/app/routes.js +15 -0
  23. package/cli/scaffold/{app → default/app}/store.js +15 -10
  24. package/cli/scaffold/{global.css → default/global.css} +238 -252
  25. package/cli/scaffold/{index.html → default/index.html} +35 -0
  26. package/cli/scaffold/{app → minimal/app}/app.js +37 -39
  27. package/cli/scaffold/minimal/app/components/about.js +68 -0
  28. package/cli/scaffold/minimal/app/components/counter.js +122 -0
  29. package/cli/scaffold/minimal/app/components/home.js +68 -0
  30. package/cli/scaffold/minimal/app/components/not-found.js +16 -0
  31. package/cli/scaffold/minimal/app/routes.js +9 -0
  32. package/cli/scaffold/minimal/app/store.js +36 -0
  33. package/cli/scaffold/minimal/assets/.gitkeep +0 -0
  34. package/cli/scaffold/minimal/global.css +291 -0
  35. package/cli/scaffold/minimal/index.html +44 -0
  36. package/dist/zquery.dist.zip +0 -0
  37. package/dist/zquery.js +1942 -1925
  38. package/dist/zquery.min.js +2 -2
  39. package/index.d.ts +10 -1
  40. package/index.js +4 -3
  41. package/package.json +1 -1
  42. package/src/component.js +6 -3
  43. package/src/diff.js +15 -2
  44. package/tests/cli.test.js +304 -0
  45. package/cli/scaffold/app/components/about.js +0 -131
  46. package/cli/scaffold/app/components/api-demo.js +0 -103
  47. package/cli/scaffold/app/components/contacts/contacts.css +0 -246
  48. package/cli/scaffold/app/components/contacts/contacts.html +0 -140
  49. package/cli/scaffold/app/components/contacts/contacts.js +0 -153
  50. package/cli/scaffold/app/components/counter.js +0 -85
  51. package/cli/scaffold/app/components/home.js +0 -137
  52. package/cli/scaffold/app/components/todos.js +0 -131
  53. package/cli/scaffold/app/routes.js +0 -13
  54. /package/cli/scaffold/{LICENSE → default/LICENSE} +0 -0
  55. /package/cli/scaffold/{assets → default/assets}/.gitkeep +0 -0
  56. /package/cli/scaffold/{favicon.ico → default/favicon.ico} +0 -0
@@ -1,24 +1,26 @@
1
- // app/app.js — application entry point
1
+ // app.js — Application entry point
2
2
  //
3
- // This file bootstraps the zQuery app: imports all components,
4
- // sets up routing, wires the responsive nav, and demonstrates
5
- // several core APIs: $.router, $.ready, $.bus, $.on, and $.storage.
3
+ // Bootstraps the app: imports components, sets up routing,
4
+ // and wires the responsive sidebar.
5
+ //
6
+ // Key APIs used:
7
+ // $.router — SPA navigation (history mode)
8
+ // $.ready — run after DOM is loaded
9
+ // $.on — global delegated event listeners
10
+ // $.storage — localStorage wrapper
6
11
 
7
12
  import './store.js';
8
13
  import './components/home.js';
9
14
  import './components/counter.js';
10
- import './components/todos.js';
11
- import './components/api-demo.js';
12
15
  import './components/about.js';
13
- import './components/contacts/contacts.js';
14
16
  import './components/not-found.js';
15
17
  import { routes } from './routes.js';
16
18
 
17
19
  // ---------------------------------------------------------------------------
18
- // Router — SPA navigation with history mode
20
+ // Router
19
21
  // ---------------------------------------------------------------------------
20
22
  const router = $.router({
21
- el: '#app', //@ Mount point (Set in index.html)
23
+ el: '#app',
22
24
  routes,
23
25
  fallback: 'not-found',
24
26
  mode: 'history'
@@ -48,47 +50,43 @@ function toggleMobileMenu(open) {
48
50
 
49
51
  function closeMobileMenu() { toggleMobileMenu(false); }
50
52
 
51
- // $.on — global delegated event listeners
52
53
  $.on('click', '#menu-toggle', () => toggleMobileMenu(!$sidebar.hasClass('open')));
53
54
  $.on('click', '#overlay', closeMobileMenu);
54
-
55
- // Close sidebar on Escape key — using $.on direct (no selector needed)
56
- $.on('keydown', (e) => {
57
- if (e.key === 'Escape') closeMobileMenu();
58
- });
59
-
60
- // ---------------------------------------------------------------------------
61
- // Toast notification system via $.bus (event bus)
62
- // ---------------------------------------------------------------------------
63
- // Any component can emit: $.bus.emit('toast', { message, type })
64
- // Types: 'success', 'error', 'info'
65
- $.bus.on('toast', ({ message, type = 'info' }) => {
66
- const toast = $.create('div')
67
- .addClass('toast', `toast-${type}`)
68
- .text(message)
69
- .appendTo('#toasts');
70
- // Auto-remove after 3 seconds
71
- setTimeout(() => {
72
- toast.addClass('toast-exit');
73
- setTimeout(() => toast.remove(), 300);
74
- }, 3000);
75
- });
55
+ $.on('keydown', (e) => { if (e.key === 'Escape') closeMobileMenu(); });
76
56
 
77
57
  // ---------------------------------------------------------------------------
78
- // On DOM ready — final setup
58
+ // On DOM ready
79
59
  // ---------------------------------------------------------------------------
80
60
  $.ready(() => {
81
61
  // Display version in the sidebar footer
82
- const versionEl = $.id('nav-version');
83
- if (versionEl) versionEl.textContent = 'v' + $.version;
62
+ $('#nav-version').text('v' + $.version);
84
63
 
85
- // Restore last theme from localStorage ($.storage)
86
- const savedTheme = $.storage.get('theme');
87
- if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme);
64
+ // Theme: restore saved preference or auto-detect from system
65
+ const saved = $.storage.get('theme');
66
+ const preference = saved || 'system';
67
+ applyTheme(preference);
88
68
 
89
- // Set active link on initial load
69
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
70
+ const current = $.storage.get('theme') || 'system';
71
+ if (current === 'system') applyTheme('system');
72
+ });
73
+
74
+ // Highlight the active link on initial load
90
75
  const current = window.location.pathname;
91
76
  $.all(`.nav-link[z-link="${current}"]`).addClass('active');
92
77
 
93
78
  console.log('⚡ {{NAME}} — powered by zQuery v' + $.version);
94
79
  });
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Theme helper
83
+ // ---------------------------------------------------------------------------
84
+ function applyTheme(preference) {
85
+ let resolved = preference;
86
+ if (preference === 'system') {
87
+ resolved = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
88
+ }
89
+ $('html').attr('data-theme', resolved);
90
+ }
91
+
92
+ window.__applyTheme = applyTheme;
@@ -0,0 +1,68 @@
1
+ // about.js — About page with theme switcher
2
+ //
3
+ // Features used:
4
+ // $.storage — localStorage wrapper (get / set)
5
+ // $.version — library version string
6
+ // data-theme — dark / light theming
7
+
8
+ $.component('about-page', {
9
+ styles: `
10
+ .theme-switch { display: inline-flex; border-radius: var(--radius); overflow: hidden;
11
+ border: 1px solid var(--border); background: var(--bg); }
12
+ .theme-btn { padding: 0.45rem 1rem; font-size: 0.82rem; font-weight: 500;
13
+ background: transparent; border: none; color: var(--text-muted);
14
+ cursor: pointer; transition: all .15s ease; font-family: inherit; }
15
+ .theme-btn:hover { color: var(--text); background: var(--bg-hover); }
16
+ .theme-btn.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
17
+ .theme-btn + .theme-btn { border-left: 1px solid var(--border); }
18
+
19
+ .next-steps { padding-left: 1.25rem; }
20
+ .next-steps li { margin-bottom: 0.4rem; font-size: 0.9rem; color: var(--text-muted); }
21
+ .next-steps a { color: var(--accent); }
22
+ `,
23
+
24
+ state: () => ({
25
+ theme: 'system',
26
+ }),
27
+
28
+ mounted() {
29
+ this.state.theme = $.storage.get('theme') || 'system';
30
+ },
31
+
32
+ setTheme(mode) {
33
+ this.state.theme = mode;
34
+ $.storage.set('theme', mode);
35
+ window.__applyTheme(mode);
36
+ },
37
+
38
+ render() {
39
+ const t = this.state.theme;
40
+ return `
41
+ <div class="page-header">
42
+ <h1>About</h1>
43
+ <p class="subtitle">zQuery v${$.version} — zero-dependency frontend micro-library.</p>
44
+ </div>
45
+
46
+ <div class="card">
47
+ <h3>Theme</h3>
48
+ <p>Choose your preferred appearance. <strong>System</strong> follows your OS setting.</p>
49
+ <div class="theme-switch">
50
+ <button class="theme-btn ${t === 'system' ? 'active' : ''}" @click="setTheme('system')">System</button>
51
+ <button class="theme-btn ${t === 'dark' ? 'active' : ''}" @click="setTheme('dark')">Dark</button>
52
+ <button class="theme-btn ${t === 'light' ? 'active' : ''}" @click="setTheme('light')">Light</button>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="card">
57
+ <h3>Next Steps</h3>
58
+ <ul class="next-steps">
59
+ <li>Read the <a href="https://z-query.com/docs" target="_blank" rel="noopener">full documentation</a></li>
60
+ <li>Explore the <a href="https://github.com/tonywied17/zero-query" target="_blank" rel="noopener">source on GitHub</a></li>
61
+ <li>Run <code>npx zquery bundle</code> to build for production</li>
62
+ <li>Run <code>npx zquery dev</code> for live-reload development</li>
63
+ <li>Try <code>npx zquery create my-app</code> for the full-featured scaffold</li>
64
+ </ul>
65
+ </div>
66
+ `;
67
+ }
68
+ });
@@ -0,0 +1,122 @@
1
+ // counter.js — Interactive counter
2
+ //
3
+ // Features used:
4
+ // $.getStore / store.subscribe — shared state + live re-render
5
+ // @click / z-model + z-number — events + two-way binding
6
+ // z-if / z-for / z-key — conditional & keyed list rendering
7
+
8
+ $.component('counter-page', {
9
+ styles: `
10
+ .ctr-display { padding: 2rem 0 1.25rem; text-align: center; }
11
+ .ctr-num { font-size: 4rem; font-weight: 800; font-variant-numeric: tabular-nums;
12
+ color: var(--accent); transition: color .2s; line-height: 1; }
13
+ .ctr-num.negative { color: var(--danger); }
14
+ .ctr-label { font-size: .82rem; color: var(--text-muted); margin-top: .35rem; }
15
+ .ctr-actions { display: flex; justify-content: center; gap: .65rem;
16
+ margin-bottom: 1.5rem; }
17
+ .ctr-actions .btn { min-width: 120px; justify-content: center; }
18
+ .ctr-config { display: flex; align-items: center; justify-content: center; gap: 1.25rem;
19
+ padding-top: 1.15rem; border-top: 1px solid var(--border); }
20
+ .ctr-config label { display: flex; align-items: center; gap: .5rem;
21
+ color: var(--text-muted); font-size: .88rem; }
22
+ .ctr-config .input-sm { width: 65px; text-align: center; }
23
+ .ctr-hist { display: flex; flex-wrap: wrap; gap: .4rem; }
24
+ .ctr-hist-item { display: inline-flex; align-items: center; gap: .3rem;
25
+ padding: .3rem .65rem; border-radius: var(--radius);
26
+ background: var(--bg-hover); border: 1px solid var(--border);
27
+ font-size: .82rem; font-variant-numeric: tabular-nums; }
28
+ .ctr-hist-item:last-child { border-color: var(--accent); background: rgba(88,166,255,.06); }
29
+ .ctr-hist-op { color: var(--text-muted); font-weight: 500; }
30
+ .ctr-hist-val { color: var(--accent); font-weight: 600; }
31
+
32
+ @media (max-width: 768px) {
33
+ .ctr-num { font-size: 2.75rem; }
34
+ .ctr-actions { gap: .5rem; }
35
+ .ctr-actions .btn { min-width: 100px; }
36
+ .ctr-config { flex-wrap: wrap; gap: .75rem; }
37
+ }
38
+ @media (max-width: 480px) {
39
+ .ctr-num { font-size: 2.25rem; }
40
+ .ctr-actions .btn { min-width: 0; flex: 1; }
41
+ }
42
+ `,
43
+
44
+ state: () => ({
45
+ history: [],
46
+ }),
47
+
48
+ mounted() {
49
+ this._unsub = $.getStore('main').subscribe(() => this.setState({}));
50
+ },
51
+
52
+ destroyed() {
53
+ if (this._unsub) this._unsub();
54
+ },
55
+
56
+ increment() {
57
+ const store = $.getStore('main');
58
+ store.dispatch('increment');
59
+ this._pushHistory('+', store.state.step, store.state.count);
60
+ },
61
+
62
+ decrement() {
63
+ const store = $.getStore('main');
64
+ store.dispatch('decrement');
65
+ this._pushHistory('−', store.state.step, store.state.count);
66
+ },
67
+
68
+ setStep(e) {
69
+ $.getStore('main').dispatch('setStep', Number(e.target.value));
70
+ },
71
+
72
+ _pushHistory(action, value, result) {
73
+ const raw = this.state.history.__raw || this.state.history;
74
+ const next = [...raw, { id: Date.now(), action, value, result }];
75
+ this.state.history = next.length > 8 ? next.slice(-8) : next;
76
+ },
77
+
78
+ reset() {
79
+ $.getStore('main').dispatch('reset');
80
+ this.state.history = [];
81
+ },
82
+
83
+ render() {
84
+ const { count, step } = $.getStore('main').state;
85
+
86
+ return `
87
+ <div class="page-header">
88
+ <h1>Counter</h1>
89
+ <p class="subtitle">Reactive <code>store</code>, <code>subscribe</code>, <code>dispatch</code>, and <code>z-for</code>.</p>
90
+ </div>
91
+
92
+ <div class="card">
93
+ <div class="ctr-display">
94
+ <div class="ctr-num ${count < 0 ? 'negative' : ''}">${count}</div>
95
+ <div class="ctr-label">current value${step !== 1 ? ` · step ${step}` : ''}</div>
96
+ </div>
97
+
98
+ <div class="ctr-actions">
99
+ <button class="btn btn-outline" @click="decrement">− Subtract</button>
100
+ <button class="btn btn-primary" @click="increment">+ Add</button>
101
+ </div>
102
+
103
+ <div class="ctr-config">
104
+ <label>Step size
105
+ <input type="number" value="${step}" @input="setStep" min="1" max="100" class="input input-sm" />
106
+ </label>
107
+ <button class="btn btn-ghost btn-sm" @click="reset">Reset</button>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="card" z-if="history.length > 0">
112
+ <h3>History <small style="color:var(--text-muted);font-weight:400;font-size:.85rem;">${this.state.history.length} entries</small></h3>
113
+ <div class="ctr-hist">
114
+ <span z-for="e in history" z-key="{{e.id}}" class="ctr-hist-item">
115
+ <span class="ctr-hist-op">{{e.action}}{{e.value}}</span>
116
+ <span class="ctr-hist-val">→ {{e.result}}</span>
117
+ </span>
118
+ </div>
119
+ </div>
120
+ `;
121
+ }
122
+ });
@@ -0,0 +1,68 @@
1
+ // home.js — Landing page
2
+ //
3
+ // Features used:
4
+ // $.component — define a component
5
+ // $.getStore — read/dispatch global store
6
+ // store.subscribe — re-render on store changes
7
+ // @click / z-model — event binding + two-way input
8
+
9
+ $.component('home-page', {
10
+ state: () => ({
11
+ greeting: '',
12
+ }),
13
+
14
+ mounted() {
15
+ const hour = new Date().getHours();
16
+ this.state.greeting =
17
+ hour < 12 ? 'Good morning' :
18
+ hour < 18 ? 'Good afternoon' : 'Good evening';
19
+
20
+ this._unsub = $.getStore('main').subscribe(() => this.setState({}));
21
+ },
22
+
23
+ destroyed() {
24
+ if (this._unsub) this._unsub();
25
+ },
26
+
27
+ increment() { $.getStore('main').dispatch('increment'); },
28
+ decrement() { $.getStore('main').dispatch('decrement'); },
29
+ reset() { $.getStore('main').dispatch('reset'); },
30
+ setStep(e) { $.getStore('main').dispatch('setStep', Number(e.target.value)); },
31
+
32
+ render() {
33
+ const { count, step } = $.getStore('main').state;
34
+
35
+ return `
36
+ <div class="page-header">
37
+ <h1>{{greeting}} 👋</h1>
38
+ <p class="subtitle">Welcome to your new zQuery project.</p>
39
+ </div>
40
+
41
+ <div class="card">
42
+ <h3>Getting Started</h3>
43
+ <p>
44
+ This is the <strong>minimal</strong> scaffold — three pages, a global store,
45
+ and the router. Edit the files in <code>app/</code> to start building.
46
+ </p>
47
+ <p>
48
+ Run <code>npx zquery dev</code> for live-reload, or
49
+ <code>npx zquery bundle</code> when you're ready to ship.
50
+ </p>
51
+ </div>
52
+
53
+ <div class="card">
54
+ <h3>Global Store</h3>
55
+ <p>The counter from <a z-link="/counter">Counter</a> is backed by <code>$.store</code> — its value persists across pages:</p>
56
+ <div style="display:flex;align-items:center;gap:.75rem;margin-top:.75rem;">
57
+ <button class="btn btn-outline btn-sm" @click="decrement">−</button>
58
+ <span style="font-size:1.25rem;font-weight:700;color:var(--accent);min-width:2rem;text-align:center;">${count}</span>
59
+ <button class="btn btn-primary btn-sm" @click="increment">+</button>
60
+ <button class="btn btn-outline btn-sm" @click="reset" style="margin-left:.5rem;">Reset</button>
61
+ <label style="display:flex;align-items:center;gap:.4rem;margin-left:.5rem;font-size:.85rem;color:var(--text-muted);">
62
+ Step <input type="number" value="${step}" @input="setStep" min="1" max="100" class="input input-sm" style="width:55px;text-align:center;" />
63
+ </label>
64
+ </div>
65
+ </div>
66
+ `;
67
+ },
68
+ });
@@ -0,0 +1,16 @@
1
+ // not-found.js — 404 fallback page
2
+ //
3
+ // Uses $.getRouter() to display the unmatched path.
4
+
5
+ $.component('not-found', {
6
+ render() {
7
+ const router = $.getRouter();
8
+ return `
9
+ <div class="page-header center">
10
+ <h1>404</h1>
11
+ <p class="subtitle">The page <code>${$.escapeHtml(router.current?.path || '')}</code> was not found.</p>
12
+ <a z-link="/" class="btn btn-primary">← Go Home</a>
13
+ </div>
14
+ `;
15
+ }
16
+ });
@@ -0,0 +1,9 @@
1
+ // routes.js — Route definitions
2
+ //
3
+ // Maps URL paths to component tag names.
4
+
5
+ export const routes = [
6
+ { path: '/', component: 'home-page' },
7
+ { path: '/counter', component: 'counter-page' },
8
+ { path: '/about', component: 'about-page' },
9
+ ];
@@ -0,0 +1,36 @@
1
+ // store.js — Global state management
2
+ //
3
+ // A simple centralized store. Any component can access it
4
+ // via $.getStore('main') and dispatch actions to update state.
5
+
6
+ export const store = $.store('main', {
7
+ state: {
8
+ count: 0,
9
+ step: 1,
10
+ },
11
+
12
+ actions: {
13
+ increment(state) {
14
+ state.count += state.step;
15
+ },
16
+
17
+ decrement(state) {
18
+ state.count -= state.step;
19
+ },
20
+
21
+ reset(state) {
22
+ state.count = 0;
23
+ },
24
+
25
+ setStep(state, value) {
26
+ state.step = Math.max(1, Math.min(100, value));
27
+ },
28
+ },
29
+
30
+ getters: {
31
+ isNegative: (state) => state.count < 0,
32
+ isPositive: (state) => state.count > 0,
33
+ },
34
+
35
+ debug: true,
36
+ });
File without changes