@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,58 @@
|
|
|
1
|
+
// ─── Actions ──────────────────────────────────────────────────
|
|
2
|
+
// Every action receives (store, refs, ...clickArgs).
|
|
3
|
+
// refs = { refName: nodeId } - stable handles to view nodes.
|
|
4
|
+
//
|
|
5
|
+
// PROOF 1 - Conditional rendering:
|
|
6
|
+
// The action toggles a view node's class via store.set().
|
|
7
|
+
// No showIf. No v-if. The logic lives HERE, behind the dot path.
|
|
8
|
+
//
|
|
9
|
+
// PROOF 2 - Parameterized components:
|
|
10
|
+
// The action writes data to store paths.
|
|
11
|
+
// Specs interpolate those paths via {selectedAuthor}, {statsText}, etc.
|
|
12
|
+
// No props. No passing data down. The store IS the parameter bus.
|
|
13
|
+
|
|
14
|
+
export const actions = {
|
|
15
|
+
|
|
16
|
+
// ── Select an author (parameterization + conditional show) ──────
|
|
17
|
+
'selectAuthor': (store, refs, authorName) => {
|
|
18
|
+
// Parameterization: write to store → specs interpolate
|
|
19
|
+
store.set('selectedAuthor', authorName);
|
|
20
|
+
const allQuotes = store.get('quotes');
|
|
21
|
+
const filtered = allQuotes.filter(q => q.author === authorName);
|
|
22
|
+
store.set('selectedQuotes', filtered);
|
|
23
|
+
|
|
24
|
+
// Conditional rendering: show the detail panel
|
|
25
|
+
store.set(`view.nodes.${refs.detail}.class`, 'detail-panel');
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// ── Close detail (conditional hide) ────────────────────────────
|
|
29
|
+
'closeDetail': (store, refs) => {
|
|
30
|
+
// Conditional rendering: hide the detail panel
|
|
31
|
+
store.set(`view.nodes.${refs.detail}.class`, 'detail-panel hidden');
|
|
32
|
+
store.set('selectedAuthor', '');
|
|
33
|
+
store.set('selectedQuotes', []);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// ── Toggle stats (conditional toggle + parameterization) ───────
|
|
37
|
+
'toggleStats': (store, refs) => {
|
|
38
|
+
const visible = store.get('statsVisible');
|
|
39
|
+
store.set('statsVisible', !visible);
|
|
40
|
+
|
|
41
|
+
if (!visible) {
|
|
42
|
+
// Conditional rendering: show
|
|
43
|
+
store.set(`view.nodes.${refs.statsContent}.class`, 'stats-content');
|
|
44
|
+
store.set('toggleLabel', '📊 Hide Stats');
|
|
45
|
+
|
|
46
|
+
// Parameterization: compute stats and write to store
|
|
47
|
+
const quotes = store.get('quotes');
|
|
48
|
+
const authors = store.get('authors');
|
|
49
|
+
store.set('statsText',
|
|
50
|
+
`${quotes.length} quotes from ${authors.length} authors`
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
// Conditional rendering: hide
|
|
54
|
+
store.set(`view.nodes.${refs.statsContent}.class`, 'stats-content hidden');
|
|
55
|
+
store.set('toggleLabel', '📊 Show Stats');
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { flatten } from '@everystate/view/resolve';
|
|
2
|
+
import { mount } from '@everystate/view/project';
|
|
3
|
+
import { createPerfMonitor, mountOverlay } from '@everystate/perf';
|
|
4
|
+
import { store } from './store.js';
|
|
5
|
+
import { c } from './components.js';
|
|
6
|
+
import { actions } from './actions.js';
|
|
7
|
+
import { resolveTree, resolveActions, buildRefs } from './resolve.js';
|
|
8
|
+
|
|
9
|
+
// 1. Resolve component tree from dot-path registry
|
|
10
|
+
const spec = resolveTree(c, 'app');
|
|
11
|
+
|
|
12
|
+
// 2. Flatten into store → get normalized nodes
|
|
13
|
+
const { nodes } = flatten(spec, store, 'view');
|
|
14
|
+
|
|
15
|
+
// 3. Build refs: { refName → nodeId } for conditional rendering
|
|
16
|
+
const refs = buildRefs(nodes);
|
|
17
|
+
console.log('[refs]', refs);
|
|
18
|
+
|
|
19
|
+
// 4. Resolve actions with refs as dependency
|
|
20
|
+
const handlers = resolveActions(actions, store, refs);
|
|
21
|
+
|
|
22
|
+
// 5. Mount
|
|
23
|
+
mount(store, 'view', document.getElementById('app'), handlers);
|
|
24
|
+
|
|
25
|
+
// 6. Perf overlay
|
|
26
|
+
const perf = createPerfMonitor(store);
|
|
27
|
+
mountOverlay(perf, document.body);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// --- Component Registry ---------------------------------------
|
|
2
|
+
// Flat dot-path map. Every component is one key → one spec.
|
|
3
|
+
//
|
|
4
|
+
// ref: 'name' - gives actions a stable handle to this view node.
|
|
5
|
+
// Actions use refs to toggle classes for conditional rendering.
|
|
6
|
+
|
|
7
|
+
export const c = {
|
|
8
|
+
|
|
9
|
+
//- - Root --
|
|
10
|
+
'app': {
|
|
11
|
+
tag: 'div', class: 'app',
|
|
12
|
+
children: ['app.header', 'app.tags', 'app.detail', 'app.stats']
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// -- Header --
|
|
16
|
+
'app.header': {
|
|
17
|
+
tag: 'header', class: 'app-header',
|
|
18
|
+
children: ['app.header.title', 'app.header.subtitle']
|
|
19
|
+
},
|
|
20
|
+
'app.header.title': { tag: 'h1', text: '💬 Quote Explorer' },
|
|
21
|
+
'app.header.subtitle': {
|
|
22
|
+
tag: 'p', class: 'subtitle',
|
|
23
|
+
text: 'Conditional rendering & parameterization - no showIf, no props'
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// -- Author Tags (forEach over string array) --
|
|
27
|
+
'app.tags': {
|
|
28
|
+
tag: 'div', class: 'author-tags',
|
|
29
|
+
children: ['app.tags.label', 'app.tags.list']
|
|
30
|
+
},
|
|
31
|
+
'app.tags.label': { tag: 'span', class: 'tags-label', text: 'Pick an author:' },
|
|
32
|
+
'app.tags.list': {
|
|
33
|
+
tag: 'div', class: 'tags-row',
|
|
34
|
+
forEach: 'authors',
|
|
35
|
+
as: 'author',
|
|
36
|
+
template: {
|
|
37
|
+
tag: 'button',
|
|
38
|
+
class: 'tag',
|
|
39
|
+
text: 'author',
|
|
40
|
+
onClick: 'actions.selectAuthor(author)'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// -- Detail Panel (CONDITIONAL - hidden by default via class) --
|
|
45
|
+
'app.detail': {
|
|
46
|
+
tag: 'div', class: 'detail-panel hidden', ref: 'detail',
|
|
47
|
+
children: ['app.detail.bar', 'app.detail.quotes']
|
|
48
|
+
},
|
|
49
|
+
'app.detail.bar': {
|
|
50
|
+
tag: 'div', class: 'detail-bar',
|
|
51
|
+
children: ['app.detail.bar.heading', 'app.detail.bar.count', 'app.detail.bar.close']
|
|
52
|
+
},
|
|
53
|
+
'app.detail.bar.heading': { tag: 'h2', text: '{selectedAuthor}' },
|
|
54
|
+
'app.detail.bar.count': {
|
|
55
|
+
tag: 'span', class: 'badge', text: '{selectedQuotes.length} quotes'
|
|
56
|
+
},
|
|
57
|
+
'app.detail.bar.close': {
|
|
58
|
+
tag: 'button', class: 'btn btn-close', text: '✕', onClick: 'actions.closeDetail'
|
|
59
|
+
},
|
|
60
|
+
'app.detail.quotes': {
|
|
61
|
+
tag: 'ul', class: 'detail-quotes',
|
|
62
|
+
forEach: 'selectedQuotes',
|
|
63
|
+
as: 'item',
|
|
64
|
+
template: {
|
|
65
|
+
tag: 'li', class: 'detail-quote', text: 'item.text'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// -- Stats Section (CONDITIONAL - content hidden by default) --
|
|
70
|
+
'app.stats': {
|
|
71
|
+
tag: 'div', class: 'stats-section',
|
|
72
|
+
children: ['app.stats.toggle', 'app.stats.content']
|
|
73
|
+
},
|
|
74
|
+
'app.stats.toggle': {
|
|
75
|
+
tag: 'button', class: 'btn btn-secondary',
|
|
76
|
+
text: '{toggleLabel}', onClick: 'actions.toggleStats'
|
|
77
|
+
},
|
|
78
|
+
'app.stats.content': {
|
|
79
|
+
tag: 'div', class: 'stats-content hidden', ref: 'statsContent',
|
|
80
|
+
children: ['app.stats.content.text']
|
|
81
|
+
},
|
|
82
|
+
'app.stats.content.text': { tag: 'p', class: 'stats-text', text: '{statsText}' },
|
|
83
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/* ─── Base ───────────────────────────────────────────────────── */
|
|
2
|
+
:root {
|
|
3
|
+
--color-bg: #f0f2f5;
|
|
4
|
+
--color-surface: #ffffff;
|
|
5
|
+
--color-primary: #6750a4;
|
|
6
|
+
--color-primary-light: #e8def8;
|
|
7
|
+
--color-text: #1c1b1f;
|
|
8
|
+
--color-text-secondary: #49454f;
|
|
9
|
+
--color-text-muted: #79747e;
|
|
10
|
+
--color-border: #e0e0e0;
|
|
11
|
+
--color-accent: #b4261e;
|
|
12
|
+
--radius: 12px;
|
|
13
|
+
--radius-sm: 8px;
|
|
14
|
+
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
15
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
22
|
+
background: var(--color-bg);
|
|
23
|
+
color: var(--color-text);
|
|
24
|
+
line-height: 1.6;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ─── App Shell ──────────────────────────────────────────────── */
|
|
28
|
+
.app {
|
|
29
|
+
max-width: 720px;
|
|
30
|
+
margin: 0 auto;
|
|
31
|
+
padding: 2rem 1.5rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.app-header {
|
|
35
|
+
text-align: center;
|
|
36
|
+
margin-bottom: 2rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.app-header h1 {
|
|
40
|
+
font-size: 2rem;
|
|
41
|
+
font-weight: 700;
|
|
42
|
+
color: var(--color-primary);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.subtitle {
|
|
46
|
+
color: var(--color-text-muted);
|
|
47
|
+
font-size: 0.9rem;
|
|
48
|
+
margin-top: 0.25rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ─── Author Tags ────────────────────────────────────────────── */
|
|
52
|
+
.author-tags {
|
|
53
|
+
margin-bottom: 1.5rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tags-label {
|
|
57
|
+
display: block;
|
|
58
|
+
font-size: 0.85rem;
|
|
59
|
+
color: var(--color-text-muted);
|
|
60
|
+
margin-bottom: 0.5rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tags-row {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.tag {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
padding: 0.45rem 1rem;
|
|
73
|
+
border: 2px solid var(--color-primary-light);
|
|
74
|
+
border-radius: 999px;
|
|
75
|
+
background: var(--color-surface);
|
|
76
|
+
color: var(--color-primary);
|
|
77
|
+
font-size: 0.9rem;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
transition: all 0.15s ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.tag:hover {
|
|
84
|
+
background: var(--color-primary-light);
|
|
85
|
+
border-color: var(--color-primary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ─── Buttons ────────────────────────────────────────────────── */
|
|
89
|
+
.btn {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 0.4rem;
|
|
93
|
+
padding: 0.5rem 1.1rem;
|
|
94
|
+
border: none;
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
font-size: 0.88rem;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: all 0.15s ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.btn-secondary {
|
|
103
|
+
background: var(--color-primary-light);
|
|
104
|
+
color: var(--color-primary);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.btn-secondary:hover {
|
|
108
|
+
background: #d9cdf0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.btn-close {
|
|
112
|
+
background: none;
|
|
113
|
+
color: var(--color-text-muted);
|
|
114
|
+
font-size: 1.1rem;
|
|
115
|
+
padding: 0.3rem 0.6rem;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.btn-close:hover {
|
|
120
|
+
background: rgba(0, 0, 0, 0.06);
|
|
121
|
+
color: var(--color-accent);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ─── Detail Panel (CONDITIONAL) ─────────────────────────────── */
|
|
125
|
+
.detail-panel {
|
|
126
|
+
background: var(--color-surface);
|
|
127
|
+
border-radius: var(--radius);
|
|
128
|
+
padding: 1.5rem;
|
|
129
|
+
box-shadow: var(--shadow-md);
|
|
130
|
+
margin-bottom: 1.5rem;
|
|
131
|
+
border-left: 4px solid var(--color-primary);
|
|
132
|
+
animation: slideIn 0.2s ease-out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.detail-panel.hidden {
|
|
136
|
+
display: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@keyframes slideIn {
|
|
140
|
+
from { opacity: 0; transform: translateY(-8px); }
|
|
141
|
+
to { opacity: 1; transform: translateY(0); }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.detail-bar {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
gap: 0.75rem;
|
|
148
|
+
margin-bottom: 1rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.detail-bar h2 {
|
|
152
|
+
font-size: 1.3rem;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
color: var(--color-primary);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.badge {
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
background: var(--color-primary-light);
|
|
161
|
+
color: var(--color-primary);
|
|
162
|
+
padding: 0.2rem 0.65rem;
|
|
163
|
+
border-radius: 999px;
|
|
164
|
+
font-size: 0.78rem;
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.detail-bar .btn-close {
|
|
169
|
+
margin-left: auto;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.detail-quotes {
|
|
173
|
+
list-style: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.detail-quote {
|
|
177
|
+
padding: 0.65rem 0;
|
|
178
|
+
border-bottom: 1px solid var(--color-border);
|
|
179
|
+
font-size: 0.95rem;
|
|
180
|
+
color: var(--color-text-secondary);
|
|
181
|
+
line-height: 1.6;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.detail-quote:last-child {
|
|
185
|
+
border-bottom: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.detail-quote::before {
|
|
189
|
+
content: '\201C';
|
|
190
|
+
color: var(--color-primary);
|
|
191
|
+
font-weight: 700;
|
|
192
|
+
margin-right: 0.15rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.detail-quote::after {
|
|
196
|
+
content: '\201D';
|
|
197
|
+
color: var(--color-primary);
|
|
198
|
+
font-weight: 700;
|
|
199
|
+
margin-left: 0.05rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ─── Stats Section (CONDITIONAL) ────────────────────────────── */
|
|
203
|
+
.stats-section {
|
|
204
|
+
margin-top: 1rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.stats-content {
|
|
208
|
+
margin-top: 0.75rem;
|
|
209
|
+
background: var(--color-surface);
|
|
210
|
+
border-radius: var(--radius-sm);
|
|
211
|
+
padding: 1rem 1.25rem;
|
|
212
|
+
box-shadow: var(--shadow);
|
|
213
|
+
animation: slideIn 0.2s ease-out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.stats-content.hidden {
|
|
217
|
+
display: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.stats-text {
|
|
221
|
+
color: var(--color-text-secondary);
|
|
222
|
+
font-size: 0.95rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ─── Responsive ─────────────────────────────────────────────── */
|
|
226
|
+
@media (max-width: 600px) {
|
|
227
|
+
.app { padding: 1rem; }
|
|
228
|
+
.app-header h1 { font-size: 1.5rem; }
|
|
229
|
+
.tags-row { gap: 0.4rem; }
|
|
230
|
+
.tag { font-size: 0.82rem; padding: 0.35rem 0.75rem; }
|
|
231
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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: 004_05 - Conditional Rendering & Parameterization</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
|
+
"@everystate/perf": "../../../../everystate-perf/index.js"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
<link rel="stylesheet" href="index.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="app"></div>
|
|
21
|
+
<script type="module" src="app.js"></script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ─── Unified resolver ──────────────────────────────────────────
|
|
2
|
+
// Three functions, one pattern: dot paths → values.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* resolveTree - recursively builds a spec tree from the component registry.
|
|
6
|
+
* Replaces dot-path strings in children/template with resolved spec objects.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveTree(registry, key) {
|
|
9
|
+
const node = registry[key];
|
|
10
|
+
if (!node) return null;
|
|
11
|
+
const resolved = { ...node };
|
|
12
|
+
if (Array.isArray(resolved.children)) {
|
|
13
|
+
resolved.children = resolved.children.map(child =>
|
|
14
|
+
typeof child === 'string' ? resolveTree(registry, child) : child
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
if (typeof resolved.template === 'string') {
|
|
18
|
+
resolved.template = resolveTree(registry, resolved.template);
|
|
19
|
+
}
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* resolveActions - builds a handlers map from the action registry.
|
|
25
|
+
* Maps 'actions.selectAuthor' → () => actions['selectAuthor'](store, ...deps)
|
|
26
|
+
*/
|
|
27
|
+
export function resolveActions(actionRegistry, store, ...deps) {
|
|
28
|
+
const handlers = {};
|
|
29
|
+
for (const [key, fn] of Object.entries(actionRegistry)) {
|
|
30
|
+
handlers[`actions.${key}`] = (...args) => fn(store, ...deps, ...args);
|
|
31
|
+
}
|
|
32
|
+
return handlers;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* buildRefs - scans normalized nodes for `ref` properties.
|
|
37
|
+
* Returns { refName: nodeId } so actions can target view nodes by name.
|
|
38
|
+
*
|
|
39
|
+
* This is the key to conditional rendering:
|
|
40
|
+
* spec: { tag: 'div', class: 'panel hidden', ref: 'detail', ... }
|
|
41
|
+
* refs: { detail: 'v5' }
|
|
42
|
+
* action: store.set(`view.nodes.${refs.detail}.class`, 'panel')
|
|
43
|
+
*/
|
|
44
|
+
export function buildRefs(nodes) {
|
|
45
|
+
const refs = {};
|
|
46
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
47
|
+
if (node.ref) refs[node.ref] = id;
|
|
48
|
+
}
|
|
49
|
+
return refs;
|
|
50
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createEveryState } from '@everystate/core';
|
|
2
|
+
|
|
3
|
+
export const store = createEveryState({
|
|
4
|
+
// ── Quote data (hardcoded - focus is the pattern, not the API) ──
|
|
5
|
+
quotes: [
|
|
6
|
+
{ author: 'Rumi', text: 'Your heart is the size of an ocean. Go find yourself in its hidden depths.' },
|
|
7
|
+
{ author: 'Rumi', text: 'Respond to every call that excites your spirit.' },
|
|
8
|
+
{ author: 'Rumi', text: 'Everything in the universe is within you. Ask all from yourself.' },
|
|
9
|
+
{ author: 'Rumi', text: 'When I am silent, I have thunder hidden inside.' },
|
|
10
|
+
{ author: 'Einstein', text: 'Imagination is more important than knowledge.' },
|
|
11
|
+
{ author: 'Einstein', text: 'Life is like riding a bicycle. To keep your balance, you must keep moving.' },
|
|
12
|
+
{ author: 'Einstein', text: "If you can't explain it simply, you don't understand it well enough." },
|
|
13
|
+
{ author: 'Mother Teresa', text: 'Spread love everywhere you go.' },
|
|
14
|
+
{ author: 'Mother Teresa', text: 'If you judge people, you have no time to love them.' },
|
|
15
|
+
{ author: 'Mother Teresa', text: 'Not all of us can do great things. But we can do small things with great love.' },
|
|
16
|
+
{ author: 'Muhammad Ali', text: 'Float like a butterfly, sting like a bee.' },
|
|
17
|
+
{ author: 'Muhammad Ali', text: "Don't count the days, make the days count." },
|
|
18
|
+
{ author: 'Muhammad Ali', text: 'Service to others is the rent you pay for your room here on earth.' },
|
|
19
|
+
{ author: 'Socrates', text: 'The unexamined life is not worth living.' },
|
|
20
|
+
{ author: 'Socrates', text: 'I know that I know nothing.' },
|
|
21
|
+
{ author: 'Socrates', text: 'Wonder is the beginning of wisdom.' },
|
|
22
|
+
],
|
|
23
|
+
authors: ['Rumi', 'Einstein', 'Mother Teresa', 'Muhammad Ali', 'Socrates'],
|
|
24
|
+
|
|
25
|
+
// ── Parameterized state (written by actions, read by specs via {path}) ──
|
|
26
|
+
selectedAuthor: '',
|
|
27
|
+
selectedQuotes: [],
|
|
28
|
+
|
|
29
|
+
// ── Conditional state (drives class toggles) ──
|
|
30
|
+
statsVisible: false,
|
|
31
|
+
statsText: '',
|
|
32
|
+
toggleLabel: '📊 Show Stats',
|
|
33
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// ─── Actions: thin intent emitters ──────────────────────────────
|
|
2
|
+
// Actions extract values from DOM events and emit clean intents.
|
|
3
|
+
// All business logic lives in intents.js, not here.
|
|
4
|
+
|
|
5
|
+
export const actions = {
|
|
6
|
+
'selectAuthor': (store, refs, authorName) => {
|
|
7
|
+
store.set('intent.selectAuthor', authorName);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
'closeDetail': (store, refs) => {
|
|
11
|
+
store.set('intent.closeDetail', Date.now());
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
'toggleFav': (store, refs, quoteId) => {
|
|
15
|
+
store.set('intent.toggleFav', quoteId);
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
'toggleStats': (store, refs) => {
|
|
19
|
+
store.set('intent.toggleStats', Date.now());
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ─── App: boot sequence ─────────────────────────────────────────
|
|
2
|
+
// 1. Resolve component tree from flat dot-path registry
|
|
3
|
+
// 2. Flatten into store (normalize → view.nodes.*)
|
|
4
|
+
// 3. Build refs (refName → nodeId)
|
|
5
|
+
// 4. Mount reactive subscribers: derived, policies, intents
|
|
6
|
+
// 5. Resolve actions (thin intent emitters)
|
|
7
|
+
// 6. Mount DOM
|
|
8
|
+
// 7. Performance overlay
|
|
9
|
+
|
|
10
|
+
import { flatten } from '@everystate/view/resolve';
|
|
11
|
+
import { mount } from '@everystate/view/project';
|
|
12
|
+
import { createPerfMonitor, mountOverlay } from '@everystate/perf';
|
|
13
|
+
import { store } from './store.js';
|
|
14
|
+
import { c } from './components.js';
|
|
15
|
+
import { actions } from './actions.js';
|
|
16
|
+
import { resolveTree, resolveActions, buildRefs } from './resolve.js';
|
|
17
|
+
import { mountIntents } from './intents.js';
|
|
18
|
+
import { mountDerived } from './derived.js';
|
|
19
|
+
import { mountPolicies } from './policies.js';
|
|
20
|
+
|
|
21
|
+
// 1. Resolve component tree
|
|
22
|
+
const spec = resolveTree(c, 'app');
|
|
23
|
+
|
|
24
|
+
// 2. Flatten into store
|
|
25
|
+
const { nodes } = flatten(spec, store, 'view');
|
|
26
|
+
|
|
27
|
+
// 3. Build refs
|
|
28
|
+
const refs = buildRefs(nodes);
|
|
29
|
+
console.log('[004_06 refs]', refs);
|
|
30
|
+
|
|
31
|
+
// 4. Mount reactive subscribers (order matters: derived first, then policies, then intents)
|
|
32
|
+
mountDerived(store);
|
|
33
|
+
mountPolicies(store);
|
|
34
|
+
mountIntents(store, refs);
|
|
35
|
+
|
|
36
|
+
// 5. Resolve actions (thin intent emitters with store + refs pre-injected)
|
|
37
|
+
const handlers = resolveActions(actions, store, refs);
|
|
38
|
+
|
|
39
|
+
// 6. Mount DOM
|
|
40
|
+
mount(store, 'view', document.getElementById('app'), handlers);
|
|
41
|
+
|
|
42
|
+
// 7. Perf overlay
|
|
43
|
+
const perf = createPerfMonitor(store);
|
|
44
|
+
mountOverlay(perf, document.body);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ─── Component registry ─────────────────────────────────────────
|
|
2
|
+
// Flat dot-path keyed specs. Every component is a resource.
|
|
3
|
+
// children/template are dot-path strings or inline objects.
|
|
4
|
+
// onClick values use 'actions.*' dot paths into the action registry.
|
|
5
|
+
// {path} interpolation reads from the store at mount time.
|
|
6
|
+
// ref: stable handle for intent handlers to toggle view node classes.
|
|
7
|
+
// bind: two-way binding to a store path (no action needed).
|
|
8
|
+
|
|
9
|
+
export const c = {
|
|
10
|
+
// ── Root ──
|
|
11
|
+
'app': {
|
|
12
|
+
tag: 'div', class: 'app',
|
|
13
|
+
children: ['app.header', 'app.search', 'app.tags', 'app.detail', 'app.stats', 'app.log']
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
// ── Header ──
|
|
17
|
+
'app.header': { tag: 'header', class: 'app-header', children: ['app.header.title', 'app.header.subtitle'] },
|
|
18
|
+
'app.header.title': { tag: 'h1', text: '💬 Quote Explorer Pro' },
|
|
19
|
+
'app.header.subtitle': { tag: 'p', class: 'subtitle', text: 'Intent-driven · Derived state · Policy subscriptions' },
|
|
20
|
+
|
|
21
|
+
// ── Search (two-way bind - no action needed) ──
|
|
22
|
+
'app.search': { tag: 'div', class: 'search-bar', children: ['app.search.input'] },
|
|
23
|
+
'app.search.input': { tag: 'input', class: 'search-input', placeholder: 'Search quotes…', bind: 'state.searchTerm' },
|
|
24
|
+
|
|
25
|
+
// ── Author Tags ──
|
|
26
|
+
'app.tags': { tag: 'div', class: 'author-tags', children: ['app.tags.label', 'app.tags.list'] },
|
|
27
|
+
'app.tags.label': { tag: 'span', class: 'tags-label', text: 'Pick an author:' },
|
|
28
|
+
'app.tags.list': {
|
|
29
|
+
tag: 'div', class: 'tags-row',
|
|
30
|
+
forEach: 'state.authors', as: 'author',
|
|
31
|
+
template: { tag: 'button', class: 'tag', text: 'author', onClick: 'actions.selectAuthor(author)' }
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// ── Detail Panel (conditional - hidden by default) ──
|
|
35
|
+
'app.detail': {
|
|
36
|
+
tag: 'div', class: 'detail-panel hidden', ref: 'detail',
|
|
37
|
+
children: ['app.detail.bar', 'app.detail.quotes']
|
|
38
|
+
},
|
|
39
|
+
'app.detail.bar': { tag: 'div', class: 'detail-bar', children: ['app.detail.bar.heading', 'app.detail.bar.count', 'app.detail.bar.close'] },
|
|
40
|
+
'app.detail.bar.heading': { tag: 'h2', class: 'detail-heading', text: '{state.selectedAuthor}' },
|
|
41
|
+
'app.detail.bar.count': { tag: 'span', class: 'badge', text: '{derived.quoteCount} quotes' },
|
|
42
|
+
'app.detail.bar.close': { tag: 'button', class: 'btn btn-close', text: '✕', onClick: 'actions.closeDetail' },
|
|
43
|
+
'app.detail.quotes': {
|
|
44
|
+
tag: 'ul', class: 'detail-quotes',
|
|
45
|
+
forEach: 'derived.filteredQuotes', as: 'item',
|
|
46
|
+
template: {
|
|
47
|
+
tag: 'li', class: 'quote-item',
|
|
48
|
+
children: [
|
|
49
|
+
{ tag: 'span', class: 'quote-text', text: 'item.text' },
|
|
50
|
+
{ tag: 'button', class: 'fav-btn', classIf: { 'is-fav': 'item.fav' }, text: '♥', onClick: 'actions.toggleFav(item.id)' }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// ── Stats Section (conditional toggle) ──
|
|
56
|
+
'app.stats': { tag: 'div', class: 'stats-section', children: ['app.stats.toggle', 'app.stats.content'] },
|
|
57
|
+
'app.stats.toggle': { tag: 'button', class: 'btn btn-secondary', text: '{ui.toggleLabel}', onClick: 'actions.toggleStats' },
|
|
58
|
+
'app.stats.content': {
|
|
59
|
+
tag: 'div', class: 'stats-content hidden', ref: 'statsContent',
|
|
60
|
+
children: ['app.stats.content.text', 'app.stats.content.favcount']
|
|
61
|
+
},
|
|
62
|
+
'app.stats.content.text': { tag: 'p', class: 'stats-text', text: '{derived.stats}' },
|
|
63
|
+
'app.stats.content.favcount': { tag: 'p', class: 'stats-fav', text: '❤ {derived.favCount} favorites' },
|
|
64
|
+
|
|
65
|
+
// ── Activity Log (policy-driven via intent.* wildcard) ──
|
|
66
|
+
'app.log': { tag: 'div', class: 'log-section', children: ['app.log.heading', 'app.log.list'] },
|
|
67
|
+
'app.log.heading': { tag: 'h3', class: 'log-heading', text: '📋 Activity Log' },
|
|
68
|
+
'app.log.list': {
|
|
69
|
+
tag: 'ul', class: 'log-list',
|
|
70
|
+
forEach: 'derived.activityLog', as: 'entry',
|
|
71
|
+
template: {
|
|
72
|
+
tag: 'li', class: 'log-entry',
|
|
73
|
+
children: [
|
|
74
|
+
{ tag: 'span', class: 'log-time', text: 'entry.time' },
|
|
75
|
+
{ tag: 'span', class: 'log-intent', text: 'entry.intent' },
|
|
76
|
+
{ tag: 'span', class: 'log-value', text: 'entry.value' },
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|