zero-query 1.0.9 → 1.2.0

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 (154) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +443 -0
  5. package/cli/commands/build.js +254 -216
  6. package/cli/commands/bundle.js +1228 -1183
  7. package/cli/commands/create.js +137 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -167
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
  73. package/cli/scaffold/webrtc/app/lib/room.js +252 -0
  74. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  75. package/cli/scaffold/webrtc/global.css +250 -0
  76. package/cli/scaffold/webrtc/index.html +21 -0
  77. package/cli/utils.js +305 -287
  78. package/dist/API.md +7264 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6252
  81. package/dist/zquery.min.js +8 -601
  82. package/index.d.ts +570 -365
  83. package/index.js +311 -232
  84. package/package.json +76 -69
  85. package/src/component.js +1709 -1454
  86. package/src/core.js +921 -921
  87. package/src/diff.js +497 -497
  88. package/src/errors.js +209 -209
  89. package/src/expression.js +922 -922
  90. package/src/http.js +242 -242
  91. package/src/package.json +1 -1
  92. package/src/reactive.js +255 -254
  93. package/src/router.js +843 -773
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -272
  96. package/src/utils.js +515 -515
  97. package/src/webrtc/e2ee.js +351 -0
  98. package/src/webrtc/errors.js +116 -0
  99. package/src/webrtc/ice.js +301 -0
  100. package/src/webrtc/index.js +131 -0
  101. package/src/webrtc/joinToken.js +119 -0
  102. package/src/webrtc/observe.js +172 -0
  103. package/src/webrtc/peer.js +351 -0
  104. package/src/webrtc/reactive.js +268 -0
  105. package/src/webrtc/room.js +625 -0
  106. package/src/webrtc/sdp.js +302 -0
  107. package/src/webrtc/sfu/index.js +43 -0
  108. package/src/webrtc/sfu/livekit.js +131 -0
  109. package/src/webrtc/sfu/mediasoup.js +150 -0
  110. package/src/webrtc/signaling.js +373 -0
  111. package/src/webrtc/turn.js +237 -0
  112. package/tests/_helpers/webrtcFakes.js +289 -0
  113. package/tests/audit.test.js +4158 -4158
  114. package/tests/cli.test.js +1136 -1023
  115. package/tests/compare.test.js +497 -0
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -0
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -0
  121. package/tests/electron-features.test.js +864 -0
  122. package/tests/errors.test.js +619 -619
  123. package/tests/expression.test.js +1056 -1056
  124. package/tests/http.test.js +648 -648
  125. package/tests/reactive.test.js +819 -819
  126. package/tests/router.test.js +2327 -2327
  127. package/tests/ssr.test.js +870 -870
  128. package/tests/store.test.js +830 -830
  129. package/tests/test-minifier.js +153 -153
  130. package/tests/test-ssr.js +27 -27
  131. package/tests/utils.test.js +1377 -1377
  132. package/tests/webrtc/e2ee.test.js +283 -0
  133. package/tests/webrtc/ice.test.js +202 -0
  134. package/tests/webrtc/joinToken.test.js +89 -0
  135. package/tests/webrtc/observe.test.js +111 -0
  136. package/tests/webrtc/peer.test.js +373 -0
  137. package/tests/webrtc/reactive.test.js +235 -0
  138. package/tests/webrtc/room.test.js +406 -0
  139. package/tests/webrtc/sdp.test.js +151 -0
  140. package/tests/webrtc/sfu-livekit.test.js +119 -0
  141. package/tests/webrtc/sfu.test.js +160 -0
  142. package/tests/webrtc/signaling.test.js +251 -0
  143. package/tests/webrtc/turn.test.js +256 -0
  144. package/types/collection.d.ts +383 -383
  145. package/types/component.d.ts +186 -186
  146. package/types/errors.d.ts +135 -135
  147. package/types/http.d.ts +92 -92
  148. package/types/misc.d.ts +201 -201
  149. package/types/reactive.d.ts +98 -98
  150. package/types/router.d.ts +190 -190
  151. package/types/ssr.d.ts +102 -102
  152. package/types/store.d.ts +146 -145
  153. package/types/utils.d.ts +245 -245
  154. package/types/webrtc.d.ts +653 -0
@@ -1,225 +1,225 @@
1
- // todos.js - Todo list with global store
2
- //
3
- // Features used:
4
- // $.getStore / dispatch / subscribe - centralized state management
5
- // z-model / z-ref - form bindings
6
- // z-for + z-key - keyed list rendering with diffing
7
- // z-class / z-if / z-show - conditional rendering
8
- // @submit.prevent / @keydown.escape - event modifiers
9
- // mounted / destroyed lifecycle - setup & teardown
10
-
11
- $.component('todos-page', {
12
- styles: `
13
- .td-header { display: flex; align-items: center; justify-content: space-between;
14
- gap: 1rem; flex-wrap: wrap; }
15
- .td-stats { display: flex; gap: 1.5rem; }
16
- .td-stat { display: flex; flex-direction: column; align-items: center; }
17
- .td-stat-num { font-size: 1.5rem; font-weight: 700; color: var(--accent);
18
- font-variant-numeric: tabular-nums; line-height: 1.2; }
19
- .td-stat-label { font-size: .72rem; color: var(--text-muted); text-transform: uppercase;
20
- letter-spacing: .04em; font-weight: 500; }
21
-
22
- .td-progress { height: 4px; background: var(--bg-hover); border-radius: 4px;
23
- overflow: hidden; margin-bottom: 1.25rem; }
24
- .td-progress-fill { height: 100%; background: var(--accent); border-radius: 4px;
25
- transition: width .3s var(--ease-out); }
26
-
27
- .td-form { display: flex; gap: .5rem; }
28
- .td-form .input { flex: 1; }
29
-
30
- .td-bar { display: flex; align-items: center; justify-content: space-between;
31
- gap: .75rem; flex-wrap: wrap; margin-bottom: .75rem; }
32
- .td-pills { display: flex; gap: .35rem; }
33
- .td-pill { padding: .3rem .75rem; border-radius: 999px; font-size: .82rem;
34
- font-weight: 500; border: 1px solid var(--border); background: transparent;
35
- color: var(--text-muted); cursor: pointer; transition: all .15s; }
36
- .td-pill:hover { border-color: var(--accent); color: var(--text); }
37
- .td-pill.on { background: var(--accent); color: #fff; border-color: var(--accent); }
38
- .td-search { width: 160px; }
39
-
40
- .td-list { list-style: none; padding: 0; margin: 0; }
41
- .td-item { display: flex; align-items: center; gap: .75rem; padding: .7rem .65rem;
42
- border-radius: var(--radius); margin-bottom: 2px;
43
- transition: background .12s; }
44
- .td-item:hover { background: var(--bg-hover); }
45
- .td-item.done .td-text { text-decoration: line-through; opacity: .45; }
46
- .td-chk { width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border);
47
- background: transparent; cursor: pointer; flex-shrink: 0;
48
- transition: all .15s; display: flex; align-items: center; justify-content: center;
49
- font-size: 11px; color: transparent; }
50
- .td-chk:hover { border-color: var(--accent); box-shadow: 0 0 8px rgba(88,166,255,.15); }
51
- .td-item.done .td-chk { background: var(--accent); border-color: var(--accent); color: #fff; }
52
- .td-text { flex: 1; font-size: .92rem; }
53
- .td-rm { background: none; border: none; color: var(--text-muted); cursor: pointer;
54
- font-size: .85rem; opacity: 0; transition: opacity .12s, color .12s; padding: .25rem; }
55
- .td-item:hover .td-rm { opacity: 1; }
56
- .td-rm:hover { color: var(--danger); }
57
-
58
- .td-footer { display: flex; align-items: center; justify-content: space-between;
59
- padding-top: .75rem; border-top: 1px solid var(--border); margin-top: .5rem; }
60
- .td-footer-msg { font-size: .82rem; color: var(--text-muted); }
61
- .td-footer-msg b { color: var(--accent); }
62
-
63
- .td-empty { text-align: center; padding: 2.5rem 1rem; }
64
- .td-empty-icon { font-size: 2.5rem; margin-bottom: .5rem; opacity: .4; }
65
- .td-empty p { color: var(--text-muted); font-size: .9rem; }
66
-
67
- @media (max-width: 768px) {
68
- .td-header { flex-direction: column; align-items: stretch; }
69
- .td-stats { justify-content: center; gap: 1rem; }
70
- .td-bar { flex-direction: column; align-items: stretch; }
71
- .td-search { width: 100%; }
72
- .td-pills { justify-content: center; }
73
- .td-footer { flex-direction: column; gap: .5rem; text-align: center; }
74
- }
75
- @media (max-width: 480px) {
76
- .td-stats { gap: .75rem; }
77
- .td-stat-num { font-size: 1.2rem; }
78
- }
79
- `,
80
-
81
- state: () => ({
82
- newTodo: '',
83
- filter: 'all',
84
- search: '',
85
- filtered: [],
86
- total: 0,
87
- done: 0,
88
- pending: 0,
89
- }),
90
-
91
- mounted() {
92
- const store = $.getStore('main');
93
- this._unsub = store.subscribe(() => this.setState({}));
94
- },
95
-
96
- destroyed() {
97
- if (this._unsub) this._unsub();
98
- },
99
-
100
- addTodo() {
101
- const text = this.state.newTodo.trim();
102
- if (!text) return;
103
- $.getStore('main').dispatch('addTodo', text);
104
- this.state.newTodo = '';
105
- this.state.search = '';
106
- this.state.filter = 'all';
107
- $.bus.emit('toast', { message: 'Todo added!', type: 'success' });
108
- },
109
-
110
- toggleTodo(id) {
111
- $.getStore('main').dispatch('toggleTodo', id);
112
- },
113
-
114
- removeTodo(id) {
115
- $.getStore('main').dispatch('removeTodo', id);
116
- $.bus.emit('toast', { message: 'Todo removed', type: 'error' });
117
- },
118
-
119
- clearCompleted() {
120
- $.getStore('main').dispatch('clearCompleted');
121
- $.bus.emit('toast', { message: 'Completed todos cleared', type: 'info' });
122
- },
123
-
124
- setFilter(f) {
125
- this.state.filter = f;
126
- },
127
-
128
- clearSearch() {
129
- this.state.search = '';
130
- },
131
-
132
- clearNewTodo() {
133
- this.state.newTodo = '';
134
- },
135
-
136
- render() {
137
- const store = $.getStore('main');
138
- const todos = store.state.todos;
139
- const { filter, search } = this.state;
140
-
141
- let list = todos;
142
- if (filter === 'active') list = todos.filter(t => !t.done);
143
- if (filter === 'done') list = todos.filter(t => t.done);
144
- if (search) {
145
- const q = search.toLowerCase();
146
- list = list.filter(t => t.text.toLowerCase().includes(q));
147
- }
148
- this.state.filtered = list;
149
- this.state.total = store.getters.todoCount;
150
- this.state.done = store.getters.doneCount;
151
- this.state.pending = store.getters.pendingCount;
152
-
153
- const pct = this.state.total > 0 ? Math.round((this.state.done / this.state.total) * 100) : 0;
154
-
155
- return `
156
- <div class="page-header">
157
- <h1>Todos</h1>
158
- <p class="subtitle">Global store with <code>$.store()</code>, <code>z-for</code> + <code>z-key</code>, <code>z-class</code>, <code>z-if</code>, <code>z-debounce</code>, and <code>@keydown.escape</code>.</p>
159
- </div>
160
-
161
- <div class="card">
162
- <div class="td-header">
163
- <form class="td-form" style="flex:1;" @submit.prevent="addTodo">
164
- <input
165
- type="text"
166
- z-model="newTodo" z-trim
167
- placeholder="What needs to be done?"
168
- class="input"
169
- z-ref="todoInput"
170
- @keydown.escape="clearNewTodo"
171
- />
172
- <button type="submit" class="btn btn-primary">Add</button>
173
- </form>
174
- <div class="td-stats">
175
- <div class="td-stat">
176
- <span class="td-stat-num">${this.state.total}</span>
177
- <span class="td-stat-label">Total</span>
178
- </div>
179
- <div class="td-stat">
180
- <span class="td-stat-num">${this.state.done}</span>
181
- <span class="td-stat-label">Done</span>
182
- </div>
183
- <div class="td-stat">
184
- <span class="td-stat-num">${pct}%</span>
185
- <span class="td-stat-label">Complete</span>
186
- </div>
187
- </div>
188
- </div>
189
- </div>
190
-
191
- <div class="card">
192
- ${this.state.total > 0 ? `
193
- <div class="td-progress"><div class="td-progress-fill" style="width:${pct}%;"></div></div>
194
- ` : ''}
195
-
196
- <div class="td-bar">
197
- <div class="td-pills">
198
- <button class="td-pill" z-class="{'on': filter === 'all'}" @click="setFilter('all')">All ${this.state.total}</button>
199
- <button class="td-pill" z-class="{'on': filter === 'active'}" @click="setFilter('active')">Active ${this.state.pending}</button>
200
- <button class="td-pill" z-class="{'on': filter === 'done'}" @click="setFilter('done')">Done ${this.state.done}</button>
201
- </div>
202
- <input type="text" placeholder="Search…" class="input input-sm td-search" z-model="search" z-debounce="300" z-trim @keydown.escape="clearSearch" />
203
- </div>
204
-
205
- <div z-if="filtered.length === 0" class="td-empty">
206
- <div class="td-empty-icon">${this.state.total === 0 ? '📝' : '🔍'}</div>
207
- <p>${this.state.total === 0 ? 'No todos yet - type something above and hit Add!' : 'No matching todos for this filter.'}</p>
208
- </div>
209
-
210
- <ul z-else class="td-list">
211
- <li z-for="t in filtered" z-key="{{t.id}}" class="td-item {{t.done ? 'done' : ''}}">
212
- <button class="td-chk" @click="toggleTodo('{{t.id}}')">✓</button>
213
- <span class="td-text">{{$.escapeHtml(t.text)}}</span>
214
- <button class="td-rm" @click="removeTodo('{{t.id}}')">✕</button>
215
- </li>
216
- </ul>
217
-
218
- <div class="td-footer" z-show="done > 0">
219
- <span class="td-footer-msg"><b>${this.state.done}</b> completed · <b>${this.state.pending}</b> remaining</span>
220
- <button class="btn btn-ghost btn-sm" @click="clearCompleted">Clear done</button>
221
- </div>
222
- </div>
223
- `;
224
- }
225
- });
1
+ // todos.js - Todo list with global store
2
+ //
3
+ // Features used:
4
+ // $.getStore / dispatch / subscribe - centralized state management
5
+ // z-model / z-ref - form bindings
6
+ // z-for + z-key - keyed list rendering with diffing
7
+ // z-class / z-if / z-show - conditional rendering
8
+ // @submit.prevent / @keydown.escape - event modifiers
9
+ // mounted / destroyed lifecycle - setup & teardown
10
+
11
+ $.component('todos-page', {
12
+ styles: `
13
+ .td-header { display: flex; align-items: center; justify-content: space-between;
14
+ gap: 1rem; flex-wrap: wrap; }
15
+ .td-stats { display: flex; gap: 1.5rem; }
16
+ .td-stat { display: flex; flex-direction: column; align-items: center; }
17
+ .td-stat-num { font-size: 1.5rem; font-weight: 700; color: var(--accent);
18
+ font-variant-numeric: tabular-nums; line-height: 1.2; }
19
+ .td-stat-label { font-size: .72rem; color: var(--text-muted); text-transform: uppercase;
20
+ letter-spacing: .04em; font-weight: 500; }
21
+
22
+ .td-progress { height: 4px; background: var(--bg-hover); border-radius: 4px;
23
+ overflow: hidden; margin-bottom: 1.25rem; }
24
+ .td-progress-fill { height: 100%; background: var(--accent); border-radius: 4px;
25
+ transition: width .3s var(--ease-out); }
26
+
27
+ .td-form { display: flex; gap: .5rem; }
28
+ .td-form .input { flex: 1; }
29
+
30
+ .td-bar { display: flex; align-items: center; justify-content: space-between;
31
+ gap: .75rem; flex-wrap: wrap; margin-bottom: .75rem; }
32
+ .td-pills { display: flex; gap: .35rem; }
33
+ .td-pill { padding: .3rem .75rem; border-radius: 999px; font-size: .82rem;
34
+ font-weight: 500; border: 1px solid var(--border); background: transparent;
35
+ color: var(--text-muted); cursor: pointer; transition: all .15s; }
36
+ .td-pill:hover { border-color: var(--accent); color: var(--text); }
37
+ .td-pill.on { background: var(--accent); color: #fff; border-color: var(--accent); }
38
+ .td-search { width: 160px; }
39
+
40
+ .td-list { list-style: none; padding: 0; margin: 0; }
41
+ .td-item { display: flex; align-items: center; gap: .75rem; padding: .7rem .65rem;
42
+ border-radius: var(--radius); margin-bottom: 2px;
43
+ transition: background .12s; }
44
+ .td-item:hover { background: var(--bg-hover); }
45
+ .td-item.done .td-text { text-decoration: line-through; opacity: .45; }
46
+ .td-chk { width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border);
47
+ background: transparent; cursor: pointer; flex-shrink: 0;
48
+ transition: all .15s; display: flex; align-items: center; justify-content: center;
49
+ font-size: 11px; color: transparent; }
50
+ .td-chk:hover { border-color: var(--accent); box-shadow: 0 0 8px rgba(88,166,255,.15); }
51
+ .td-item.done .td-chk { background: var(--accent); border-color: var(--accent); color: #fff; }
52
+ .td-text { flex: 1; font-size: .92rem; }
53
+ .td-rm { background: none; border: none; color: var(--text-muted); cursor: pointer;
54
+ font-size: .85rem; opacity: 0; transition: opacity .12s, color .12s; padding: .25rem; }
55
+ .td-item:hover .td-rm { opacity: 1; }
56
+ .td-rm:hover { color: var(--danger); }
57
+
58
+ .td-footer { display: flex; align-items: center; justify-content: space-between;
59
+ padding-top: .75rem; border-top: 1px solid var(--border); margin-top: .5rem; }
60
+ .td-footer-msg { font-size: .82rem; color: var(--text-muted); }
61
+ .td-footer-msg b { color: var(--accent); }
62
+
63
+ .td-empty { text-align: center; padding: 2.5rem 1rem; }
64
+ .td-empty-icon { font-size: 2.5rem; margin-bottom: .5rem; opacity: .4; }
65
+ .td-empty p { color: var(--text-muted); font-size: .9rem; }
66
+
67
+ @media (max-width: 768px) {
68
+ .td-header { flex-direction: column; align-items: stretch; }
69
+ .td-stats { justify-content: center; gap: 1rem; }
70
+ .td-bar { flex-direction: column; align-items: stretch; }
71
+ .td-search { width: 100%; }
72
+ .td-pills { justify-content: center; }
73
+ .td-footer { flex-direction: column; gap: .5rem; text-align: center; }
74
+ }
75
+ @media (max-width: 480px) {
76
+ .td-stats { gap: .75rem; }
77
+ .td-stat-num { font-size: 1.2rem; }
78
+ }
79
+ `,
80
+
81
+ state: () => ({
82
+ newTodo: '',
83
+ filter: 'all',
84
+ search: '',
85
+ filtered: [],
86
+ total: 0,
87
+ done: 0,
88
+ pending: 0,
89
+ }),
90
+
91
+ mounted() {
92
+ const store = $.getStore('main');
93
+ this._unsub = store.subscribe(() => this.setState({}));
94
+ },
95
+
96
+ destroyed() {
97
+ if (this._unsub) this._unsub();
98
+ },
99
+
100
+ addTodo() {
101
+ const text = this.state.newTodo.trim();
102
+ if (!text) return;
103
+ $.getStore('main').dispatch('addTodo', text);
104
+ this.state.newTodo = '';
105
+ this.state.search = '';
106
+ this.state.filter = 'all';
107
+ $.bus.emit('toast', { message: 'Todo added!', type: 'success' });
108
+ },
109
+
110
+ toggleTodo(id) {
111
+ $.getStore('main').dispatch('toggleTodo', id);
112
+ },
113
+
114
+ removeTodo(id) {
115
+ $.getStore('main').dispatch('removeTodo', id);
116
+ $.bus.emit('toast', { message: 'Todo removed', type: 'error' });
117
+ },
118
+
119
+ clearCompleted() {
120
+ $.getStore('main').dispatch('clearCompleted');
121
+ $.bus.emit('toast', { message: 'Completed todos cleared', type: 'info' });
122
+ },
123
+
124
+ setFilter(f) {
125
+ this.state.filter = f;
126
+ },
127
+
128
+ clearSearch() {
129
+ this.state.search = '';
130
+ },
131
+
132
+ clearNewTodo() {
133
+ this.state.newTodo = '';
134
+ },
135
+
136
+ render() {
137
+ const store = $.getStore('main');
138
+ const todos = store.state.todos;
139
+ const { filter, search } = this.state;
140
+
141
+ let list = todos;
142
+ if (filter === 'active') list = todos.filter(t => !t.done);
143
+ if (filter === 'done') list = todos.filter(t => t.done);
144
+ if (search) {
145
+ const q = search.toLowerCase();
146
+ list = list.filter(t => t.text.toLowerCase().includes(q));
147
+ }
148
+ this.state.filtered = list;
149
+ this.state.total = store.getters.todoCount;
150
+ this.state.done = store.getters.doneCount;
151
+ this.state.pending = store.getters.pendingCount;
152
+
153
+ const pct = this.state.total > 0 ? Math.round((this.state.done / this.state.total) * 100) : 0;
154
+
155
+ return `
156
+ <div class="page-header">
157
+ <h1>Todos</h1>
158
+ <p class="subtitle">Global store with <code>$.store()</code>, <code>z-for</code> + <code>z-key</code>, <code>z-class</code>, <code>z-if</code>, <code>z-debounce</code>, and <code>@keydown.escape</code>.</p>
159
+ </div>
160
+
161
+ <div class="card">
162
+ <div class="td-header">
163
+ <form class="td-form" style="flex:1;" @submit.prevent="addTodo">
164
+ <input
165
+ type="text"
166
+ z-model="newTodo" z-trim
167
+ placeholder="What needs to be done?"
168
+ class="input"
169
+ z-ref="todoInput"
170
+ @keydown.escape="clearNewTodo"
171
+ />
172
+ <button type="submit" class="btn btn-primary">Add</button>
173
+ </form>
174
+ <div class="td-stats">
175
+ <div class="td-stat">
176
+ <span class="td-stat-num">${this.state.total}</span>
177
+ <span class="td-stat-label">Total</span>
178
+ </div>
179
+ <div class="td-stat">
180
+ <span class="td-stat-num">${this.state.done}</span>
181
+ <span class="td-stat-label">Done</span>
182
+ </div>
183
+ <div class="td-stat">
184
+ <span class="td-stat-num">${pct}%</span>
185
+ <span class="td-stat-label">Complete</span>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <div class="card">
192
+ ${this.state.total > 0 ? `
193
+ <div class="td-progress"><div class="td-progress-fill" style="width:${pct}%;"></div></div>
194
+ ` : ''}
195
+
196
+ <div class="td-bar">
197
+ <div class="td-pills">
198
+ <button class="td-pill" z-class="{'on': filter === 'all'}" @click="setFilter('all')">All ${this.state.total}</button>
199
+ <button class="td-pill" z-class="{'on': filter === 'active'}" @click="setFilter('active')">Active ${this.state.pending}</button>
200
+ <button class="td-pill" z-class="{'on': filter === 'done'}" @click="setFilter('done')">Done ${this.state.done}</button>
201
+ </div>
202
+ <input type="text" placeholder="Search…" class="input input-sm td-search" z-model="search" z-debounce="300" z-trim @keydown.escape="clearSearch" />
203
+ </div>
204
+
205
+ <div z-if="filtered.length === 0" class="td-empty">
206
+ <div class="td-empty-icon">${this.state.total === 0 ? '📝' : '🔍'}</div>
207
+ <p>${this.state.total === 0 ? 'No todos yet - type something above and hit Add!' : 'No matching todos for this filter.'}</p>
208
+ </div>
209
+
210
+ <ul z-else class="td-list">
211
+ <li z-for="t in filtered" z-key="{{t.id}}" class="td-item {{t.done ? 'done' : ''}}">
212
+ <button class="td-chk" @click="toggleTodo('{{t.id}}')">✓</button>
213
+ <span class="td-text">{{$.escapeHtml(t.text)}}</span>
214
+ <button class="td-rm" @click="removeTodo('{{t.id}}')">✕</button>
215
+ </li>
216
+ </ul>
217
+
218
+ <div class="td-footer" z-show="done > 0">
219
+ <span class="td-footer-msg"><b>${this.state.done}</b> completed · <b>${this.state.pending}</b> remaining</span>
220
+ <button class="btn btn-ghost btn-sm" @click="clearCompleted">Clear done</button>
221
+ </div>
222
+ </div>
223
+ `;
224
+ }
225
+ });
@@ -1,97 +1,97 @@
1
- /* toolkit.css - scoped styles for the toolkit page component */
2
-
3
- .tk-tabs { display: flex; padding: .35rem;
4
- background: var(--bg-surface);
5
- border-radius: var(--radius-lg);
6
- border: 1px solid var(--border); gap: .25rem; }
7
- .tk-tab { flex: 1; padding: .55rem .9rem; font-size: .85rem; font-weight: 500;
8
- border-radius: var(--radius); cursor: pointer; border: none;
9
- color: var(--text-muted); background: transparent;
10
- transition: all .15s; text-align: center; font-family: inherit; }
11
- .tk-tab:hover { color: var(--text); background: var(--bg-hover); }
12
- .tk-tab.active { color: #fff; background: var(--accent);
13
- box-shadow: 0 2px 8px rgba(88,166,255,.25); }
14
-
15
- .tk-method { display: inline-flex; align-items: center; padding: .2rem .5rem;
16
- border-radius: 4px; font-size: .75rem; font-weight: 700;
17
- font-family: monospace; letter-spacing: .03em; }
18
- .tk-method-get { background: rgba(52,211,153,.15); color: var(--success); }
19
- .tk-method-post{ background: rgba(96,165,250,.15); color: var(--info); }
20
- .tk-method-put { background: rgba(88,166,255,.15); color: var(--accent); }
21
- .tk-method-del { background: rgba(248,113,113,.15); color: var(--danger); }
22
-
23
- .tk-endpoint { font-family: monospace; font-size: .85rem; color: var(--text-muted); }
24
-
25
- .tk-status { display: inline-flex; align-items: center; padding: .15rem .45rem;
26
- border-radius: 4px; font-size: .75rem; font-weight: 600;
27
- margin-left: auto; }
28
- .tk-status-ok { background: rgba(52,211,153,.12); color: var(--success); }
29
- .tk-status-err { background: rgba(248,113,113,.12); color: var(--danger); }
30
-
31
- .tk-response { margin-top: .75rem; border-radius: var(--radius);
32
- border: 1px solid var(--border); overflow: hidden; }
33
- .tk-response-hdr { display: flex; align-items: center; gap: .5rem;
34
- padding: .5rem .75rem; background: var(--bg-surface);
35
- border-bottom: 1px solid var(--border); font-size: .85rem; }
36
- .tk-response-body { padding: .75rem 1rem; font-size: .8rem; line-height: 1.5;
37
- overflow-x: auto; white-space: pre-wrap; word-break: break-word;
38
- max-height: 260px; overflow-y: auto; color: var(--text-muted);
39
- background: rgba(0,0,0,.15); }
40
-
41
- .tk-results { list-style: none; padding: 0; margin: .5rem 0 0; }
42
- .tk-results li { display: flex; align-items: center; gap: .6rem;
43
- padding: .5rem 0; border-bottom: 1px solid var(--border); }
44
- .tk-results li:last-child { border-bottom: none; }
45
-
46
- .tk-log { font-size: .8rem; line-height: 1.7; max-height: 200px; overflow-y: auto; }
47
- .tk-log-entry { display: flex; align-items: center; gap: .5rem; padding: .3rem 0;
48
- border-bottom: 1px solid rgba(255,255,255,.04); }
49
- .tk-btns { display: flex; gap: .5rem; flex-wrap: wrap; }
50
-
51
- .tk-util-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
52
- margin-bottom: 1rem; }
53
- @media (max-width: 640px) { .tk-util-grid { grid-template-columns: 1fr; } }
54
- .tk-util-card { padding: .85rem 1rem; border-radius: var(--radius);
55
- background: var(--bg-hover); border: 1px solid var(--border);
56
- cursor: pointer; transition: all .15s; }
57
- .tk-util-card:hover { border-color: var(--accent); background: rgba(88,166,255,.04); }
58
- .tk-util-card.selected { border-color: var(--accent);
59
- background: rgba(88,166,255,.06);
60
- box-shadow: 0 0 0 1px var(--accent); }
61
- .tk-util-card h4 { margin: 0 0 .25rem; font-size: .9rem; }
62
- .tk-util-card p { margin: 0; font-size: .8rem; color: var(--text-muted); }
63
-
64
- .tk-util-result { padding: .75rem 1rem; border-radius: var(--radius);
65
- background: rgba(0,0,0,.2); border: 1px solid var(--border);
66
- font-size: .82rem; line-height: 1.6; color: var(--text-muted);
67
- overflow-x: auto; white-space: pre-wrap;
68
- max-height: 250px; overflow-y: auto; font-family: monospace; }
69
-
70
- .tk-store-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
71
- @media (max-width: 640px) { .tk-store-grid { grid-template-columns: 1fr; } }
72
- .tk-store-card { padding: 1rem 1.15rem; border-radius: var(--radius-lg);
73
- background: var(--bg-hover); border: 1px solid var(--border);
74
- transition: border-color .15s; }
75
- .tk-store-card:hover { border-color: rgba(88,166,255,.2); }
76
- .tk-store-card h4 { margin: 0 0 .4rem; font-size: .95rem;
77
- display: flex; align-items: center; gap: .4rem; }
78
- .tk-store-card p { margin: 0 0 .65rem; font-size: .82rem; color: var(--text-muted); }
79
- .tk-store-pre { padding: .65rem .85rem; border-radius: var(--radius);
80
- background: rgba(0,0,0,.2); border: 1px solid var(--border);
81
- font-size: .78rem; line-height: 1.5; color: var(--text-muted);
82
- overflow-x: auto; white-space: pre-wrap;
83
- max-height: 200px; overflow-y: auto; }
84
- .tk-stat { display: flex; align-items: center; gap: .5rem;
85
- padding: .4rem .65rem; border-radius: var(--radius);
86
- background: rgba(88,166,255,.06); font-size: .82rem;
87
- border: 1px solid rgba(88,166,255,.1); }
88
- .tk-stat-val { font-weight: 700; color: var(--accent); }
89
-
90
- /* ── Responsive: tabs ── */
91
- @media (max-width: 768px) {
92
- .tk-tabs { flex-wrap: wrap; }
93
- .tk-tab { flex: 1 1 auto; min-width: 0; padding: .5rem .6rem; font-size: .8rem; }
94
- }
95
- @media (max-width: 480px) {
96
- .tk-tab { font-size: .75rem; padding: .45rem .5rem; }
97
- }
1
+ /* toolkit.css - scoped styles for the toolkit page component */
2
+
3
+ .tk-tabs { display: flex; padding: .35rem;
4
+ background: var(--bg-surface);
5
+ border-radius: var(--radius-lg);
6
+ border: 1px solid var(--border); gap: .25rem; }
7
+ .tk-tab { flex: 1; padding: .55rem .9rem; font-size: .85rem; font-weight: 500;
8
+ border-radius: var(--radius); cursor: pointer; border: none;
9
+ color: var(--text-muted); background: transparent;
10
+ transition: all .15s; text-align: center; font-family: inherit; }
11
+ .tk-tab:hover { color: var(--text); background: var(--bg-hover); }
12
+ .tk-tab.active { color: #fff; background: var(--accent);
13
+ box-shadow: 0 2px 8px rgba(88,166,255,.25); }
14
+
15
+ .tk-method { display: inline-flex; align-items: center; padding: .2rem .5rem;
16
+ border-radius: 4px; font-size: .75rem; font-weight: 700;
17
+ font-family: monospace; letter-spacing: .03em; }
18
+ .tk-method-get { background: rgba(52,211,153,.15); color: var(--success); }
19
+ .tk-method-post{ background: rgba(96,165,250,.15); color: var(--info); }
20
+ .tk-method-put { background: rgba(88,166,255,.15); color: var(--accent); }
21
+ .tk-method-del { background: rgba(248,113,113,.15); color: var(--danger); }
22
+
23
+ .tk-endpoint { font-family: monospace; font-size: .85rem; color: var(--text-muted); }
24
+
25
+ .tk-status { display: inline-flex; align-items: center; padding: .15rem .45rem;
26
+ border-radius: 4px; font-size: .75rem; font-weight: 600;
27
+ margin-left: auto; }
28
+ .tk-status-ok { background: rgba(52,211,153,.12); color: var(--success); }
29
+ .tk-status-err { background: rgba(248,113,113,.12); color: var(--danger); }
30
+
31
+ .tk-response { margin-top: .75rem; border-radius: var(--radius);
32
+ border: 1px solid var(--border); overflow: hidden; }
33
+ .tk-response-hdr { display: flex; align-items: center; gap: .5rem;
34
+ padding: .5rem .75rem; background: var(--bg-surface);
35
+ border-bottom: 1px solid var(--border); font-size: .85rem; }
36
+ .tk-response-body { padding: .75rem 1rem; font-size: .8rem; line-height: 1.5;
37
+ overflow-x: auto; white-space: pre-wrap; word-break: break-word;
38
+ max-height: 260px; overflow-y: auto; color: var(--text-muted);
39
+ background: rgba(0,0,0,.15); }
40
+
41
+ .tk-results { list-style: none; padding: 0; margin: .5rem 0 0; }
42
+ .tk-results li { display: flex; align-items: center; gap: .6rem;
43
+ padding: .5rem 0; border-bottom: 1px solid var(--border); }
44
+ .tk-results li:last-child { border-bottom: none; }
45
+
46
+ .tk-log { font-size: .8rem; line-height: 1.7; max-height: 200px; overflow-y: auto; }
47
+ .tk-log-entry { display: flex; align-items: center; gap: .5rem; padding: .3rem 0;
48
+ border-bottom: 1px solid rgba(255,255,255,.04); }
49
+ .tk-btns { display: flex; gap: .5rem; flex-wrap: wrap; }
50
+
51
+ .tk-util-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
52
+ margin-bottom: 1rem; }
53
+ @media (max-width: 640px) { .tk-util-grid { grid-template-columns: 1fr; } }
54
+ .tk-util-card { padding: .85rem 1rem; border-radius: var(--radius);
55
+ background: var(--bg-hover); border: 1px solid var(--border);
56
+ cursor: pointer; transition: all .15s; }
57
+ .tk-util-card:hover { border-color: var(--accent); background: rgba(88,166,255,.04); }
58
+ .tk-util-card.selected { border-color: var(--accent);
59
+ background: rgba(88,166,255,.06);
60
+ box-shadow: 0 0 0 1px var(--accent); }
61
+ .tk-util-card h4 { margin: 0 0 .25rem; font-size: .9rem; }
62
+ .tk-util-card p { margin: 0; font-size: .8rem; color: var(--text-muted); }
63
+
64
+ .tk-util-result { padding: .75rem 1rem; border-radius: var(--radius);
65
+ background: rgba(0,0,0,.2); border: 1px solid var(--border);
66
+ font-size: .82rem; line-height: 1.6; color: var(--text-muted);
67
+ overflow-x: auto; white-space: pre-wrap;
68
+ max-height: 250px; overflow-y: auto; font-family: monospace; }
69
+
70
+ .tk-store-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
71
+ @media (max-width: 640px) { .tk-store-grid { grid-template-columns: 1fr; } }
72
+ .tk-store-card { padding: 1rem 1.15rem; border-radius: var(--radius-lg);
73
+ background: var(--bg-hover); border: 1px solid var(--border);
74
+ transition: border-color .15s; }
75
+ .tk-store-card:hover { border-color: rgba(88,166,255,.2); }
76
+ .tk-store-card h4 { margin: 0 0 .4rem; font-size: .95rem;
77
+ display: flex; align-items: center; gap: .4rem; }
78
+ .tk-store-card p { margin: 0 0 .65rem; font-size: .82rem; color: var(--text-muted); }
79
+ .tk-store-pre { padding: .65rem .85rem; border-radius: var(--radius);
80
+ background: rgba(0,0,0,.2); border: 1px solid var(--border);
81
+ font-size: .78rem; line-height: 1.5; color: var(--text-muted);
82
+ overflow-x: auto; white-space: pre-wrap;
83
+ max-height: 200px; overflow-y: auto; }
84
+ .tk-stat { display: flex; align-items: center; gap: .5rem;
85
+ padding: .4rem .65rem; border-radius: var(--radius);
86
+ background: rgba(88,166,255,.06); font-size: .82rem;
87
+ border: 1px solid rgba(88,166,255,.1); }
88
+ .tk-stat-val { font-weight: 700; color: var(--accent); }
89
+
90
+ /* -- Responsive: tabs -- */
91
+ @media (max-width: 768px) {
92
+ .tk-tabs { flex-wrap: wrap; }
93
+ .tk-tab { flex: 1 1 auto; min-width: 0; padding: .5rem .6rem; font-size: .8rem; }
94
+ }
95
+ @media (max-width: 480px) {
96
+ .tk-tab { font-size: .75rem; padding: .45rem .5rem; }
97
+ }