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.
Files changed (129) hide show
  1. package/README.md +145 -123
  2. package/app/benchmark/benchmark.js +633 -0
  3. package/app/benchmark/index.html +102 -0
  4. package/app/benchmark/styles.css +249 -0
  5. package/app/components/Counter.js +17 -7
  6. package/app/components/CounterSection.js +3 -4
  7. package/app/main.js +50 -62
  8. package/app/main.ts +78 -63
  9. package/app/pages/404.js +16 -0
  10. package/app/pages/[...all].js +35 -0
  11. package/app/pages/[...all].ts +35 -0
  12. package/app/pages/_layout.js +53 -0
  13. package/app/pages/_layout.ts +26 -0
  14. package/app/pages/about.js +23 -0
  15. package/app/pages/about.ts +29 -0
  16. package/app/pages/async-work.js +65 -0
  17. package/app/pages/composables.js +158 -0
  18. package/app/pages/composables.ts +158 -0
  19. package/app/pages/dynamic.js +40 -0
  20. package/app/pages/dynamic.ts +40 -0
  21. package/app/pages/index.js +137 -0
  22. package/app/pages/index.ts +87 -0
  23. package/app/pages/login.ts +21 -0
  24. package/app/pages/objects/[id].js +56 -0
  25. package/app/pages/objects/[id].ts +56 -0
  26. package/app/pages/posts/[category]/[slug].js +56 -0
  27. package/app/pages/posts/[category]/[slug].ts +56 -0
  28. package/app/pages/router-example.js +79 -0
  29. package/app/pages/router-example.ts +56 -0
  30. package/app/pages/scroll-demo.ts +102 -0
  31. package/app/pages/test-for.js +65 -0
  32. package/app/pages/test-for.ts +80 -0
  33. package/app/pages/users/[id].js +24 -0
  34. package/app/pages/users/[id].ts +46 -0
  35. package/app/pages/users/index.js +22 -0
  36. package/app/pages/users/index.ts +29 -0
  37. package/app/pages/users.js +43 -0
  38. package/app/pages/users.ts +78 -0
  39. package/app/providers/ChildComponent.js +8 -2
  40. package/app/providers/ChildComponent.ts +47 -47
  41. package/app/providers/GrandchildComponent.js +19 -0
  42. package/app/providers/ParentComponent.js +5 -1
  43. package/app/routes/About.js +1 -1
  44. package/app/routes/About.ts +1 -1
  45. package/app/routes/AuthRoutes.ts +41 -0
  46. package/app/routes/Home.js +9 -0
  47. package/app/routes/NotFound.js +16 -0
  48. package/app/routes/NotFound.ts +16 -0
  49. package/app/routes/RouterExample.js +78 -0
  50. package/app/routes/RouterExample.ts +56 -0
  51. package/app/routes/TestResource.js +47 -0
  52. package/app/routes/WildcardDemo.js +35 -0
  53. package/app/routes/WildcardDemo.ts +35 -0
  54. package/app/services.js +15 -0
  55. package/auto-imports.d.ts +9 -0
  56. package/bin/create-fusee-linux +0 -0
  57. package/bin/create-fusee-mac-arm64 +0 -0
  58. package/bin/create-fusee-mac-intel +0 -0
  59. package/bin/create-fusee-win.exe +0 -0
  60. package/bin/create-fusee.exe +0 -0
  61. package/bin/index.js +38 -0
  62. package/create-fusee-go/cmd/generate.go +72 -0
  63. package/create-fusee-go/cmd/init.go +38 -0
  64. package/create-fusee-go/cmd/root.go +121 -0
  65. package/create-fusee-go/go.mod +10 -0
  66. package/create-fusee-go/go.sum +10 -0
  67. package/create-fusee-go/internal/assets/assets.go +78 -0
  68. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  69. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  70. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  71. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  72. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  73. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  74. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  75. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  76. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  77. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  78. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  79. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  80. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  83. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  84. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  85. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  86. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  87. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  88. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  89. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  90. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  91. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  92. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  93. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  94. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  95. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  96. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  97. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  98. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  99. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  100. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  101. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  102. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  103. package/create-fusee-go/main.go +7 -0
  104. package/framework/auto-import-preset.js +13 -9
  105. package/framework/core/compiler.js +252 -249
  106. package/framework/core/component.js +279 -260
  107. package/framework/core/di.js +187 -0
  108. package/framework/core/directives.js +819 -786
  109. package/framework/core/evaluator.js +153 -70
  110. package/framework/core/event-delegation.js +33 -14
  111. package/framework/core/reconcile.js +151 -0
  112. package/framework/core/signal.js +679 -403
  113. package/framework/core/store.js +322 -81
  114. package/framework/index.js +12 -11
  115. package/framework/router/file-router.js +177 -0
  116. package/framework/router/router.js +554 -80
  117. package/framework/types/component.d.ts +89 -88
  118. package/framework/types/di.d.ts +43 -0
  119. package/framework/types/event-delegation.d.ts +0 -58
  120. package/framework/types/index.d.ts +32 -22
  121. package/framework/types/reconcile.d.ts +7 -0
  122. package/framework/types/router.d.ts +42 -15
  123. package/framework/types/signal.d.ts +147 -78
  124. package/framework/types/store.d.ts +66 -8
  125. package/index.html +638 -626
  126. package/package.json +5 -3
  127. package/tsconfig.json +1 -0
  128. package/vite.config.js +7 -1
  129. /package/{framework β†’ create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
@@ -0,0 +1,35 @@
1
+ import { currentRoute, navigate } from '../../framework/index.js'
2
+
3
+ export const WildcardDemo = defineComponent({
4
+ setup() {
5
+ const path = currentRoute
6
+
7
+ return {
8
+ path,
9
+ navigate,
10
+ template: `
11
+ <div class="page">
12
+ <h1 style="color: #f59e0b;">🎯 Wildcard Match!</h1>
13
+ <p>This route matched via wildcard <code>path: '*'</code></p>
14
+
15
+ <div class="demo-card">
16
+ <h2>Current Path</h2>
17
+ <code style="color: #f59e0b; font-size: 1.5rem;">{{ path }}</code>
18
+ </div>
19
+
20
+ <div class="demo-card">
21
+ <h2>Test Wildcard</h2>
22
+ <p>Any path works here:</p>
23
+ <div style="display: flex; gap: 10px; flex-wrap: wrap;">
24
+ <button @click="navigate('/random-123')" class="cmp-btn">/random-123</button>
25
+ <button @click="navigate('/test/abc')" class="cmp-btn">/test/abc</button>
26
+ <button @click="navigate('/deep/nested/path')" class="cmp-btn">/deep/nested/path</button>
27
+ </div>
28
+ </div>
29
+
30
+ <button @click="navigate('/')" class="primary-btn">Back Home</button>
31
+ </div>
32
+ `
33
+ }
34
+ }
35
+ })
@@ -0,0 +1,35 @@
1
+ import { currentRoute, navigate } from '../../framework/index.js'
2
+
3
+ export const WildcardDemo = defineComponent({
4
+ setup() {
5
+ const path = currentRoute
6
+
7
+ return {
8
+ path,
9
+ navigate,
10
+ template: `
11
+ <div class="page">
12
+ <h1 style="color: #f59e0b;">🎯 Wildcard Match!</h1>
13
+ <p>This route matched via wildcard <code>path: '*'</code></p>
14
+
15
+ <div class="demo-card">
16
+ <h2>Current Path</h2>
17
+ <code style="color: #f59e0b; font-size: 1.5rem;">{{ path }}</code>
18
+ </div>
19
+
20
+ <div class="demo-card">
21
+ <h2>Test Wildcard</h2>
22
+ <p>Any path works here:</p>
23
+ <div style="display: flex; gap: 10px; flex-wrap: wrap;">
24
+ <button @click="navigate('/random-123')" class="cmp-btn">/random-123</button>
25
+ <button @click="navigate('/test/abc')" class="cmp-btn">/test/abc</button>
26
+ <button @click="navigate('/deep/nested/path')" class="cmp-btn">/deep/nested/path</button>
27
+ </div>
28
+ </div>
29
+
30
+ <button @click="navigate('/')" class="primary-btn">Back Home</button>
31
+ </div>
32
+ `
33
+ }
34
+ }
35
+ })
@@ -0,0 +1,53 @@
1
+ import { defineComponent } from '../../framework/index.js'
2
+
3
+
4
+
5
+ export const RootLayout = defineComponent({
6
+
7
+ setup() {
8
+
9
+ return {
10
+
11
+ template: `
12
+
13
+ <div class="root-layout">
14
+
15
+ <nav>
16
+
17
+ <a href="/" f-link>Home</a>
18
+
19
+ <a href="/about" f-link>About</a>
20
+
21
+ <a href="/test-for" f-link>Loops Test</a>
22
+
23
+ <a href="/dynamic" f-link style="color: #ec4899;">Dynamic Components</a>
24
+
25
+ <a href="/composables" f-link style="color: #34d399;">Composables</a>
26
+
27
+ <a href="/users" f-link style="color: #f59e0b;">πŸ‘₯ Users (Nested)</a>
28
+
29
+ <a href="/router-example" f-link style="color: #8b8bff;">πŸ”— Router Example</a>
30
+ <a href="/async-work" f-link style="color: #34d399;">⚑ Async Work</a>
31
+
32
+ </nav>
33
+
34
+
35
+
36
+ <main class="content-wrap">
37
+
38
+ <!-- This is where all pages will be rendered -->
39
+
40
+ <div data-router-view></div>
41
+
42
+ </main>
43
+
44
+ </div>
45
+
46
+ `
47
+
48
+ }
49
+
50
+ }
51
+
52
+ })
53
+
@@ -0,0 +1,26 @@
1
+ import { defineComponent } from '../../framework/index.js'
2
+
3
+ export const RootLayout = defineComponent({
4
+ setup() {
5
+ return {
6
+ template: `
7
+ <div class="root-layout">
8
+ <nav>
9
+ <a href="/" f-link>Home</a>
10
+ <a href="/about" f-link>About</a>
11
+ <a href="/test-for" f-link>Loops Test</a>
12
+ <a href="/dynamic" f-link style="color: #ec4899;">Dynamic Components</a>
13
+ <a href="/composables" f-link style="color: #34d399;">Composables</a>
14
+ <a href="/users" f-link style="color: #f59e0b;">πŸ‘₯ Users (Nested)</a>
15
+ <a href="/router-example" f-link style="color: #8b8bff;">πŸ”— Router Example</a>
16
+ </nav>
17
+
18
+ <main class="content-wrap">
19
+ <!-- This is where all pages will be rendered -->
20
+ <div data-router-view></div>
21
+ </main>
22
+ </div>
23
+ `
24
+ }
25
+ }
26
+ })
@@ -0,0 +1,23 @@
1
+ export const About = defineComponent({
2
+ props: [],
3
+ setup() {
4
+ const loaded = signal(false)
5
+ inspect(loaded)
6
+
7
+ onMount(() => {
8
+ setTimeout(() => loaded(true), 300)
9
+ })
10
+
11
+ return {
12
+ loaded,
13
+ template: `
14
+ <div class="page">
15
+ <h1>About</h1>
16
+ <p>Framework custom β€” SPA prototipe</p>
17
+ <p><strong>Features:</strong> signals, defineComponent, History API router, lifecycle hooks.</p>
18
+ <p class="status">{{ loaded }}</p>
19
+ </div>
20
+ `
21
+ }
22
+ }
23
+ })
@@ -0,0 +1,29 @@
1
+ import type { Signal, EmptyProps } from '../../framework/index.js'
2
+
3
+ type AboutResult = {
4
+ loaded: Signal<boolean>
5
+ template: string
6
+ }
7
+
8
+ export const About = defineComponent({
9
+ props: [],
10
+ setup(): AboutResult {
11
+ const loaded = signal<boolean>(false)
12
+
13
+ onMount(() => {
14
+ setTimeout(() => loaded(true), 300)
15
+ })
16
+
17
+ return {
18
+ loaded,
19
+ template: `
20
+ <div class="page">
21
+ <h1>About</h1>
22
+ <p>Framework custom β€” prototip SPA.</p>
23
+ <p><strong>Features:</strong> signals, defineComponent, History API router, lifecycle hooks.</p>
24
+ <p class="status">{{ loaded }}</p>
25
+ </div>
26
+ `
27
+ }
28
+ }
29
+ })
@@ -0,0 +1,65 @@
1
+ import { defineComponent, signal, resource } from '../../framework/index.js'
2
+
3
+ export default defineComponent({
4
+ setup() {
5
+ const userId = signal(1)
6
+
7
+ const [user, { refetch }] = resource(userId, async (id) => {
8
+ const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
9
+ if (!response.ok) throw new Error('User not found')
10
+ return response.json()
11
+ })
12
+
13
+ return {
14
+ userId,
15
+ user,
16
+ refetch,
17
+ next: () => userId(userId() + 1),
18
+ prev: () => userId() > 1 && userId(userId() - 1),
19
+ template: `
20
+ <div class="page">
21
+ <h1>⚑ Async Work with Resources</h1>
22
+ <p>This page demonstrates the new <code>resource</code> primitive, inspired by SolidJS. It handles async data fetching with reactive loading and error states.</p>
23
+
24
+ <div class="demo-section">
25
+ <div class="flex gap-2 mb-6">
26
+ <button @click="prev" class="cmp-btn minus" :disabled="userId() <= 1">← Previous</button>
27
+ <button @click="next" class="cmp-btn plus">Next User β†’</button>
28
+ <button @click="refetch" class="cmp-btn reset">πŸ”„ Refetch</button>
29
+ <span class="ml-4 flex items-center">Current ID: <strong>{{ userId }}</strong></span>
30
+ </div>
31
+
32
+ <div f-if="user.loading()">
33
+ <div class="action-btn btn-loading">Fetching data from API...</div>
34
+ </div>
35
+
36
+ <div f-if="user.error()">
37
+ <div class="action-btn btn-error">
38
+ <strong>Error:</strong> {{ user.error().message }}
39
+ </div>
40
+ </div>
41
+
42
+ <div f-if="user() && !user.loading()">
43
+ <div class="theme-box theme-dark">
44
+ <h2 class="text-xl font-bold mb-2">{{ user().name }}</h2>
45
+ <p><strong>Username:</strong> @{{ user().username }}</p>
46
+ <p><strong>Email:</strong> {{ user().email }}</p>
47
+ <p><strong>Website:</strong> <a :href="'https://' + user().website" target="_blank" style="color: #8b8bff">{{ user().website }}</a></p>
48
+ <p><strong>Company:</strong> {{ user().company.name }}</p>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="info-section">
54
+ <h2>How it works:</h2>
55
+ <ul>
56
+ <li><strong>Reactive Source:</strong> The fetcher automatically reruns when <code>userId</code> changes.</li>
57
+ <li><strong>State Tracking:</strong> <code>user.loading()</code> and <code>user.error()</code> are signals updated by the framework.</li>
58
+ <li><strong>Batching:</strong> Loading and data states are updated atomically to prevent UI flickers.</li>
59
+ </ul>
60
+ </div>
61
+ </div>
62
+ `
63
+ }
64
+ }
65
+ })
@@ -0,0 +1,158 @@
1
+ import { counterLogic, mouseTracker } from '../composables/demo.js'
2
+ import { useFetch } from '../composables/useFetch.js'
3
+ import { useLocalStorage } from '../composables/useLocalStorage.js'
4
+
5
+ export const TestComposables = defineComponent({
6
+ setup() {
7
+ // ── 1. counterLogic β€” pure signal composable, no context needed ──────
8
+ const counter = counterLogic(0, 1)
9
+ const bigCounter = counterLogic(100, 10)
10
+
11
+ // ── 2. mouseTracker β€” lifecycle composable (needs setup context) ─────
12
+ const mouse = mouseTracker()
13
+
14
+ // ── 3. useLocalStorage β€” persists across page reloads ────────────────
15
+ const theme = useLocalStorage('demo-theme', 'dark')
16
+ const nickname = useLocalStorage('demo-nickname', '')
17
+
18
+ const toggleTheme = () => theme(theme() === 'dark' ? 'light' : 'dark')
19
+
20
+ // ── 4. useFetch β€” reactive data fetching ─────────────────────────────
21
+ const { data: posts, loading, error } = useFetch(
22
+ 'https://jsonplaceholder.typicode.com/posts?_limit=3'
23
+ )
24
+ // ── 5. Custom Directives Test ────────────────────────────────────────
25
+ const hColor = signal('#8b8bff33')
26
+ const colors = ['#8b8bff33', '#10b98133', '#f59e0b33', '#ef444433']
27
+ let colorIdx = 0
28
+ const nextColor = () => hColor(colors[++colorIdx % colors.length])
29
+
30
+ return {
31
+ hColor,
32
+ nextColor,
33
+
34
+ // counter
35
+ count: counter.count,
36
+ doubled: counter.doubled,
37
+ isEven: counter.isEven,
38
+ increment: counter.increment,
39
+ decrement: counter.decrement,
40
+ reset: counter.reset,
41
+
42
+ // big counter
43
+ bigCount: bigCounter.count,
44
+ bigIncrement: bigCounter.increment,
45
+ bigDecrement: bigCounter.decrement,
46
+
47
+ // mouse
48
+ mouseX: mouse.x,
49
+ mouseY: mouse.y,
50
+
51
+ // localStorage
52
+ theme,
53
+ nickname,
54
+ toggleTheme,
55
+
56
+ // fetch
57
+ posts,
58
+ loading,
59
+ error,
60
+
61
+ template: `
62
+ <div class="page">
63
+ <h1>🧩 Composables Demo</h1>
64
+ <p style="color:#888; margin-bottom: 2rem;">
65
+ Each section below is powered by a <code style="color:#ff3333">defineComposable()</code> function.
66
+ No naming rules β€” just reactive logic.
67
+ </p>
68
+
69
+ <!-- ── 1. counterLogic ─────────────────────────────────────── -->
70
+ <section class="demo-card">
71
+ <h2>πŸ“¦ counterLogic(0, step=1)</h2>
72
+ <p class="demo-desc">Pure signal composable β€” works without setup() context.</p>
73
+ <div class="counter-display">
74
+ <span class="big-num" :class="isEven ? 'even' : 'odd'">{{ count }}</span>
75
+ <span class="derived-label">Γ— 2 = {{ doubled }}</span>
76
+ </div>
77
+ <div class="btn-row">
78
+ <button @click="decrement" class="cmp-btn minus">βˆ’</button>
79
+ <button @click="reset" class="cmp-btn reset">β†Ί</button>
80
+ <button @click="increment" class="cmp-btn plus">+</button>
81
+ </div>
82
+ <p class="badge" :class="isEven ? 'badge-blue' : 'badge-red'">{{ isEven ? 'Even' : 'Odd' }}</p>
83
+ </section>
84
+
85
+ <!-- ── 1b. Second independent counter (step=10) ───────────── -->
86
+ <section class="demo-card">
87
+ <h2>πŸ“¦ counterLogic(100, step=10)</h2>
88
+ <p class="demo-desc">Same composable, different instance. Fully isolated state.</p>
89
+ <div class="counter-display">
90
+ <span class="big-num">{{ bigCount }}</span>
91
+ </div>
92
+ <div class="btn-row">
93
+ <button @click="bigDecrement" class="cmp-btn minus">βˆ’10</button>
94
+ <button @click="bigIncrement" class="cmp-btn plus">+10</button>
95
+ </div>
96
+ </section>
97
+
98
+ <!-- ── 2. mouseTracker ────────────────────────────────────── -->
99
+ <section class="demo-card">
100
+ <h2>πŸ–±οΈ mouseTracker()</h2>
101
+ <p class="demo-desc">Lifecycle composable β€” registers window listener on mount, cleans up on unmount.</p>
102
+ <div class="mouse-display">
103
+ <span>X: <strong>{{ mouseX }}</strong></span>
104
+ <span>Y: <strong>{{ mouseY }}</strong></span>
105
+ </div>
106
+ </section>
107
+
108
+ <!-- ── 3. useLocalStorage ─────────────────────────────────── -->
109
+ <section class="demo-card">
110
+ <h2>πŸ’Ύ useLocalStorage()</h2>
111
+ <p class="demo-desc">State persists across page reloads. Try changing the nickname, then refresh.</p>
112
+ <div style="margin-bottom: 12px;">
113
+ <input f-model="nickname"
114
+ class="cmp-input"
115
+ placeholder="Your nickname…" />
116
+ <p style="margin-top: 8px; color: #888; font-size: 0.85rem;">
117
+ Stored: <strong style="color:#ff3333">{{ nickname || '(empty)' }}</strong>
118
+ </p>
119
+ </div>
120
+ <button @click="toggleTheme" class="cmp-btn reset">
121
+ Theme: {{ theme }}
122
+ </button>
123
+ </section>
124
+
125
+ <!-- ── 4. useFetch ────────────────────────────────────────── -->
126
+ <section class="demo-card">
127
+ <h2>🌐 useFetch()</h2>
128
+ <p class="demo-desc">Fetches from JSONPlaceholder. Loading state handled reactively.</p>
129
+ <div f-if="loading">
130
+ <p style="color: #888;">⏳ Loading posts…</p>
131
+ </div>
132
+ <div f-elif="error">
133
+ <p style="color: #ff6b6b;">❌ {{ error }}</p>
134
+ </div>
135
+ <div f-else>
136
+ <div f-for="post in posts" style="margin-bottom: 10px; padding: 10px; background:#1a1a24; border-radius:6px; border-left: 3px solid #ff3333;">
137
+ <strong style="color:#ff9999">{{ post.title }}</strong>
138
+ <p style="margin-top: 4px; font-size: 0.8rem; color: #666;">{{ post.body }}</p>
139
+ </div>
140
+ </div>
141
+ </section>
142
+
143
+ <!-- ── 6. Custom Directives ────────────────────────────────────── -->
144
+ <section class="demo-card">
145
+ <h2>πŸ› οΈ Custom Directives</h2>
146
+ <p class="demo-desc">Testing <code>f-focus</code> and <code>f-highlight</code>.</p>
147
+
148
+ <div f-highlight="hColor" style="padding: 15px; border-radius: 8px; margin-bottom: 10px;">
149
+ <input f-focus class="cmp-input" placeholder="I have f-focus!" />
150
+ </div>
151
+
152
+ <button @click="nextColor" class="cmp-btn plus">Change Highlight Color</button>
153
+ </section>
154
+ </div>
155
+ `
156
+ }
157
+ }
158
+ })
@@ -0,0 +1,158 @@
1
+ import { defineComponent } from '../../framework/index.js'
2
+ import { counterLogic, mouseTracker } from '../composables/demo.js'
3
+ import { useFetch } from '../composables/useFetch.js'
4
+ import { useLocalStorage } from '../composables/useLocalStorage.js'
5
+
6
+ export const TestComposables = defineComponent({
7
+ setup() {
8
+ // ── 1. counterLogic β€” pure signal composable, no context needed ──────
9
+ const counter = counterLogic(0, 1)
10
+ const bigCounter = counterLogic(100, 10)
11
+
12
+ // ── 2. mouseTracker β€” lifecycle composable (needs setup context) ─────
13
+ const mouse = mouseTracker()
14
+
15
+ // ── 3. useLocalStorage β€” persists across page reloads ────────────────
16
+ const theme = useLocalStorage<string>('demo-theme', 'dark')
17
+ const nickname = useLocalStorage<string>('demo-nickname', '')
18
+
19
+ const toggleTheme = () => theme(theme() === 'dark' ? 'light' : 'dark')
20
+
21
+ // ── 4. useFetch β€” reactive data fetching ─────────────────────────────
22
+ const { data: posts, loading, error } = useFetch(
23
+ 'https://jsonplaceholder.typicode.com/posts?_limit=3'
24
+ )
25
+ // ── 5. Custom Directives Test ────────────────────────────────────────
26
+ const hColor = signal('#8b8bff33')
27
+ const colors = ['#8b8bff33', '#10b98133', '#f59e0b33', '#ef444433']
28
+ let colorIdx = 0
29
+ const nextColor = () => hColor(colors[++colorIdx % colors.length])
30
+
31
+ return {
32
+ hColor,
33
+ nextColor,
34
+
35
+ // counter
36
+ count: counter.count,
37
+ doubled: counter.doubled,
38
+ isEven: counter.isEven,
39
+ increment: counter.increment,
40
+ decrement: counter.decrement,
41
+ reset: counter.reset,
42
+
43
+ // big counter
44
+ bigCount: bigCounter.count,
45
+ bigIncrement: bigCounter.increment,
46
+ bigDecrement: bigCounter.decrement,
47
+
48
+ // mouse
49
+ mouseX: mouse.x,
50
+ mouseY: mouse.y,
51
+
52
+ // localStorage
53
+ theme,
54
+ nickname,
55
+ toggleTheme,
56
+
57
+ // fetch
58
+ posts,
59
+ loading,
60
+ error,
61
+
62
+ template: `
63
+ <div class="page">
64
+ <h1>🧩 Composables Demo</h1>
65
+ <p style="color:#888; margin-bottom: 2rem;">
66
+ Each section below is powered by a <code style="color:#ff3333">defineComposable()</code> function.
67
+ No naming rules β€” just reactive logic.
68
+ </p>
69
+
70
+ <!-- ── 1. counterLogic ─────────────────────────────────────── -->
71
+ <section class="demo-card">
72
+ <h2>πŸ“¦ counterLogic(0, step=1)</h2>
73
+ <p class="demo-desc">Pure signal composable β€” works without setup() context.</p>
74
+ <div class="counter-display">
75
+ <span class="big-num" :class="isEven ? 'even' : 'odd'">{{ count }}</span>
76
+ <span class="derived-label">Γ— 2 = {{ doubled }}</span>
77
+ </div>
78
+ <div class="btn-row">
79
+ <button @click="decrement" class="cmp-btn minus">βˆ’</button>
80
+ <button @click="reset" class="cmp-btn reset">β†Ί</button>
81
+ <button @click="increment" class="cmp-btn plus">+</button>
82
+ </div>
83
+ <p class="badge" :class="isEven ? 'badge-blue' : 'badge-red'">{{ isEven ? 'Even' : 'Odd' }}</p>
84
+ </section>
85
+
86
+ <!-- ── 1b. Second independent counter (step=10) ───────────── -->
87
+ <section class="demo-card">
88
+ <h2>πŸ“¦ counterLogic(100, step=10)</h2>
89
+ <p class="demo-desc">Same composable, different instance. Fully isolated state.</p>
90
+ <div class="counter-display">
91
+ <span class="big-num">{{ bigCount }}</span>
92
+ </div>
93
+ <div class="btn-row">
94
+ <button @click="bigDecrement" class="cmp-btn minus">βˆ’10</button>
95
+ <button @click="bigIncrement" class="cmp-btn plus">+10</button>
96
+ </div>
97
+ </section>
98
+
99
+ <!-- ── 2. mouseTracker ────────────────────────────────────── -->
100
+ <section class="demo-card">
101
+ <h2>πŸ–±οΈ mouseTracker()</h2>
102
+ <p class="demo-desc">Lifecycle composable β€” registers window listener on mount, cleans up on unmount.</p>
103
+ <div class="mouse-display">
104
+ <span>X: <strong>{{ mouseX }}</strong></span>
105
+ <span>Y: <strong>{{ mouseY }}</strong></span>
106
+ </div>
107
+ </section>
108
+
109
+ <!-- ── 3. useLocalStorage ─────────────────────────────────── -->
110
+ <section class="demo-card">
111
+ <h2>πŸ’Ύ useLocalStorage()</h2>
112
+ <p class="demo-desc">State persists across page reloads. Try changing the nickname, then refresh.</p>
113
+ <div style="margin-bottom: 12px;">
114
+ <input f-model="nickname"
115
+ class="cmp-input"
116
+ placeholder="Your nickname…" />
117
+ <p style="margin-top: 8px; color: #888; font-size: 0.85rem;">
118
+ Stored: <strong style="color:#ff3333">{{ nickname || '(empty)' }}</strong>
119
+ </p>
120
+ </div>
121
+ <button @click="toggleTheme" class="cmp-btn reset">
122
+ Theme: {{ theme }}
123
+ </button>
124
+ </section>
125
+
126
+ <!-- ── 4. useFetch ────────────────────────────────────────── -->
127
+ <section class="demo-card">
128
+ <h2>🌐 useFetch()</h2>
129
+ <p class="demo-desc">Fetches from JSONPlaceholder. Loading state handled reactively.</p>
130
+ <div f-if="loading">
131
+ <p style="color: #888;">⏳ Loading posts…</p>
132
+ </div>
133
+ <div f-elif="error">
134
+ <p style="color: #ff6b6b;">❌ {{ error }}</p>
135
+ </div>
136
+ <div f-else>
137
+ <div f-for="post in posts" style="margin-bottom: 10px; padding: 10px; background:#1a1a24; border-radius:6px; border-left: 3px solid #ff3333;">
138
+ <strong style="color:#ff9999">{{ post.title }}</strong>
139
+ <p style="margin-top: 4px; font-size: 0.8rem; color: #666;">{{ post.body }}</p>
140
+ </div>
141
+ </div>
142
+ </section>
143
+ <!-- ── 6. Custom Directives ────────────────────────────────────── -->
144
+ <section class="demo-card">
145
+ <h2>πŸ› οΈ Custom Directives</h2>
146
+ <p class="demo-desc">Testing <code>f-focus</code> and <code>f-highlight</code>.</p>
147
+
148
+ <div f-highlight="hColor" style="padding: 15px; border-radius: 8px; margin-bottom: 10px;">
149
+ <input f-focus class="cmp-input" placeholder="I have f-focus!" />
150
+ </div>
151
+
152
+ <button @click="nextColor" class="cmp-btn plus">Change Highlight Color</button>
153
+ </section>
154
+ </div>
155
+ `
156
+ }
157
+ }
158
+ })
@@ -0,0 +1,40 @@
1
+ import { DynamicA } from '../components/DynamicA.js'
2
+ import { DynamicB } from '../components/DynamicB.js'
3
+
4
+ export const TestDynamic = defineComponent({
5
+ components: { DynamicA, DynamicB },
6
+ setup() {
7
+ const currentType = signal('DynamicA')
8
+ const sharedTitle = signal('Test Dinamic active')
9
+
10
+ return {
11
+ currentType,
12
+ sharedTitle,
13
+ toggle: () => {
14
+ currentType(currentType() === 'DynamicA' ? 'DynamicB' : 'DynamicA')
15
+ },
16
+ template: `
17
+ <div class="page">
18
+ <h1>Dynamic Components System (Vue-style)</h1>
19
+ <p>Using the custom <code>f-is</code> directive.</p>
20
+
21
+ <div style="margin: 20px 0; display: flex; gap: 10px; align-items: center;">
22
+ <button @click="toggle" class="primary-btn">Toggle Component</button>
23
+ <span>Active: <strong>{{ currentType }}</strong></span>
24
+ </div>
25
+
26
+ <input f-model="sharedTitle" style="margin-bottom: 20px; padding: 8px; border-radius: 4px; border: 1px solid #444; background: #222; color: white; width: 100%;">
27
+
28
+ <hr style="border: 0; border-top: 1px solid #444; margin: 30px 0;">
29
+
30
+ <div f-is="currentType" f-keepAlive :title="sharedTitle"></div>
31
+
32
+ <p style="margin-top: 40px; color: #888; font-size: 0.9em;">
33
+ Note: The component above changes reactively when you click the button.
34
+ The <code>:title</code> prop is automatically passed to the active component.
35
+ </p>
36
+ </div>
37
+ `
38
+ }
39
+ }
40
+ })