@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.
- package/README.md +15 -0
- package/everyState-core/001-counter/README.md +44 -0
- package/everyState-core/001-counter/index.html +79 -0
- package/everyState-core/002-counter-improved/README.md +44 -0
- package/everyState-core/002-counter-improved/index.html +83 -0
- package/everyState-core/003-input-reactive/README.md +44 -0
- package/everyState-core/003-input-reactive/index.html +68 -0
- package/everyState-core/004-computed-state/README.md +45 -0
- package/everyState-core/004-computed-state/index.html +83 -0
- package/everyState-core/005-conditional-rendering/README.md +42 -0
- package/everyState-core/005-conditional-rendering/index.html +68 -0
- package/everyState-core/006-list-rendering/README.md +49 -0
- package/everyState-core/006-list-rendering/index.html +92 -0
- package/everyState-core/007-form-validation/README.md +52 -0
- package/everyState-core/007-form-validation/index.html +108 -0
- package/everyState-core/008-undo-redo/README.md +70 -0
- package/everyState-core/008-undo-redo/index.html +133 -0
- package/everyState-core/009-localStorage-side-effects/README.md +72 -0
- package/everyState-core/009-localStorage-side-effects/index.html +80 -0
- package/everyState-core/010-decoupled-components/README.md +74 -0
- package/everyState-core/010-decoupled-components/index.html +117 -0
- package/everyState-core/011-async-patterns/README.md +98 -0
- package/everyState-core/011-async-patterns/index.html +132 -0
- package/everyState-css/001-stateDrivenCSS/index.html +377 -0
- package/everyState-css/002-cssV2FullDemo/index.html +630 -0
- package/everyState-view/001/counter/index.css +31 -0
- package/everyState-view/001/counter/index.html +50 -0
- package/everyState-view/002/datatable/index.css +70 -0
- package/everyState-view/002/datatable/index.html +118 -0
- package/everyState-view/003/todo/index.css +260 -0
- package/everyState-view/003/todo/index.html +218 -0
- package/everyState-view/003-input-reactive/README.md +44 -0
- package/everyState-view/003-input-reactive/index.html +68 -0
- package/everyState-view/004/quotesFetcher/index.css +124 -0
- package/everyState-view/004/quotesFetcher/index.html +108 -0
- package/everyState-view/004_01/quotesFetcher/app.js +32 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader/appSubtitle.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader/appTitle.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader.js +9 -0
- package/everyState-view/004_01/quotesFetcher/components/historyHeading.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList/histAuthor.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList/histQuote.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList.js +14 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/fetchButton.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteAuthor.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteMessage.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteText.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard.js +11 -0
- package/everyState-view/004_01/quotesFetcher/index.css +124 -0
- package/everyState-view/004_01/quotesFetcher/index.html +23 -0
- package/everyState-view/004_01/quotesFetcher/store.js +35 -0
- package/everyState-view/004_02/quotesFetcher/app.js +20 -0
- package/everyState-view/004_02/quotesFetcher/components.js +46 -0
- package/everyState-view/004_02/quotesFetcher/index.css +124 -0
- package/everyState-view/004_02/quotesFetcher/index.html +23 -0
- package/everyState-view/004_02/quotesFetcher/store.js +35 -0
- package/everyState-view/004_03/quotesFetcher/actions.js +27 -0
- package/everyState-view/004_03/quotesFetcher/app.js +19 -0
- package/everyState-view/004_03/quotesFetcher/components.js +28 -0
- package/everyState-view/004_03/quotesFetcher/index.css +124 -0
- package/everyState-view/004_03/quotesFetcher/index.html +23 -0
- package/everyState-view/004_03/quotesFetcher/resolve.js +34 -0
- package/everyState-view/004_03/quotesFetcher/store.js +11 -0
- package/everyState-view/004_04/quotesFetcher/actions.js +66 -0
- package/everyState-view/004_04/quotesFetcher/app.js +24 -0
- package/everyState-view/004_04/quotesFetcher/components/archive.js +40 -0
- package/everyState-view/004_04/quotesFetcher/components/fetcher.js +29 -0
- package/everyState-view/004_04/quotesFetcher/components.js +20 -0
- package/everyState-view/004_04/quotesFetcher/index.css +283 -0
- package/everyState-view/004_04/quotesFetcher/index.html +24 -0
- package/everyState-view/004_04/quotesFetcher/resolve.js +34 -0
- package/everyState-view/004_04/quotesFetcher/store.js +21 -0
- package/everyState-view/004_04/statedump.json +826 -0
- package/everyState-view/004_05/quoteExplorer/actions.js +58 -0
- package/everyState-view/004_05/quoteExplorer/app.js +27 -0
- package/everyState-view/004_05/quoteExplorer/components.js +83 -0
- package/everyState-view/004_05/quoteExplorer/index.css +231 -0
- package/everyState-view/004_05/quoteExplorer/index.html +23 -0
- package/everyState-view/004_05/quoteExplorer/resolve.js +50 -0
- package/everyState-view/004_05/quoteExplorer/store.js +33 -0
- package/everyState-view/004_06/quoteExplorer/actions.js +21 -0
- package/everyState-view/004_06/quoteExplorer/app.js +44 -0
- package/everyState-view/004_06/quoteExplorer/components.js +80 -0
- package/everyState-view/004_06/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_06/quoteExplorer/index.css +346 -0
- package/everyState-view/004_06/quoteExplorer/index.html +25 -0
- package/everyState-view/004_06/quoteExplorer/intents.js +44 -0
- package/everyState-view/004_06/quoteExplorer/policies.js +25 -0
- package/everyState-view/004_06/quoteExplorer/resolve.js +51 -0
- package/everyState-view/004_06/quoteExplorer/store.js +44 -0
- package/everyState-view/004_07/quoteExplorer/app.js +47 -0
- package/everyState-view/004_07/quoteExplorer/components.js +85 -0
- package/everyState-view/004_07/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_07/quoteExplorer/index.css +346 -0
- package/everyState-view/004_07/quoteExplorer/index.html +25 -0
- package/everyState-view/004_07/quoteExplorer/intents.js +51 -0
- package/everyState-view/004_07/quoteExplorer/policies.js +21 -0
- package/everyState-view/004_07/quoteExplorer/resolve.js +39 -0
- package/everyState-view/004_07/quoteExplorer/store.js +44 -0
- package/everyState-view/004_08/quoteExplorer/app.js +78 -0
- package/everyState-view/004_08/quoteExplorer/components.js +85 -0
- package/everyState-view/004_08/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_08/quoteExplorer/index.css +346 -0
- package/everyState-view/004_08/quoteExplorer/index.html +25 -0
- package/everyState-view/004_08/quoteExplorer/intents.js +51 -0
- package/everyState-view/004_08/quoteExplorer/policies.js +21 -0
- package/everyState-view/004_08/quoteExplorer/resolve.js +39 -0
- package/everyState-view/004_08/quoteExplorer/store.js +44 -0
- package/everyState-view/004_08_V2/app.js +78 -0
- package/everyState-view/004_08_V2/components/appDetail.js +8 -0
- package/everyState-view/004_08_V2/components/appDetailBar.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailBarClose.js +8 -0
- package/everyState-view/004_08_V2/components/appDetailBarCount.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailBarHeading.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailQuotes.js +15 -0
- package/everyState-view/004_08_V2/components/appHeader.js +7 -0
- package/everyState-view/004_08_V2/components/appHeaderSubtitle.js +7 -0
- package/everyState-view/004_08_V2/components/appHeaderTitle.js +7 -0
- package/everyState-view/004_08_V2/components/appLog.js +7 -0
- package/everyState-view/004_08_V2/components/appLogHeading.js +7 -0
- package/everyState-view/004_08_V2/components/appLogList.js +16 -0
- package/everyState-view/004_08_V2/components/appSearch.js +7 -0
- package/everyState-view/004_08_V2/components/appSearchInput.js +9 -0
- package/everyState-view/004_08_V2/components/appStats.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsContent.js +8 -0
- package/everyState-view/004_08_V2/components/appStatsContentFavcount.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsContentText.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsToggle.js +8 -0
- package/everyState-view/004_08_V2/components/appTags.js +7 -0
- package/everyState-view/004_08_V2/components/appTagsLabel.js +7 -0
- package/everyState-view/004_08_V2/components/appTagsRow.js +15 -0
- package/everyState-view/004_08_V2/components/index.js +59 -0
- package/everyState-view/004_08_V2/components/utils/css.js +88 -0
- package/everyState-view/004_08_V2/components/utils/elements.js +87 -0
- package/everyState-view/004_08_V2/components.js +79 -0
- package/everyState-view/004_08_V2/derived.js +43 -0
- package/everyState-view/004_08_V2/index.css +350 -0
- package/everyState-view/004_08_V2/index.html +25 -0
- package/everyState-view/004_08_V2/intents.js +51 -0
- package/everyState-view/004_08_V2/policies.js +21 -0
- package/everyState-view/004_08_V2/resolve.js +39 -0
- package/everyState-view/004_08_V2/store.js +44 -0
- package/everyState-view/006/api-datatable/index.css +388 -0
- package/everyState-view/006/api-datatable/index.html +355 -0
- package/everyState-view/007/apiUsers/index.html +307 -0
- package/everyState-view/007-form-validation/README.md +52 -0
- package/everyState-view/007-form-validation/index.html +108 -0
- package/everyState-view/010-decoupled-components/README.md +74 -0
- package/everyState-view/010-decoupled-components/index.html +117 -0
- package/everyState-view/index.html +36 -0
- package/index.js +0 -5
- package/package.json +2 -4
|
@@ -0,0 +1,117 @@
|
|
|
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>010 Decoupled Components - EveryState</title>
|
|
7
|
+
<style>
|
|
8
|
+
.component { border: 2px solid #333; padding: 20px; margin: 10px 0; }
|
|
9
|
+
</style>
|
|
10
|
+
<script type="importmap">
|
|
11
|
+
{ "imports": {
|
|
12
|
+
"@everystate/core": "../../../everystate-core/index.js",
|
|
13
|
+
"@everystate/perf": "../../../everystate-perf/index.js"
|
|
14
|
+
}}
|
|
15
|
+
</script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<h1>Decoupled Components</h1>
|
|
19
|
+
<p>Three independent "components" sharing state without knowing about each other.</p>
|
|
20
|
+
|
|
21
|
+
<div class="component">
|
|
22
|
+
<h2>Component A: Writer</h2>
|
|
23
|
+
<input type="text" id="messageInput" placeholder="Type a message...">
|
|
24
|
+
<button id="sendBtn">Send</button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="component">
|
|
28
|
+
<h2>Component B: Reader</h2>
|
|
29
|
+
<p><strong>Received:</strong> <span id="messageDisplay">(nothing yet)</span></p>
|
|
30
|
+
<p><small>Message count: <span id="messageCount">0</span></small></p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="component">
|
|
34
|
+
<h2>Component C: Logger</h2>
|
|
35
|
+
<ul id="messageLog"></ul>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<hr>
|
|
39
|
+
<button id="runTests">Run Tests</button>
|
|
40
|
+
<pre id="testLog"></pre>
|
|
41
|
+
|
|
42
|
+
<script type="module">
|
|
43
|
+
import { createEveryState } from '@everystate/core';
|
|
44
|
+
import { createPerfMonitor, mountOverlay } from '@everystate/perf';
|
|
45
|
+
|
|
46
|
+
const store = createEveryState({ message: '', messageCount: 0 });
|
|
47
|
+
const perf = createPerfMonitor(store);
|
|
48
|
+
mountOverlay(perf, document.body);
|
|
49
|
+
|
|
50
|
+
// Component A: Writer
|
|
51
|
+
document.getElementById('sendBtn').onclick = () => {
|
|
52
|
+
const input = document.getElementById('messageInput');
|
|
53
|
+
const value = input.value.trim();
|
|
54
|
+
if (value) {
|
|
55
|
+
store.set('message', value);
|
|
56
|
+
store.set('messageCount', store.get('messageCount') + 1);
|
|
57
|
+
input.value = '';
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
document.getElementById('messageInput').onkeypress = (e) => {
|
|
62
|
+
if (e.key === 'Enter') document.getElementById('sendBtn').click();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Component B: Reader
|
|
66
|
+
store.subscribe('message', (value) => {
|
|
67
|
+
document.getElementById('messageDisplay').textContent = value || '(nothing yet)';
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
store.subscribe('messageCount', (value) => {
|
|
71
|
+
document.getElementById('messageCount').textContent = value;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Component C: Logger
|
|
75
|
+
store.subscribe('message', (value) => {
|
|
76
|
+
if (value) {
|
|
77
|
+
const li = document.createElement('li');
|
|
78
|
+
li.textContent = `[${new Date().toLocaleTimeString()}] ${value}`;
|
|
79
|
+
document.getElementById('messageLog').appendChild(li);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Event Sequence Tests
|
|
84
|
+
document.getElementById('runTests').onclick = () => {
|
|
85
|
+
const results = [];
|
|
86
|
+
let pass = 0, fail = 0;
|
|
87
|
+
function assert(label, cond) {
|
|
88
|
+
if (cond) { pass++; results.push(' OK ' + label); }
|
|
89
|
+
else { fail++; results.push(' FAIL ' + label); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
store.set('message', 'test-msg');
|
|
93
|
+
assert('reader shows message', document.getElementById('messageDisplay').textContent === 'test-msg');
|
|
94
|
+
|
|
95
|
+
store.set('messageCount', 0);
|
|
96
|
+
store.set('messageCount', store.get('messageCount') + 1);
|
|
97
|
+
assert('count incremented', store.get('messageCount') === 1);
|
|
98
|
+
assert('count DOM updated', document.getElementById('messageCount').textContent === '1');
|
|
99
|
+
|
|
100
|
+
// Multiple subscribers on same path
|
|
101
|
+
let fires = 0;
|
|
102
|
+
const u1 = store.subscribe('message', () => fires++);
|
|
103
|
+
const u2 = store.subscribe('message', () => fires++);
|
|
104
|
+
store.set('message', 'multi');
|
|
105
|
+
assert('multiple subs both fire', fires === 2);
|
|
106
|
+
u1(); u2();
|
|
107
|
+
|
|
108
|
+
assert('logger has entries', document.getElementById('messageLog').querySelectorAll('li').length > 0);
|
|
109
|
+
assert('perf tracked', perf.report().summary.totalSets > 0);
|
|
110
|
+
|
|
111
|
+
store.set('message', '');
|
|
112
|
+
store.set('messageCount', 0);
|
|
113
|
+
document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
|
|
114
|
+
};
|
|
115
|
+
</script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 011 Async Patterns - Debouncing & Loading States
|
|
2
|
+
|
|
3
|
+
Demonstrates how to handle async operations with EveryState.
|
|
4
|
+
|
|
5
|
+
## What's Here
|
|
6
|
+
|
|
7
|
+
- **Debounced search:** Wait for user to stop typing before searching
|
|
8
|
+
- **Loading states:** Show loading indicator during async operations
|
|
9
|
+
- **Error handling:** Graceful failure with try/catch
|
|
10
|
+
- **No async library needed** - just Promises and state
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
### Debounced Search
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
let searchTimeout = null;
|
|
18
|
+
|
|
19
|
+
store.subscribe('searchQuery', async (value) => {
|
|
20
|
+
clearTimeout(searchTimeout);
|
|
21
|
+
|
|
22
|
+
store.set('isSearching', true);
|
|
23
|
+
|
|
24
|
+
// Wait 300ms before searching
|
|
25
|
+
searchTimeout = setTimeout(async () => {
|
|
26
|
+
const result = await performSearch(value);
|
|
27
|
+
store.set('searchResult', result);
|
|
28
|
+
store.set('isSearching', false);
|
|
29
|
+
}, 300);
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Loading States
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
document.getElementById('fetchBtn').onclick = async () => {
|
|
37
|
+
store.set('isLoading', true);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const data = await fetchData();
|
|
41
|
+
store.set('apiData', data);
|
|
42
|
+
} finally {
|
|
43
|
+
store.set('isLoading', false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Key Insight
|
|
49
|
+
|
|
50
|
+
**Async is just state changes over time.**
|
|
51
|
+
|
|
52
|
+
Pattern for any async operation:
|
|
53
|
+
1. Set loading state to `true`
|
|
54
|
+
2. Perform async operation
|
|
55
|
+
3. Set result state
|
|
56
|
+
4. Set loading state to `false`
|
|
57
|
+
|
|
58
|
+
Other frameworks need:
|
|
59
|
+
- React: `useState` + `useEffect` + cleanup functions
|
|
60
|
+
- Vue: `ref` + `watch` + async watchers
|
|
61
|
+
- Svelte: Reactive statements + `#await` blocks
|
|
62
|
+
|
|
63
|
+
**EveryState:** Just use async/await in subscribers. That's it.
|
|
64
|
+
|
|
65
|
+
## Patterns Demonstrated
|
|
66
|
+
|
|
67
|
+
### 1. Debouncing
|
|
68
|
+
Wait for user to stop typing before triggering expensive operations.
|
|
69
|
+
|
|
70
|
+
### 2. Loading Indicators
|
|
71
|
+
Show feedback during async operations to improve UX.
|
|
72
|
+
|
|
73
|
+
### 3. Disabling Actions
|
|
74
|
+
Prevent duplicate requests by disabling buttons during loading.
|
|
75
|
+
|
|
76
|
+
### 4. Cleanup
|
|
77
|
+
Clear timeouts to prevent race conditions.
|
|
78
|
+
|
|
79
|
+
## Run It
|
|
80
|
+
|
|
81
|
+
Open `index.html`:
|
|
82
|
+
- Type in the search box (notice the 300ms delay)
|
|
83
|
+
- Click "Fetch Data" (notice the loading state)
|
|
84
|
+
|
|
85
|
+
## Try This
|
|
86
|
+
|
|
87
|
+
Open DevTools Network tab and throttle to "Slow 3G" to see loading states more clearly.
|
|
88
|
+
|
|
89
|
+
## Real-World Usage
|
|
90
|
+
|
|
91
|
+
This pattern works for:
|
|
92
|
+
- ✅ API calls
|
|
93
|
+
- ✅ Autocomplete
|
|
94
|
+
- ✅ Form submission
|
|
95
|
+
- ✅ File uploads
|
|
96
|
+
- ✅ Any async operation
|
|
97
|
+
|
|
98
|
+
Just manage loading/error/success states in the store.
|
|
@@ -0,0 +1,132 @@
|
|
|
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>011 Async Patterns - 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>Async Patterns</h1>
|
|
16
|
+
|
|
17
|
+
<h2>Debounced Search</h2>
|
|
18
|
+
<input type="text" id="searchInput" placeholder="Type to search...">
|
|
19
|
+
<p id="searchStatus"></p>
|
|
20
|
+
<p><strong>Search result:</strong> <span id="searchResult">(type something)</span></p>
|
|
21
|
+
|
|
22
|
+
<hr>
|
|
23
|
+
|
|
24
|
+
<h2>Simulated API Call</h2>
|
|
25
|
+
<button id="fetchBtn">Fetch Data</button>
|
|
26
|
+
<p id="loadingStatus"></p>
|
|
27
|
+
<p><strong>Data:</strong> <span id="apiData">(not loaded)</span></p>
|
|
28
|
+
|
|
29
|
+
<hr>
|
|
30
|
+
<button id="runTests">Run Tests</button>
|
|
31
|
+
<pre id="testLog"></pre>
|
|
32
|
+
|
|
33
|
+
<script type="module">
|
|
34
|
+
import { createEveryState } from '@everystate/core';
|
|
35
|
+
import { createPerfMonitor, mountOverlay } from '@everystate/perf';
|
|
36
|
+
|
|
37
|
+
const store = createEveryState({
|
|
38
|
+
searchQuery: '', searchResult: '', isSearching: false,
|
|
39
|
+
apiData: null, isLoading: false
|
|
40
|
+
});
|
|
41
|
+
const perf = createPerfMonitor(store);
|
|
42
|
+
mountOverlay(perf, document.body);
|
|
43
|
+
|
|
44
|
+
let searchTimeout = null;
|
|
45
|
+
|
|
46
|
+
const performSearch = (query) => new Promise((resolve) => {
|
|
47
|
+
setTimeout(() => resolve(`Results for "${query}": Found ${query.length} items`), 500);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
store.subscribe('searchQuery', async (value) => {
|
|
51
|
+
clearTimeout(searchTimeout);
|
|
52
|
+
if (!value) { store.set('searchResult', ''); store.set('isSearching', false); return; }
|
|
53
|
+
store.set('isSearching', true);
|
|
54
|
+
searchTimeout = setTimeout(async () => {
|
|
55
|
+
const result = await performSearch(value);
|
|
56
|
+
store.set('searchResult', result);
|
|
57
|
+
store.set('isSearching', false);
|
|
58
|
+
}, 300);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
store.subscribe('isSearching', (value) => {
|
|
62
|
+
document.getElementById('searchStatus').textContent = value ? 'Searching...' : '';
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
store.subscribe('searchResult', (value) => {
|
|
66
|
+
document.getElementById('searchResult').textContent = value || '(type something)';
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
document.getElementById('searchInput').oninput = (e) => { store.set('searchQuery', e.target.value); };
|
|
70
|
+
|
|
71
|
+
const fetchData = () => new Promise((resolve) => {
|
|
72
|
+
setTimeout(() => resolve({ id: Math.floor(Math.random() * 1000), timestamp: new Date().toLocaleTimeString() }), 1000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
store.subscribe('isLoading', (value) => {
|
|
76
|
+
document.getElementById('loadingStatus').textContent = value ? 'Loading...' : '';
|
|
77
|
+
document.getElementById('fetchBtn').disabled = value;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
store.subscribe('apiData', (value) => {
|
|
81
|
+
if (value) document.getElementById('apiData').textContent = `ID: ${value.id}, Time: ${value.timestamp}`;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
document.getElementById('fetchBtn').onclick = async () => {
|
|
85
|
+
store.set('isLoading', true);
|
|
86
|
+
try {
|
|
87
|
+
const data = await fetchData();
|
|
88
|
+
store.set('apiData', data);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Fetch failed:', error);
|
|
91
|
+
} finally {
|
|
92
|
+
store.set('isLoading', false);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Event Sequence Tests
|
|
97
|
+
document.getElementById('runTests').onclick = () => {
|
|
98
|
+
const results = [];
|
|
99
|
+
let pass = 0, fail = 0;
|
|
100
|
+
function assert(label, cond) {
|
|
101
|
+
if (cond) { pass++; results.push(' OK ' + label); }
|
|
102
|
+
else { fail++; results.push(' FAIL ' + label); }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Sync tests (async search tested manually)
|
|
106
|
+
store.set('isLoading', true);
|
|
107
|
+
assert('isLoading set', store.get('isLoading') === true);
|
|
108
|
+
assert('button disabled while loading', document.getElementById('fetchBtn').disabled === true);
|
|
109
|
+
|
|
110
|
+
store.set('isLoading', false);
|
|
111
|
+
assert('button enabled after load', document.getElementById('fetchBtn').disabled === false);
|
|
112
|
+
|
|
113
|
+
store.set('searchQuery', 'test');
|
|
114
|
+
assert('searchQuery stored', store.get('searchQuery') === 'test');
|
|
115
|
+
assert('isSearching set', store.get('isSearching') === true);
|
|
116
|
+
|
|
117
|
+
// Wildcard test
|
|
118
|
+
let wildFired = false;
|
|
119
|
+
const unsub = store.subscribe('*', () => { wildFired = true; });
|
|
120
|
+
store.set('isLoading', true);
|
|
121
|
+
assert('global wildcard fires', wildFired);
|
|
122
|
+
unsub();
|
|
123
|
+
|
|
124
|
+
assert('perf tracked', perf.report().summary.totalSets > 0);
|
|
125
|
+
|
|
126
|
+
store.set('searchQuery', '');
|
|
127
|
+
store.set('isLoading', false);
|
|
128
|
+
document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
|
|
129
|
+
};
|
|
130
|
+
</script>
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|