@everystate/examples 1.0.0 → 1.0.1

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 (152) hide show
  1. package/README.md +15 -0
  2. package/everyState-core/001-counter/README.md +44 -0
  3. package/everyState-core/001-counter/index.html +79 -0
  4. package/everyState-core/002-counter-improved/README.md +44 -0
  5. package/everyState-core/002-counter-improved/index.html +83 -0
  6. package/everyState-core/003-input-reactive/README.md +44 -0
  7. package/everyState-core/003-input-reactive/index.html +68 -0
  8. package/everyState-core/004-computed-state/README.md +45 -0
  9. package/everyState-core/004-computed-state/index.html +83 -0
  10. package/everyState-core/005-conditional-rendering/README.md +42 -0
  11. package/everyState-core/005-conditional-rendering/index.html +68 -0
  12. package/everyState-core/006-list-rendering/README.md +49 -0
  13. package/everyState-core/006-list-rendering/index.html +92 -0
  14. package/everyState-core/007-form-validation/README.md +52 -0
  15. package/everyState-core/007-form-validation/index.html +108 -0
  16. package/everyState-core/008-undo-redo/README.md +70 -0
  17. package/everyState-core/008-undo-redo/index.html +133 -0
  18. package/everyState-core/009-localStorage-side-effects/README.md +72 -0
  19. package/everyState-core/009-localStorage-side-effects/index.html +80 -0
  20. package/everyState-core/010-decoupled-components/README.md +74 -0
  21. package/everyState-core/010-decoupled-components/index.html +117 -0
  22. package/everyState-core/011-async-patterns/README.md +98 -0
  23. package/everyState-core/011-async-patterns/index.html +132 -0
  24. package/everyState-css/001-stateDrivenCSS/index.html +377 -0
  25. package/everyState-css/002-cssV2FullDemo/index.html +630 -0
  26. package/everyState-view/001/counter/index.css +31 -0
  27. package/everyState-view/001/counter/index.html +50 -0
  28. package/everyState-view/002/datatable/index.css +70 -0
  29. package/everyState-view/002/datatable/index.html +118 -0
  30. package/everyState-view/003/todo/index.css +260 -0
  31. package/everyState-view/003/todo/index.html +218 -0
  32. package/everyState-view/003-input-reactive/README.md +44 -0
  33. package/everyState-view/003-input-reactive/index.html +68 -0
  34. package/everyState-view/004/quotesFetcher/index.css +124 -0
  35. package/everyState-view/004/quotesFetcher/index.html +108 -0
  36. package/everyState-view/004_01/quotesFetcher/app.js +32 -0
  37. package/everyState-view/004_01/quotesFetcher/components/appHeader/appSubtitle.js +2 -0
  38. package/everyState-view/004_01/quotesFetcher/components/appHeader/appTitle.js +2 -0
  39. package/everyState-view/004_01/quotesFetcher/components/appHeader.js +9 -0
  40. package/everyState-view/004_01/quotesFetcher/components/historyHeading.js +2 -0
  41. package/everyState-view/004_01/quotesFetcher/components/historyList/histAuthor.js +2 -0
  42. package/everyState-view/004_01/quotesFetcher/components/historyList/histQuote.js +2 -0
  43. package/everyState-view/004_01/quotesFetcher/components/historyList.js +14 -0
  44. package/everyState-view/004_01/quotesFetcher/components/quoteCard/fetchButton.js +2 -0
  45. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteAuthor.js +2 -0
  46. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteMessage.js +2 -0
  47. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteText.js +2 -0
  48. package/everyState-view/004_01/quotesFetcher/components/quoteCard.js +11 -0
  49. package/everyState-view/004_01/quotesFetcher/index.css +124 -0
  50. package/everyState-view/004_01/quotesFetcher/index.html +23 -0
  51. package/everyState-view/004_01/quotesFetcher/store.js +35 -0
  52. package/everyState-view/004_02/quotesFetcher/app.js +20 -0
  53. package/everyState-view/004_02/quotesFetcher/components.js +46 -0
  54. package/everyState-view/004_02/quotesFetcher/index.css +124 -0
  55. package/everyState-view/004_02/quotesFetcher/index.html +23 -0
  56. package/everyState-view/004_02/quotesFetcher/store.js +35 -0
  57. package/everyState-view/004_03/quotesFetcher/actions.js +27 -0
  58. package/everyState-view/004_03/quotesFetcher/app.js +19 -0
  59. package/everyState-view/004_03/quotesFetcher/components.js +28 -0
  60. package/everyState-view/004_03/quotesFetcher/index.css +124 -0
  61. package/everyState-view/004_03/quotesFetcher/index.html +23 -0
  62. package/everyState-view/004_03/quotesFetcher/resolve.js +34 -0
  63. package/everyState-view/004_03/quotesFetcher/store.js +11 -0
  64. package/everyState-view/004_04/quotesFetcher/actions.js +66 -0
  65. package/everyState-view/004_04/quotesFetcher/app.js +24 -0
  66. package/everyState-view/004_04/quotesFetcher/components/archive.js +40 -0
  67. package/everyState-view/004_04/quotesFetcher/components/fetcher.js +29 -0
  68. package/everyState-view/004_04/quotesFetcher/components.js +20 -0
  69. package/everyState-view/004_04/quotesFetcher/index.css +283 -0
  70. package/everyState-view/004_04/quotesFetcher/index.html +24 -0
  71. package/everyState-view/004_04/quotesFetcher/resolve.js +34 -0
  72. package/everyState-view/004_04/quotesFetcher/store.js +21 -0
  73. package/everyState-view/004_04/statedump.json +826 -0
  74. package/everyState-view/004_05/quoteExplorer/actions.js +58 -0
  75. package/everyState-view/004_05/quoteExplorer/app.js +27 -0
  76. package/everyState-view/004_05/quoteExplorer/components.js +83 -0
  77. package/everyState-view/004_05/quoteExplorer/index.css +231 -0
  78. package/everyState-view/004_05/quoteExplorer/index.html +23 -0
  79. package/everyState-view/004_05/quoteExplorer/resolve.js +50 -0
  80. package/everyState-view/004_05/quoteExplorer/store.js +33 -0
  81. package/everyState-view/004_06/quoteExplorer/actions.js +21 -0
  82. package/everyState-view/004_06/quoteExplorer/app.js +44 -0
  83. package/everyState-view/004_06/quoteExplorer/components.js +80 -0
  84. package/everyState-view/004_06/quoteExplorer/derived.js +43 -0
  85. package/everyState-view/004_06/quoteExplorer/index.css +346 -0
  86. package/everyState-view/004_06/quoteExplorer/index.html +25 -0
  87. package/everyState-view/004_06/quoteExplorer/intents.js +44 -0
  88. package/everyState-view/004_06/quoteExplorer/policies.js +25 -0
  89. package/everyState-view/004_06/quoteExplorer/resolve.js +51 -0
  90. package/everyState-view/004_06/quoteExplorer/store.js +44 -0
  91. package/everyState-view/004_07/quoteExplorer/app.js +47 -0
  92. package/everyState-view/004_07/quoteExplorer/components.js +85 -0
  93. package/everyState-view/004_07/quoteExplorer/derived.js +43 -0
  94. package/everyState-view/004_07/quoteExplorer/index.css +346 -0
  95. package/everyState-view/004_07/quoteExplorer/index.html +25 -0
  96. package/everyState-view/004_07/quoteExplorer/intents.js +51 -0
  97. package/everyState-view/004_07/quoteExplorer/policies.js +21 -0
  98. package/everyState-view/004_07/quoteExplorer/resolve.js +39 -0
  99. package/everyState-view/004_07/quoteExplorer/store.js +44 -0
  100. package/everyState-view/004_08/quoteExplorer/app.js +78 -0
  101. package/everyState-view/004_08/quoteExplorer/components.js +85 -0
  102. package/everyState-view/004_08/quoteExplorer/derived.js +43 -0
  103. package/everyState-view/004_08/quoteExplorer/index.css +346 -0
  104. package/everyState-view/004_08/quoteExplorer/index.html +25 -0
  105. package/everyState-view/004_08/quoteExplorer/intents.js +51 -0
  106. package/everyState-view/004_08/quoteExplorer/policies.js +21 -0
  107. package/everyState-view/004_08/quoteExplorer/resolve.js +39 -0
  108. package/everyState-view/004_08/quoteExplorer/store.js +44 -0
  109. package/everyState-view/004_08_V2/app.js +78 -0
  110. package/everyState-view/004_08_V2/components/appDetail.js +8 -0
  111. package/everyState-view/004_08_V2/components/appDetailBar.js +7 -0
  112. package/everyState-view/004_08_V2/components/appDetailBarClose.js +8 -0
  113. package/everyState-view/004_08_V2/components/appDetailBarCount.js +7 -0
  114. package/everyState-view/004_08_V2/components/appDetailBarHeading.js +7 -0
  115. package/everyState-view/004_08_V2/components/appDetailQuotes.js +15 -0
  116. package/everyState-view/004_08_V2/components/appHeader.js +7 -0
  117. package/everyState-view/004_08_V2/components/appHeaderSubtitle.js +7 -0
  118. package/everyState-view/004_08_V2/components/appHeaderTitle.js +7 -0
  119. package/everyState-view/004_08_V2/components/appLog.js +7 -0
  120. package/everyState-view/004_08_V2/components/appLogHeading.js +7 -0
  121. package/everyState-view/004_08_V2/components/appLogList.js +16 -0
  122. package/everyState-view/004_08_V2/components/appSearch.js +7 -0
  123. package/everyState-view/004_08_V2/components/appSearchInput.js +9 -0
  124. package/everyState-view/004_08_V2/components/appStats.js +7 -0
  125. package/everyState-view/004_08_V2/components/appStatsContent.js +8 -0
  126. package/everyState-view/004_08_V2/components/appStatsContentFavcount.js +7 -0
  127. package/everyState-view/004_08_V2/components/appStatsContentText.js +7 -0
  128. package/everyState-view/004_08_V2/components/appStatsToggle.js +8 -0
  129. package/everyState-view/004_08_V2/components/appTags.js +7 -0
  130. package/everyState-view/004_08_V2/components/appTagsLabel.js +7 -0
  131. package/everyState-view/004_08_V2/components/appTagsRow.js +15 -0
  132. package/everyState-view/004_08_V2/components/index.js +59 -0
  133. package/everyState-view/004_08_V2/components/utils/css.js +88 -0
  134. package/everyState-view/004_08_V2/components/utils/elements.js +87 -0
  135. package/everyState-view/004_08_V2/components.js +79 -0
  136. package/everyState-view/004_08_V2/derived.js +43 -0
  137. package/everyState-view/004_08_V2/index.css +350 -0
  138. package/everyState-view/004_08_V2/index.html +25 -0
  139. package/everyState-view/004_08_V2/intents.js +51 -0
  140. package/everyState-view/004_08_V2/policies.js +21 -0
  141. package/everyState-view/004_08_V2/resolve.js +39 -0
  142. package/everyState-view/004_08_V2/store.js +44 -0
  143. package/everyState-view/006/api-datatable/index.css +388 -0
  144. package/everyState-view/006/api-datatable/index.html +355 -0
  145. package/everyState-view/007/apiUsers/index.html +307 -0
  146. package/everyState-view/007-form-validation/README.md +52 -0
  147. package/everyState-view/007-form-validation/index.html +108 -0
  148. package/everyState-view/010-decoupled-components/README.md +74 -0
  149. package/everyState-view/010-decoupled-components/index.html +117 -0
  150. package/everyState-view/index.html +36 -0
  151. package/index.js +0 -5
  152. package/package.json +2 -4
@@ -0,0 +1,355 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>@everystate/view: API DataTable Demo</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@everystate/core": "../../../../everystate-core/index.js",
11
+ "@everystate/core/queryClient": "../../../../everystate-core/queryClient.js",
12
+ "@everystate/perf": "../../../../everystate-perf/index.js",
13
+ "@everystate/view/resolve": "../../../../everystate-view/resolve.js",
14
+ "@everystate/view/project": "../../../../everystate-view/project.js"
15
+ }
16
+ }
17
+ </script>
18
+ <link rel="stylesheet" href="index.css">
19
+ </head>
20
+ <body>
21
+ <div id="app"></div>
22
+
23
+ <script type="module">
24
+ import { createEveryState } from '@everystate/core';
25
+ import { createQueryClient } from '@everystate/core/queryClient';
26
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
27
+ import { flatten } from '@everystate/view/resolve';
28
+ import { mount } from '@everystate/view/project';
29
+
30
+ // 1. Store + QueryClient + Perf
31
+ const store = createEveryState({
32
+ users: [],
33
+ loading: false,
34
+ error: '',
35
+ sortBy: 'name',
36
+ sortDir: 'asc',
37
+ searchTerm: '',
38
+ currentPage: 1,
39
+ usersPerPage: 5,
40
+ loadingStatus: '',
41
+ filteredCount: 0,
42
+ totalPages: 1,
43
+ paginatedUsers: []
44
+ });
45
+
46
+ const qc = createQueryClient(store);
47
+ const perf = createPerfMonitor(store);
48
+ mountOverlay(perf, document.body);
49
+
50
+ // Fetch users from API
51
+ async function fetchUsers() {
52
+ store.set('loading', true);
53
+ store.set('error', '');
54
+ store.set('loadingStatus', 'Loading...');
55
+ try {
56
+ const data = await qc.query('users', async (signal) => {
57
+ const res = await fetch('https://jsonplaceholder.typicode.com/users', { signal });
58
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
59
+ return res.json();
60
+ });
61
+
62
+ // Store users as a simple array (like todo example)
63
+ const users = data.map(user => ({
64
+ id: user.id,
65
+ name: user.name,
66
+ email: user.email,
67
+ company: user.company.name,
68
+ phone: user.phone,
69
+ website: user.website
70
+ }));
71
+ store.set('users', users);
72
+ updateComputedValues();
73
+ } catch (err) {
74
+ if (err.name !== 'AbortError') {
75
+ store.set('error', 'Failed to fetch users: ' + err.message);
76
+ store.set('loadingStatus', 'Failed to fetch users: ' + err.message);
77
+ }
78
+ } finally {
79
+ store.set('loading', false);
80
+ if (!store.get('error')) {
81
+ store.set('loadingStatus', '');
82
+ }
83
+ }
84
+ }
85
+
86
+ // Simple filtering and sorting (like todo example)
87
+ function getFilteredUsers() {
88
+ const users = store.get('users') || [];
89
+ const searchTerm = store.get('searchTerm') || '';
90
+ const sortBy = store.get('sortBy');
91
+ const sortDir = store.get('sortDir');
92
+
93
+ // Filter
94
+ let filtered = users;
95
+ if (searchTerm) {
96
+ filtered = users.filter(user =>
97
+ user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
98
+ user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
99
+ user.company.toLowerCase().includes(searchTerm.toLowerCase())
100
+ );
101
+ }
102
+
103
+ // Sort
104
+ filtered = [...filtered].sort((a, b) => {
105
+ const va = a[sortBy], vb = b[sortBy];
106
+ if (typeof va === 'number') return sortDir === 'asc' ? va - vb : vb - va;
107
+ return sortDir === 'asc'
108
+ ? String(va).localeCompare(String(vb))
109
+ : String(vb).localeCompare(String(va));
110
+ });
111
+
112
+ return filtered;
113
+ }
114
+
115
+ function getPaginatedUsers() {
116
+ const filtered = getFilteredUsers();
117
+ const currentPage = store.get('currentPage');
118
+ const usersPerPage = store.get('usersPerPage');
119
+ const start = (currentPage - 1) * usersPerPage;
120
+ return filtered.slice(start, start + usersPerPage);
121
+ }
122
+
123
+ function getTotalPages() {
124
+ const filtered = getFilteredUsers();
125
+ const usersPerPage = store.get('usersPerPage');
126
+ return Math.ceil(filtered.length / usersPerPage) || 1;
127
+ }
128
+
129
+ // Update computed values when relevant state changes
130
+ function updateComputedValues() {
131
+ const filtered = getFilteredUsers();
132
+ const paginated = getPaginatedUsers();
133
+ store.set('filteredCount', filtered.length);
134
+ store.set('totalPages', getTotalPages());
135
+ store.set('paginatedUsers', paginated);
136
+ }
137
+
138
+ // Subscribe to changes - let EveryState's reactivity do the work
139
+ store.subscribe('users', updateComputedValues);
140
+ store.subscribe('searchTerm', () => {
141
+ store.set('currentPage', 1); // Reset to first page on search
142
+ updateComputedValues();
143
+ });
144
+ store.subscribe('sortBy', updateComputedValues);
145
+ store.subscribe('sortDir', updateComputedValues);
146
+ store.subscribe('currentPage', updateComputedValues);
147
+
148
+ // 2. View
149
+ flatten({
150
+ tag: 'div',
151
+ class: 'api-datatable',
152
+ children: [
153
+ { tag: 'h1', text: '📊 API DataTable + 🚀 Performance Monitor' },
154
+ { tag: 'p', class: 'subtitle', text: 'Real-time data from JSONPlaceholder API with performance tracking' },
155
+ // Controls
156
+ {
157
+ tag: 'div',
158
+ class: 'controls',
159
+ children: [
160
+ {
161
+ tag: 'div',
162
+ class: 'search-box',
163
+ children: [
164
+ { tag: 'input',
165
+ type: 'text',
166
+ placeholder: 'Search users...',
167
+ bind: 'searchTerm'
168
+ }
169
+ ]
170
+ },
171
+ {
172
+ tag: 'div',
173
+ class: 'actions',
174
+ children: [
175
+ { tag: 'button', text: '🔄 Refresh', onClick: 'fetchUsers', disabled: '{loading}' },
176
+ { tag: 'span', class: 'status', text: '{loadingStatus}' }
177
+ ]
178
+ }
179
+ ]
180
+ },
181
+
182
+ // Stats
183
+ {
184
+ tag: 'div',
185
+ class: 'stats',
186
+ children: [
187
+ { tag: 'span', text: 'Total: {users.length} users' },
188
+ { tag: 'span', text: 'Filtered: {filteredCount} results' },
189
+ { tag: 'span', text: 'Page {currentPage} of {totalPages}' }
190
+ ]
191
+ },
192
+
193
+ // Table
194
+ {
195
+ tag: 'div',
196
+ class: 'table-container',
197
+ children: [
198
+ {
199
+ tag: 'table',
200
+ children: [
201
+ {
202
+ tag: 'thead',
203
+ children: [{
204
+ tag: 'tr',
205
+ children: [
206
+ { tag: 'th', text: 'Name ↕', onClick: 'sortByCol(name)' },
207
+ { tag: 'th', text: 'Email ↕', onClick: 'sortByCol(email)' },
208
+ { tag: 'th', text: 'Company ↕', onClick: 'sortByCol(company)' },
209
+ { tag: 'th', text: 'Phone ↕', onClick: 'sortByCol(phone)' },
210
+ { tag: 'th', text: 'Website ↕', onClick: 'sortByCol(website)' }
211
+ ]
212
+ }]
213
+ },
214
+ {
215
+ tag: 'tbody',
216
+ forEach: 'paginatedUsers',
217
+ as: 'user',
218
+ template: {
219
+ tag: 'tr',
220
+ children: [
221
+ { tag: 'td', text: 'user.name' },
222
+ { tag: 'td', text: 'user.email' },
223
+ { tag: 'td', text: 'user.company' },
224
+ { tag: 'td', text: 'user.phone' },
225
+ {
226
+ tag: 'td',
227
+ children: [
228
+ {
229
+ tag: 'a',
230
+ href: 'http://{user.website}',
231
+ target: '_blank',
232
+ text: 'user.website'
233
+ }
234
+ ]
235
+ }
236
+ ]
237
+ }
238
+ }
239
+ ]
240
+ }
241
+ ]
242
+ },
243
+
244
+ // Pagination
245
+ {
246
+ tag: 'div',
247
+ class: 'pagination',
248
+ children: [
249
+ {
250
+ tag: 'button',
251
+ text: '← Previous',
252
+ onClick: 'prevPage',
253
+ disabled: '{currentPage === 1}'
254
+ },
255
+ {
256
+ tag: 'span',
257
+ class: 'page-info',
258
+ text: '{currentPage} / {totalPages}'
259
+ },
260
+ {
261
+ tag: 'button',
262
+ text: 'Next →',
263
+ onClick: 'nextPage',
264
+ disabled: '{currentPage >= totalPages}'
265
+ }
266
+ ]
267
+ },
268
+
269
+ // Performance Comparison
270
+ {
271
+ tag: 'div',
272
+ class: 'performance-section',
273
+ children: [
274
+ { tag: 'h3', text: '⚡ Performance Comparison' },
275
+ { tag: 'p', class: 'perf-note', text: 'EveryState vanilla JS: No bundler, no virtual DOM, direct state-to-DOM rendering' },
276
+ {
277
+ tag: 'div',
278
+ class: 'perf-stats',
279
+ children: [
280
+ { tag: 'div', class: 'perf-item', children: [
281
+ { tag: 'strong', text: 'Bundle Size:' },
282
+ { tag: 'span', text: '~0KB (vanilla JS)' }
283
+ ]},
284
+ { tag: 'div', class: 'perf-item', children: [
285
+ { tag: 'strong', text: 'Build Step:' },
286
+ { tag: 'span', text: 'None' }
287
+ ]},
288
+ { tag: 'div', class: 'perf-item', children: [
289
+ { tag: 'strong', text: 'Virtual DOM:' },
290
+ { tag: 'span', text: 'No (direct DOM)' }
291
+ ]},
292
+ { tag: 'div', class: 'perf-item', children: [
293
+ { tag: 'strong', text: 'Testing:' },
294
+ { tag: 'span', text: 'DOMless (state-only)' }
295
+ ]},
296
+ { tag: 'div', class: 'perf-item', children: [
297
+ { tag: 'strong', text: 'Hot Reload:' },
298
+ { tag: 'span', text: 'Instant (no compilation)' }
299
+ ]}
300
+ ]
301
+ },
302
+ {
303
+ tag: 'div',
304
+ class: 'perf-display',
305
+ children: [
306
+ { tag: 'h4', text: 'Performance Stats' },
307
+ { tag: 'div', class: 'perf-note', text: 'Performance tracking via @everystate/perf overlay (bottom-right corner). Download JSON reports for analysis.' }
308
+ ]
309
+ }
310
+ ]
311
+ }
312
+ ]
313
+ }, store, 'view');
314
+
315
+ // 3. Handlers
316
+ const handlers = {
317
+ fetchUsers() {
318
+ fetchUsers();
319
+ },
320
+
321
+ sortByCol(col) {
322
+ if (!col || typeof col !== 'string') return;
323
+ const current = store.get('sortBy');
324
+ if (current === col) {
325
+ store.set('sortDir', store.get('sortDir') === 'asc' ? 'desc' : 'asc');
326
+ } else {
327
+ store.set('sortBy', col);
328
+ store.set('sortDir', 'asc');
329
+ }
330
+ },
331
+
332
+ prevPage() {
333
+ const current = store.get('currentPage');
334
+ if (current > 1) {
335
+ store.set('currentPage', current - 1);
336
+ }
337
+ },
338
+
339
+ nextPage() {
340
+ const current = store.get('currentPage');
341
+ const totalPages = getTotalPages();
342
+ if (current < totalPages) {
343
+ store.set('currentPage', current + 1);
344
+ }
345
+ }
346
+ };
347
+
348
+ // 4. Mount
349
+ mount(store, 'view', document.getElementById('app'), handlers);
350
+
351
+ // 5. Initial data fetch
352
+ fetchUsers();
353
+ </script>
354
+ </body>
355
+ </html>
@@ -0,0 +1,307 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>@everystate/view: API Users DataTable</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@everystate/core": "../../../../everystate-core/index.js",
11
+ "@everystate/perf": "../../../../everystate-perf/index.js",
12
+ "@everystate/view/resolve": "../../../../everystate-view/resolve.js",
13
+ "@everystate/view/project": "../../../../everystate-view/project.js"
14
+ }
15
+ }
16
+ </script>
17
+ <style>
18
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
19
+ body { font-family: system-ui, -apple-system, sans-serif; background: #f8fafc; color: #1e293b; padding: 2rem; }
20
+ h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
21
+ .subtitle { color: #64748b; margin-bottom: 1.5rem; font-size: 0.9rem; }
22
+ .toolbar { display: flex; gap: 0.75rem; margin-bottom: 1rem; align-items: center; flex-wrap: wrap; }
23
+ .toolbar input { padding: 0.5rem 0.75rem; border: 1px solid #cbd5e1; border-radius: 0.5rem; font-size: 0.9rem; width: 250px; }
24
+ .toolbar input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.15); }
25
+ .toolbar button { padding: 0.5rem 1rem; border: 1px solid #cbd5e1; border-radius: 0.5rem; background: #fff; cursor: pointer; font-size: 0.9rem; transition: background 0.15s; }
26
+ .toolbar button:hover { background: #f1f5f9; }
27
+ .toolbar .active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
28
+ .status { padding: 0.5rem 0.75rem; background: #e0f2fe; border-radius: 0.5rem; font-size: 0.85rem; color: #0369a1; }
29
+ .status.error { background: #fef2f2; color: #dc2626; }
30
+ table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 0.75rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
31
+ th { background: #f1f5f9; padding: 0.75rem 1rem; text-align: left; font-weight: 600; font-size: 0.85rem; color: #475569; cursor: pointer; user-select: none; border-bottom: 2px solid #e2e8f0; }
32
+ th:hover { background: #e2e8f0; }
33
+ th .sort-indicator { margin-left: 0.35rem; font-size: 0.7rem; }
34
+ td { padding: 0.65rem 1rem; border-bottom: 1px solid #f1f5f9; font-size: 0.9rem; }
35
+ tr:hover td { background: #f8fafc; }
36
+ .pagination { display: flex; gap: 0.5rem; align-items: center; margin-top: 1rem; font-size: 0.9rem; }
37
+ .pagination button { padding: 0.35rem 0.75rem; border: 1px solid #cbd5e1; border-radius: 0.375rem; background: #fff; cursor: pointer; }
38
+ .pagination button:disabled { opacity: 0.4; cursor: default; }
39
+ .pagination button:not(:disabled):hover { background: #f1f5f9; }
40
+ .pagination .page-info { color: #64748b; }
41
+ .stats { margin-top: 1rem; font-size: 0.85rem; color: #64748b; }
42
+ #app { max-width: 960px; margin: 0 auto; }
43
+ .loading { text-align: center; padding: 2rem; color: #64748b; }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <div id="app">
48
+ <h1>@everystate/view: API Users</h1>
49
+ <p class="subtitle">Live data from JSONPlaceholder. Search, sort, paginate - all reactive state.</p>
50
+
51
+ <div class="toolbar">
52
+ <input type="text" id="search" placeholder="Search users..." oninput="onSearch(this.value)">
53
+ <button onclick="fetchUsers()">Refresh</button>
54
+ <span class="status" id="statusEl">Loading...</span>
55
+ </div>
56
+
57
+ <table>
58
+ <thead>
59
+ <tr>
60
+ <th onclick="sortBy('id')">ID <span class="sort-indicator" id="sort-id"></span></th>
61
+ <th onclick="sortBy('name')">Name <span class="sort-indicator" id="sort-name"></span></th>
62
+ <th onclick="sortBy('email')">Email <span class="sort-indicator" id="sort-email"></span></th>
63
+ <th onclick="sortBy('company')">Company <span class="sort-indicator" id="sort-company"></span></th>
64
+ <th onclick="sortBy('city')">City <span class="sort-indicator" id="sort-city"></span></th>
65
+ </tr>
66
+ </thead>
67
+ <tbody id="tableBody"></tbody>
68
+ </table>
69
+
70
+ <div class="pagination">
71
+ <button onclick="prevPage()" id="prevBtn" disabled>Prev</button>
72
+ <span class="page-info" id="pageInfo">Page 1 of 1</span>
73
+ <button onclick="nextPage()" id="nextBtn" disabled>Next</button>
74
+ </div>
75
+
76
+ <div class="stats" id="statsEl"></div>
77
+ </div>
78
+
79
+ <script type="module">
80
+ import { createEveryState } from '@everystate/core';
81
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
82
+
83
+ // ============================================================
84
+ // Store + Perf (perf wraps store BEFORE any subscriptions)
85
+ // ============================================================
86
+ const store = createEveryState({
87
+ users: [],
88
+ filteredUsers: [],
89
+ paginatedUsers: [],
90
+ searchTerm: '',
91
+ sortCol: 'id',
92
+ sortDir: 'asc',
93
+ currentPage: 1,
94
+ pageSize: 5,
95
+ totalPages: 1,
96
+ status: 'loading',
97
+ error: null,
98
+ });
99
+
100
+ const perf = createPerfMonitor(store);
101
+ mountOverlay(perf, document.body);
102
+
103
+ // ============================================================
104
+ // Derived state: filter + sort + paginate
105
+ // ============================================================
106
+ function recompute() {
107
+ const users = store.get('users') || [];
108
+ const term = (store.get('searchTerm') || '').toLowerCase();
109
+ const col = store.get('sortCol');
110
+ const dir = store.get('sortDir');
111
+ const page = store.get('currentPage');
112
+ const size = store.get('pageSize');
113
+
114
+ // Filter
115
+ let filtered = users;
116
+ if (term) {
117
+ filtered = users.filter(u =>
118
+ u.name.toLowerCase().includes(term) ||
119
+ u.email.toLowerCase().includes(term) ||
120
+ u.company.toLowerCase().includes(term) ||
121
+ u.city.toLowerCase().includes(term)
122
+ );
123
+ }
124
+
125
+ // Sort
126
+ filtered = [...filtered].sort((a, b) => {
127
+ const va = (a[col] ?? '').toString().toLowerCase();
128
+ const vb = (b[col] ?? '').toString().toLowerCase();
129
+ if (col === 'id') return dir === 'asc' ? a.id - b.id : b.id - a.id;
130
+ return dir === 'asc' ? va.localeCompare(vb) : vb.localeCompare(va);
131
+ });
132
+
133
+ // Paginate
134
+ const totalPages = Math.max(1, Math.ceil(filtered.length / size));
135
+ const safePage = Math.min(page, totalPages);
136
+ const start = (safePage - 1) * size;
137
+ const paginated = filtered.slice(start, start + size);
138
+
139
+ store.set('filteredUsers', filtered);
140
+ store.set('paginatedUsers', paginated);
141
+ store.set('totalPages', totalPages);
142
+ if (safePage !== page) store.set('currentPage', safePage);
143
+ }
144
+
145
+ // Subscribe to triggers
146
+ store.subscribe('users', recompute);
147
+ store.subscribe('searchTerm', recompute);
148
+ store.subscribe('sortCol', recompute);
149
+ store.subscribe('sortDir', recompute);
150
+ store.subscribe('currentPage', recompute);
151
+
152
+ // ============================================================
153
+ // DOM rendering (reactive subscriptions)
154
+ // ============================================================
155
+ const tableBody = document.getElementById('tableBody');
156
+ const statusEl = document.getElementById('statusEl');
157
+ const pageInfo = document.getElementById('pageInfo');
158
+ const prevBtn = document.getElementById('prevBtn');
159
+ const nextBtn = document.getElementById('nextBtn');
160
+ const statsEl = document.getElementById('statsEl');
161
+
162
+ store.subscribe('paginatedUsers', () => {
163
+ const users = store.get('paginatedUsers') || [];
164
+ tableBody.innerHTML = '';
165
+ if (users.length === 0) {
166
+ const tr = document.createElement('tr');
167
+ tr.innerHTML = '<td colspan="5" style="text-align:center;color:#94a3b8;padding:2rem">No users found</td>';
168
+ tableBody.appendChild(tr);
169
+ return;
170
+ }
171
+ for (const u of users) {
172
+ const tr = document.createElement('tr');
173
+ tr.innerHTML = `<td>${u.id}</td><td>${u.name}</td><td>${u.email}</td><td>${u.company}</td><td>${u.city}</td>`;
174
+ tableBody.appendChild(tr);
175
+ }
176
+ });
177
+
178
+ store.subscribe('status', () => {
179
+ const s = store.get('status');
180
+ const err = store.get('error');
181
+ statusEl.className = 'status' + (s === 'error' ? ' error' : '');
182
+ if (s === 'loading') statusEl.textContent = 'Loading...';
183
+ else if (s === 'error') statusEl.textContent = 'Error: ' + (err || 'unknown');
184
+ else {
185
+ const total = (store.get('users') || []).length;
186
+ const filtered = (store.get('filteredUsers') || []).length;
187
+ statusEl.textContent = filtered === total ? `${total} users` : `${filtered} of ${total} users`;
188
+ }
189
+ });
190
+
191
+ store.subscribe('searchTerm', () => {
192
+ const total = (store.get('users') || []).length;
193
+ const filtered = (store.get('filteredUsers') || []).length;
194
+ const s = store.get('status');
195
+ if (s === 'ready') {
196
+ statusEl.className = 'status';
197
+ statusEl.textContent = filtered === total ? `${total} users` : `${filtered} of ${total} users`;
198
+ }
199
+ });
200
+
201
+ store.subscribe('totalPages', () => {
202
+ const page = store.get('currentPage');
203
+ const total = store.get('totalPages');
204
+ pageInfo.textContent = `Page ${page} of ${total}`;
205
+ prevBtn.disabled = page <= 1;
206
+ nextBtn.disabled = page >= total;
207
+ });
208
+
209
+ store.subscribe('currentPage', () => {
210
+ const page = store.get('currentPage');
211
+ const total = store.get('totalPages');
212
+ pageInfo.textContent = `Page ${page} of ${total}`;
213
+ prevBtn.disabled = page <= 1;
214
+ nextBtn.disabled = page >= total;
215
+ });
216
+
217
+ store.subscribe('sortCol', updateSortIndicators);
218
+ store.subscribe('sortDir', updateSortIndicators);
219
+
220
+ function updateSortIndicators() {
221
+ const col = store.get('sortCol');
222
+ const dir = store.get('sortDir');
223
+ for (const c of ['id', 'name', 'email', 'company', 'city']) {
224
+ const el = document.getElementById('sort-' + c);
225
+ if (el) el.textContent = c === col ? (dir === 'asc' ? '\u25B2' : '\u25BC') : '';
226
+ }
227
+ }
228
+
229
+ // Stats line
230
+ store.subscribe('filteredUsers', () => {
231
+ const all = (store.get('users') || []).length;
232
+ const filtered = (store.get('filteredUsers') || []).length;
233
+ const page = store.get('currentPage');
234
+ const size = store.get('pageSize');
235
+ const start = (page - 1) * size + 1;
236
+ const end = Math.min(page * size, filtered);
237
+ if (filtered > 0) {
238
+ statsEl.textContent = `Showing ${start}-${end} of ${filtered} users` + (filtered < all ? ` (filtered from ${all})` : '');
239
+ } else {
240
+ statsEl.textContent = `No results from ${all} users`;
241
+ }
242
+ });
243
+
244
+ // ============================================================
245
+ // Fetch real data from JSONPlaceholder
246
+ // ============================================================
247
+ async function fetchUsers() {
248
+ store.set('status', 'loading');
249
+ store.set('error', null);
250
+ try {
251
+ const res = await fetch('https://jsonplaceholder.typicode.com/users');
252
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
253
+ const raw = await res.json();
254
+ // Flatten to table-friendly shape
255
+ const users = raw.map(u => ({
256
+ id: u.id,
257
+ name: u.name,
258
+ email: u.email.toLowerCase(),
259
+ company: u.company?.name || '',
260
+ city: u.address?.city || '',
261
+ phone: u.phone,
262
+ website: u.website,
263
+ }));
264
+ store.set('users', users);
265
+ store.set('currentPage', 1);
266
+ store.set('status', 'ready');
267
+ } catch (e) {
268
+ store.set('error', e.message);
269
+ store.set('status', 'error');
270
+ }
271
+ }
272
+
273
+ // ============================================================
274
+ // User actions (exposed to onclick handlers)
275
+ // ============================================================
276
+ window.fetchUsers = fetchUsers;
277
+
278
+ window.onSearch = function(val) {
279
+ store.set('searchTerm', val);
280
+ store.set('currentPage', 1);
281
+ };
282
+
283
+ window.sortBy = function(col) {
284
+ const current = store.get('sortCol');
285
+ if (current === col) {
286
+ store.set('sortDir', store.get('sortDir') === 'asc' ? 'desc' : 'asc');
287
+ } else {
288
+ store.set('sortCol', col);
289
+ store.set('sortDir', 'asc');
290
+ }
291
+ };
292
+
293
+ window.prevPage = function() {
294
+ const p = store.get('currentPage');
295
+ if (p > 1) store.set('currentPage', p - 1);
296
+ };
297
+
298
+ window.nextPage = function() {
299
+ const p = store.get('currentPage');
300
+ if (p < store.get('totalPages')) store.set('currentPage', p + 1);
301
+ };
302
+
303
+ // Boot
304
+ fetchUsers();
305
+ </script>
306
+ </body>
307
+ </html>