@mulanjs/mulanjs 1.0.1-dev.20260212143840
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/compiler/compiler.js +90 -0
- package/dist/compiler/script-compiler.js +314 -0
- package/dist/compiler/sfc-parser.js +93 -0
- package/dist/compiler/style-compiler.js +56 -0
- package/dist/compiler/template-compiler.js +442 -0
- package/dist/components/bloch-sphere.js +252 -0
- package/dist/core/component.js +145 -0
- package/dist/core/hooks.js +229 -0
- package/dist/core/quantum.js +284 -0
- package/dist/core/query.js +63 -0
- package/dist/core/reactive.js +105 -0
- package/dist/core/renderer.js +70 -0
- package/dist/core/vault.js +81 -0
- package/dist/index.js +52 -0
- package/dist/mulan.esm.js +1948 -0
- package/dist/mulan.js +215 -0
- package/dist/router/index.js +210 -0
- package/dist/security/sanitizer.js +47 -0
- package/dist/store/index.js +42 -0
- package/dist/types/compiler/compiler.d.ts +7 -0
- package/dist/types/compiler/script-compiler.d.ts +8 -0
- package/dist/types/compiler/sfc-parser.d.ts +21 -0
- package/dist/types/compiler/style-compiler.d.ts +7 -0
- package/dist/types/compiler/template-compiler.d.ts +7 -0
- package/dist/types/compiler.d.ts +7 -0
- package/dist/types/components/bloch-sphere.d.ts +16 -0
- package/dist/types/core/component.d.ts +54 -0
- package/dist/types/core/hooks.d.ts +49 -0
- package/dist/types/core/quantum.d.ts +50 -0
- package/dist/types/core/query.d.ts +14 -0
- package/dist/types/core/reactive.d.ts +21 -0
- package/dist/types/core/renderer.d.ts +4 -0
- package/dist/types/core/vault.d.ts +12 -0
- package/dist/types/index.d.ts +70 -0
- package/dist/types/router/index.d.ts +24 -0
- package/dist/types/script-compiler.d.ts +8 -0
- package/dist/types/security/sanitizer.d.ts +17 -0
- package/dist/types/sfc-parser.d.ts +21 -0
- package/dist/types/store/index.d.ts +10 -0
- package/dist/types/style-compiler.d.ts +7 -0
- package/dist/types/template-compiler.d.ts +7 -0
- package/package.json +64 -0
- package/src/cli/extensions/mulanjs-vscode-1.0.0.vsix +0 -0
- package/src/cli/index.js +600 -0
- package/src/compiler/compiler.ts +102 -0
- package/src/compiler/script-compiler.ts +336 -0
- package/src/compiler/sfc-parser.ts +118 -0
- package/src/compiler/style-compiler.ts +66 -0
- package/src/compiler/template-compiler.ts +519 -0
- package/src/compiler/tsconfig.json +13 -0
- package/src/loader/index.js +81 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { effect } from '../core/reactive';
|
|
2
|
+
export class MuBlochSphereElement extends HTMLElement {
|
|
3
|
+
static get observedAttributes() {
|
|
4
|
+
return ['size'];
|
|
5
|
+
}
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this._arrow = null;
|
|
9
|
+
this._container = null;
|
|
10
|
+
this._disposeEffect = null;
|
|
11
|
+
this.attachShadow({ mode: 'open' });
|
|
12
|
+
}
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
this.render();
|
|
15
|
+
}
|
|
16
|
+
disconnectedCallback() {
|
|
17
|
+
if (this._disposeEffect) {
|
|
18
|
+
this._disposeEffect();
|
|
19
|
+
this._disposeEffect = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Property setter for 'qubit' (passed as .qubit="${q}" in Mulan)
|
|
23
|
+
set qubit(val) {
|
|
24
|
+
this._qubit = val;
|
|
25
|
+
this.initReactivity();
|
|
26
|
+
}
|
|
27
|
+
get qubit() {
|
|
28
|
+
return this._qubit;
|
|
29
|
+
}
|
|
30
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
31
|
+
if (name === 'size' && this._container) {
|
|
32
|
+
this._container.style.width = newValue + 'px';
|
|
33
|
+
this._container.style.height = newValue + 'px';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
initReactivity() {
|
|
37
|
+
if (this._disposeEffect)
|
|
38
|
+
this._disposeEffect();
|
|
39
|
+
if (!this._qubit)
|
|
40
|
+
return;
|
|
41
|
+
// MulanJS Effect: Run whenever the qubit state changes
|
|
42
|
+
this._disposeEffect = effect(() => {
|
|
43
|
+
const state = this._qubit.value; // Access reactive proxy
|
|
44
|
+
if (!state || !state.amplitudes)
|
|
45
|
+
return;
|
|
46
|
+
// Assume Single Qubit for Visualizer (Index 0 if passed a register)
|
|
47
|
+
// If passed a register, we visualize the first qubit's logical projection (partial trace simplified)
|
|
48
|
+
// Or assume input IS a specific qubit projection?
|
|
49
|
+
// For simplified demo, we assume the register is size 1 OR we visualize Q0 of the register.
|
|
50
|
+
const amps = state.amplitudes;
|
|
51
|
+
// Support 1-qubit visualization from N-qubit register requires partial trace.
|
|
52
|
+
// For now, let's assume the user passes a 1-qubit register OR we visualize index 0.
|
|
53
|
+
// |psi> = a|0> + b|1>
|
|
54
|
+
// a = amps[0] (re, im)
|
|
55
|
+
// b = amps[1] (re, im)
|
|
56
|
+
// Dealing with multi-qubit registers (naive projection for visualization):
|
|
57
|
+
// We sum up probabilities for 0xxxx vs 1xxxx to get Z-axis.
|
|
58
|
+
// This is "marginal probability".
|
|
59
|
+
let p0 = 0;
|
|
60
|
+
let p1 = 0;
|
|
61
|
+
// Calculate Probabilities
|
|
62
|
+
for (let i = 0; i < amps.length; i++) {
|
|
63
|
+
const mag = amps[i].re * amps[i].re + amps[i].im * amps[i].im;
|
|
64
|
+
if ((i & 1) === 0)
|
|
65
|
+
p0 += mag;
|
|
66
|
+
else
|
|
67
|
+
p1 += mag;
|
|
68
|
+
}
|
|
69
|
+
// Theta from Z-projection
|
|
70
|
+
// P0 = cos^2(theta/2) -> theta = 2 * acos(sqrt(P0))
|
|
71
|
+
const theta = 2 * Math.acos(Math.min(1, Math.sqrt(p0)));
|
|
72
|
+
// Phi?
|
|
73
|
+
// Phase is relative phase between |0> and |1>.
|
|
74
|
+
// We can look at the phase of the '1' component relative to '0'.
|
|
75
|
+
// Simple approach: Look at amps[1] phase vs amps[0] phase.
|
|
76
|
+
// But with entanglement, pure state phase is tricky.
|
|
77
|
+
// Let's implement full density matrix if needed, but for now:
|
|
78
|
+
// naive: atan2(amps[1].im, amps[1].re) - atan2(amps[0].im, amps[0].re)
|
|
79
|
+
// We use the first pair (0 and 1) as proxy if multiple.
|
|
80
|
+
const a0 = amps[0];
|
|
81
|
+
const a1 = amps[1]; // Valid for N>=1
|
|
82
|
+
const phase0 = Math.atan2(a0.im, a0.re);
|
|
83
|
+
const phase1 = Math.atan2(a1.im, a1.re);
|
|
84
|
+
let phi = phase1 - phase0;
|
|
85
|
+
// Update Arrow
|
|
86
|
+
this.updateArrow(theta, phi);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
updateArrow(theta, phi) {
|
|
90
|
+
if (!this._arrow)
|
|
91
|
+
return;
|
|
92
|
+
// Convert Quantum Coords (Theta, Phi) to CSS Transforms
|
|
93
|
+
// Theta 0 = Top (|0>)
|
|
94
|
+
// Theta PI = Bottom (|1>)
|
|
95
|
+
// Theta PI/2 = Equator
|
|
96
|
+
// CSS Rotate sequence:
|
|
97
|
+
// 1. Start pointing UP (Y or Z axis in CSS?)
|
|
98
|
+
// Let's say Arrow starts pointing UP (Y-).
|
|
99
|
+
// Rotate Z by Phi (Azimuth)
|
|
100
|
+
// Rotate X by Theta (Polar) - No, that's not standard Euler.
|
|
101
|
+
// Standard Physics:
|
|
102
|
+
// Z is Up (in Bloch), but CSS 3D Y is usually 'Down' or 'Up'.
|
|
103
|
+
// Let's Map:
|
|
104
|
+
// Bloch Z+ (|0>) -> CSS Y- (Top)
|
|
105
|
+
// Bloch Z- (|1>) -> CSS Y+ (Bottom)
|
|
106
|
+
// Bloch X+ (|+>) -> CSS Z+ (Front)
|
|
107
|
+
// Transform:
|
|
108
|
+
// rotateY(phi) ? No, phi rotates around Z-axis (Vertical).
|
|
109
|
+
// theta rotates from Z-axis down.
|
|
110
|
+
// CSS:
|
|
111
|
+
// rotateY(phi) -> Rotates around Vertical axis.
|
|
112
|
+
// rotateZ(theta) -> Rotates "down" from up?
|
|
113
|
+
// Let's simplify:
|
|
114
|
+
// transform: rotateY(${phi}rad) rotateZ(${theta}rad)
|
|
115
|
+
// Note: CSS rotations order matters.
|
|
116
|
+
const degTheta = theta * (180 / Math.PI);
|
|
117
|
+
const degPhi = phi * (180 / Math.PI);
|
|
118
|
+
// Adjustment for visual alignment
|
|
119
|
+
this._arrow.style.transform = `rotateY(${degPhi}deg) rotateZ(${degTheta}deg)`;
|
|
120
|
+
// Color based on state
|
|
121
|
+
const isSuperposition = Math.abs(theta - Math.PI / 2) < 0.1;
|
|
122
|
+
this._arrow.style.backgroundColor = isSuperposition ? '#00ffff' : '#ff00ff';
|
|
123
|
+
}
|
|
124
|
+
render() {
|
|
125
|
+
if (!this.shadowRoot)
|
|
126
|
+
return;
|
|
127
|
+
const size = this.getAttribute('size') || '200';
|
|
128
|
+
this.shadowRoot.innerHTML = `
|
|
129
|
+
<style>
|
|
130
|
+
:host {
|
|
131
|
+
display: inline-block;
|
|
132
|
+
perspective: 1000px;
|
|
133
|
+
}
|
|
134
|
+
.sphere-container {
|
|
135
|
+
width: ${size}px;
|
|
136
|
+
height: ${size}px;
|
|
137
|
+
position: relative;
|
|
138
|
+
transform-style: preserve-3d;
|
|
139
|
+
margin: 0 auto;
|
|
140
|
+
}
|
|
141
|
+
.sphere {
|
|
142
|
+
width: 100%;
|
|
143
|
+
height: 100%;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
146
|
+
position: absolute;
|
|
147
|
+
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.8));
|
|
148
|
+
box-shadow: inset 0 0 20px rgba(255, 255, 255, 0.05);
|
|
149
|
+
transform: rotateX(15deg) rotateY(15deg); /* Initial tilt for view */
|
|
150
|
+
}
|
|
151
|
+
/* Equator Ring */
|
|
152
|
+
.equator {
|
|
153
|
+
position: absolute;
|
|
154
|
+
top: 50%;
|
|
155
|
+
left: 0;
|
|
156
|
+
width: 100%;
|
|
157
|
+
height: 100%;
|
|
158
|
+
border: 1px dashed rgba(255, 255, 255, 0.3);
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
transform: rotateX(90deg);
|
|
161
|
+
pointer-events: none;
|
|
162
|
+
}
|
|
163
|
+
/* Axis Lines */
|
|
164
|
+
.axis {
|
|
165
|
+
position: absolute;
|
|
166
|
+
background: rgba(255, 255, 255, 0.1);
|
|
167
|
+
}
|
|
168
|
+
.z-axis { width: 2px; height: 100%; left: 50%; top: 0; }
|
|
169
|
+
|
|
170
|
+
/* The Quantum Arrow */
|
|
171
|
+
.arrow-container {
|
|
172
|
+
position: absolute;
|
|
173
|
+
top: 50%;
|
|
174
|
+
left: 50%;
|
|
175
|
+
width: 0;
|
|
176
|
+
height: 0;
|
|
177
|
+
transform-style: preserve-3d;
|
|
178
|
+
transform: rotateX(15deg) rotateY(15deg); /* Match sphere tilt */
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.arrow-pivot {
|
|
182
|
+
position: absolute;
|
|
183
|
+
top: 0;
|
|
184
|
+
left: 0;
|
|
185
|
+
width: 4px;
|
|
186
|
+
height: 50%; /* Length of radius */
|
|
187
|
+
/* Pivot logic: we want to rotate around the center point */
|
|
188
|
+
/* CSS Default transform-origin is 50% 50% */
|
|
189
|
+
/* We construct the arrow such that it points UP from center */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.arrow-rod {
|
|
193
|
+
width: 4px;
|
|
194
|
+
height: ${parseInt(size) / 2 - 10}px;
|
|
195
|
+
background: #ff00ff;
|
|
196
|
+
position: absolute;
|
|
197
|
+
bottom: 0;
|
|
198
|
+
left: -2px;
|
|
199
|
+
transform-origin: bottom center;
|
|
200
|
+
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s;
|
|
201
|
+
border-radius: 2px;
|
|
202
|
+
box-shadow: 0 0 10px currentColor;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.arrow-head {
|
|
206
|
+
width: 0;
|
|
207
|
+
height: 0;
|
|
208
|
+
border-left: 6px solid transparent;
|
|
209
|
+
border-right: 6px solid transparent;
|
|
210
|
+
border-bottom: 12px solid #ff00ff;
|
|
211
|
+
position: absolute;
|
|
212
|
+
top: -10px;
|
|
213
|
+
left: -6px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.label {
|
|
217
|
+
position: absolute;
|
|
218
|
+
color: #aaa;
|
|
219
|
+
font-family: monospace;
|
|
220
|
+
font-size: 10px;
|
|
221
|
+
}
|
|
222
|
+
.label-0 { top: 5px; left: 50%; transform: translateX(-50%); }
|
|
223
|
+
.label-1 { bottom: 5px; left: 50%; transform: translateX(-50%); }
|
|
224
|
+
</style>
|
|
225
|
+
|
|
226
|
+
<div class="sphere-container">
|
|
227
|
+
<div class="sphere">
|
|
228
|
+
<div class="equator"></div>
|
|
229
|
+
<div class="axis z-axis"></div>
|
|
230
|
+
<div class="label label-0">|0></div>
|
|
231
|
+
<div class="label label-1">|1></div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div class="arrow-container">
|
|
235
|
+
<!-- The rod is the actual rotating element -->
|
|
236
|
+
<div class="arrow-rod">
|
|
237
|
+
<div class="arrow-head"></div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
`;
|
|
242
|
+
this._container = this.shadowRoot.querySelector('.sphere-container');
|
|
243
|
+
this._arrow = this.shadowRoot.querySelector('.arrow-rod');
|
|
244
|
+
// Re-init reactivity if qubit was set before render
|
|
245
|
+
if (this._qubit)
|
|
246
|
+
this.initReactivity();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Register the custom element
|
|
250
|
+
if (typeof customElements !== 'undefined') {
|
|
251
|
+
customElements.define('mu-bloch-sphere', MuBlochSphereElement);
|
|
252
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { render } from './renderer';
|
|
2
|
+
import { setCurrentInstance } from './hooks';
|
|
3
|
+
import { effect } from './reactive';
|
|
4
|
+
// Or a simple component that returns an object of state/methods used by template?
|
|
5
|
+
// For Mulan 2.0, let's say functional component returns a template string render function explicitly.
|
|
6
|
+
export class MuComponent {
|
|
7
|
+
constructor(container) {
|
|
8
|
+
this._hooks = {};
|
|
9
|
+
this._effects = [];
|
|
10
|
+
this._isDestroyed = false;
|
|
11
|
+
this._propsQueue = [];
|
|
12
|
+
this._eventQueue = [];
|
|
13
|
+
this.container = container;
|
|
14
|
+
this.state = {};
|
|
15
|
+
this.$uid = 'mu_' + Math.random().toString(36).substr(2, 9);
|
|
16
|
+
// MULAN INSIGHT: Global Registry for Debugging
|
|
17
|
+
if (typeof window !== 'undefined') {
|
|
18
|
+
const global = window;
|
|
19
|
+
global.__MULAN_INSIGHT__ = global.__MULAN_INSIGHT__ || { components: new Map() };
|
|
20
|
+
global.__MULAN_INSIGHT__.components.set(this.$uid, this);
|
|
21
|
+
}
|
|
22
|
+
// Setup context for hooks
|
|
23
|
+
setCurrentInstance(this);
|
|
24
|
+
this.setup();
|
|
25
|
+
setCurrentInstance(null);
|
|
26
|
+
}
|
|
27
|
+
// Optional setup method for class components wanting to use hooks
|
|
28
|
+
setup() { }
|
|
29
|
+
onMount() {
|
|
30
|
+
var _a, _c;
|
|
31
|
+
// Mulan Cycle: Init
|
|
32
|
+
(_a = this._hooks.onMuInit) === null || _a === void 0 ? void 0 : _a.forEach(fn => fn());
|
|
33
|
+
// Mulan Cycle: Mount (Simulated immediately after init for now in this version)
|
|
34
|
+
(_c = this._hooks.onMuMount) === null || _c === void 0 ? void 0 : _c.forEach(fn => fn());
|
|
35
|
+
}
|
|
36
|
+
onUpdate() { }
|
|
37
|
+
onDestroy() {
|
|
38
|
+
var _a, _c;
|
|
39
|
+
if (this._isDestroyed)
|
|
40
|
+
return;
|
|
41
|
+
this._isDestroyed = true;
|
|
42
|
+
console.log(`[Mulan Cycle] Destroying component ${this.$uid} (${this.constructor.name})`);
|
|
43
|
+
// MULAN INSIGHT: Cleanup
|
|
44
|
+
if (typeof window !== 'undefined') {
|
|
45
|
+
const global = window;
|
|
46
|
+
(_a = global.__MULAN_INSIGHT__) === null || _a === void 0 ? void 0 : _a.components.delete(this.$uid);
|
|
47
|
+
}
|
|
48
|
+
// Mulan Cycle: Stop all effects to prevent leaks
|
|
49
|
+
console.log(`[Mulan Cycle] Stopping ${this._effects.length} effects for ${this.$uid}`);
|
|
50
|
+
this._effects.forEach(stop => stop());
|
|
51
|
+
this._effects = [];
|
|
52
|
+
// Mulan Cycle: Destroy hooks
|
|
53
|
+
(_c = this._hooks.onMuDestroy) === null || _c === void 0 ? void 0 : _c.forEach(fn => fn());
|
|
54
|
+
}
|
|
55
|
+
// Helper for compiler to register property bindings
|
|
56
|
+
_b(id, prop, value) {
|
|
57
|
+
this._propsQueue.push([id, prop, value]);
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
// Helper for compiler to register event bindings
|
|
61
|
+
_e(id, type, handler) {
|
|
62
|
+
this._eventQueue.push([id, type, handler]);
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
mount() {
|
|
66
|
+
console.log(`[Mulan Cycle] Mounting component ${this.$uid}`);
|
|
67
|
+
const stop = effect(() => {
|
|
68
|
+
console.log(`[Mulan Reactivity] Triggering update for ${this.$uid}`);
|
|
69
|
+
this.update();
|
|
70
|
+
});
|
|
71
|
+
this._effects.push(stop);
|
|
72
|
+
this.onMount();
|
|
73
|
+
}
|
|
74
|
+
update() {
|
|
75
|
+
if (this._isDestroyed) {
|
|
76
|
+
console.warn(`[Mulan Warning] Update called on destroyed component ${this.$uid}. Blocking render.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Clear queues before render
|
|
80
|
+
this._propsQueue = [];
|
|
81
|
+
this._eventQueue = [];
|
|
82
|
+
const template = this.template();
|
|
83
|
+
// Render HTML
|
|
84
|
+
render(template, this.container);
|
|
85
|
+
// Flush Side Effects
|
|
86
|
+
this.flushProps();
|
|
87
|
+
this.flushEvents();
|
|
88
|
+
this.onUpdate();
|
|
89
|
+
}
|
|
90
|
+
flushProps() {
|
|
91
|
+
for (const [id, prop, value] of this._propsQueue) {
|
|
92
|
+
const el = this.container.querySelector(`[data-mu-id="${id}"]`);
|
|
93
|
+
if (el) {
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
el[prop] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this._propsQueue = [];
|
|
99
|
+
}
|
|
100
|
+
flushEvents() {
|
|
101
|
+
for (const [id, type, handler] of this._eventQueue) {
|
|
102
|
+
const el = this.container.querySelector(`[data-mu-id="${id}"]`);
|
|
103
|
+
if (el) {
|
|
104
|
+
// Bind handler to this component instance to ensure 'this' works in methods
|
|
105
|
+
el.addEventListener(type, handler.bind(this));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this._eventQueue = [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Helper to create functional components wrapped in the class system
|
|
112
|
+
// Helper to create functional components wrapped in the class system
|
|
113
|
+
// Supports both () => render and { setup() } signatures
|
|
114
|
+
export function defineComponent(optionsOrSetup) {
|
|
115
|
+
return class UniversalComponent extends MuComponent {
|
|
116
|
+
constructor(container) {
|
|
117
|
+
super(container);
|
|
118
|
+
// setup() is called by super constructor
|
|
119
|
+
}
|
|
120
|
+
setup() {
|
|
121
|
+
let setupResult;
|
|
122
|
+
if (typeof optionsOrSetup === 'function') {
|
|
123
|
+
// It's a setup function returning render fn
|
|
124
|
+
setupResult = optionsOrSetup.call(this);
|
|
125
|
+
}
|
|
126
|
+
else if (typeof optionsOrSetup === 'object' && optionsOrSetup.setup) {
|
|
127
|
+
// It's an options object with setup()
|
|
128
|
+
setupResult = optionsOrSetup.setup.call(this);
|
|
129
|
+
}
|
|
130
|
+
// Handle result
|
|
131
|
+
if (typeof setupResult === 'function') {
|
|
132
|
+
// It's a render function
|
|
133
|
+
this.template = setupResult;
|
|
134
|
+
}
|
|
135
|
+
else if (setupResult && typeof setupResult === 'object') {
|
|
136
|
+
// It's bindings
|
|
137
|
+
Object.assign(this, setupResult);
|
|
138
|
+
// We assume template is assigned to prototype by compiler
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
template() {
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { reactive, effect } from './reactive';
|
|
2
|
+
import { persistent } from './vault';
|
|
3
|
+
// Global context to track the current component instance
|
|
4
|
+
let currentInstance = null;
|
|
5
|
+
export function setCurrentInstance(instance) {
|
|
6
|
+
currentInstance = instance;
|
|
7
|
+
}
|
|
8
|
+
export function getCurrentInstance() {
|
|
9
|
+
return currentInstance;
|
|
10
|
+
}
|
|
11
|
+
// --- Mulan Unique Reactivity Hooks ---
|
|
12
|
+
export function muState(initialValue) {
|
|
13
|
+
if (typeof initialValue === 'object' && initialValue !== null) {
|
|
14
|
+
return reactive(initialValue);
|
|
15
|
+
}
|
|
16
|
+
// Core reactive state container for primitives
|
|
17
|
+
return reactive({ value: initialValue });
|
|
18
|
+
}
|
|
19
|
+
export function muMemo(computeFn) {
|
|
20
|
+
const signal = reactive({ value: undefined });
|
|
21
|
+
const stop = effect(() => {
|
|
22
|
+
signal.value = computeFn();
|
|
23
|
+
});
|
|
24
|
+
const instance = getCurrentInstance();
|
|
25
|
+
if (instance) {
|
|
26
|
+
if (!instance._effects)
|
|
27
|
+
instance._effects = [];
|
|
28
|
+
instance._effects.push(stop);
|
|
29
|
+
}
|
|
30
|
+
return signal;
|
|
31
|
+
}
|
|
32
|
+
export function muEffect(fn) {
|
|
33
|
+
const stop = effect(fn);
|
|
34
|
+
const instance = getCurrentInstance();
|
|
35
|
+
if (instance) {
|
|
36
|
+
if (!instance._effects)
|
|
37
|
+
instance._effects = [];
|
|
38
|
+
instance._effects.push(stop);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// --- The "Mulan Cycle" (Lifecycle) ---
|
|
42
|
+
export function onMuInit(fn) {
|
|
43
|
+
const instance = getCurrentInstance();
|
|
44
|
+
if (instance) {
|
|
45
|
+
if (!instance._hooks)
|
|
46
|
+
instance._hooks = {};
|
|
47
|
+
if (!instance._hooks.onMuInit)
|
|
48
|
+
instance._hooks.onMuInit = [];
|
|
49
|
+
instance._hooks.onMuInit.push(fn);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.warn('onMuInit called outside of component setup context.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function onMuMount(fn) {
|
|
56
|
+
// New hook for when component is actually in DOM (simulated for now via update)
|
|
57
|
+
const instance = getCurrentInstance();
|
|
58
|
+
if (instance) {
|
|
59
|
+
if (!instance._hooks)
|
|
60
|
+
instance._hooks = {};
|
|
61
|
+
if (!instance._hooks.onMuMount)
|
|
62
|
+
instance._hooks.onMuMount = [];
|
|
63
|
+
instance._hooks.onMuMount.push(fn);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function onMuDestroy(fn) {
|
|
67
|
+
const instance = getCurrentInstance();
|
|
68
|
+
if (instance) {
|
|
69
|
+
if (!instance._hooks)
|
|
70
|
+
instance._hooks = {};
|
|
71
|
+
if (!instance._hooks.onMuDestroy)
|
|
72
|
+
instance._hooks.onMuDestroy = [];
|
|
73
|
+
instance._hooks.onMuDestroy.push(fn);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* onMuIdle - The "Environment Life" Hook.
|
|
78
|
+
* Executes heavy logic ONLY when the browser is taking a nap (idle).
|
|
79
|
+
*/
|
|
80
|
+
export function onMuIdle(fn) {
|
|
81
|
+
const instance = getCurrentInstance();
|
|
82
|
+
// Polyfill for Safari/Old Browsers
|
|
83
|
+
const requestIdleCallback = window.requestIdleCallback || function (cb) {
|
|
84
|
+
return setTimeout(() => {
|
|
85
|
+
cb({
|
|
86
|
+
didTimeout: false,
|
|
87
|
+
timeRemaining: function () { return 50; }
|
|
88
|
+
});
|
|
89
|
+
}, 1);
|
|
90
|
+
};
|
|
91
|
+
const cancelIdleCallback = window.cancelIdleCallback || function (id) {
|
|
92
|
+
clearTimeout(id);
|
|
93
|
+
};
|
|
94
|
+
const idleId = requestIdleCallback(() => {
|
|
95
|
+
fn();
|
|
96
|
+
});
|
|
97
|
+
// Auto-cleanup if component dies before idle time
|
|
98
|
+
if (instance) {
|
|
99
|
+
onMuDestroy(() => {
|
|
100
|
+
cancelIdleCallback(idleId);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* onMuResume - The "Tab Life" Hook.
|
|
106
|
+
* Executes when the user switches BACK to this tab.
|
|
107
|
+
* Perfect for refreshing data or resuming animations to save battery.
|
|
108
|
+
*/
|
|
109
|
+
export function onMuResume(fn) {
|
|
110
|
+
const handler = () => {
|
|
111
|
+
if (document.visibilityState === 'visible') {
|
|
112
|
+
fn();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
document.addEventListener('visibilitychange', handler);
|
|
116
|
+
onMuDestroy(() => document.removeEventListener('visibilitychange', handler));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* onMuShake - The "Physical Life" Hook.
|
|
120
|
+
* Executes when the device is shaken.
|
|
121
|
+
* Usage: Undo, Refresh, or "Rage Quit" easter eggs.
|
|
122
|
+
*/
|
|
123
|
+
export function onMuShake(fn) {
|
|
124
|
+
// Threshold for shake detection
|
|
125
|
+
const threshold = 15;
|
|
126
|
+
let lastX = 0, lastY = 0, lastZ = 0;
|
|
127
|
+
let lastTime = 0;
|
|
128
|
+
const handler = (e) => {
|
|
129
|
+
const current = e.accelerationIncludingGravity;
|
|
130
|
+
if (!current)
|
|
131
|
+
return;
|
|
132
|
+
const time = Date.now();
|
|
133
|
+
if ((time - lastTime) > 100) {
|
|
134
|
+
const diffTime = time - lastTime;
|
|
135
|
+
lastTime = time;
|
|
136
|
+
const x = current.x || 0;
|
|
137
|
+
const y = current.y || 0;
|
|
138
|
+
const z = current.z || 0;
|
|
139
|
+
const speed = Math.abs(x + y + z - lastX - lastY - lastZ) / diffTime * 10000;
|
|
140
|
+
if (speed > threshold) {
|
|
141
|
+
fn();
|
|
142
|
+
}
|
|
143
|
+
lastX = x;
|
|
144
|
+
lastY = y;
|
|
145
|
+
lastZ = z;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
if (window.DeviceMotionEvent) {
|
|
149
|
+
window.addEventListener('devicemotion', handler);
|
|
150
|
+
onMuDestroy(() => window.removeEventListener('devicemotion', handler));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.warn("[MulanJS] Device Motion not supported on this device.");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* onMuVoice - The "Sound Life" Hook.
|
|
158
|
+
* Executes when a specific word is spoken.
|
|
159
|
+
* @param command The word to listen for (e.g., "save", "next")
|
|
160
|
+
* @param fn The action to take
|
|
161
|
+
*/
|
|
162
|
+
export function onMuVoice(command, fn) {
|
|
163
|
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
164
|
+
if (!SpeechRecognition) {
|
|
165
|
+
console.warn("[MulanJS] Voice Control (Web Speech API) not supported in this browser.");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const recognition = new SpeechRecognition();
|
|
169
|
+
recognition.continuous = true;
|
|
170
|
+
recognition.lang = 'en-US';
|
|
171
|
+
recognition.interimResults = false;
|
|
172
|
+
recognition.onresult = (event) => {
|
|
173
|
+
const last = event.results.length - 1;
|
|
174
|
+
const spoken = event.results[last][0].transcript.trim().toLowerCase();
|
|
175
|
+
console.log(`[Mulan Voice] Heard: "${spoken}"`);
|
|
176
|
+
if (spoken.includes(command.toLowerCase())) {
|
|
177
|
+
fn();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
recognition.start();
|
|
181
|
+
// Auto-restart if it stops (Continuous listening)
|
|
182
|
+
recognition.onend = () => {
|
|
183
|
+
// Simple check to see if we should still be listening
|
|
184
|
+
// In a real app we might want more control
|
|
185
|
+
// recognition.start();
|
|
186
|
+
};
|
|
187
|
+
onMuDestroy(() => {
|
|
188
|
+
recognition.stop();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// --- "Outside The Box" Hooks (Mulan Exclusives) ---
|
|
192
|
+
/**
|
|
193
|
+
* muGeom - Tracks window or element dimensions reactively.
|
|
194
|
+
*/
|
|
195
|
+
export function muGeom() {
|
|
196
|
+
const dims = muState({ width: window.innerWidth, height: window.innerHeight });
|
|
197
|
+
const handler = () => {
|
|
198
|
+
dims.width = window.innerWidth;
|
|
199
|
+
dims.height = window.innerHeight;
|
|
200
|
+
};
|
|
201
|
+
window.addEventListener('resize', handler);
|
|
202
|
+
// Auto-cleanup
|
|
203
|
+
onMuDestroy(() => {
|
|
204
|
+
window.removeEventListener('resize', handler);
|
|
205
|
+
});
|
|
206
|
+
return dims;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* muPulse - Reactive network status.
|
|
210
|
+
*/
|
|
211
|
+
export function muPulse() {
|
|
212
|
+
const status = muState({ online: navigator.onLine });
|
|
213
|
+
const setOnline = () => status.online = true;
|
|
214
|
+
const setOffline = () => status.online = false;
|
|
215
|
+
window.addEventListener('online', setOnline);
|
|
216
|
+
window.addEventListener('offline', setOffline);
|
|
217
|
+
onMuDestroy(() => {
|
|
218
|
+
window.removeEventListener('online', setOnline);
|
|
219
|
+
window.removeEventListener('offline', setOffline);
|
|
220
|
+
});
|
|
221
|
+
return status;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* muVault - Secure reactive LocalStorage wrapper.
|
|
225
|
+
* Powered by the Iron Fortress persistent primitive.
|
|
226
|
+
*/
|
|
227
|
+
export function muVault(key, initial, options = {}) {
|
|
228
|
+
return persistent(key, initial, options);
|
|
229
|
+
}
|