create-nativecore 0.1.0 → 0.2.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 +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signals - Fine-Grained Reactivity System
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface Signal<T> {
|
|
6
|
+
get(): T;
|
|
7
|
+
set(value: T): void;
|
|
8
|
+
subscribe(fn: () => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SignalTracker {
|
|
12
|
+
accessed: Set<Signal<any>>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let currentTracker: SignalTracker | null = null;
|
|
16
|
+
|
|
17
|
+
export function createSignal<T>(initialValue: T): Signal<T> {
|
|
18
|
+
let value = initialValue;
|
|
19
|
+
const subscribers = new Set<() => void>();
|
|
20
|
+
|
|
21
|
+
const signal: Signal<T> = {
|
|
22
|
+
get(): T {
|
|
23
|
+
if (currentTracker) {
|
|
24
|
+
currentTracker.accessed.add(signal);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
},
|
|
28
|
+
set(nextValue: T): void {
|
|
29
|
+
if (Object.is(value, nextValue)) return;
|
|
30
|
+
value = nextValue;
|
|
31
|
+
subscribers.forEach(fn => fn());
|
|
32
|
+
},
|
|
33
|
+
subscribe(callback: () => void): () => void {
|
|
34
|
+
subscribers.add(callback);
|
|
35
|
+
return () => subscribers.delete(callback);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return signal;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useState<T>(
|
|
43
|
+
initialValue: T,
|
|
44
|
+
onChange?: (value: T) => void
|
|
45
|
+
): [() => T, (value: T) => void] {
|
|
46
|
+
const signal = createSignal(initialValue);
|
|
47
|
+
|
|
48
|
+
if (onChange) {
|
|
49
|
+
signal.subscribe(() => onChange(signal.get()));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return [
|
|
53
|
+
() => signal.get(),
|
|
54
|
+
(value: T) => signal.set(value)
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createComputed<T>(computeFn: () => T): () => T {
|
|
59
|
+
let value = undefined as T;
|
|
60
|
+
const trackedDeps = new Set<Signal<any>>();
|
|
61
|
+
const unsubscribers = new Map<Signal<any>, () => void>();
|
|
62
|
+
const tracker: SignalTracker = {
|
|
63
|
+
accessed: new Set()
|
|
64
|
+
};
|
|
65
|
+
let isComputing = false;
|
|
66
|
+
|
|
67
|
+
recompute();
|
|
68
|
+
|
|
69
|
+
return () => value;
|
|
70
|
+
|
|
71
|
+
function recompute(): void {
|
|
72
|
+
if (isComputing) return;
|
|
73
|
+
isComputing = true;
|
|
74
|
+
|
|
75
|
+
tracker.accessed.clear();
|
|
76
|
+
currentTracker = tracker;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
value = computeFn();
|
|
80
|
+
} finally {
|
|
81
|
+
currentTracker = null;
|
|
82
|
+
isComputing = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
syncTrackedSignals(trackedDeps, tracker.accessed, unsubscribers, recompute);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createEffect(effectFn: () => void | (() => void)): () => void {
|
|
90
|
+
let cleanup: void | (() => void);
|
|
91
|
+
const trackedDeps = new Set<Signal<any>>();
|
|
92
|
+
const unsubscribers = new Map<Signal<any>, () => void>();
|
|
93
|
+
const tracker: SignalTracker = {
|
|
94
|
+
accessed: new Set()
|
|
95
|
+
};
|
|
96
|
+
let isRunning = false;
|
|
97
|
+
|
|
98
|
+
execute();
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
if (typeof cleanup === 'function') cleanup();
|
|
102
|
+
unsubscribers.forEach(unsub => unsub());
|
|
103
|
+
unsubscribers.clear();
|
|
104
|
+
trackedDeps.clear();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function execute(): void {
|
|
108
|
+
if (isRunning) return;
|
|
109
|
+
isRunning = true;
|
|
110
|
+
|
|
111
|
+
tracker.accessed.clear();
|
|
112
|
+
currentTracker = tracker;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
if (typeof cleanup === 'function') cleanup();
|
|
116
|
+
cleanup = effectFn();
|
|
117
|
+
} finally {
|
|
118
|
+
currentTracker = null;
|
|
119
|
+
isRunning = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
syncTrackedSignals(trackedDeps, tracker.accessed, unsubscribers, execute);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function syncTrackedSignals(
|
|
127
|
+
trackedDeps: Set<Signal<any>>,
|
|
128
|
+
nextDeps: Set<Signal<any>>,
|
|
129
|
+
unsubscribers: Map<Signal<any>, () => void>,
|
|
130
|
+
onDependencyChange: () => void
|
|
131
|
+
): void {
|
|
132
|
+
trackedDeps.forEach(dep => {
|
|
133
|
+
if (!nextDeps.has(dep)) {
|
|
134
|
+
unsubscribers.get(dep)?.();
|
|
135
|
+
unsubscribers.delete(dep);
|
|
136
|
+
trackedDeps.delete(dep);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
nextDeps.forEach(dep => {
|
|
141
|
+
if (!trackedDeps.has(dep)) {
|
|
142
|
+
trackedDeps.add(dep);
|
|
143
|
+
unsubscribers.set(dep, dep.subscribe(onDependencyChange));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Management - React-like useState API
|
|
3
|
+
* Provides intuitive reactive state management for components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export interface State<T> {
|
|
8
|
+
value: T;
|
|
9
|
+
set(value: T | ((prev: T) => T)): void;
|
|
10
|
+
watch(callback: (value: T) => void): () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ComputedState<T> {
|
|
14
|
+
readonly value: T;
|
|
15
|
+
watch(callback: (value: T) => void): () => void;
|
|
16
|
+
/** Release all dependency subscriptions. Call when the computed is no longer needed. */
|
|
17
|
+
dispose(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type EffectCleanup = void | (() => void);
|
|
21
|
+
export type EffectCallback = () => EffectCleanup;
|
|
22
|
+
|
|
23
|
+
interface Watchable<T = any> {
|
|
24
|
+
watch(callback: (value: T) => void): () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Tracker {
|
|
28
|
+
accessed: Set<Watchable<unknown>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Unsubscribe = () => void;
|
|
32
|
+
|
|
33
|
+
// Global tracker for dependency collection
|
|
34
|
+
let currentTracker: Tracker | null = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a reactive state (React-like useState)
|
|
38
|
+
*/
|
|
39
|
+
function createState<T>(initialValue: T): State<T> {
|
|
40
|
+
let _value = initialValue;
|
|
41
|
+
const subscribers = new Set<() => void>();
|
|
42
|
+
|
|
43
|
+
const state = {
|
|
44
|
+
// Getter/setter for value property
|
|
45
|
+
get value(): T {
|
|
46
|
+
return _value;
|
|
47
|
+
},
|
|
48
|
+
set value(newValue: T) {
|
|
49
|
+
if (Object.is(_value, newValue)) return;
|
|
50
|
+
_value = newValue;
|
|
51
|
+
notify();
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// Set method (supports updater function like React)
|
|
55
|
+
set(valueOrUpdater: T | ((prev: T) => T)): void {
|
|
56
|
+
const newValue = typeof valueOrUpdater === 'function'
|
|
57
|
+
? (valueOrUpdater as (prev: T) => T)(_value)
|
|
58
|
+
: valueOrUpdater;
|
|
59
|
+
|
|
60
|
+
if (Object.is(_value, newValue)) return;
|
|
61
|
+
_value = newValue;
|
|
62
|
+
notify();
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Watch for changes (like useEffect watching a dependency)
|
|
66
|
+
watch(callback: (value: T) => void): Unsubscribe {
|
|
67
|
+
const wrappedCallback = () => callback(_value);
|
|
68
|
+
subscribers.add(wrappedCallback);
|
|
69
|
+
|
|
70
|
+
// Return unsubscribe function
|
|
71
|
+
return () => subscribers.delete(wrappedCallback);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function notify(): void {
|
|
76
|
+
subscribers.forEach(fn => fn());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Enhanced useState that tracks accesses for computed values
|
|
84
|
+
*/
|
|
85
|
+
export function useState<T>(initialValue: T): State<T> {
|
|
86
|
+
const state = createState(initialValue);
|
|
87
|
+
|
|
88
|
+
// Wrap the getter to track accesses
|
|
89
|
+
const trackedState: State<T> = {
|
|
90
|
+
get value(): T {
|
|
91
|
+
// If we're inside a computed, track this dependency
|
|
92
|
+
if (currentTracker) {
|
|
93
|
+
currentTracker.accessed.add(state);
|
|
94
|
+
}
|
|
95
|
+
return state.value;
|
|
96
|
+
},
|
|
97
|
+
set value(newValue: T) {
|
|
98
|
+
state.value = newValue;
|
|
99
|
+
},
|
|
100
|
+
set: state.set.bind(state),
|
|
101
|
+
watch: state.watch.bind(state)
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return trackedState;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create multiple states at once
|
|
109
|
+
*/
|
|
110
|
+
export function createStates<T extends Record<string, any>>(
|
|
111
|
+
initialStates: T
|
|
112
|
+
): { [K in keyof T]: State<T[K]> } {
|
|
113
|
+
const states: any = {};
|
|
114
|
+
for (const [key, value] of Object.entries(initialStates)) {
|
|
115
|
+
states[key] = useState(value);
|
|
116
|
+
}
|
|
117
|
+
return states;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a computed/derived state from other states
|
|
122
|
+
* Automatically tracks dependencies and recalculates when they change
|
|
123
|
+
*/
|
|
124
|
+
export function computed<T>(computeFn: () => T): ComputedState<T> {
|
|
125
|
+
const derivedState = createState<T>(undefined as T);
|
|
126
|
+
const trackedDeps = new Set<Watchable<any>>();
|
|
127
|
+
const depUnsubscribers = new Map<Watchable<any>, () => void>();
|
|
128
|
+
let isComputing = false;
|
|
129
|
+
|
|
130
|
+
// Create a proxy to track which states are accessed
|
|
131
|
+
const tracker: Tracker = {
|
|
132
|
+
accessed: new Set()
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Initial computation
|
|
136
|
+
recompute();
|
|
137
|
+
|
|
138
|
+
function recompute(): void {
|
|
139
|
+
if (isComputing) return; // Prevent infinite loops
|
|
140
|
+
isComputing = true;
|
|
141
|
+
|
|
142
|
+
tracker.accessed.clear();
|
|
143
|
+
currentTracker = tracker;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const newValue = computeFn();
|
|
147
|
+
derivedState.value = newValue;
|
|
148
|
+
} finally {
|
|
149
|
+
currentTracker = null;
|
|
150
|
+
isComputing = false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
syncTrackedDependencies(trackedDeps, tracker.accessed, depUnsubscribers, recompute);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function dispose(): void {
|
|
157
|
+
depUnsubscribers.forEach(unsub => unsub());
|
|
158
|
+
depUnsubscribers.clear();
|
|
159
|
+
trackedDeps.clear();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
get value(): T {
|
|
164
|
+
// Track this access if we're inside another computed
|
|
165
|
+
if (currentTracker) {
|
|
166
|
+
currentTracker.accessed.add(derivedState);
|
|
167
|
+
}
|
|
168
|
+
return derivedState.value;
|
|
169
|
+
},
|
|
170
|
+
watch: derivedState.watch.bind(derivedState),
|
|
171
|
+
dispose,
|
|
172
|
+
// Read-only, no setter
|
|
173
|
+
set value(_: T) {
|
|
174
|
+
console.warn('Computed values are read-only. Cannot set value directly.');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Run a reactive side effect that automatically tracks accessed state dependencies.
|
|
181
|
+
*
|
|
182
|
+
* @param effectFn Effect callback that can optionally return a cleanup function.
|
|
183
|
+
* @returns A disposer that unsubscribes all tracked dependencies and runs the latest cleanup.
|
|
184
|
+
*/
|
|
185
|
+
export function effect(effectFn: EffectCallback): () => void {
|
|
186
|
+
const trackedDeps = new Set<Watchable<any>>();
|
|
187
|
+
const depUnsubscribers = new Map<Watchable<any>, () => void>();
|
|
188
|
+
const tracker: Tracker = {
|
|
189
|
+
accessed: new Set()
|
|
190
|
+
};
|
|
191
|
+
let cleanup: EffectCleanup;
|
|
192
|
+
let isRunning = false;
|
|
193
|
+
|
|
194
|
+
runEffect();
|
|
195
|
+
|
|
196
|
+
return () => {
|
|
197
|
+
if (typeof cleanup === 'function') {
|
|
198
|
+
cleanup();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
depUnsubscribers.forEach(unsub => unsub());
|
|
202
|
+
depUnsubscribers.clear();
|
|
203
|
+
trackedDeps.clear();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
function runEffect(): void {
|
|
207
|
+
if (isRunning) return;
|
|
208
|
+
isRunning = true;
|
|
209
|
+
|
|
210
|
+
tracker.accessed.clear();
|
|
211
|
+
currentTracker = tracker;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
if (typeof cleanup === 'function') {
|
|
215
|
+
cleanup();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
cleanup = effectFn();
|
|
219
|
+
} finally {
|
|
220
|
+
currentTracker = null;
|
|
221
|
+
isRunning = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
syncTrackedDependencies(trackedDeps, tracker.accessed, depUnsubscribers, runEffect);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function syncTrackedDependencies(
|
|
229
|
+
trackedDeps: Set<Watchable<any>>,
|
|
230
|
+
nextDeps: Set<Watchable<any>>,
|
|
231
|
+
depUnsubscribers: Map<Watchable<any>, () => void>,
|
|
232
|
+
onDependencyChange: () => void
|
|
233
|
+
): void {
|
|
234
|
+
trackedDeps.forEach(dep => {
|
|
235
|
+
if (!nextDeps.has(dep)) {
|
|
236
|
+
depUnsubscribers.get(dep)?.();
|
|
237
|
+
depUnsubscribers.delete(dep);
|
|
238
|
+
trackedDeps.delete(dep);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
nextDeps.forEach(dep => {
|
|
243
|
+
if (!trackedDeps.has(dep)) {
|
|
244
|
+
trackedDeps.add(dep);
|
|
245
|
+
depUnsubscribers.set(dep, dep.watch(() => onDependencyChange()));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|