fusee-framework 1.5.3 → 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.
- package/README.md +30 -13
- 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/Counter.ts +1 -1
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +29 -62
- package/app/main.ts +72 -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 +26 -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/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 +117 -0
- package/app/pages/index.ts +78 -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 +56 -0
- package/app/pages/router-example.ts +56 -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/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 +56 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/auto-imports.d.ts +1 -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 +1 -1
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +265 -260
- package/framework/core/directives.js +875 -632
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +191 -0
- package/framework/core/signal.js +412 -404
- package/framework/index.js +10 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +462 -80
- package/framework/types/component.d.ts +0 -4
- package/framework/types/event-delegation.d.ts +32 -0
- package/framework/types/index.d.ts +3 -0
- package/framework/types/router.d.ts +32 -15
- package/framework/types/signal.d.ts +1 -0
- package/index.html +638 -563
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
|
@@ -1,80 +1,462 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { signal } from '../core/signal.js'
|
|
2
|
+
|
|
3
|
+
export const currentRoute = signal('/')
|
|
4
|
+
export const routeParams = signal({})
|
|
5
|
+
export const matchedRoutes = signal([])
|
|
6
|
+
|
|
7
|
+
let _routes = []
|
|
8
|
+
let _rootOutlet = null
|
|
9
|
+
let _clickHandler = null
|
|
10
|
+
let _activeChain = []
|
|
11
|
+
let _routeCache = new Map()
|
|
12
|
+
let _cacheMaxSize = 100
|
|
13
|
+
let _routerViewTimeout = 10000
|
|
14
|
+
|
|
15
|
+
function _getPath() {
|
|
16
|
+
return window.location.pathname || '/'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _getRoutePaths(routePath) {
|
|
20
|
+
return Array.isArray(routePath) ? routePath : [routePath]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _updateRoute() {
|
|
24
|
+
currentRoute(_getPath())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _matchSegments(routePath, urlSegments) {
|
|
28
|
+
const cleanPath = routePath.startsWith('/') ? routePath.slice(1) : routePath
|
|
29
|
+
|
|
30
|
+
if (cleanPath === '') {
|
|
31
|
+
return { matched: true, consumed: 0, params: {} }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (cleanPath === '*') {
|
|
35
|
+
return { matched: true, consumed: urlSegments.length, params: {} }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const routeSegs = cleanPath.split('/').filter(Boolean)
|
|
39
|
+
const hasWildcard = routeSegs[routeSegs.length - 1] === '*'
|
|
40
|
+
const checkSegs = hasWildcard ? routeSegs.slice(0, -1) : routeSegs
|
|
41
|
+
|
|
42
|
+
if (hasWildcard) {
|
|
43
|
+
if (urlSegments.length < checkSegs.length) return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (urlSegments.length < checkSegs.length) return null
|
|
47
|
+
|
|
48
|
+
const params = {}
|
|
49
|
+
for (let i = 0; i < checkSegs.length; i++) {
|
|
50
|
+
const rSeg = checkSegs[i]
|
|
51
|
+
const uSeg = urlSegments[i]
|
|
52
|
+
if (rSeg.startsWith(':')) {
|
|
53
|
+
params[rSeg.slice(1)] = uSeg
|
|
54
|
+
} else if (rSeg !== uSeg) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const consumed = hasWildcard ? urlSegments.length : checkSegs.length
|
|
60
|
+
return { matched: true, consumed, params }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _matchRouteTree(routes, urlSegments) {
|
|
64
|
+
for (const route of routes) {
|
|
65
|
+
const paths = _getRoutePaths(route.path)
|
|
66
|
+
for (const path of paths) {
|
|
67
|
+
const result = _matchSegments(path, urlSegments)
|
|
68
|
+
if (!result) continue
|
|
69
|
+
|
|
70
|
+
const remaining = urlSegments.slice(result.consumed)
|
|
71
|
+
|
|
72
|
+
if (route.children && route.children.length > 0) {
|
|
73
|
+
const childChain = _matchRouteTree(route.children, remaining)
|
|
74
|
+
if (childChain) {
|
|
75
|
+
return [
|
|
76
|
+
{ route, params: result.params, matchedSegments: urlSegments.slice(0, result.consumed), matchedPath: path },
|
|
77
|
+
...childChain
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (remaining.length === 0 || path === '*' || path.endsWith('/*')) {
|
|
84
|
+
return [{ route, params: result.params, matchedSegments: urlSegments.slice(0, result.consumed), matchedPath: path }]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _matchSingleFlat(routePath, actualPath) {
|
|
93
|
+
if (routePath === '*') return true
|
|
94
|
+
|
|
95
|
+
const routeParts = routePath.split('/').filter(Boolean)
|
|
96
|
+
const actualParts = actualPath.split('/').filter(Boolean)
|
|
97
|
+
|
|
98
|
+
const hasWildcard = routeParts.length > 0 && routeParts[routeParts.length - 1] === '*'
|
|
99
|
+
|
|
100
|
+
if (hasWildcard) {
|
|
101
|
+
const baseParts = routeParts.slice(0, -1)
|
|
102
|
+
if (actualParts.length < baseParts.length) return false
|
|
103
|
+
return baseParts.every((part, i) => part.startsWith(':') || part === actualParts[i])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (routeParts.length !== actualParts.length) return false
|
|
107
|
+
return routeParts.every((part, i) => part.startsWith(':') || part === actualParts[i])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function _matchFlat(routePath, actualPath) {
|
|
111
|
+
const paths = _getRoutePaths(routePath)
|
|
112
|
+
return paths.some(p => _matchSingleFlat(p, actualPath))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function _findMatchingPath(routePath, actualPath) {
|
|
116
|
+
const paths = _getRoutePaths(routePath)
|
|
117
|
+
return paths.find(p => _matchSingleFlat(p, actualPath)) || null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _extractParamsFromPath(singlePath, actualPath) {
|
|
121
|
+
const params = {}
|
|
122
|
+
if (singlePath === '*') return params
|
|
123
|
+
|
|
124
|
+
const routeParts = singlePath.split('/').filter(Boolean)
|
|
125
|
+
const actualParts = actualPath.split('/').filter(Boolean)
|
|
126
|
+
|
|
127
|
+
const hasWildcard = routeParts.length > 0 && routeParts[routeParts.length - 1] === '*'
|
|
128
|
+
const checkLen = hasWildcard ? routeParts.length - 1 : routeParts.length
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < checkLen; i++) {
|
|
131
|
+
if (routeParts[i] && routeParts[i].startsWith(':')) {
|
|
132
|
+
params[routeParts[i].slice(1)] = actualParts[i] || ''
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return params
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _extractParamsFlat(routePath, actualPath) {
|
|
139
|
+
const matchingPath = _findMatchingPath(routePath, actualPath)
|
|
140
|
+
if (!matchingPath) return {}
|
|
141
|
+
return _extractParamsFromPath(matchingPath, actualPath)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function _findMatchingChain(path) {
|
|
145
|
+
if (_routeCache.has(path)) {
|
|
146
|
+
const cached = _routeCache.get(path)
|
|
147
|
+
_routeCache.delete(path)
|
|
148
|
+
_routeCache.set(path, cached)
|
|
149
|
+
return cached
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const urlSegments = path.split('/').filter(Boolean)
|
|
153
|
+
const hasNested = _routes.some(r => r.children && r.children.length > 0)
|
|
154
|
+
|
|
155
|
+
let chain = null
|
|
156
|
+
|
|
157
|
+
if (hasNested) {
|
|
158
|
+
chain = _matchRouteTree(_routes, urlSegments)
|
|
159
|
+
if (chain) {
|
|
160
|
+
_cacheResult(path, chain)
|
|
161
|
+
return chain
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const paths = _routes.filter(r => {
|
|
166
|
+
const routePaths = _getRoutePaths(r.path)
|
|
167
|
+
return !routePaths.includes('*') && !routePaths.some(p => p.endsWith('/*')) && !r.children
|
|
168
|
+
})
|
|
169
|
+
const exactMatch = paths.find(r => _matchFlat(r.path, path))
|
|
170
|
+
if (exactMatch) {
|
|
171
|
+
const matchedPath = _findMatchingPath(exactMatch.path, path)
|
|
172
|
+
chain = [{ route: exactMatch, params: _extractParamsFlat(exactMatch.path, path), matchedSegments: urlSegments, matchedPath }]
|
|
173
|
+
_cacheResult(path, chain)
|
|
174
|
+
return chain
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const wildcardPaths = _routes.filter(r => {
|
|
178
|
+
const routePaths = _getRoutePaths(r.path)
|
|
179
|
+
return !r.children && routePaths.some(p => p.endsWith('/*'))
|
|
180
|
+
})
|
|
181
|
+
const wildcardMatch = wildcardPaths.find(r => _matchFlat(r.path, path))
|
|
182
|
+
if (wildcardMatch) {
|
|
183
|
+
const matchedPath = _findMatchingPath(wildcardMatch.path, path)
|
|
184
|
+
chain = [{ route: wildcardMatch, params: _extractParamsFlat(wildcardMatch.path, path), matchedSegments: urlSegments, matchedPath }]
|
|
185
|
+
_cacheResult(path, chain)
|
|
186
|
+
return chain
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const catchAll = _routes.find(r => {
|
|
190
|
+
const routePaths = _getRoutePaths(r.path)
|
|
191
|
+
return routePaths.includes('*')
|
|
192
|
+
})
|
|
193
|
+
if (catchAll) {
|
|
194
|
+
chain = [{ route: catchAll, params: {}, matchedSegments: urlSegments, matchedPath: '*' }]
|
|
195
|
+
_cacheResult(path, chain)
|
|
196
|
+
return chain
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _cacheResult(path, chain) {
|
|
203
|
+
if (_routeCache.size >= _cacheMaxSize) {
|
|
204
|
+
const firstKey = _routeCache.keys().next().value
|
|
205
|
+
_routeCache.delete(firstKey)
|
|
206
|
+
}
|
|
207
|
+
_routeCache.set(path, chain)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function _clearRouteCache() {
|
|
211
|
+
_routeCache.clear()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function _unmountFromLevel(level) {
|
|
215
|
+
for (let i = _activeChain.length - 1; i >= level; i--) {
|
|
216
|
+
const entry = _activeChain[i]
|
|
217
|
+
if (entry && entry.instance) {
|
|
218
|
+
try { entry.instance.unmount() } catch { }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
_activeChain.length = level
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function _getRoutePathCount(route) {
|
|
225
|
+
return Array.isArray(route.path) ? route.path.length : 1
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function _renderChain(chain) {
|
|
229
|
+
try {
|
|
230
|
+
let reuseUntil = 0
|
|
231
|
+
for (let i = 0; i < Math.min(_activeChain.length, chain.length); i++) {
|
|
232
|
+
const active = _activeChain[i]
|
|
233
|
+
const incoming = chain[i]
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
active.route === incoming.route &&
|
|
237
|
+
_segmentsEqual(active.matchedSegments, incoming.matchedSegments)
|
|
238
|
+
) {
|
|
239
|
+
reuseUntil = i + 1
|
|
240
|
+
}
|
|
241
|
+
else if (
|
|
242
|
+
active.route === incoming.route &&
|
|
243
|
+
_getRoutePathCount(active.route) > 1
|
|
244
|
+
) {
|
|
245
|
+
reuseUntil = i + 1
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_unmountFromLevel(reuseUntil)
|
|
253
|
+
|
|
254
|
+
const allParams = {}
|
|
255
|
+
for (const entry of chain) {
|
|
256
|
+
Object.assign(allParams, entry.params)
|
|
257
|
+
}
|
|
258
|
+
routeParams(allParams)
|
|
259
|
+
matchedRoutes(chain.map(e => e.route))
|
|
260
|
+
_renderFromLevel(chain, reuseUntil)
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('[framework] Error rendering route chain:', error)
|
|
263
|
+
_unmountFromLevel(0)
|
|
264
|
+
if (_rootOutlet) {
|
|
265
|
+
_rootOutlet.innerHTML = `<p style="color:red">[framework] Error rendering route: ${error.message}</p>`
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function _renderFromLevel(chain, level) {
|
|
271
|
+
if (level >= chain.length) return
|
|
272
|
+
|
|
273
|
+
const entry = chain[level]
|
|
274
|
+
const outlet = _getOutletForLevel(level)
|
|
275
|
+
|
|
276
|
+
if (!outlet) {
|
|
277
|
+
_waitForRouterView(level, () => _renderFromLevel(chain, level))
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
outlet.innerHTML = ''
|
|
282
|
+
|
|
283
|
+
const componentFn = entry.route.component
|
|
284
|
+
const instance = componentFn()
|
|
285
|
+
instance.render(outlet)
|
|
286
|
+
|
|
287
|
+
_activeChain[level] = {
|
|
288
|
+
route: entry.route,
|
|
289
|
+
matchedSegments: entry.matchedSegments,
|
|
290
|
+
params: entry.params,
|
|
291
|
+
instance,
|
|
292
|
+
outlet
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (level + 1 < chain.length) _scheduleNextLevel(chain, level + 1)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _scheduleNextLevel(chain, level) {
|
|
299
|
+
const outlet = _getOutletForLevel(level)
|
|
300
|
+
if (outlet) {
|
|
301
|
+
_renderFromLevel(chain, level)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
_waitForRouterView(level, () => _renderFromLevel(chain, level))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _getOutletForLevel(level) {
|
|
309
|
+
if (level === 0) return _rootOutlet
|
|
310
|
+
|
|
311
|
+
const parentEntry = _activeChain[level - 1]
|
|
312
|
+
if (!parentEntry || !parentEntry.outlet) return null
|
|
313
|
+
|
|
314
|
+
return parentEntry.outlet.querySelector('[data-router-view]')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function _waitForRouterView(level, callback) {
|
|
318
|
+
const parentEntry = _activeChain[level - 1]
|
|
319
|
+
if (!parentEntry || !parentEntry.outlet) return
|
|
320
|
+
|
|
321
|
+
const parentOutlet = parentEntry.outlet
|
|
322
|
+
|
|
323
|
+
const existing = parentOutlet.querySelector('[data-router-view]')
|
|
324
|
+
if (existing) {
|
|
325
|
+
callback()
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const observer = new MutationObserver(() => {
|
|
330
|
+
const el = parentOutlet.querySelector('[data-router-view]')
|
|
331
|
+
if (el) {
|
|
332
|
+
observer.disconnect()
|
|
333
|
+
callback()
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
observer.observe(parentOutlet, { childList: true, subtree: true })
|
|
338
|
+
|
|
339
|
+
setTimeout(() => observer.disconnect(), _routerViewTimeout)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function _segmentsEqual(a, b) {
|
|
343
|
+
if (a.length !== b.length) return false
|
|
344
|
+
for (let i = 0; i < a.length; i++) {
|
|
345
|
+
if (a[i] !== b[i]) return false
|
|
346
|
+
}
|
|
347
|
+
return true
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function _resolveRoute() {
|
|
351
|
+
if (!_rootOutlet || _routes.length === 0) return
|
|
352
|
+
|
|
353
|
+
const path = _getPath()
|
|
354
|
+
const chain = _findMatchingChain(path)
|
|
355
|
+
|
|
356
|
+
if (!chain) {
|
|
357
|
+
_unmountFromLevel(0)
|
|
358
|
+
_rootOutlet.innerHTML = `<p style="color:red">[framework] No route matched for "${path}"</p>`
|
|
359
|
+
routeParams({})
|
|
360
|
+
matchedRoutes([])
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
_renderChain(chain)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function navigate(path) {
|
|
368
|
+
if (path === _getPath()) return
|
|
369
|
+
window.history.pushState({}, '', path)
|
|
370
|
+
_updateRoute()
|
|
371
|
+
_resolveRoute()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function mountOutlet(el) {
|
|
375
|
+
_rootOutlet = el
|
|
376
|
+
_resolveRoute()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function _isInternalLink(anchor) {
|
|
380
|
+
const href = anchor.getAttribute('href')
|
|
381
|
+
if (!href) return false
|
|
382
|
+
if (href.startsWith('#')) return false
|
|
383
|
+
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//')) {
|
|
384
|
+
const url = new URL(href, window.location.origin)
|
|
385
|
+
return url.origin === window.location.origin
|
|
386
|
+
}
|
|
387
|
+
return href.startsWith('/')
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function _getAnchorFromEvent(e) {
|
|
391
|
+
if (e.composedPath) {
|
|
392
|
+
return e.composedPath().find(el => el.tagName === 'A')
|
|
393
|
+
}
|
|
394
|
+
return e.target.tagName === 'A' ? e.target : null
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function _handleLinkClick(e) {
|
|
398
|
+
const anchor = _getAnchorFromEvent(e)
|
|
399
|
+
if (!anchor) return
|
|
400
|
+
|
|
401
|
+
if (anchor.hasAttribute('f-link') || _isInternalLink(anchor)) {
|
|
402
|
+
e.preventDefault()
|
|
403
|
+
const href = anchor.getAttribute('href')
|
|
404
|
+
if (href && href !== _getPath()) {
|
|
405
|
+
navigate(href)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function _setupPopstateHandler() {
|
|
411
|
+
const handler = () => {
|
|
412
|
+
_updateRoute()
|
|
413
|
+
_resolveRoute()
|
|
414
|
+
}
|
|
415
|
+
window.addEventListener('popstate', handler)
|
|
416
|
+
return handler
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function _setupClickHandler() {
|
|
420
|
+
_clickHandler = _handleLinkClick
|
|
421
|
+
document.addEventListener('click', _clickHandler)
|
|
422
|
+
return _clickHandler
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function _setupInitialRoute() {
|
|
426
|
+
if (document.readyState === 'loading') {
|
|
427
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
428
|
+
_updateRoute()
|
|
429
|
+
_resolveRoute()
|
|
430
|
+
}, { once: true })
|
|
431
|
+
} else {
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
if (_activeChain.length === 0) _resolveRoute()
|
|
434
|
+
}, 0)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function createRouter(routes, options = {}) {
|
|
439
|
+
_routes = routes
|
|
440
|
+
_activeChain = []
|
|
441
|
+
_cacheMaxSize = options.cacheSize || 100
|
|
442
|
+
_routerViewTimeout = options.routerViewTimeout || 10000
|
|
443
|
+
|
|
444
|
+
_updateRoute()
|
|
445
|
+
|
|
446
|
+
const popstateHandler = _setupPopstateHandler()
|
|
447
|
+
_setupClickHandler()
|
|
448
|
+
_setupInitialRoute()
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
navigate,
|
|
452
|
+
destroy() {
|
|
453
|
+
window.removeEventListener('popstate', popstateHandler)
|
|
454
|
+
document.removeEventListener('click', _clickHandler)
|
|
455
|
+
_unmountFromLevel(0)
|
|
456
|
+
_routes = []
|
|
457
|
+
_rootOutlet = null
|
|
458
|
+
_activeChain = []
|
|
459
|
+
_clearRouteCache()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
// ─── Component Types ─────────────────────────────────────────────────────────
|
|
2
|
-
|
|
3
1
|
export type ComponentProps = Record<string, any>
|
|
4
|
-
|
|
5
2
|
export type EmptyProps = Record<string, never>
|
|
6
|
-
|
|
7
3
|
export type PropSchema = string[] | Record<string, PropConfig>
|
|
8
4
|
|
|
9
5
|
export type PropConfig = {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const DELEGATED_EVENTS: Set<string>
|
|
2
|
+
|
|
3
|
+
export function isDelegatedEvent(eventName: string): boolean
|
|
4
|
+
|
|
5
|
+
export interface DelegatedEventOptions {
|
|
6
|
+
modifiers?: string[]
|
|
7
|
+
context?: Record<string, any>
|
|
8
|
+
expr?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function registerDelegatedEvent(
|
|
12
|
+
element: Element,
|
|
13
|
+
eventType: string,
|
|
14
|
+
handler: (event: Event) => void,
|
|
15
|
+
options?: DelegatedEventOptions
|
|
16
|
+
): () => void
|
|
17
|
+
|
|
18
|
+
export function createEventHandler(
|
|
19
|
+
handler: Function | string,
|
|
20
|
+
modifiers: string[],
|
|
21
|
+
context: Record<string, any>,
|
|
22
|
+
expr: string
|
|
23
|
+
): (event: Event) => void
|
|
24
|
+
|
|
25
|
+
export interface EventHandlerSyntax {
|
|
26
|
+
delegated: `@${string}`
|
|
27
|
+
native: `on:${string}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare global {
|
|
31
|
+
const isDelegatedEvent: typeof import('./event-delegation').isDelegatedEvent
|
|
32
|
+
}
|
|
@@ -5,6 +5,7 @@ export * from './router'
|
|
|
5
5
|
export * from './compiler'
|
|
6
6
|
export * from './directives'
|
|
7
7
|
export * from './store'
|
|
8
|
+
export * from './event-delegation'
|
|
8
9
|
|
|
9
10
|
declare global {
|
|
10
11
|
type Signal<T = any> = import('./signal').Signal<T>
|
|
@@ -13,7 +14,9 @@ declare global {
|
|
|
13
14
|
type Composable<T extends (...args: any[]) => any> = import('./composable').Composable<T>
|
|
14
15
|
type DirectiveBinding<V = any> = import('./directives').DirectiveBinding<V>
|
|
15
16
|
type DirectiveHooks<V = any> = import('./directives').DirectiveHooks<V>
|
|
17
|
+
type DelegatedEventOptions = import('./event-delegation').DelegatedEventOptions
|
|
16
18
|
const defineStore: typeof import('./store').defineStore
|
|
19
|
+
const isDelegatedEvent: typeof import('./event-delegation').isDelegatedEvent
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export as namespace Fusee
|
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import type { ComponentApi } from './component'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export
|
|
1
|
+
import type { ComponentApi, ComponentFactory } from './component'
|
|
2
|
+
import type { Signal } from './signal'
|
|
3
|
+
|
|
4
|
+
export type Route = {
|
|
5
|
+
path: string | string[]
|
|
6
|
+
component: ComponentFactory<any>
|
|
7
|
+
children?: Route[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type Router = {
|
|
11
|
+
navigate: (path: string) => void
|
|
12
|
+
destroy: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RouteParams = Record<string, string>
|
|
16
|
+
|
|
17
|
+
export declare const currentRoute: Signal<string>
|
|
18
|
+
export declare const routeParams: Signal<RouteParams>
|
|
19
|
+
export declare const matchedRoutes: Signal<Route[]>
|
|
20
|
+
|
|
21
|
+
export declare function createRouter(routes: Route[], options?: { cacheSize?: number; routerViewTimeout?: number }): Router
|
|
22
|
+
export declare function navigate(path: string): void
|
|
23
|
+
export declare function mountOutlet(el: HTMLElement): void
|
|
24
|
+
|
|
25
|
+
export type FileRouterOptions = {
|
|
26
|
+
loadingComponent?: ComponentFactory<any>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export declare function generateRoutes(
|
|
30
|
+
globResults: Record<string, () => Promise<any>>,
|
|
31
|
+
options?: FileRouterOptions
|
|
32
|
+
): Route[]
|
|
@@ -73,6 +73,7 @@ export declare function watch<T extends any[]>(
|
|
|
73
73
|
options?: WatchOptions
|
|
74
74
|
): () => void
|
|
75
75
|
export declare function inspect(...args: any[]): (() => void) | void
|
|
76
|
+
export declare function onCleanup(fn: () => void): void
|
|
76
77
|
|
|
77
78
|
declare function setEffectHook(fn: (eff: any) => void): void
|
|
78
79
|
|