fusee-framework 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -123
- package/app/benchmark/benchmark.js +633 -0
- package/app/benchmark/index.html +102 -0
- package/app/benchmark/styles.css +249 -0
- package/app/components/Counter.js +17 -7
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +50 -62
- package/app/main.ts +78 -63
- package/app/pages/404.js +16 -0
- package/app/pages/[...all].js +35 -0
- package/app/pages/[...all].ts +35 -0
- package/app/pages/_layout.js +53 -0
- package/app/pages/_layout.ts +26 -0
- package/app/pages/about.js +23 -0
- package/app/pages/about.ts +29 -0
- package/app/pages/async-work.js +65 -0
- package/app/pages/composables.js +158 -0
- package/app/pages/composables.ts +158 -0
- package/app/pages/dynamic.js +40 -0
- package/app/pages/dynamic.ts +40 -0
- package/app/pages/index.js +137 -0
- package/app/pages/index.ts +87 -0
- package/app/pages/login.ts +21 -0
- package/app/pages/objects/[id].js +56 -0
- package/app/pages/objects/[id].ts +56 -0
- package/app/pages/posts/[category]/[slug].js +56 -0
- package/app/pages/posts/[category]/[slug].ts +56 -0
- package/app/pages/router-example.js +79 -0
- package/app/pages/router-example.ts +56 -0
- package/app/pages/scroll-demo.ts +102 -0
- package/app/pages/test-for.js +65 -0
- package/app/pages/test-for.ts +80 -0
- package/app/pages/users/[id].js +24 -0
- package/app/pages/users/[id].ts +46 -0
- package/app/pages/users/index.js +22 -0
- package/app/pages/users/index.ts +29 -0
- package/app/pages/users.js +43 -0
- package/app/pages/users.ts +78 -0
- package/app/providers/ChildComponent.js +8 -2
- package/app/providers/ChildComponent.ts +47 -47
- package/app/providers/GrandchildComponent.js +19 -0
- package/app/providers/ParentComponent.js +5 -1
- package/app/routes/About.js +1 -1
- package/app/routes/About.ts +1 -1
- package/app/routes/AuthRoutes.ts +41 -0
- package/app/routes/Home.js +9 -0
- package/app/routes/NotFound.js +16 -0
- package/app/routes/NotFound.ts +16 -0
- package/app/routes/RouterExample.js +78 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/TestResource.js +47 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/app/services.js +15 -0
- package/auto-imports.d.ts +9 -0
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/bin/create-fusee.exe +0 -0
- package/bin/index.js +38 -0
- package/create-fusee-go/cmd/generate.go +72 -0
- package/create-fusee-go/cmd/init.go +38 -0
- package/create-fusee-go/cmd/root.go +121 -0
- package/create-fusee-go/go.mod +10 -0
- package/create-fusee-go/go.sum +10 -0
- package/create-fusee-go/internal/assets/assets.go +78 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
- package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
- package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
- package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
- package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
- package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
- package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
- package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
- package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
- package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
- package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
- package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
- package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
- package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
- package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
- package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
- package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
- package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
- package/create-fusee-go/main.go +7 -0
- package/framework/auto-import-preset.js +13 -9
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +279 -260
- package/framework/core/di.js +187 -0
- package/framework/core/directives.js +819 -786
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +33 -14
- package/framework/core/reconcile.js +151 -0
- package/framework/core/signal.js +679 -403
- package/framework/core/store.js +322 -81
- package/framework/index.js +12 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +554 -80
- package/framework/types/component.d.ts +89 -88
- package/framework/types/di.d.ts +43 -0
- package/framework/types/event-delegation.d.ts +0 -58
- package/framework/types/index.d.ts +32 -22
- package/framework/types/reconcile.d.ts +7 -0
- package/framework/types/router.d.ts +42 -15
- package/framework/types/signal.d.ts +147 -78
- package/framework/types/store.d.ts +66 -8
- package/index.html +638 -626
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- package/vite.config.js +7 -1
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { currentRoute, navigate, routeParams, computed } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
export const RouterExample = defineComponent({
|
|
4
|
+
setup() {
|
|
5
|
+
const path = currentRoute
|
|
6
|
+
const params = routeParams
|
|
7
|
+
const paramsJson = computed(() => JSON.stringify(params()))
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
path,
|
|
11
|
+
paramsJson,
|
|
12
|
+
navigate,
|
|
13
|
+
template: `
|
|
14
|
+
<div class="page">
|
|
15
|
+
<h1>🔗 Router API Demo</h1>
|
|
16
|
+
|
|
17
|
+
<div class="demo-card">
|
|
18
|
+
<h2>currentRoute Signal</h2>
|
|
19
|
+
<code style="color: #8b8bff; font-size: 1.5rem;">{{ path }}</code>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="demo-card">
|
|
23
|
+
<h2>routeParams Signal (Dynamic Routes)</h2>
|
|
24
|
+
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
|
|
25
|
+
<a href="/objects/123" f-link class="cmp-btn" style="text-decoration: none;">Object 123</a>
|
|
26
|
+
<a href="/objects/456" f-link class="cmp-btn" style="text-decoration: none;">Object 456</a>
|
|
27
|
+
<a href="/posts/tech/my-post" f-link class="cmp-btn" style="text-decoration: none;">Post Route</a>
|
|
28
|
+
</div>
|
|
29
|
+
<code style="color: #34d399; font-size: 1.2rem;">{{ paramsJson }}</code>
|
|
30
|
+
<p style="margin-top: 10px; color: #888; font-size: 0.9rem;">
|
|
31
|
+
Dynamic routes use :param syntax (e.g., /users/:id or /posts/:category/:slug)
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="demo-card">
|
|
36
|
+
<h2>navigate() Test</h2>
|
|
37
|
+
<div style="display: flex; gap: 10px;">
|
|
38
|
+
<button @click="navigate('/')" class="cmp-btn">Home</button>
|
|
39
|
+
<button @click="navigate('/about')" class="cmp-btn">About</button>
|
|
40
|
+
<button @click="navigate('/wildcard-test-123')" class="cmp-btn" style="border-color: #f59e0b; color: #f59e0b;">Wildcard</button>
|
|
41
|
+
<button @click="navigate('/nonexistent')" class="cmp-btn" style="border-color: #ff6b6b; color: #ff6b6b;">404 Test</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="demo-card">
|
|
46
|
+
<h2>f-link Test</h2>
|
|
47
|
+
<div style="display: flex; gap: 10px;">
|
|
48
|
+
<a href="/" f-link class="cmp-btn" style="text-decoration: none;">Home</a>
|
|
49
|
+
<a href="/about" f-link class="cmp-btn" style="text-decoration: none;">About</a>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
`
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defineComponent, currentRoute } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scroll Demo - demonstrates router scroll behavior
|
|
5
|
+
*/
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
setup() {
|
|
8
|
+
const route = currentRoute
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
template: `
|
|
12
|
+
<div class="content-wrap">
|
|
13
|
+
<h1>🔽 Scroll Behavior Demo</h1>
|
|
14
|
+
<p>This page demonstrates the router's scroll behavior. Click the navigation links to jump to sections.</p>
|
|
15
|
+
|
|
16
|
+
<!-- Navigation -->
|
|
17
|
+
<div class="demo-section">
|
|
18
|
+
<h2>Navigation</h2>
|
|
19
|
+
<div class="controls">
|
|
20
|
+
<a href="/scroll-demo#intro" f-link class="cmp-btn">Introduction</a>
|
|
21
|
+
<a href="/scroll-demo#features" f-link class="cmp-btn">Features</a>
|
|
22
|
+
<a href="/scroll-demo#examples" f-link class="cmp-btn">Examples</a>
|
|
23
|
+
<a href="/scroll-demo#api" f-link class="cmp-btn">API</a>
|
|
24
|
+
<a href="/scroll-demo#bottom" f-link class="cmp-btn">Bottom</a>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Scroll indicator -->
|
|
29
|
+
<div class="info-section">
|
|
30
|
+
<h2>Current Route</h2>
|
|
31
|
+
<p><strong>Scroll Features:</strong></p>
|
|
32
|
+
<ul>
|
|
33
|
+
<li>✓ scrollToTop</li>
|
|
34
|
+
<li>✓ saveScrollPosition</li>
|
|
35
|
+
<li>✓ custom scroll function</li>
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Sections -->
|
|
40
|
+
<div style="display: flex; flex-direction: column; gap: 2rem; margin-top: 2rem;">
|
|
41
|
+
<section id="intro" class="demo-section" style="min-height: 40vh; scroll-margin-top: 100px;">
|
|
42
|
+
<h2>Introduction</h2>
|
|
43
|
+
<p>Welcome to the scroll behavior demo! Navigate between sections using the links below.</p>
|
|
44
|
+
<div style="opacity: 0.5; margin-top: 1rem;">
|
|
45
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
46
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
47
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<section id="features" class="demo-section" style="min-height: 40vh; scroll-margin-top: 100px;">
|
|
52
|
+
<h2>Features</h2>
|
|
53
|
+
<p>Smooth scrolling, anchor navigation, and scroll position restoration are all supported by the Fusee router.</p>
|
|
54
|
+
<div style="opacity: 0.5; margin-top: 1rem;">
|
|
55
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
56
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
57
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section id="examples" class="demo-section" style="min-height: 40vh; scroll-margin-top: 100px;">
|
|
62
|
+
<h2>Examples</h2>
|
|
63
|
+
<p>Click on any section link to see smooth scroll to anchor. Use browser back/forward to see scroll position restore.</p>
|
|
64
|
+
<div style="opacity: 0.5; margin-top: 1rem;">
|
|
65
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
66
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
67
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section id="api" class="demo-section" style="min-height: 40vh; scroll-margin-top: 100px;">
|
|
72
|
+
<h2>API</h2>
|
|
73
|
+
<p>Configure scroll behavior in createRouter(): scrollToTop, saveScrollPosition, or custom function.</p>
|
|
74
|
+
<div style="opacity: 0.5; margin-top: 1rem;">
|
|
75
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
76
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
77
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section id="bottom" class="demo-section" style="min-height: 40vh; scroll-margin-top: 100px;">
|
|
82
|
+
<h2>Bottom Section</h2>
|
|
83
|
+
<p>You made it to the bottom! The scroll behavior automatically scrolls to anchors when navigating with #hash URLs.</p>
|
|
84
|
+
<div style="opacity: 0.5; margin-top: 1rem;">
|
|
85
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
86
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
87
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
|
88
|
+
</div>
|
|
89
|
+
</section>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Back to top -->
|
|
93
|
+
<div style="text-align: center; margin-top: 3rem; padding: 2rem;">
|
|
94
|
+
<a href="/scroll-demo" f-link class="action-btn btn-primary">
|
|
95
|
+
⬆️ Back to Top
|
|
96
|
+
</a>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
`
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const TestFor = defineComponent({
|
|
2
|
+
setup() {
|
|
3
|
+
const items = signal([
|
|
4
|
+
{ id: 1, name: 'Item 1' },
|
|
5
|
+
{ id: 2, name: 'Item 2' },
|
|
6
|
+
{ id: 3, name: 'Item 3' }
|
|
7
|
+
])
|
|
8
|
+
const search = signal('')
|
|
9
|
+
|
|
10
|
+
const filteredItems = items.filter(item =>
|
|
11
|
+
item.name.toLowerCase().includes(search().toLowerCase())
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
inspect(items)
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
items,
|
|
18
|
+
search,
|
|
19
|
+
filteredItems,
|
|
20
|
+
namesSummary: filteredItems.map(i => i.name).join(', '),
|
|
21
|
+
|
|
22
|
+
updateItem: (id, newName) => {
|
|
23
|
+
items(items().map(i => i.id === id ? { ...i, name: newName } : i))
|
|
24
|
+
},
|
|
25
|
+
addItem: () => {
|
|
26
|
+
const nextId = Math.max(0, ...items().map(i => i.id)) + 1
|
|
27
|
+
items.push({ id: nextId, name: `Item ${nextId}` })
|
|
28
|
+
},
|
|
29
|
+
shuffleItems: () => {
|
|
30
|
+
items.sort(() => Math.random() - 0.5)
|
|
31
|
+
},
|
|
32
|
+
template: `
|
|
33
|
+
<div class="page">
|
|
34
|
+
<h1>Keyed f-for Test</h1>
|
|
35
|
+
<p>Focus an input below, then click "Shuffle" or "Add". The focus should stay!</p>
|
|
36
|
+
<p style="color: #8b8bff; background: #8b8bff11; padding: 10px; border-radius: 4px; border-left: 3px solid #8b8bff;">
|
|
37
|
+
🔗 <strong>Reactive Summary:</strong> {{ namesSummary || 'No items found' }}
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<div style="margin-bottom: 20px; display: flex; flex-direction: column; gap: 10px;">
|
|
41
|
+
<input type="text" f-model="search" placeholder="🔍 Filter items..."
|
|
42
|
+
style="padding: 10px; border-radius: 4px; border: 1px solid #444; background: #111; color: #fff; width: 100%;" />
|
|
43
|
+
|
|
44
|
+
<div style="display: flex; gap: 10px;">
|
|
45
|
+
<button @click="addItem" class="primary-btn">Add Item</button>
|
|
46
|
+
<button @click="shuffleItems" class="primary-btn">Shuffle Items</button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div style="display: flex; flex-direction: column; gap: 10px;">
|
|
51
|
+
<div f-for="item in filteredItems" :key="item.id"
|
|
52
|
+
style="padding: 10px; border: 1px solid #333; display: flex; gap: 10px; align-items: center;">
|
|
53
|
+
<span>ID: {{ item.id }}</span>
|
|
54
|
+
<input type="text"
|
|
55
|
+
:value="item.name"
|
|
56
|
+
@input="updateItem(item().id, $event.target.value)"
|
|
57
|
+
placeholder="Type here..."
|
|
58
|
+
style="background: #111; color: #fff; border: 1px solid #444; padding: 5px;" />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
type Item = {
|
|
2
|
+
id: number
|
|
3
|
+
name: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type TestForResult = {
|
|
7
|
+
items: Signal<Item[]>
|
|
8
|
+
search: Signal<string>
|
|
9
|
+
filteredItems: Computed<Item[]>
|
|
10
|
+
namesSummary: Computed<string>
|
|
11
|
+
updateItem: (id: number, name: string) => void
|
|
12
|
+
addItem: () => void
|
|
13
|
+
shuffleItems: () => void
|
|
14
|
+
template: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TestFor = defineComponent({
|
|
18
|
+
setup(): TestForResult {
|
|
19
|
+
const items = signal<Item[]>([
|
|
20
|
+
{ id: 1, name: 'Item 1' },
|
|
21
|
+
{ id: 2, name: 'Item 2' },
|
|
22
|
+
{ id: 3, name: 'Item 3' }
|
|
23
|
+
])
|
|
24
|
+
const search = signal('')
|
|
25
|
+
const filteredItems = items.filter(i =>
|
|
26
|
+
i.name.toLowerCase().includes(search().toLowerCase())
|
|
27
|
+
)
|
|
28
|
+
const namesSummary = filteredItems.map(i => i.name).join(', ')
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
items,
|
|
32
|
+
search,
|
|
33
|
+
filteredItems,
|
|
34
|
+
namesSummary,
|
|
35
|
+
updateItem: (id, newName) => {
|
|
36
|
+
items(items().map(i => i.id === id ? { ...i, name: newName } : i))
|
|
37
|
+
},
|
|
38
|
+
addItem: () => {
|
|
39
|
+
const nextIds = items().map(i => i.id)
|
|
40
|
+
const nextId = nextIds.length > 0 ? Math.max(...nextIds) + 1 : 1
|
|
41
|
+
items.push({ id: nextId, name: `Item ${nextId}` })
|
|
42
|
+
},
|
|
43
|
+
shuffleItems: () => {
|
|
44
|
+
items.sort(() => Math.random() - 0.5)
|
|
45
|
+
},
|
|
46
|
+
template: `
|
|
47
|
+
<div class="page">
|
|
48
|
+
<h1>Keyed f-for Test (TypeScript)</h1>
|
|
49
|
+
<p>Focus an input below, then click "Shuffle" or "Add". The focus should stay!</p>
|
|
50
|
+
|
|
51
|
+
<p style="color: #8b8bff; background: #8b8bff11; padding: 10px; border-radius: 4px; border-left: 3px solid #8b8bff;">
|
|
52
|
+
🔗 <strong>Reactive Summary:</strong> {{ namesSummary || 'No items found' }}
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<div style="margin-bottom: 20px; display: flex; flex-direction: column; gap: 10px;">
|
|
56
|
+
<input type="text" f-model="search" placeholder="🔍 Filter items..."
|
|
57
|
+
style="padding: 10px; border-radius: 4px; border: 1px solid #444; background: #111; color: #fff; width: 100%;" />
|
|
58
|
+
|
|
59
|
+
<div style="display: flex; gap: 10px;">
|
|
60
|
+
<button @click="addItem" class="primary-btn">Add Item</button>
|
|
61
|
+
<button @click="shuffleItems" class="primary-btn">Shuffle Items</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div style="display: flex; flex-direction: column; gap: 10px;">
|
|
66
|
+
<div f-for="item in filteredItems" :key="item.id"
|
|
67
|
+
style="padding: 10px; border: 1px solid #333; display: flex; gap: 10px; align-items: center;">
|
|
68
|
+
<span>ID: {{ item.id }}</span>
|
|
69
|
+
<input type="text"
|
|
70
|
+
:value="item.name"
|
|
71
|
+
@input="updateItem(item().id, $event.target.value)"
|
|
72
|
+
placeholder="Type here..."
|
|
73
|
+
style="background: #111; color: #fff; border: 1px solid #444; padding: 5px;" />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
`
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { routeParams, computed } from '../../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User Detail (JS) — child of the /users layout.
|
|
5
|
+
*/
|
|
6
|
+
export const UserDetail = defineComponent({
|
|
7
|
+
setup() {
|
|
8
|
+
const params = routeParams
|
|
9
|
+
const userId = computed(() => params().id || '?')
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
userId,
|
|
13
|
+
template: `
|
|
14
|
+
<div class="page">
|
|
15
|
+
<h1>👤 User #{{ userId }} (JS)</h1>
|
|
16
|
+
<div class="demo-card">
|
|
17
|
+
<p>This content area changes while the sidebar persists.</p>
|
|
18
|
+
<p>Current User ID: <strong style="color: #8b8bff;">{{ userId }}</strong></p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { routeParams, computed, defineComponent } from '../../../framework/index'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User Detail — child of the /users layout.
|
|
5
|
+
* Renders inside the layout's <div data-router-view> for /users/:id.
|
|
6
|
+
*/
|
|
7
|
+
export const UserDetail = defineComponent({
|
|
8
|
+
setup() {
|
|
9
|
+
const params = routeParams
|
|
10
|
+
const userId = computed(() => params().id || '?')
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
userId,
|
|
14
|
+
params,
|
|
15
|
+
template: `
|
|
16
|
+
<div class="page">
|
|
17
|
+
<h1>👤 User #{{ userId }}</h1>
|
|
18
|
+
|
|
19
|
+
<div class="demo-card">
|
|
20
|
+
<h2>User Profile</h2>
|
|
21
|
+
<p style="color: #8b8bff; font-size: 1.2rem; margin-bottom: 0.5rem;">
|
|
22
|
+
ID: <strong>{{ userId }}</strong>
|
|
23
|
+
</p>
|
|
24
|
+
<p style="color: #888; font-size: 0.9rem;">
|
|
25
|
+
This page renders inside the Users layout.
|
|
26
|
+
The sidebar persists — only this content area changes.
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="demo-card" style="margin-top: 1rem;">
|
|
31
|
+
<h2>Navigate to Other Users</h2>
|
|
32
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
33
|
+
<a href="/users/1" f-link class="cmp-btn" style="text-decoration: none;">User #1</a>
|
|
34
|
+
<a href="/users/2" f-link class="cmp-btn" style="text-decoration: none;">User #2</a>
|
|
35
|
+
<a href="/users/42" f-link class="cmp-btn" style="text-decoration: none;">User #42</a>
|
|
36
|
+
<a href="/users/999" f-link class="cmp-btn" style="text-decoration: none;">User #999</a>
|
|
37
|
+
</div>
|
|
38
|
+
<p style="margin-top: 0.75rem; color: #555; font-size: 0.8rem;">
|
|
39
|
+
⚡ Notice: the layout sidebar does NOT re-render when switching users!
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const UsersIndex = defineComponent({
|
|
2
|
+
setup() {
|
|
3
|
+
return {
|
|
4
|
+
template: `
|
|
5
|
+
<div class="page">
|
|
6
|
+
<h1>📋 All Users (JS)</h1>
|
|
7
|
+
<p>Select a user from the sidebar to view their profile.</p>
|
|
8
|
+
<div style="display: grid; gap: 0.75rem; margin-top: 1rem;">
|
|
9
|
+
<a href="/users/1" f-link class="demo-card" style="text-decoration: none;">
|
|
10
|
+
<h2>👤 Alice Johnson</h2>
|
|
11
|
+
<p>ID: 1 · Engineer</p>
|
|
12
|
+
</a>
|
|
13
|
+
<a href="/users/2" f-link class="demo-card" style="text-decoration: none;">
|
|
14
|
+
<h2>👤 Bob Smith</h2>
|
|
15
|
+
<p>ID: 2 · Designer</p>
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineComponent } from '../../../framework/index'
|
|
2
|
+
|
|
3
|
+
export const UsersIndex = defineComponent({
|
|
4
|
+
setup() {
|
|
5
|
+
return {
|
|
6
|
+
template: `
|
|
7
|
+
<div class="page">
|
|
8
|
+
<h1>📋 All Users</h1>
|
|
9
|
+
<p style="margin-bottom: 1.5rem;">Select a user from the sidebar to view their profile.</p>
|
|
10
|
+
|
|
11
|
+
<div style="display: grid; gap: 0.75rem;">
|
|
12
|
+
<a href="/users/1" f-link class="demo-card" style="text-decoration: none; cursor: pointer; transition: border-color 0.2s;">
|
|
13
|
+
<h2 style="font-size: 1rem;">👤 Alice Johnson</h2>
|
|
14
|
+
<p style="font-size: 0.85rem; color: #888;">ID: 1 · Engineer</p>
|
|
15
|
+
</a>
|
|
16
|
+
<a href="/users/2" f-link class="demo-card" style="text-decoration: none; cursor: pointer; transition: border-color 0.2s;">
|
|
17
|
+
<h2 style="font-size: 1rem;">👤 Bob Smith</h2>
|
|
18
|
+
<p style="font-size: 0.85rem; color: #888;">ID: 2 · Designer</p>
|
|
19
|
+
</a>
|
|
20
|
+
<a href="/users/42" f-link class="demo-card" style="text-decoration: none; cursor: pointer; transition: border-color 0.2s;">
|
|
21
|
+
<h2 style="font-size: 1rem;">👤 Charlie Dev</h2>
|
|
22
|
+
<p style="font-size: 0.85rem; color: #888;">ID: 42 · Architect</p>
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
`
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { currentRoute, routeParams, computed } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Users Layout (JS) — persists across all /users/* child routes.
|
|
5
|
+
*/
|
|
6
|
+
export const UsersLayout = defineComponent({
|
|
7
|
+
setup() {
|
|
8
|
+
const path = currentRoute
|
|
9
|
+
const params = routeParams
|
|
10
|
+
const paramsJson = computed(() => JSON.stringify(params()))
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
path,
|
|
14
|
+
paramsJson,
|
|
15
|
+
template: `
|
|
16
|
+
<div style="display: flex; gap: 2rem; min-height: 60vh;">
|
|
17
|
+
<aside style="
|
|
18
|
+
width: 220px;
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
background: #13131d;
|
|
21
|
+
border: 1px solid #2a2a3a;
|
|
22
|
+
border-radius: 12px;
|
|
23
|
+
padding: 1.25rem;
|
|
24
|
+
">
|
|
25
|
+
<h3 style="color: #8b8bff; font-size: 0.85rem; text-transform: uppercase; margin-bottom: 1rem;">👥 Users</h3>
|
|
26
|
+
<nav style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
27
|
+
<a href="/users" f-link class="cmp-btn" style="text-decoration: none; text-align: left;">📋 All Users</a>
|
|
28
|
+
<a href="/users/1" f-link class="cmp-btn" style="text-decoration: none; text-align: left;">👤 User #1</a>
|
|
29
|
+
<a href="/users/2" f-link class="cmp-btn" style="text-decoration: none; text-align: left;">👤 User #2</a>
|
|
30
|
+
</nav>
|
|
31
|
+
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #2a2a3a; font-size: 0.75rem; color: #555;">
|
|
32
|
+
<p>Route: <span style="color: #8b8bff;">{{ path }}</span></p>
|
|
33
|
+
<p>Params: <span style="color: #34d399;">{{ paramsJson }}</span></p>
|
|
34
|
+
</div>
|
|
35
|
+
</aside>
|
|
36
|
+
<main style="flex: 1;">
|
|
37
|
+
<div data-router-view></div>
|
|
38
|
+
</main>
|
|
39
|
+
</div>
|
|
40
|
+
`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { currentRoute, routeParams, computed, defineComponent } from '../../framework/index'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Users Layout — persists across all /users/* child routes.
|
|
5
|
+
* Contains a sidebar nav and a <div data-router-view> where children render.
|
|
6
|
+
*/
|
|
7
|
+
export const UsersLayout = defineComponent({
|
|
8
|
+
setup() {
|
|
9
|
+
const path = currentRoute
|
|
10
|
+
const params = routeParams
|
|
11
|
+
const paramsJson = computed(() => JSON.stringify(params()))
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
path,
|
|
15
|
+
paramsJson,
|
|
16
|
+
template: `
|
|
17
|
+
<div style="display: flex; gap: 2rem; min-height: 60vh;">
|
|
18
|
+
<!-- Sidebar (persists across child navigations) -->
|
|
19
|
+
<aside style="
|
|
20
|
+
width: 220px;
|
|
21
|
+
flex-shrink: 0;
|
|
22
|
+
background: #13131d;
|
|
23
|
+
border: 1px solid #2a2a3a;
|
|
24
|
+
border-radius: 12px;
|
|
25
|
+
padding: 1.25rem;
|
|
26
|
+
">
|
|
27
|
+
<h3 style="
|
|
28
|
+
color: #8b8bff;
|
|
29
|
+
font-size: 0.85rem;
|
|
30
|
+
text-transform: uppercase;
|
|
31
|
+
letter-spacing: 0.1em;
|
|
32
|
+
margin-bottom: 1rem;
|
|
33
|
+
">👥 Users</h3>
|
|
34
|
+
|
|
35
|
+
<nav style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
36
|
+
<a href="/users" f-link class="cmp-btn" style="
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
text-align: left;
|
|
39
|
+
font-size: 0.9rem;
|
|
40
|
+
">📋 All Users</a>
|
|
41
|
+
<a href="/users/1" f-link class="cmp-btn" style="
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
text-align: left;
|
|
44
|
+
font-size: 0.9rem;
|
|
45
|
+
">👤 User #1</a>
|
|
46
|
+
<a href="/users/2" f-link class="cmp-btn" style="
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
text-align: left;
|
|
49
|
+
font-size: 0.9rem;
|
|
50
|
+
">👤 User #2</a>
|
|
51
|
+
<a href="/users/42" f-link class="cmp-btn" style="
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
text-align: left;
|
|
54
|
+
font-size: 0.9rem;
|
|
55
|
+
">👤 User #42</a>
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
58
|
+
<div style="
|
|
59
|
+
margin-top: 1.5rem;
|
|
60
|
+
padding-top: 1rem;
|
|
61
|
+
border-top: 1px solid #2a2a3a;
|
|
62
|
+
font-size: 0.75rem;
|
|
63
|
+
color: #555;
|
|
64
|
+
">
|
|
65
|
+
<p style="margin-bottom: 4px;">Route: <span style="color: #8b8bff;">{{ path }}</span></p>
|
|
66
|
+
<p>Params: <span style="color: #34d399;">{{ paramsJson }}</span></p>
|
|
67
|
+
</div>
|
|
68
|
+
</aside>
|
|
69
|
+
|
|
70
|
+
<!-- Child route renders here -->
|
|
71
|
+
<main style="flex: 1; min-width: 0;">
|
|
72
|
+
<div data-router-view></div>
|
|
73
|
+
</main>
|
|
74
|
+
</div>
|
|
75
|
+
`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { inject } from '../../framework/index.js'
|
|
2
|
+
import { LoggerService } from '../services.js'
|
|
2
3
|
import { GrandchildComponent } from './GrandchildComponent.js'
|
|
3
4
|
|
|
4
5
|
// ChildComponent - injects values from parent (ParentComponent)
|
|
@@ -9,8 +10,11 @@ export const ChildComponent = defineComponent({
|
|
|
9
10
|
const count = inject('count')
|
|
10
11
|
const message = inject('message')
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const logger = inject(LoggerService)
|
|
14
|
+
logger.log('ChildComponent initialized!')
|
|
15
|
+
|
|
16
|
+
// These are NOT provided by parent, so we use optional: true to avoid throwing NullInjectorError
|
|
17
|
+
const nonExistent = inject('nonExistent', { optional: true })
|
|
14
18
|
|
|
15
19
|
function updateMessage() {
|
|
16
20
|
message('Message updated by Child!')
|
|
@@ -20,6 +24,7 @@ export const ChildComponent = defineComponent({
|
|
|
20
24
|
count,
|
|
21
25
|
message,
|
|
22
26
|
nonExistent,
|
|
27
|
+
logger,
|
|
23
28
|
updateMessage,
|
|
24
29
|
template: `
|
|
25
30
|
<div style="padding: 15px; border: 2px solid #2196F3; border-radius: 8px; margin-top: 15px; background: #1a1a1a; color: #fff;">
|
|
@@ -29,6 +34,7 @@ export const ChildComponent = defineComponent({
|
|
|
29
34
|
<li><strong>count:</strong> {{ count }}</li>
|
|
30
35
|
<li><strong>message:</strong> {{ message }}</li>
|
|
31
36
|
<li><strong>nonExistent:</strong> {{ nonExistent || 'undefined' }}</li>
|
|
37
|
+
<li><strong>Logger ID:</strong> {{ logger.id }} <em>(Singleton!)</em></li>
|
|
32
38
|
</ul>
|
|
33
39
|
<button @click="updateMessage" style="margin-top: 10px; padding: 6px 12px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
|
34
40
|
Update Message
|