fusee-framework 0.1.0-alpha.1 → 1.0.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 CHANGED
@@ -1,8 +1,76 @@
1
- \# 🚀 Fusée v0.1.0-alpha
2
-
3
-
4
-
5
- Peak Performance JS Framework
6
-
7
- \*\*Signals + Components + SPA\*\*
8
-
1
+ # Fusée Framework 🚀
2
+ **v1.0.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
3
+
4
+ Fusée is a custom, high-performance reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, and a comprehensive CLI for instant application scaffolding.
5
+
6
+ ---
7
+
8
+ ## ⚡ Quick Start
9
+
10
+ The fastest way to get started with Fusée is via the CLI:
11
+
12
+ ```bash
13
+ # 1. Install globally (linking the engine)
14
+ npm link
15
+
16
+ # 2. Scaffold your first project
17
+ create-fusee-app .
18
+ ```
19
+ Follow the interactive prompt to choose your template (**JavaScript** or **TypeScript**) and launch your app with `npm run dev`!
20
+
21
+ ---
22
+
23
+ ## 🔥 Key Features
24
+
25
+ - **Signals-First Reactivity**: Modern atomic updates that ensure only the modified parts of the DOM are touched.
26
+ - **Recursive Hybrid Compiler**: A robust architectural approach to node traversal, eliminating rendering bugs while maintaining "greedy" directive processing for speed.
27
+ - **Strict Component System**: Use `defineComponent` for prop validation, lifecycle hooks (`onMount`, `onUnmount`), and automated cleanup.
28
+ - **Optimized Directives**: Native support for `f-if`, `f-for`, `f-model`, `f-text`, `f-cloak` and more.
29
+ - **Performance Shield (`f-once`)**: Isolate and stabilize static subtrees to prevent unnecessary reactivity.
30
+ - **Vite Integration**: Full support for the fastest development workflow and Hot Module Replacement (HMR).
31
+
32
+ ---
33
+
34
+ ## 🛠 Project Structure
35
+
36
+ A typical Fusée project looks like this:
37
+
38
+ - **`framework/`**: The core reactive engine.
39
+ - **`app/`**: Your application logic (routes, components).
40
+ - **`index.html`**: The entry point of your reactive world.
41
+
42
+ ---
43
+
44
+ ## 📝 Example Component
45
+
46
+ ```javascript
47
+ import { defineComponent, signal } from './framework/core/index.js';
48
+
49
+ export const Counter = defineComponent({
50
+ setup() {
51
+ const count = signal(0);
52
+ return {
53
+ count,
54
+ inc: () => count(count() + 1),
55
+ dec: () => count(count() - 1),
56
+ template: `
57
+ <div class="counter-card">
58
+ <h2>Counter: {{ count }}</h2>
59
+ <button @click="dec">-</button>
60
+ <button @click="inc">+</button>
61
+ </div>
62
+ `
63
+ };
64
+ }
65
+ });
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 🚀 Future Roadmap
71
+
72
+ - [ ] Global Store Management
73
+ - [ ] Keyed `f-for` for list reconciliation
74
+ - [ ] Advanced router transitions support
75
+
76
+ Built with ❤️ by a forward-thinking JavaScript Architect. 🏎️🔥🏆🥇_
@@ -1,38 +1,115 @@
1
- import { defineComponent, signal, onMount, onUnmount } from '../../framework/index.js'
1
+ import { defineComponent, signal, computed, effect, batch, untrack, onMount, onUnmount } from '../../framework/index.js'
2
2
 
3
3
  export const Counter = defineComponent({
4
- props: ['initialValue'],
5
- setup(props) {
6
- const count = signal(Number(props.initialValue) || 0)
7
- const double = signal(0)
8
-
9
- // Manual computed: keep double in sync
10
- // (could use computed() from framework too)
11
- function increment() { count(count() + 1); double(count() * 2) }
12
- function decrement() { count(count() - 1); double(count() * 2) }
13
- function reset() { count(0); double(0) }
14
-
15
- onMount(() => console.log('[Counter] mounted, initial:', count()))
16
- onUnmount(() => console.log('[Counter] unmounted'))
17
-
18
- return {
19
- count,
20
- double,
21
- increment,
22
- decrement,
23
- reset,
24
- template: `
25
- <div class="counter">
26
- <h2>Counter</h2>
27
- <p class="value">{{ count }}</p>
28
- <p class="derived">double: {{ double }}</p>
29
- <div class="actions">
30
- <button @click="decrement">−</button>
31
- <button @click="reset">reset</button>
32
- <button @click="increment">+</button>
33
- </div>
34
- </div>
35
- `
4
+ props: {
5
+ initialValue: { type: String, default: '0' },
6
+ parentTitle: { type: String, default: '' }
7
+ },
8
+ setup(props) {
9
+ const count = signal(Number(props.initialValue) || 0)
10
+ const multiplier = signal(1)
11
+
12
+ const double = computed(() => {
13
+ console.log('[Counter] computing double...')
14
+ return count() * 2
15
+ })
16
+
17
+ const isZero = computed(() => count() === 0)
18
+
19
+ const counterTitle = computed(() =>
20
+ `Counter: ${count()} (×${multiplier()})`
21
+ )
22
+
23
+ const displayStyle = computed(() => ({
24
+ fontSize: '16rem',
25
+ fontWeight: '900',
26
+ lineHeight: '1',
27
+ margin: '0',
28
+ color: count() >= 0 ? '#3b82f6' : '#ef4444',
29
+ transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
30
+ transform: `scale(${1 + (Math.abs(count()) % 3) * 0.1})`
31
+ }))
32
+
33
+ effect(() => {
34
+ console.log(`[Counter Effect] Count: ${count()} | Step ignored in tracking: ${untrack(() => multiplier())}`)
35
+ })
36
+
37
+ function updateMultiple() {
38
+ batch(() => {
39
+ count(count() + 5)
40
+ multiplier(multiplier() + 1)
41
+ })
42
+ }
43
+
44
+ function increment() { count(count() + 1) }
45
+ function decrement() { count(count() - 1) }
46
+ function reset() { count(0) }
47
+
48
+ onMount(() => console.log('[Counter] mounted, initial:', count()))
49
+ onUnmount(() => console.log('[Counter] unmounted'))
50
+
51
+ return {
52
+ count,
53
+ multiplier,
54
+ double,
55
+ isZero,
56
+ counterTitle,
57
+ increment,
58
+ decrement,
59
+ reset,
60
+ updateMultiple,
61
+ get parentTitle() { return props.parentTitle },
62
+ displayStyle,
63
+ template: `
64
+ <div class="counter" :title="counterTitle" @keydown.arrowup.window="increment" @keydown.arrowdown.window="decrement">
65
+ <h2>Counter</h2>
66
+ <p style="font-size: 0.8rem; color: #888;">Message from Home: {{ parentTitle }}</p>
67
+
68
+ <div class="counter-display" style="text-align: center; margin: 60px 0;">
69
+ <span style="display: block; font-size: 1.2rem; text-transform: uppercase; letter-spacing: 3px; color: #888;">Current Value</span>
70
+ <p class="value"
71
+ :class="{ positive: count >= 0, negative: count < 0 }"
72
+ :style="displayStyle">
73
+ {{ count }}
74
+ </p>
75
+ <span style="font-size: 1.2rem; color: #aaa;">(Multiplier step: {{ multiplier }})</span>
76
+ </div>
77
+ <p class="derived">double: {{ double }}</p>
78
+
79
+ <div class="actions">
80
+ <button @click.prevent="decrement">−</button>
81
+ <button @click.once="reset" :disabled="isZero">Reset (Once only demo)</button>
82
+ <button @click="increment">+</button>
83
+ <button @click="updateMultiple">+5 & Step (Batched)</button>
84
+ </div>
85
+
86
+ <div class="extras" style="margin-top: 24px; border-top: 1px solid rgba(0,0,0,0.08); padding-top: 20px;">
87
+ <p style="font-weight: 600; font-size: 0.9rem; margin-bottom: 12px; color: #444;">Advanced Controls</p>
88
+
89
+ <div style="display: flex; gap: 10px; margin-bottom: 15px;">
90
+ <input
91
+ type="text"
92
+ placeholder="Set count..."
93
+ style="flex: 1; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 0.9rem; outline: none; transition: border-color 0.2s;"
94
+ onfocus="this.style.borderColor = '#3b82f6'"
95
+ onblur="this.style.borderColor = '#ddd'"
96
+ @keyup.enter="count(Number($event.target.value) || 0); $event.target.value = ''"
97
+ >
98
+ <button
99
+ style="padding: 10px 16px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; font-size: 0.9rem; transition: opacity 0.2s;"
100
+ onmouseover="this.style.opacity = '0.9'"
101
+ onmouseout="this.style.opacity = '1'"
102
+ @click="console.log('Clicked at: ' + $event.clientX + 'x' + $event.clientY)"
103
+ >
104
+ Log Position
105
+ </button>
106
+ </div>
107
+ <p style="font-size: 0.75rem; color: #888;">
108
+ Tip: Use Up/Down arrows to nudge globally, or Enter in the box above.
109
+ </p>
110
+ </div>
111
+ </div>
112
+ `
113
+ }
36
114
  }
37
- }
38
115
  })
@@ -0,0 +1,99 @@
1
+ import { defineComponent, signal, computed, effect, batch, untrack, onMount, onUnmount, type Signal, type Computed } from '../../framework/index.js'
2
+
3
+ type CounterProps = {
4
+ initialValue?: string
5
+ parentTitle?: string
6
+ }
7
+
8
+ type CounterResult = {
9
+ count: Signal<number>
10
+ multiplier: Signal<number>
11
+ double: Computed<number>
12
+ valueClass: Computed<string>
13
+ isZero: Computed<boolean>
14
+ counterTitle: Computed<string>
15
+ increment: () => void
16
+ decrement: () => void
17
+ reset: () => void
18
+ updateMultiple: () => void
19
+ parentTitle: string
20
+ template: string
21
+ }
22
+
23
+ export const Counter = defineComponent({
24
+ props: {
25
+ initialValue: { type: String, default: '0' },
26
+ parentTitle: { type: String, default: '' }
27
+ },
28
+ setup(props: CounterProps): CounterResult {
29
+ const count = signal<number>(Number(props.initialValue) || 0)
30
+ const multiplier = signal<number>(1)
31
+
32
+ const double = computed(() => count() * 2)
33
+
34
+ const valueClass = computed(() =>
35
+ count() >= 0 ? 'value positive' : 'value negative'
36
+ )
37
+
38
+ const isZero = computed(() => count() === 0)
39
+
40
+ const counterTitle = computed(() =>
41
+ `Counter: ${count()} (×${multiplier()})`
42
+ )
43
+
44
+ effect(() => {
45
+ console.log(`[Counter Effect] Count: ${count()} | Step ignored in tracking: ${untrack(() => multiplier())}`)
46
+ })
47
+
48
+ function updateMultiple(): void {
49
+ batch(() => {
50
+ count(count() + 5)
51
+ multiplier(multiplier() + 1)
52
+ })
53
+ }
54
+
55
+ function increment(): void {
56
+ count(count() + 1)
57
+ }
58
+
59
+ function decrement(): void {
60
+ count(count() - 1)
61
+ }
62
+
63
+ function reset(): void {
64
+ count(0)
65
+ }
66
+
67
+ onMount(() => console.log('[Counter] mounted, initial:', count()))
68
+ onUnmount(() => console.log('[Counter] unmounted'))
69
+
70
+ return {
71
+ count,
72
+ multiplier,
73
+ double,
74
+ valueClass,
75
+ isZero,
76
+ counterTitle,
77
+ increment,
78
+ decrement,
79
+ reset,
80
+ updateMultiple,
81
+ get parentTitle() { return props.parentTitle || '' },
82
+ template: `
83
+ <div class="counter" :title="counterTitle">
84
+ <h2>Counter</h2>
85
+ <p style="font-size: 0.8rem; color: #888;">Message from Home: {{ parentTitle }}</p>
86
+ <p class="{{ valueClass }}">Count: {{ count }} (Multiplier step: {{ multiplier }})</p>
87
+ <p class="derived">double: {{ double }}</p>
88
+ <div class="actions">
89
+ <button @click="decrement">−</button>
90
+ <button @click="reset" :disabled="isZero">reset</button>
91
+ <button @click="increment">+</button>
92
+ <button @click="updateMultiple">+5 & Step (Batched)</button>
93
+ </div>
94
+ </div>
95
+ `
96
+ }
97
+ }
98
+ })
99
+
@@ -0,0 +1,34 @@
1
+ import { defineComponent, signal } from '../../framework/index.js'
2
+ import { Counter } from './Counter.js'
3
+
4
+ export const CounterSection = defineComponent({
5
+ props: {
6
+ pageTitle: { type: String, default: '' }
7
+ },
8
+ components: { Counter },
9
+ setup(props) {
10
+ const showCounter = signal(true)
11
+
12
+ return {
13
+ showCounter,
14
+ get title() { return props.pageTitle },
15
+ toggleCounter: () => showCounter(!showCounter()),
16
+ template: `
17
+ <div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 20px;">
18
+ <h3>Counter Section</h3>
19
+ <button @click="toggleCounter" class="primary-btn" style="margin: 10px 0;">Toggle Counter (f-if)</button>
20
+
21
+ <div f-if="showCounter">
22
+ {{ Counter initialValue="10" :parentTitle="title" }}
23
+ </div>
24
+ <div f-elif="title === 'Welcome!'">
25
+ <p style="color: yellow;">Counter is hidden, but title is "Welcome!"</p>
26
+ </div>
27
+ <div f-else>
28
+ <p style="color: red;">Counter is hidden and title is changed.</p>
29
+ </div>
30
+ </div>
31
+ `
32
+ }
33
+ }
34
+ })
@@ -0,0 +1,45 @@
1
+ import { defineComponent, signal, type SignalAccessor } from '../../framework/index.js'
2
+ import { Counter } from './Counter.js'
3
+
4
+ type CounterSectionProps = {
5
+ pageTitle: string
6
+ }
7
+
8
+ type CounterSectionResult = {
9
+ showCounter: SignalAccessor<boolean>
10
+ title: string
11
+ toggleCounter: () => void
12
+ template: string
13
+ }
14
+
15
+ export const CounterSection = defineComponent({
16
+ props: {
17
+ pageTitle: { type: String, default: '' }
18
+ },
19
+ components: { Counter },
20
+ setup(props: CounterSectionProps): CounterSectionResult {
21
+ const showCounter = signal(true)
22
+
23
+ return {
24
+ showCounter,
25
+ get title() { return props.pageTitle },
26
+ toggleCounter: () => showCounter(!showCounter()),
27
+ template: `
28
+ <div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 20px;">
29
+ <h3>Counter Section</h3>
30
+ <button @click="toggleCounter" class="primary-btn" style="margin: 10px 0;">Toggle Counter (f-if)</button>
31
+
32
+ <div f-if="showCounter">
33
+ {{ Counter initialValue="10" :parentTitle="title" }}
34
+ </div>
35
+ <div f-elif="title === 'Welcome!'">
36
+ <p style="color: yellow;">Counter is hidden, but title is "Welcome!"</p>
37
+ </div>
38
+ <div f-else>
39
+ <p style="color: red;">Counter is hidden and title is changed.</p>
40
+ </div>
41
+ </div>
42
+ `
43
+ }
44
+ }
45
+ })
@@ -0,0 +1,31 @@
1
+ import { defineComponent, signal } from '../../framework/index.js'
2
+
3
+ export const UserForm = defineComponent({
4
+ setup() {
5
+ const userName = signal('Guest')
6
+ const inputRef = signal(null)
7
+
8
+ return {
9
+ userName,
10
+ inputRef,
11
+ focusInput: () => {
12
+ const el = inputRef()
13
+ if (el) {
14
+ console.log('[UserForm] Focused input via f-ref:', el)
15
+ el.focus()
16
+ }
17
+ },
18
+ template: `
19
+ <div style="margin: 20px 0; background: #1a1a24; padding: 15px; border-radius: 8px;">
20
+ <h2 style="color: #fff;">Hello, {{ userName || 'Guest' }}!</h2>
21
+ <label style="display: block; margin-bottom: 8px; color: #8b8bff;">Two-way binding (f-model):</label>
22
+ <div style="display: flex; gap: 10px;">
23
+ <input type="text" f-ref="inputRef" f-model="userName" placeholder="Enter your name..."
24
+ style="padding: 8px 12px; border-radius: 4px; border: 1px solid #3a3a5a; background: #0f0f1a; color: #fff; width: 100%; max-width: 300px;" />
25
+ <button @click="focusInput" class="primary-btn">Focus Field</button>
26
+ </div>
27
+ </div>
28
+ `
29
+ }
30
+ }
31
+ })
@@ -0,0 +1,38 @@
1
+ import { defineComponent, signal, type SignalAccessor } from '../../framework/index.js'
2
+
3
+ type UserFormResult = {
4
+ userName: SignalAccessor<string>
5
+ inputRef: SignalAccessor<HTMLInputElement | null>
6
+ focusInput: () => void
7
+ template: string
8
+ }
9
+
10
+ export const UserForm = defineComponent({
11
+ setup(): UserFormResult {
12
+ const userName = signal('Guest')
13
+ const inputRef = signal<HTMLInputElement | null>(null)
14
+
15
+ return {
16
+ userName,
17
+ inputRef,
18
+ focusInput: () => {
19
+ const el = inputRef()
20
+ if (el) {
21
+ console.log('[UserForm] Focused input via f-ref:', el)
22
+ el.focus()
23
+ }
24
+ },
25
+ template: `
26
+ <div style="margin: 20px 0; background: #1a1a24; padding: 15px; border-radius: 8px;">
27
+ <h2 style="color: #fff;">Hello, {{ userName || 'Guest' }}!</h2>
28
+ <label style="display: block; margin-bottom: 8px; color: #8b8bff;">Two-way binding (f-model):</label>
29
+ <div style="display: flex; gap: 10px;">
30
+ <input type="text" f-ref="inputRef" f-model="userName" placeholder="Enter your name..."
31
+ style="padding: 8px 12px; border-radius: 4px; border: 1px solid #3a3a5a; background: #0f0f1a; color: #fff; width: 100%; max-width: 300px;" />
32
+ <button @click="focusInput" class="primary-btn">Focus Field</button>
33
+ </div>
34
+ </div>
35
+ `
36
+ }
37
+ }
38
+ })
package/app/main.js CHANGED
@@ -3,8 +3,8 @@ import { Home } from './routes/Home.js'
3
3
  import { About } from './routes/About.js'
4
4
 
5
5
  createRouter([
6
- { path: '/', component: Home },
7
- { path: '/about', component: About },
6
+ { path: '/', component: Home },
7
+ { path: '/about', component: About },
8
8
  ])
9
9
 
10
10
  const outlet = document.getElementById('app')
package/app/main.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { createRouter, mountOutlet } from '../framework/index.js'
2
+ import type { Route } from '../framework/types/index.js'
3
+ import { Home } from './routes/Home.js'
4
+ import { About } from './routes/About.js'
5
+
6
+ const routes: Route[] = [
7
+ { path: '/', component: Home },
8
+ { path: '/about', component: About },
9
+ ]
10
+
11
+ createRouter(routes)
12
+
13
+ const outlet = document.getElementById('app')
14
+ if (!outlet) throw new Error('[app] #app element not found in DOM')
15
+ mountOutlet(outlet)
@@ -1,24 +1,24 @@
1
1
  import { defineComponent, signal, onMount } from '../../framework/index.js'
2
2
 
3
3
  export const About = defineComponent({
4
- props: [],
5
- setup() {
6
- const loaded = signal(false)
4
+ props: [],
5
+ setup() {
6
+ const loaded = signal(false)
7
7
 
8
- onMount(() => {
9
- setTimeout(() => loaded(true), 300)
10
- })
8
+ onMount(() => {
9
+ setTimeout(() => loaded(true), 300)
10
+ })
11
11
 
12
- return {
13
- loaded,
14
- template: `
15
- <div class="page">
16
- <h1>About</h1>
17
- <p>Framework custom — prototip SPA.</p>
18
- <p><strong>Features:</strong> signals, defineComponent, hash router, lifecycle hooks.</p>
19
- <p class="status">{{ loaded }}</p>
20
- </div>
21
- `
12
+ return {
13
+ loaded,
14
+ template: `
15
+ <div class="page">
16
+ <h1>About</h1>
17
+ <p>Framework custom — SPA prototipe</p>
18
+ <p><strong>Features:</strong> signals, defineComponent, hash router, lifecycle hooks.</p>
19
+ <p class="status">{{ loaded }}</p>
20
+ </div>
21
+ `
22
+ }
22
23
  }
23
- }
24
24
  })
@@ -0,0 +1,29 @@
1
+ import { defineComponent, signal, onMount, type Signal, type 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, hash router, lifecycle hooks.</p>
24
+ <p class="status">{{ loaded }}</p>
25
+ </div>
26
+ `
27
+ }
28
+ }
29
+ })
@@ -1,22 +1,27 @@
1
1
  import { defineComponent, signal } from '../../framework/index.js'
2
- import { Counter } from '../components/Counter.js'
2
+ import { UserForm } from '../components/UserForm.js'
3
+ import { CounterSection } from '../components/CounterSection.js'
3
4
 
4
5
  export const Home = defineComponent({
5
- props: [],
6
- components: { Counter },
7
- setup() {
8
- const title = signal('Welcome!')
9
-
10
- return {
11
- title,
12
- template: `
13
- <div class="page">
14
- <h1>{{ title }}</h1>
15
- <p>This is an SPA built with my own JavaScript custom framework!</p>
16
- <p>Made with passion by Sebastian Șomu</p>
17
- {{ Counter }}
18
- </div>
19
- `
6
+ components: { UserForm, CounterSection },
7
+ setup() {
8
+ const title = signal('Welcome!')
9
+ return {
10
+ title,
11
+ changeTitle: () => title('Changed Title!'),
12
+ template: `
13
+ <div class="page">
14
+ <h1>{{ title.toUpperCase() }}</h1>
15
+ <p>This is an SPA built with my own JavaScript custom framework!</p>
16
+ <p>Made with passion by Sebastian Șomu</p>
17
+
18
+ <button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
19
+
20
+ {{ UserForm }}
21
+
22
+ {{ CounterSection :pageTitle="title" }}
23
+ </div>
24
+ `
25
+ }
20
26
  }
21
- }
22
27
  })
@@ -0,0 +1,34 @@
1
+ import { defineComponent, signal, type SignalAccessor } from '../../framework/index.js'
2
+ import { UserForm } from '../components/UserForm.js'
3
+ import { CounterSection } from '../components/CounterSection.js'
4
+
5
+ type HomeResult = {
6
+ title: SignalAccessor<string>
7
+ changeTitle: () => void
8
+ template: string
9
+ }
10
+
11
+ export const Home = defineComponent({
12
+ components: { UserForm, CounterSection },
13
+ setup(): HomeResult {
14
+ const title = signal('Welcome!')
15
+
16
+ return {
17
+ title,
18
+ changeTitle: () => title('Changed Title!'),
19
+ template: `
20
+ <div class="page">
21
+ <h1>{{ title.toUpperCase() }}</h1>
22
+ <p>This is an SPA built with my own JavaScript custom framework!</p>
23
+ <p>Made with passion by Sebastian Șomu</p>
24
+
25
+ <button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
26
+
27
+ {{ UserForm }}
28
+
29
+ {{ CounterSection :pageTitle="title" }}
30
+ </div>
31
+ `
32
+ }
33
+ }
34
+ })