zero-query 0.3.1 → 0.5.2

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.
@@ -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>⚡ 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>🔢 Counter</h3>
65
+ <p>Component state, two-way binding with <code>z-model</code>, and event handling.</p>
66
+ <a z-link="/counter" class="btn btn-outline">Try It →</a>
67
+ </div>
68
+
69
+ <div class="card">
70
+ <h3>✅ Todos</h3>
71
+ <p>Global store with actions & getters. <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>📇 Contacts</h3>
77
+ <p>External templates &amp; styles via <code>templateUrl</code> / <code>styleUrl</code>. <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>🌐 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>📊 App Stats</h3>
90
+ <div class="stats-grid">
91
+ <div class="stat-group">
92
+ <span class="stat-group-title">🏠 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">✅ 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">📇 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
+ });
@@ -0,0 +1,16 @@
1
+ // scripts/components/not-found.js — 404 fallback page
2
+ //
3
+ // Demonstrates: $.getRouter() to read the current 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,130 @@
1
+ // scripts/components/todos.js — todo list with global store
2
+ //
3
+ // Demonstrates: $.getStore, store.dispatch, store.subscribe,
4
+ // store getters, z-model, z-ref, z-class, z-for,
5
+ // z-if, z-show, @click with args, @submit.prevent,
6
+ // mounted/destroyed lifecycle, $.bus toast, $.debounce
7
+
8
+ $.component('todos-page', {
9
+ state: () => ({
10
+ newTodo: '',
11
+ filter: 'all', // 'all' | 'active' | 'done'
12
+ search: '',
13
+ filtered: [], // computed in render() for z-for access
14
+ total: 0,
15
+ done: 0,
16
+ pending: 0,
17
+ }),
18
+
19
+ mounted() {
20
+ const store = $.getStore('main');
21
+ this._unsub = store.subscribe(() => this.setState({}));
22
+
23
+ // $.debounce — debounced search filter (300ms)
24
+ this._debouncedSearch = $.debounce((val) => {
25
+ this.state.search = val;
26
+ }, 300);
27
+ },
28
+
29
+ destroyed() {
30
+ if (this._unsub) this._unsub();
31
+ },
32
+
33
+ addTodo() {
34
+ const text = this.state.newTodo.trim();
35
+ if (!text) return;
36
+ $.getStore('main').dispatch('addTodo', text);
37
+ this.state.newTodo = '';
38
+ this.state.search = '';
39
+ this.state.filter = 'all';
40
+ $.bus.emit('toast', { message: 'Todo added!', type: 'success' });
41
+ },
42
+
43
+ toggleTodo(id) {
44
+ $.getStore('main').dispatch('toggleTodo', id);
45
+ },
46
+
47
+ removeTodo(id) {
48
+ $.getStore('main').dispatch('removeTodo', id);
49
+ $.bus.emit('toast', { message: 'Todo removed', type: 'error' });
50
+ },
51
+
52
+ clearCompleted() {
53
+ $.getStore('main').dispatch('clearCompleted');
54
+ $.bus.emit('toast', { message: 'Completed todos cleared', type: 'info' });
55
+ },
56
+
57
+ setFilter(f) {
58
+ this.state.filter = f;
59
+ },
60
+
61
+ onSearch(e) {
62
+ this._debouncedSearch(e.target.value);
63
+ },
64
+
65
+ render() {
66
+ const store = $.getStore('main');
67
+ const todos = store.state.todos;
68
+ const { filter, search } = this.state;
69
+
70
+ // Compute filtered list and store stats into state for directive access
71
+ let list = todos;
72
+ if (filter === 'active') list = todos.filter(t => !t.done);
73
+ if (filter === 'done') list = todos.filter(t => t.done);
74
+ if (search) {
75
+ const q = search.toLowerCase();
76
+ list = list.filter(t => t.text.toLowerCase().includes(q));
77
+ }
78
+ this.state.filtered = list;
79
+ this.state.total = store.getters.todoCount;
80
+ this.state.done = store.getters.doneCount;
81
+ this.state.pending = store.getters.pendingCount;
82
+
83
+ return `
84
+ <div class="page-header">
85
+ <h1>Todos</h1>
86
+ <p class="subtitle">Global store with <code>$.store()</code>, <code>z-for</code>, <code>z-class</code>, <code>z-if</code>, and <code>z-show</code>.</p>
87
+ </div>
88
+
89
+ <div class="card">
90
+ <form class="todo-form" @submit.prevent="addTodo">
91
+ <input
92
+ type="text"
93
+ z-model="newTodo" z-trim
94
+ placeholder="What needs to be done?"
95
+ class="input"
96
+ z-ref="todoInput"
97
+ />
98
+ <button type="submit" class="btn btn-primary">Add</button>
99
+ </form>
100
+ </div>
101
+
102
+ <div class="card">
103
+ <div class="todo-toolbar">
104
+ <div class="todo-filters">
105
+ <button class="btn btn-sm" z-class="{'btn-primary': filter === 'all', 'btn-ghost': filter !== 'all'}" @click="setFilter('all')">All (${this.state.total})</button>
106
+ <button class="btn btn-sm" z-class="{'btn-primary': filter === 'active', 'btn-ghost': filter !== 'active'}" @click="setFilter('active')">Active (${this.state.pending})</button>
107
+ <button class="btn btn-sm" z-class="{'btn-primary': filter === 'done', 'btn-ghost': filter !== 'done'}" @click="setFilter('done')">Done (${this.state.done})</button>
108
+ </div>
109
+ <input type="text" placeholder="Search…" class="input input-sm" @input="onSearch" value="${$.escapeHtml(this.state.search)}" />
110
+ </div>
111
+
112
+ <div z-if="filtered.length === 0" class="empty-state">
113
+ <p>${this.state.total === 0 ? 'No todos yet — add one above!' : 'No matching todos.'}</p>
114
+ </div>
115
+
116
+ <ul z-else class="todo-list">
117
+ <li z-for="t in filtered" class="todo-item {{t.done ? 'done' : ''}}">
118
+ <button class="todo-check" @click="toggleTodo('{{t.id}}')"></button>
119
+ <span class="todo-text">{{$.escapeHtml(t.text)}}</span>
120
+ <button class="todo-remove" @click="removeTodo('{{t.id}}')">✕</button>
121
+ </li>
122
+ </ul>
123
+
124
+ <div class="todo-footer" z-show="done > 0">
125
+ <button class="btn btn-ghost btn-sm" @click="clearCompleted">Clear completed (${this.state.done})</button>
126
+ </div>
127
+ </div>
128
+ `;
129
+ }
130
+ });
@@ -0,0 +1,13 @@
1
+ // scripts/routes.js — route definitions
2
+ //
3
+ // Each route maps a URL path to a component tag name.
4
+ // Supports: static paths, :params, wildcards, and lazy loading via `load`.
5
+
6
+ export const routes = [
7
+ { path: '/', component: 'home-page' },
8
+ { path: '/counter', component: 'counter-page' },
9
+ { path: '/todos', component: 'todos-page' },
10
+ { path: '/contacts', component: 'contacts-page' },
11
+ { path: '/api', component: 'api-demo' },
12
+ { path: '/about', component: 'about-page' },
13
+ ];
@@ -0,0 +1,96 @@
1
+ // scripts/store.js — global state management
2
+ //
3
+ // $.store() creates a centralized store with state, actions, and getters.
4
+ // Components can dispatch actions and subscribe to changes.
5
+ // The store is accessible anywhere via $.getStore('main').
6
+
7
+ export const store = $.store('main', {
8
+ state: {
9
+ todos: [],
10
+ visits: 0,
11
+
12
+ // Contacts
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 },
19
+ ],
20
+ contactsAdded: 0,
21
+ },
22
+
23
+ actions: {
24
+ // Increment the global visit counter
25
+ incrementVisits(state) {
26
+ state.visits++;
27
+ },
28
+
29
+ // Add a new todo item using $.uuid() for unique IDs
30
+ addTodo(state, text) {
31
+ state.todos.push({
32
+ id: $.uuid(),
33
+ text: text.trim(),
34
+ done: false,
35
+ createdAt: Date.now(),
36
+ });
37
+ },
38
+
39
+ // Toggle a todo's completion status
40
+ toggleTodo(state, id) {
41
+ const todo = state.todos.find(t => t.id === id);
42
+ if (todo) todo.done = !todo.done;
43
+ },
44
+
45
+ // Remove a todo by ID
46
+ removeTodo(state, id) {
47
+ state.todos = state.todos.filter(t => t.id !== id);
48
+ },
49
+
50
+ // Clear all completed todos
51
+ clearCompleted(state) {
52
+ state.todos = state.todos.filter(t => !t.done);
53
+ },
54
+
55
+ // -- Contact actions --
56
+
57
+ addContact(state, { name, email, role }) {
58
+ state.contacts.push({
59
+ id: Date.now(),
60
+ name,
61
+ email,
62
+ role,
63
+ status: 'offline',
64
+ favorite: false,
65
+ });
66
+ state.contactsAdded++;
67
+ },
68
+
69
+ deleteContact(state, id) {
70
+ state.contacts = state.contacts.filter(c => c.id !== id);
71
+ },
72
+
73
+ toggleFavorite(state, id) {
74
+ const c = state.contacts.find(c => c.id === id);
75
+ if (c) c.favorite = !c.favorite;
76
+ },
77
+
78
+ cycleContactStatus(state, id) {
79
+ const c = state.contacts.find(c => c.id === id);
80
+ if (!c) return;
81
+ const order = ['online', 'away', 'offline'];
82
+ c.status = order[(order.indexOf(c.status) + 1) % 3];
83
+ },
84
+ },
85
+
86
+ getters: {
87
+ todoCount: (state) => state.todos.length,
88
+ doneCount: (state) => state.todos.filter(t => t.done).length,
89
+ pendingCount: (state) => state.todos.filter(t => !t.done).length,
90
+
91
+ contactCount: (state) => state.contacts.length,
92
+ favoriteCount: (state) => state.contacts.filter(c => c.favorite).length,
93
+ },
94
+
95
+ debug: true, // logs dispatches to console in development
96
+ });