fusee-framework 1.2.1 → 1.4.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 +19 -9
- package/app/components/DynamicA.js +24 -0
- package/app/components/DynamicA.ts +24 -0
- package/app/components/DynamicB.js +24 -0
- package/app/components/DynamicB.ts +24 -0
- package/app/components/EmitTest.js +5 -6
- package/app/components/Loading.js +26 -0
- package/app/components/Loading.ts +26 -0
- package/app/composables/demo.js +29 -0
- package/app/composables/demo.ts +30 -0
- package/app/composables/useFetch.js +53 -0
- package/app/composables/useFetch.ts +63 -0
- package/app/composables/useLocalStorage.js +31 -0
- package/app/composables/useLocalStorage.ts +26 -0
- package/app/main.js +58 -9
- package/app/main.ts +53 -7
- package/app/routes/TestComposables.js +158 -0
- package/app/routes/TestComposables.ts +158 -0
- package/app/routes/TestDynamic.js +40 -0
- package/app/routes/TestDynamic.ts +40 -0
- package/app/routes/TestFor.js +2 -3
- package/app/routes/TestFor.ts +2 -3
- package/auto-imports.d.ts +1 -0
- package/framework/auto-import-preset.js +1 -0
- package/framework/core/component.js +76 -21
- package/framework/core/composable.js +27 -0
- package/framework/core/directives.js +234 -6
- package/framework/core/signal.js +58 -0
- package/framework/index.js +4 -2
- package/framework/types/component.d.ts +11 -0
- package/framework/types/composable.d.ts +14 -0
- package/framework/types/directives.d.ts +22 -0
- package/framework/types/index.d.ts +5 -4
- package/framework/types/signal.d.ts +15 -2
- package/index.html +137 -2
- package/package.json +1 -1
|
@@ -2,9 +2,77 @@ import { signal, effect, batch } from './signal.js'
|
|
|
2
2
|
import { evaluateExpression } from './evaluator.js'
|
|
3
3
|
import { compileNode } from './compiler.js'
|
|
4
4
|
|
|
5
|
+
const customDirectives = new Map()
|
|
6
|
+
|
|
7
|
+
export function directive(name, definition) {
|
|
8
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
9
|
+
console.error('[framework] directive() requires a non-empty name')
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
if (!definition || typeof definition !== 'object') {
|
|
13
|
+
console.error(`[framework] directive("${name}") requires a definition object`)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
customDirectives.set(name, definition)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function processCustomDirectives(el, context, effects) {
|
|
20
|
+
if (el.nodeType !== 1 || !el.hasAttribute || !el.attributes) return
|
|
21
|
+
|
|
22
|
+
for (const attr of [...el.attributes]) {
|
|
23
|
+
const match = attr.name.match(/^f-([a-zA-Z0-9-]+)(?::([a-zA-Z0-9-]+))?(?:\.(.+))?$/)
|
|
24
|
+
|
|
25
|
+
if (!match) continue
|
|
26
|
+
|
|
27
|
+
const directiveName = match[1]
|
|
28
|
+
const arg = match[2] || null
|
|
29
|
+
const modifiersStr = match[3]
|
|
30
|
+
const modifiers = modifiersStr ? modifiersStr.split('.') : []
|
|
31
|
+
|
|
32
|
+
const definition = customDirectives.get(directiveName)
|
|
33
|
+
if (!definition) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
el.removeAttribute(attr.name)
|
|
38
|
+
|
|
39
|
+
const value = evaluateExpression(attr.value, context)
|
|
40
|
+
const expression = attr.value
|
|
41
|
+
|
|
42
|
+
const binding = {
|
|
43
|
+
value,
|
|
44
|
+
expression,
|
|
45
|
+
arg,
|
|
46
|
+
modifiers
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof definition.mounted === 'function') {
|
|
50
|
+
definition.mounted(el, binding)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof definition.updated === 'function') {
|
|
54
|
+
effects.push(effect(() => {
|
|
55
|
+
const newValue = evaluateExpression(expression, context)
|
|
56
|
+
|
|
57
|
+
if (!Object.is(binding.value, newValue)) {
|
|
58
|
+
binding.value = newValue
|
|
59
|
+
definition.updated(el, binding)
|
|
60
|
+
}
|
|
61
|
+
}))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof definition.unmounted === 'function') {
|
|
65
|
+
effects.push(() => {
|
|
66
|
+
definition.unmounted(el, binding)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
5
72
|
export function processDirectives(root, context, components, effects) {
|
|
6
73
|
if (processFor(root, context, components, effects)) return true
|
|
7
74
|
if (processIf(root, context, components, effects)) return true
|
|
75
|
+
if (processIs(root, context, components, effects)) return true
|
|
8
76
|
|
|
9
77
|
processShow(root, context, effects)
|
|
10
78
|
processModel(root, context, effects)
|
|
@@ -12,6 +80,7 @@ export function processDirectives(root, context, components, effects) {
|
|
|
12
80
|
processHtml(root, context, effects)
|
|
13
81
|
processText(root, context, effects)
|
|
14
82
|
processEvents(root, context, effects)
|
|
83
|
+
processCustomDirectives(root, context, effects)
|
|
15
84
|
processCloak(root)
|
|
16
85
|
|
|
17
86
|
return false
|
|
@@ -275,6 +344,110 @@ export function processIf(el, context, components, effects) {
|
|
|
275
344
|
return true
|
|
276
345
|
}
|
|
277
346
|
|
|
347
|
+
export function processIs(el, context, components, effects) {
|
|
348
|
+
if (el.nodeType !== 1 || !el.hasAttribute('f-is')) return false
|
|
349
|
+
|
|
350
|
+
// 1. Prepare directive state
|
|
351
|
+
const expr = el.getAttribute('f-is')
|
|
352
|
+
const keepAlive = processKeepAlive(el)
|
|
353
|
+
|
|
354
|
+
el.removeAttribute('f-is')
|
|
355
|
+
|
|
356
|
+
const anchor = document.createComment('f-keepAlive-anchor')
|
|
357
|
+
el.parentNode.insertBefore(anchor, el)
|
|
358
|
+
|
|
359
|
+
// Keep a clone to reuse attributes (props/@events) for every dynamic instance
|
|
360
|
+
const templateNode = el.cloneNode(true)
|
|
361
|
+
el.remove()
|
|
362
|
+
|
|
363
|
+
const cache = new Map()
|
|
364
|
+
let activeKey = null
|
|
365
|
+
let activeCleanup = []
|
|
366
|
+
|
|
367
|
+
// 2. Reactive loop
|
|
368
|
+
effects.push(effect(() => {
|
|
369
|
+
const rawValue = evaluateExpression(expr, context)
|
|
370
|
+
|
|
371
|
+
// Skip if same component is already active
|
|
372
|
+
if (rawValue === activeKey) return
|
|
373
|
+
|
|
374
|
+
// 3. Handle exiting component
|
|
375
|
+
if (activeKey !== null) {
|
|
376
|
+
const currentEntry = cache.get(activeKey)
|
|
377
|
+
if (currentEntry) {
|
|
378
|
+
if (keepAlive) {
|
|
379
|
+
currentEntry.node.remove()
|
|
380
|
+
} else {
|
|
381
|
+
for (const cleanup of activeCleanup) if (typeof cleanup === 'function') cleanup()
|
|
382
|
+
currentEntry.node.remove()
|
|
383
|
+
cache.delete(activeKey)
|
|
384
|
+
activeCleanup = []
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!rawValue) {
|
|
390
|
+
activeKey = null
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 4. Handle entering component
|
|
395
|
+
let entry = cache.get(rawValue)
|
|
396
|
+
|
|
397
|
+
if (entry && keepAlive) {
|
|
398
|
+
// Restore from cache
|
|
399
|
+
anchor.parentNode.insertBefore(entry.node, anchor.nextSibling)
|
|
400
|
+
activeCleanup = entry.cleanup
|
|
401
|
+
} else {
|
|
402
|
+
// Create new instance
|
|
403
|
+
let ComponentFn = typeof rawValue === 'string' ? components[rawValue] : rawValue
|
|
404
|
+
|
|
405
|
+
if (typeof ComponentFn !== 'function') {
|
|
406
|
+
console.warn(`[framework] f-is: Component "${rawValue}" not found or invalid.`)
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const compEl = templateNode.cloneNode(true)
|
|
411
|
+
compEl._isDynamicComponent = true
|
|
412
|
+
|
|
413
|
+
const tempName = '__dynamic_component__'
|
|
414
|
+
compEl.setAttribute('data-component', tempName)
|
|
415
|
+
|
|
416
|
+
anchor.parentNode.insertBefore(compEl, anchor.nextSibling)
|
|
417
|
+
|
|
418
|
+
const childCleanup = []
|
|
419
|
+
const dynamicComponents = { ...components, [tempName]: ComponentFn }
|
|
420
|
+
|
|
421
|
+
// Trigger the internal compiler to bind props and events
|
|
422
|
+
compileNode(compEl, context, dynamicComponents, childCleanup)
|
|
423
|
+
|
|
424
|
+
entry = { node: compEl, cleanup: childCleanup }
|
|
425
|
+
cache.set(rawValue, entry)
|
|
426
|
+
activeCleanup = childCleanup
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
activeKey = rawValue
|
|
430
|
+
}))
|
|
431
|
+
|
|
432
|
+
// Cleanup all cached components when this directive is destroyed
|
|
433
|
+
effects.push(() => {
|
|
434
|
+
for (const entry of cache.values()) {
|
|
435
|
+
for (const c of entry.cleanup) if (typeof c === 'function') c()
|
|
436
|
+
entry.node.remove()
|
|
437
|
+
}
|
|
438
|
+
cache.clear()
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
return true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function processKeepAlive(el) {
|
|
445
|
+
if (el.nodeType !== 1) return false
|
|
446
|
+
const hasKeepAlive = el.hasAttribute('f-keepAlive')
|
|
447
|
+
if (hasKeepAlive) el.removeAttribute('f-keepAlive')
|
|
448
|
+
return hasKeepAlive
|
|
449
|
+
}
|
|
450
|
+
|
|
278
451
|
export function processShow(el, context, effects) {
|
|
279
452
|
if (el.nodeType !== 1 || !el.hasAttribute('f-show')) return
|
|
280
453
|
|
|
@@ -304,6 +477,7 @@ export function processHtml(el, context, effects) {
|
|
|
304
477
|
el.innerHTML = String(val ?? '')
|
|
305
478
|
}))
|
|
306
479
|
}
|
|
480
|
+
|
|
307
481
|
export function processEvents(el, context, effects) {
|
|
308
482
|
if (el.nodeType !== 1 || !el.attributes) return
|
|
309
483
|
|
|
@@ -317,6 +491,39 @@ export function processEvents(el, context, effects) {
|
|
|
317
491
|
|
|
318
492
|
el.removeAttribute(attr.name)
|
|
319
493
|
|
|
494
|
+
let timeoutId
|
|
495
|
+
let lastRun = 0
|
|
496
|
+
let throttleTimeoutId
|
|
497
|
+
|
|
498
|
+
let debounceMs = 0
|
|
499
|
+
let throttleMs = 0
|
|
500
|
+
|
|
501
|
+
const parseDuration = (idx) => {
|
|
502
|
+
const next = modifiers[idx + 1]
|
|
503
|
+
if (next && /^\d/.test(next)) {
|
|
504
|
+
if (next.endsWith('ms')) return parseInt(next) || 250
|
|
505
|
+
if (next.endsWith('s')) return (parseFloat(next) || 0.25) * 1000
|
|
506
|
+
}
|
|
507
|
+
return 250 // Default
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const debounceIdx = modifiers.indexOf('debounce')
|
|
511
|
+
if (debounceIdx > -1) debounceMs = parseDuration(debounceIdx)
|
|
512
|
+
|
|
513
|
+
const throttleIdx = modifiers.indexOf('throttle')
|
|
514
|
+
if (throttleIdx > -1) throttleMs = parseDuration(throttleIdx)
|
|
515
|
+
|
|
516
|
+
const executeLogic = (e) => {
|
|
517
|
+
batch(() => {
|
|
518
|
+
const potentialFn = context[expr]
|
|
519
|
+
if (typeof potentialFn === 'function' && !potentialFn.isSignal) {
|
|
520
|
+
potentialFn(e)
|
|
521
|
+
} else {
|
|
522
|
+
evaluateExpression(expr, context, { $event: e }, false)
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
|
|
320
527
|
const handler = (e) => {
|
|
321
528
|
if (modifiers.includes('prevent')) e.preventDefault()
|
|
322
529
|
if (modifiers.includes('stop')) e.stopPropagation()
|
|
@@ -336,14 +543,33 @@ export function processEvents(el, context, effects) {
|
|
|
336
543
|
}
|
|
337
544
|
}
|
|
338
545
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
546
|
+
// Throttle logic (ensures execution at most once every X ms)
|
|
547
|
+
if (throttleMs > 0) {
|
|
548
|
+
const now = Date.now()
|
|
549
|
+
const remaining = throttleMs - (now - lastRun)
|
|
550
|
+
|
|
551
|
+
if (remaining <= 0) {
|
|
552
|
+
clearTimeout(throttleTimeoutId)
|
|
553
|
+
lastRun = now
|
|
554
|
+
executeLogic(e)
|
|
343
555
|
} else {
|
|
344
|
-
|
|
556
|
+
// Standard throttle with trailing edge support
|
|
557
|
+
clearTimeout(throttleTimeoutId)
|
|
558
|
+
throttleTimeoutId = setTimeout(() => {
|
|
559
|
+
lastRun = Date.now()
|
|
560
|
+
executeLogic(e)
|
|
561
|
+
}, remaining)
|
|
345
562
|
}
|
|
346
|
-
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Debounce logic (waits for X ms of inactivity)
|
|
567
|
+
if (debounceMs > 0) {
|
|
568
|
+
clearTimeout(timeoutId)
|
|
569
|
+
timeoutId = setTimeout(() => executeLogic(e), debounceMs)
|
|
570
|
+
} else {
|
|
571
|
+
executeLogic(e)
|
|
572
|
+
}
|
|
347
573
|
}
|
|
348
574
|
|
|
349
575
|
const targetNode = modifiers.includes('window') ? window : (modifiers.includes('document') ? document : el)
|
|
@@ -356,6 +582,8 @@ export function processEvents(el, context, effects) {
|
|
|
356
582
|
if (effects) {
|
|
357
583
|
effects.push(() => {
|
|
358
584
|
targetNode.removeEventListener(eventName, handler, options)
|
|
585
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
586
|
+
if (throttleTimeoutId) clearTimeout(throttleTimeoutId)
|
|
359
587
|
})
|
|
360
588
|
}
|
|
361
589
|
}
|
package/framework/core/signal.js
CHANGED
|
@@ -51,6 +51,64 @@ export function signal(initialValue) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
accessor.isSignal = true
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(initialValue)) {
|
|
56
|
+
accessor.push = (...items) => {
|
|
57
|
+
const next = [...accessor(), ...items]
|
|
58
|
+
accessor(next)
|
|
59
|
+
return next.length
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
accessor.pop = () => {
|
|
63
|
+
const arr = accessor()
|
|
64
|
+
if (arr.length === 0) return undefined
|
|
65
|
+
const item = arr[arr.length - 1]
|
|
66
|
+
accessor(arr.slice(0, -1))
|
|
67
|
+
return item
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
accessor.shift = () => {
|
|
71
|
+
const arr = accessor()
|
|
72
|
+
if (arr.length === 0) return undefined
|
|
73
|
+
const item = arr[0]
|
|
74
|
+
accessor(arr.slice(1))
|
|
75
|
+
return item
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
accessor.unshift = (...items) => {
|
|
79
|
+
const next = [...items, ...accessor()]
|
|
80
|
+
accessor(next)
|
|
81
|
+
return next.length
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
accessor.splice = (start, deleteCount, ...items) => {
|
|
85
|
+
const arr = [...accessor()]
|
|
86
|
+
const removed = arr.splice(start, deleteCount, ...items)
|
|
87
|
+
accessor(arr)
|
|
88
|
+
return removed
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
accessor.remove = (predicate) => {
|
|
92
|
+
accessor(accessor().filter((item, i) => !predicate(item, i)))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
accessor.clear = () => {
|
|
96
|
+
accessor([])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
accessor.sort = (compareFn) => {
|
|
100
|
+
const next = [...accessor()].sort(compareFn)
|
|
101
|
+
accessor(next)
|
|
102
|
+
return accessor
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
accessor.reverse = () => {
|
|
106
|
+
const next = [...accessor()].reverse()
|
|
107
|
+
accessor(next)
|
|
108
|
+
return accessor
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
54
112
|
return accessor
|
|
55
113
|
}
|
|
56
114
|
|
package/framework/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// ─── Framework Public API ─────────────────────────────────────────────────────
|
|
2
2
|
export { signal, computed, effect, batch, untrack, inspect } from './core/signal.js'
|
|
3
|
-
export { defineComponent, onMount, onUnmount, parseSlots, provide, inject } from './core/component.js'
|
|
3
|
+
export { defineComponent, defineAsyncComponent, onMount, onUnmount, parseSlots, provide, inject, getCurrentInstance } from './core/component.js'
|
|
4
4
|
export { createRouter, navigate, mountOutlet } from './router/router.js'
|
|
5
5
|
export { mountTemplate } from './core/compiler.js'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
export { defineComposable, assertSetupContext } from './core/composable.js'
|
|
8
|
+
export { directive } from './core/directives.js'
|
|
9
|
+
|
|
8
10
|
export const emit = () => console.warn('[framework] emit() can only be used inside component setup()')
|
|
@@ -70,3 +70,14 @@ export declare function inject<T>(key: string): T | null
|
|
|
70
70
|
export declare function defineComponent<TProps = EmptyProps>(
|
|
71
71
|
options: ComponentOptions<TProps>
|
|
72
72
|
): ComponentFactory<TProps>
|
|
73
|
+
|
|
74
|
+
export type AsyncComponentLoader<TProps = ComponentProps> = () => Promise<ComponentFactory<TProps> | { default: ComponentFactory<TProps> } | Record<string, any>>
|
|
75
|
+
|
|
76
|
+
export type AsyncComponentOptions<TProps = ComponentProps> = {
|
|
77
|
+
loader: AsyncComponentLoader<TProps>
|
|
78
|
+
loadingComponent?: ComponentFactory<any>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export declare function defineAsyncComponent<TProps = ComponentProps>(
|
|
82
|
+
loaderOrOptions: AsyncComponentLoader<TProps> | AsyncComponentOptions<TProps>
|
|
83
|
+
): ComponentFactory<TProps>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Composable<T = any> {
|
|
2
|
+
(...args: any[]): T
|
|
3
|
+
readonly _isComposable: true
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Composable<T = any> {
|
|
8
|
+
(...args: any[]): T
|
|
9
|
+
readonly _isComposable: true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function defineComposable<T>(fn: (...args: any[]) => T): Composable<T>
|
|
14
|
+
export function assertSetupContext(name?: string): any
|
|
@@ -25,8 +25,30 @@ export function processIf(
|
|
|
25
25
|
effects: (() => void)[]
|
|
26
26
|
): void
|
|
27
27
|
|
|
28
|
+
export function processIs(
|
|
29
|
+
root: HTMLElement,
|
|
30
|
+
context: ComponentResult,
|
|
31
|
+
components: Record<string, ComponentFactory>,
|
|
32
|
+
effects: (() => void)[]
|
|
33
|
+
): void
|
|
34
|
+
|
|
28
35
|
export function processShow(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
29
36
|
export function processHtml(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
30
37
|
export function processText(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
31
38
|
export function processEvents(root: HTMLElement, context: ComponentResult): void
|
|
32
39
|
export function processCloak(root: HTMLElement): void
|
|
40
|
+
|
|
41
|
+
export interface DirectiveBinding<V = any> {
|
|
42
|
+
value: V
|
|
43
|
+
expression: string
|
|
44
|
+
arg: string | null
|
|
45
|
+
modifiers: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DirectiveHooks<V = any> {
|
|
49
|
+
mounted?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
50
|
+
updated?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
51
|
+
unmounted?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function directive<V = any>(name: string, hooks: DirectiveHooks<V>): void
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
// ─── Fusée Framework Types ───────────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
// Public API
|
|
4
1
|
export * from './signal'
|
|
5
2
|
export * from './component'
|
|
3
|
+
export * from './composable'
|
|
6
4
|
export * from './router'
|
|
7
5
|
export * from './compiler'
|
|
6
|
+
export * from './directives'
|
|
8
7
|
|
|
9
8
|
declare global {
|
|
10
9
|
type Signal<T = any> = import('./signal').Signal<T>
|
|
11
10
|
type Computed<T = any> = import('./signal').Computed<T>
|
|
12
11
|
type SignalAccessor<T> = import('./signal').SignalAccessor<T>
|
|
12
|
+
type Composable<T extends (...args: any[]) => any> = import('./composable').Composable<T>
|
|
13
|
+
type DirectiveBinding<V = any> = import('./directives').DirectiveBinding<V>
|
|
14
|
+
type DirectiveHooks<V = any> = import('./directives').DirectiveHooks<V>
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
// Re-export as namespace for convenience
|
|
16
17
|
export as namespace Fusee
|
|
@@ -6,13 +6,25 @@ export type SignalAccessor<T> = {
|
|
|
6
6
|
isSignal: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export type ArraySignalAccessor<T extends any[]> = SignalAccessor<T> & {
|
|
10
|
+
push(...items: T[number][]): number;
|
|
11
|
+
pop(): T[number] | undefined;
|
|
12
|
+
shift(): T[number] | undefined;
|
|
13
|
+
unshift(...items: T[number][]): number;
|
|
14
|
+
splice(start: number, deleteCount: number, ...items: T[number][]): T[number][];
|
|
15
|
+
remove(predicate: (item: T[number], index: number) => boolean): void;
|
|
16
|
+
clear(): void;
|
|
17
|
+
sort(compareFn?: (a: T[number], b: T[number]) => number): ArraySignalAccessor<T>;
|
|
18
|
+
reverse(): ArraySignalAccessor<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
export type ComputedAccessor<T> = {
|
|
10
22
|
(): T;
|
|
11
23
|
isSignal: boolean;
|
|
12
24
|
readonly: true;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
|
-
export type Signal<T = any> = SignalAccessor<T>
|
|
27
|
+
export type Signal<T = any> = T extends any[] ? ArraySignalAccessor<T> : SignalAccessor<T>
|
|
16
28
|
export type Computed<T = any> = ComputedAccessor<T>
|
|
17
29
|
|
|
18
30
|
type EffectRunner = {
|
|
@@ -20,7 +32,8 @@ type EffectRunner = {
|
|
|
20
32
|
deps: Set<Set<EffectRunner>>;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
export declare function signal<T>(initialValue: T):
|
|
35
|
+
export declare function signal<T extends any[]>(initialValue: T): ArraySignalAccessor<T>
|
|
36
|
+
export declare function signal<T>(initialValue: T): SignalAccessor<T>
|
|
24
37
|
export declare function effect(fn: () => void): () => void
|
|
25
38
|
export declare function computed<T>(fn: () => T): Computed<T>
|
|
26
39
|
export declare function batch<T>(fn: () => T): T
|
package/index.html
CHANGED
|
@@ -160,6 +160,139 @@
|
|
|
160
160
|
opacity: 0.3;
|
|
161
161
|
cursor: not-allowed;
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
/* ── Composables demo ──────────────────────────────────────────── */
|
|
165
|
+
.demo-card {
|
|
166
|
+
background: #13131d;
|
|
167
|
+
border: 1px solid #2a2a3a;
|
|
168
|
+
border-radius: 12px;
|
|
169
|
+
padding: 1.5rem;
|
|
170
|
+
margin-bottom: 1.5rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.demo-card h2 {
|
|
174
|
+
font-size: 1rem;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
color: #e8e8f0;
|
|
177
|
+
margin-bottom: 0.35rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.demo-desc {
|
|
181
|
+
font-size: 0.8rem;
|
|
182
|
+
color: #555 !important;
|
|
183
|
+
margin-bottom: 1rem !important;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.counter-display {
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
align-items: center;
|
|
190
|
+
margin-bottom: 1rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.big-num {
|
|
194
|
+
font-size: 3.5rem;
|
|
195
|
+
font-weight: 700;
|
|
196
|
+
line-height: 1;
|
|
197
|
+
transition: color 0.2s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.big-num.even {
|
|
201
|
+
color: #8b8bff;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.big-num.odd {
|
|
205
|
+
color: #ff3333;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.derived-label {
|
|
209
|
+
font-size: 0.8rem;
|
|
210
|
+
color: #555;
|
|
211
|
+
margin-top: 0.25rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.btn-row {
|
|
215
|
+
display: flex;
|
|
216
|
+
gap: 0.5rem;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
margin-bottom: 0.75rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.cmp-btn {
|
|
222
|
+
padding: 0.45rem 1.1rem;
|
|
223
|
+
border-radius: 6px;
|
|
224
|
+
border: 1px solid #2a2a3a;
|
|
225
|
+
background: #0f0f1a;
|
|
226
|
+
color: #e8e8f0;
|
|
227
|
+
cursor: pointer;
|
|
228
|
+
font-size: 1rem;
|
|
229
|
+
transition: background 0.15s;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.cmp-btn:hover {
|
|
233
|
+
background: #1f1f30;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.cmp-btn.plus {
|
|
237
|
+
border-color: #8b8bff;
|
|
238
|
+
color: #8b8bff;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.cmp-btn.minus {
|
|
242
|
+
border-color: #ff3333;
|
|
243
|
+
color: #ff3333;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cmp-btn.reset {
|
|
247
|
+
border-color: #555;
|
|
248
|
+
color: #888;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.badge {
|
|
252
|
+
display: inline-block;
|
|
253
|
+
padding: 0.2rem 0.65rem;
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
font-size: 0.75rem;
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.badge-blue {
|
|
260
|
+
background: rgba(139, 139, 255, 0.15);
|
|
261
|
+
color: #8b8bff;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.badge-red {
|
|
265
|
+
background: rgba(255, 51, 51, 0.12);
|
|
266
|
+
color: #ff3333;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.mouse-display {
|
|
270
|
+
display: flex;
|
|
271
|
+
gap: 2rem;
|
|
272
|
+
font-size: 1.1rem;
|
|
273
|
+
color: #aaa;
|
|
274
|
+
margin-bottom: 0.5rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.mouse-display strong {
|
|
278
|
+
color: #e8e8f0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.cmp-input {
|
|
282
|
+
width: 100%;
|
|
283
|
+
padding: 0.5rem 0.75rem;
|
|
284
|
+
background: #0f0f1a;
|
|
285
|
+
border: 1px solid #2a2a3a;
|
|
286
|
+
border-radius: 6px;
|
|
287
|
+
color: #e8e8f0;
|
|
288
|
+
font-size: 0.95rem;
|
|
289
|
+
outline: none;
|
|
290
|
+
transition: border-color 0.15s;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.cmp-input:focus {
|
|
294
|
+
border-color: #8b8bff;
|
|
295
|
+
}
|
|
163
296
|
</style>
|
|
164
297
|
</head>
|
|
165
298
|
|
|
@@ -167,10 +300,12 @@
|
|
|
167
300
|
<nav>
|
|
168
301
|
<a href="#/">Home</a>
|
|
169
302
|
<a href="#/about">About</a>
|
|
170
|
-
<a href="#/test-for">Test
|
|
303
|
+
<a href="#/test-for">Loops Test</a>
|
|
304
|
+
<a href="#/dynamic" style="color: #ec4899;">Dynamic Components</a>
|
|
305
|
+
<a href="#/composables" style="color: #34d399;">Composables</a>
|
|
171
306
|
</nav>
|
|
172
307
|
<div id="app"></div>
|
|
173
|
-
<script type="module" src="./app/main.
|
|
308
|
+
<script type="module" src="./app/main.ts"></script>
|
|
174
309
|
</body>
|
|
175
310
|
|
|
176
311
|
</html>
|