fusee-framework 1.6.0 β†’ 1.7.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 (112) hide show
  1. package/README.md +29 -13
  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 +29 -62
  8. package/app/main.ts +72 -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 +26 -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/composables.js +158 -0
  17. package/app/pages/composables.ts +158 -0
  18. package/app/pages/dynamic.js +40 -0
  19. package/app/pages/dynamic.ts +40 -0
  20. package/app/pages/index.js +117 -0
  21. package/app/pages/index.ts +78 -0
  22. package/app/pages/login.ts +21 -0
  23. package/app/pages/objects/[id].js +56 -0
  24. package/app/pages/objects/[id].ts +56 -0
  25. package/app/pages/posts/[category]/[slug].js +56 -0
  26. package/app/pages/posts/[category]/[slug].ts +56 -0
  27. package/app/pages/router-example.js +56 -0
  28. package/app/pages/router-example.ts +56 -0
  29. package/app/pages/test-for.js +65 -0
  30. package/app/pages/test-for.ts +80 -0
  31. package/app/pages/users/[id].js +24 -0
  32. package/app/pages/users/[id].ts +46 -0
  33. package/app/pages/users/index.js +22 -0
  34. package/app/pages/users/index.ts +29 -0
  35. package/app/pages/users.js +43 -0
  36. package/app/pages/users.ts +78 -0
  37. package/app/routes/About.js +1 -1
  38. package/app/routes/About.ts +1 -1
  39. package/app/routes/AuthRoutes.ts +41 -0
  40. package/app/routes/Home.js +9 -0
  41. package/app/routes/NotFound.js +16 -0
  42. package/app/routes/NotFound.ts +16 -0
  43. package/app/routes/RouterExample.js +56 -0
  44. package/app/routes/RouterExample.ts +56 -0
  45. package/app/routes/WildcardDemo.js +35 -0
  46. package/app/routes/WildcardDemo.ts +35 -0
  47. package/auto-imports.d.ts +1 -0
  48. package/bin/create-fusee-linux +0 -0
  49. package/bin/create-fusee-mac-arm64 +0 -0
  50. package/bin/create-fusee-mac-intel +0 -0
  51. package/bin/create-fusee-win.exe +0 -0
  52. package/bin/create-fusee.exe +0 -0
  53. package/bin/index.js +38 -0
  54. package/create-fusee-go/cmd/generate.go +72 -0
  55. package/create-fusee-go/cmd/init.go +38 -0
  56. package/create-fusee-go/cmd/root.go +121 -0
  57. package/create-fusee-go/go.mod +10 -0
  58. package/create-fusee-go/go.sum +10 -0
  59. package/create-fusee-go/internal/assets/assets.go +78 -0
  60. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  61. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  62. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  63. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  64. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  65. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  66. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  67. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  68. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  69. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  70. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  71. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  72. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  73. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  74. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  75. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  76. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  77. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  78. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  79. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  80. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  83. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  84. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  85. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  86. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  87. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  88. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  89. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  90. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  91. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  92. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  93. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  94. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  95. package/create-fusee-go/main.go +7 -0
  96. package/framework/auto-import-preset.js +1 -1
  97. package/framework/core/compiler.js +252 -249
  98. package/framework/core/component.js +265 -260
  99. package/framework/core/directives.js +875 -786
  100. package/framework/core/evaluator.js +153 -70
  101. package/framework/core/event-delegation.js +33 -14
  102. package/framework/core/signal.js +412 -403
  103. package/framework/index.js +10 -11
  104. package/framework/router/file-router.js +177 -0
  105. package/framework/router/router.js +462 -80
  106. package/framework/types/event-delegation.d.ts +0 -58
  107. package/framework/types/router.d.ts +32 -15
  108. package/framework/types/signal.d.ts +1 -0
  109. package/index.html +638 -626
  110. package/package.json +5 -3
  111. package/tsconfig.json +1 -0
  112. /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,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,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,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
+ })
@@ -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('Dynamic Test 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" :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
+ })