@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,218 @@
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: View-as-State Demo</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@everystate/core": "../../../../everystate-core/index.js",
11
+ "@everystate/view/resolve": "../../../../everystate-view/resolve.js",
12
+ "@everystate/view/project": "../../../../everystate-view/project.js"
13
+ }
14
+ }
15
+ </script>
16
+ <link rel="stylesheet" href="index.css">
17
+ </head>
18
+ <body>
19
+ <header>
20
+ <h1>@everystate/view: View-as-State</h1>
21
+ <p>The UI is not a function of state. The UI <strong>is</strong> state. Data + view in one store. DOMless testing. Surgical updates.</p>
22
+ </header>
23
+
24
+ <div class="layout">
25
+ <!-- Left: Live App -->
26
+ <div class="panel">
27
+ <h2>Live App (projected from store)</h2>
28
+ <div id="app"></div>
29
+ </div>
30
+
31
+ <!-- Right: State Inspector -->
32
+ <div class="panel">
33
+ <div class="tabs">
34
+ <button class="tab active" data-tab="data">Data State</button>
35
+ <button class="tab" data-tab="view">View State</button>
36
+ <button class="tab" data-tab="ssr">SSR Output</button>
37
+ </div>
38
+ <div id="dataView" class="state-inspector"></div>
39
+ <div id="viewView" class="state-inspector" style="display:none;"></div>
40
+ <div id="ssrView" class="ssr-output" style="display:none;"></div>
41
+ </div>
42
+ </div>
43
+
44
+ <script type="module">
45
+ import { createEveryState } from '@everystate/core';
46
+ import { normalize, flatten, resolveTree, serialize, resetIdCounter } from '@everystate/view/resolve';
47
+ import { mount } from '@everystate/view/project';
48
+
49
+ // == 1. Create the store with data ==================================
50
+
51
+ const store = createEveryState({
52
+ todos: [
53
+ { id: 1, text: 'Learn EveryState', done: true },
54
+ { id: 2, text: 'Build @everystate/view', done: true },
55
+ { id: 3, text: 'Ship it', done: false }
56
+ ],
57
+ inputText: '',
58
+ nextId: 4
59
+ });
60
+
61
+ // == 2. Define the view as a tree spec ==============================
62
+
63
+ flatten({
64
+ tag: 'div',
65
+ class: 'todo-app',
66
+ children: [
67
+ { tag: 'h2', text: 'Todo ({todos.length})' },
68
+ {
69
+ tag: 'div',
70
+ class: 'input-row',
71
+ children: [
72
+ { tag: 'input', type: 'text', placeholder: 'Add a todo...', bind: 'inputText', onEnter: 'addTodo' },
73
+ { tag: 'button', text: 'Add', onClick: 'addTodo' }
74
+ ]
75
+ },
76
+ {
77
+ tag: 'ul',
78
+ class: 'todo-list',
79
+ forEach: 'todos',
80
+ as: 'todo',
81
+ template: {
82
+ tag: 'li',
83
+ classIf: { done: 'todo.done' },
84
+ children: [
85
+ { tag: 'button', class: 'toggle', text: '\u2713', onClick: 'toggleTodo(todo.id)' },
86
+ { tag: 'span', text: 'todo.text', onDblClick: 'editTodo(todo.id)' },
87
+ { tag: 'button', text: '✕', onClick: 'deleteTodo(todo.id)' }
88
+ ]
89
+ }
90
+ },
91
+ {
92
+ tag: 'div',
93
+ class: 'stats',
94
+ children: [
95
+ { tag: 'span', text: 'Total: <strong>{todos.length}</strong>' },
96
+ { tag: 'span', text: 'Done: <strong>{todos.filter(t => t.done).length}</strong>' }
97
+ ]
98
+ }
99
+ ]
100
+ }, store, 'view');
101
+
102
+ // == 3. Define intent handlers ======================================
103
+
104
+ const handlers = {
105
+ addTodo() {
106
+ const text = store.get('inputText');
107
+ if (!text || !text.trim()) return;
108
+ const todos = store.get('todos') || [];
109
+ const id = store.get('nextId');
110
+ store.set('todos', [...todos, { id, text: text.trim(), done: false }]);
111
+ store.set('nextId', id + 1);
112
+ store.set('inputText', '');
113
+ },
114
+ toggleTodo(id) {
115
+ const todos = store.get('todos') || [];
116
+ store.set('todos', todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
117
+ },
118
+ deleteTodo(id) {
119
+ const todos = store.get('todos') || [];
120
+ store.set('todos', todos.filter(t => t.id !== id));
121
+ },
122
+ editTodo(id, e) {
123
+ const span = e.target;
124
+ const li = span.closest('li');
125
+ if (!li || li.querySelector('.edit-input')) return;
126
+
127
+ const input = document.createElement('input');
128
+ input.className = 'edit-input';
129
+ input.value = span.textContent;
130
+ span.replaceWith(input);
131
+ input.focus();
132
+
133
+ let saved = false;
134
+ function save() {
135
+ if (saved) return;
136
+ saved = true;
137
+ const text = input.value.trim();
138
+ if (text) {
139
+ const todos = store.get('todos') || [];
140
+ store.set('todos', todos.map(t => t.id === id ? { ...t, text } : t));
141
+ }
142
+ }
143
+ input.addEventListener('blur', save);
144
+ input.addEventListener('keydown', (ev) => {
145
+ if (ev.key === 'Enter') { ev.preventDefault(); save(); }
146
+ if (ev.key === 'Escape') { saved = true; store.set('todos', store.get('todos')); }
147
+ });
148
+ }
149
+ };
150
+
151
+ // == 4. Mount (project view state -> DOM) ============================
152
+
153
+ const container = document.getElementById('app');
154
+ const cleanup = mount(store, 'view', container, handlers);
155
+
156
+ // == 5. State inspector ============================================-
157
+
158
+ const dataView = document.getElementById('dataView');
159
+ const viewView = document.getElementById('viewView');
160
+ const ssrView = document.getElementById('ssrView');
161
+
162
+ function syntaxHighlight(json) {
163
+ return json
164
+ .replace(/(".*?")\s*:/g, '<span class="state-key">$1</span>:')
165
+ .replace(/: (".*?")/g, ': <span class="state-string">$1</span>')
166
+ .replace(/: (\d+)/g, ': <span class="state-number">$1</span>')
167
+ .replace(/: (true|false)/g, ': <span class="state-bool">$1</span>')
168
+ .replace(/: (null)/g, ': <span class="state-null">$1</span>')
169
+ .replace(/([{}[\]])/g, '<span class="state-bracket">$1</span>');
170
+ }
171
+
172
+ function updateInspector() {
173
+ // Data state
174
+ const data = {
175
+ todos: store.get('todos'),
176
+ inputText: store.get('inputText'),
177
+ nextId: store.get('nextId')
178
+ };
179
+ dataView.innerHTML = syntaxHighlight(JSON.stringify(data, null, 2));
180
+
181
+ // View state
182
+ const viewState = {
183
+ rootId: store.get('view.rootId'),
184
+ nodes: store.get('view.nodes')
185
+ };
186
+ viewView.innerHTML = syntaxHighlight(JSON.stringify(viewState, null, 2));
187
+
188
+ // SSR
189
+ try {
190
+ const nodes = store.get('view.nodes');
191
+ const rootId = store.get('view.rootId');
192
+ if (nodes && rootId) {
193
+ const resolved = resolveTree(nodes, rootId, (p) => store.get(p));
194
+ ssrView.textContent = serialize(resolved);
195
+ }
196
+ } catch (e) {
197
+ ssrView.textContent = 'Error: ' + e.message;
198
+ }
199
+ }
200
+
201
+ updateInspector();
202
+ store.subscribe('*', () => setTimeout(updateInspector, 10));
203
+
204
+ // == Tabs ==
205
+
206
+ document.querySelectorAll('.tab').forEach(tab => {
207
+ tab.addEventListener('click', () => {
208
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
209
+ tab.classList.add('active');
210
+ const which = tab.dataset.tab;
211
+ dataView.style.display = which === 'data' ? '' : 'none';
212
+ viewView.style.display = which === 'view' ? '' : 'none';
213
+ ssrView.style.display = which === 'ssr' ? '' : 'none';
214
+ });
215
+ });
216
+ </script>
217
+ </body>
218
+ </html>
@@ -0,0 +1,44 @@
1
+ # 001 Counter - Minimal EveryState Example
2
+
3
+ The simplest possible EveryState example: a counter with one button.
4
+
5
+ ## What's Here
6
+
7
+ - `everyState.js` - ~60 lines of reactive state management
8
+ - `index.html` - Counter UI with subscription
9
+
10
+ ## How It Works
11
+
12
+ ```javascript
13
+ // 1. Create store
14
+ const store = createEveryState({ count: 0 });
15
+
16
+ // 2. Subscribe to changes
17
+ store.subscribe('count', ({ value }) => {
18
+ document.getElementById('count').textContent = value;
19
+ });
20
+
21
+ // 3. Update state
22
+ store.set('count', store.get('count') + 1);
23
+ ```
24
+
25
+ That's it. No framework, no build step, just reactive data.
26
+
27
+ ## Run It
28
+
29
+ Open `index.html` in a browser. Click the `+` button to increment.
30
+
31
+ ## Key Concepts
32
+
33
+ - **`createEveryState(initial)`** - Creates a reactive store
34
+ - **`store.get(path)`** - Read state at dot-path
35
+ - **`store.set(path, value)`** - Write state and notify subscribers
36
+ - **`store.subscribe(path, handler)`** - React to changes
37
+
38
+ ## Next Steps
39
+
40
+ See `examples/009-todo-app-with-eventTest/` for a full application with:
41
+ - Router
42
+ - Bridges (intent → domain pattern)
43
+ - Tests with type generation
44
+ - Dev tools
@@ -0,0 +1,68 @@
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>003 Input Reactive - EveryState</title>
7
+ <script type="importmap">
8
+ { "imports": {
9
+ "@everystate/core": "../../../everystate-core/index.js",
10
+ "@everystate/perf": "../../../everystate-perf/index.js"
11
+ }}
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <h1>Type something:</h1>
16
+ <input type="text" id="textInput" placeholder="Start typing...">
17
+ <p id="output"></p>
18
+ <hr>
19
+ <button id="runTests">Run Tests</button>
20
+ <pre id="testLog"></pre>
21
+
22
+ <script type="module">
23
+ import { createEveryState } from '@everystate/core';
24
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
25
+
26
+ const store = createEveryState({ text: '' });
27
+ const perf = createPerfMonitor(store);
28
+ mountOverlay(perf, document.body);
29
+
30
+ store.subscribe('text', (value) => {
31
+ document.getElementById('output').textContent = value;
32
+ });
33
+
34
+ document.getElementById('textInput').oninput = (e) => {
35
+ store.set('text', e.target.value);
36
+ };
37
+
38
+ // Event Sequence Tests
39
+ document.getElementById('runTests').onclick = () => {
40
+ const results = [];
41
+ let pass = 0, fail = 0;
42
+ function assert(label, cond) {
43
+ if (cond) { pass++; results.push(' OK ' + label); }
44
+ else { fail++; results.push(' FAIL ' + label); }
45
+ }
46
+
47
+ store.set('text', 'hello');
48
+ assert('set text', store.get('text') === 'hello');
49
+ assert('DOM updated', document.getElementById('output').textContent === 'hello');
50
+
51
+ store.set('text', '');
52
+ assert('clear text', store.get('text') === '');
53
+
54
+ const seq = [];
55
+ const unsub = store.subscribe('text', (v) => seq.push(v));
56
+ store.set('text', 'a');
57
+ store.set('text', 'ab');
58
+ assert('subscriber sequence', seq[0] === 'a' && seq[1] === 'ab');
59
+ unsub();
60
+
61
+ assert('perf tracked', perf.report().summary.totalSets > 0);
62
+
63
+ store.set('text', '');
64
+ document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
65
+ };
66
+ </script>
67
+ </body>
68
+ </html>
@@ -0,0 +1,124 @@
1
+ body {
2
+ font-family: system-ui, sans-serif;
3
+ background: #0f172a;
4
+ color: #e2e8f0;
5
+ display: flex;
6
+ justify-content: center;
7
+ align-items: flex-start;
8
+ min-height: 100vh;
9
+ margin: 0;
10
+ padding: 3rem 1rem;
11
+ }
12
+
13
+ .quotes-app {
14
+ width: 100%;
15
+ max-width: 560px;
16
+ }
17
+
18
+ .quotes-app h1 {
19
+ color: #38bdf8;
20
+ font-size: 1.6rem;
21
+ margin: 0 0 0.25rem;
22
+ }
23
+
24
+ .subtitle {
25
+ color: #64748b;
26
+ font-size: 0.8rem;
27
+ margin: 0 0 1.5rem;
28
+ }
29
+
30
+ .card {
31
+ background: #1e293b;
32
+ border: 1px solid #334155;
33
+ border-radius: 12px;
34
+ padding: 1.5rem;
35
+ margin-bottom: 2rem;
36
+ min-height: 140px;
37
+ display: flex;
38
+ flex-direction: column;
39
+ justify-content: center;
40
+ }
41
+
42
+ .quote-text {
43
+ font-size: 1.15rem;
44
+ line-height: 1.6;
45
+ color: #f1f5f9;
46
+ margin: 0 0 0.5rem;
47
+ font-style: italic;
48
+ }
49
+
50
+ .quote-text::before { content: '\201C'; }
51
+ .quote-text::after { content: '\201D'; }
52
+
53
+ .quote-author {
54
+ color: #38bdf8;
55
+ font-size: 0.9rem;
56
+ font-weight: 600;
57
+ margin: 0 0 1rem;
58
+ }
59
+
60
+ .quote-author:not(:empty)::before {
61
+ content: '- ';
62
+ color: #64748b;
63
+ }
64
+
65
+ .message {
66
+ color: #94a3b8;
67
+ font-size: 0.85rem;
68
+ margin: 0 0 1rem;
69
+ min-height: 1.2em;
70
+ }
71
+
72
+ .card button {
73
+ align-self: flex-start;
74
+ font-size: 0.9rem;
75
+ padding: 0.6rem 1.5rem;
76
+ background: #38bdf8;
77
+ color: #0f172a;
78
+ border: none;
79
+ border-radius: 8px;
80
+ font-weight: 700;
81
+ cursor: pointer;
82
+ transition: background 0.2s;
83
+ }
84
+
85
+ .card button:hover {
86
+ background: #7dd3fc;
87
+ }
88
+
89
+ .quotes-app h2 {
90
+ color: #94a3b8;
91
+ font-size: 0.8rem;
92
+ text-transform: uppercase;
93
+ letter-spacing: 0.5px;
94
+ margin: 0 0 0.75rem;
95
+ }
96
+
97
+ .history {
98
+ list-style: none;
99
+ padding: 0;
100
+ margin: 0;
101
+ }
102
+
103
+ .history li {
104
+ background: #1e293b;
105
+ border: 1px solid #334155;
106
+ border-radius: 8px;
107
+ padding: 0.75rem 1rem;
108
+ margin-bottom: 0.5rem;
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 0.25rem;
112
+ }
113
+
114
+ .hist-quote {
115
+ color: #cbd5e1;
116
+ font-size: 0.85rem;
117
+ font-style: italic;
118
+ }
119
+
120
+ .hist-author {
121
+ color: #38bdf8;
122
+ font-size: 0.8rem;
123
+ font-weight: 600;
124
+ }
@@ -0,0 +1,108 @@
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: Quotes Fetcher</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/view/resolve": "../../../../everystate-view/resolve.js",
13
+ "@everystate/view/project": "../../../../everystate-view/project.js"
14
+ }
15
+ }
16
+ </script>
17
+ <link rel="stylesheet" href="index.css">
18
+ </head>
19
+ <body>
20
+ <div id="app"></div>
21
+
22
+ <script type="module">
23
+ import { createEveryState } from '@everystate/core';
24
+ import { createQueryClient } from '@everystate/core/queryClient';
25
+ import { flatten } from '@everystate/view/resolve';
26
+ import { mount } from '@everystate/view/project';
27
+
28
+ // 1. Store + QueryClient
29
+ const store = createEveryState({
30
+ quote: '',
31
+ author: '',
32
+ history: [],
33
+ message: 'Click the button to fetch a quote'
34
+ });
35
+
36
+ const qc = createQueryClient(store);
37
+
38
+ // Fetch and store a quote
39
+ async function fetchQuote() {
40
+ store.set('message', 'Fetching...');
41
+ try {
42
+ const data = await qc.query('quote', async (signal) => {
43
+ const res = await fetch('https://dummyjson.com/quotes/random', { signal });
44
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
45
+ return res.json();
46
+ });
47
+ store.set('quote', data.quote);
48
+ store.set('author', data.author);
49
+ store.set('message', '');
50
+ // Prepend to history
51
+ const history = store.get('history') || [];
52
+ store.set('history', [{ quote: data.quote, author: data.author }, ...history].slice(0, 10));
53
+ } catch (err) {
54
+ if (err.name !== 'AbortError') {
55
+ store.set('message', 'Error: ' + err.message);
56
+ }
57
+ }
58
+ }
59
+
60
+ // 2. View
61
+ flatten({
62
+ tag: 'div',
63
+ class: 'quotes-app',
64
+ children: [
65
+ { tag: 'h1', text: '💬 Quote Fetcher' },
66
+ { tag: 'p', class: 'subtitle', text: 'Powered by setAsync + QueryClient' },
67
+ {
68
+ tag: 'div',
69
+ class: 'card',
70
+ children: [
71
+ { tag: 'p', class: 'quote-text', text: '{quote}' },
72
+ { tag: 'p', class: 'quote-author', text: '{author}' },
73
+ { tag: 'p', class: 'message', text: '{message}' },
74
+ { tag: 'button', text: 'New Quote', onClick: 'fetchQuote' }
75
+ ]
76
+ },
77
+ { tag: 'h2', text: 'History ({history.length})' },
78
+ {
79
+ tag: 'ul',
80
+ class: 'history',
81
+ forEach: 'history',
82
+ as: 'item',
83
+ template: {
84
+ tag: 'li',
85
+ children: [
86
+ { tag: 'span', class: 'hist-quote', text: 'item.quote' },
87
+ { tag: 'span', class: 'hist-author', text: 'item.author' }
88
+ ]
89
+ }
90
+ }
91
+ ]
92
+ }, store, 'view');
93
+
94
+ // 3. Handlers
95
+ const handlers = {
96
+ fetchQuote() {
97
+ fetchQuote();
98
+ }
99
+ };
100
+
101
+ // 4. Mount
102
+ mount(store, 'view', document.getElementById('app'), handlers);
103
+
104
+ // Fetch one on load
105
+ fetchQuote();
106
+ </script>
107
+ </body>
108
+ </html>
@@ -0,0 +1,32 @@
1
+ import { flatten } from '@everystate/view/resolve';
2
+ import { mount } from '@everystate/view/project';
3
+ import { store, fetchQuote } from './store.js';
4
+ import { quoteCard } from './components/quoteCard.js';
5
+ import { historyList } from './components/historyList.js';
6
+ import { appHeader } from './components/appHeader.js';
7
+ import { historyHeading } from './components/historyHeading.js';
8
+
9
+ // 1. View - compose spec tree from imported fragments
10
+ flatten({
11
+ tag: 'div',
12
+ class: 'quotes-app',
13
+ children: [
14
+ appHeader,
15
+ quoteCard,
16
+ historyHeading,
17
+ historyList,
18
+ ]
19
+ }, store, 'view');
20
+
21
+ // 2. Handlers
22
+ const handlers = {
23
+ fetchQuote() {
24
+ fetchQuote();
25
+ }
26
+ };
27
+
28
+ // 3. Mount
29
+ mount(store, 'view', document.getElementById('app'), handlers);
30
+
31
+ // 4. Fetch one on load
32
+ fetchQuote();
@@ -0,0 +1,2 @@
1
+ // App subtitle
2
+ export const appSubtitle = { tag: 'p', class: 'subtitle', text: 'Powered by setAsync + QueryClient' };
@@ -0,0 +1,2 @@
1
+ // App title
2
+ export const appTitle = { tag: 'h1', text: '💬 Quote Fetcher' };
@@ -0,0 +1,9 @@
1
+ // App header - title and subtitle
2
+ import { appTitle } from './appHeader/appTitle.js';
3
+ import { appSubtitle } from './appHeader/appSubtitle.js';
4
+
5
+ export const appHeader = {
6
+ tag: 'div',
7
+ class: 'app-header',
8
+ children: [appTitle, appSubtitle]
9
+ };
@@ -0,0 +1,2 @@
1
+ // History section heading with dynamic count
2
+ export const historyHeading = { tag: 'h2', text: 'History ({history.length})' };
@@ -0,0 +1,2 @@
1
+ // History item author name
2
+ export const histAuthor = { tag: 'span', class: 'hist-author', text: 'item.author' };
@@ -0,0 +1,2 @@
1
+ // History item quote text
2
+ export const histQuote = { tag: 'span', class: 'hist-quote', text: 'item.quote' };
@@ -0,0 +1,14 @@
1
+ // History list - renders the last 10 fetched quotes
2
+ import { histQuote } from './historyList/histQuote.js';
3
+ import { histAuthor } from './historyList/histAuthor.js';
4
+
5
+ export const historyList = {
6
+ tag: 'ul',
7
+ class: 'history',
8
+ forEach: 'history',
9
+ as: 'item',
10
+ template: {
11
+ tag: 'li',
12
+ children: [histQuote, histAuthor]
13
+ }
14
+ };
@@ -0,0 +1,2 @@
1
+ // Fetch new quote button
2
+ export const fetchButton = { tag: 'button', text: 'New Quote', onClick: 'fetchQuote' };
@@ -0,0 +1,2 @@
1
+ // Quote author display
2
+ export const quoteAuthor = { tag: 'p', class: 'quote-author', text: '{author}' };
@@ -0,0 +1,2 @@
1
+ // Status message display
2
+ export const quoteMessage = { tag: 'p', class: 'message', text: '{message}' };
@@ -0,0 +1,2 @@
1
+ // Quote text display
2
+ export const quoteText = { tag: 'p', class: 'quote-text', text: '{quote}' };
@@ -0,0 +1,11 @@
1
+ // Quote card - displays current quote, author, status message, and fetch button
2
+ import { quoteText } from './quoteCard/quoteText.js';
3
+ import { quoteAuthor } from './quoteCard/quoteAuthor.js';
4
+ import { quoteMessage } from './quoteCard/quoteMessage.js';
5
+ import { fetchButton } from './quoteCard/fetchButton.js';
6
+
7
+ export const quoteCard = {
8
+ tag: 'div',
9
+ class: 'card',
10
+ children: [quoteText, quoteAuthor, quoteMessage, fetchButton]
11
+ };