mithril-mobs 0.0.1 → 0.0.2
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/package.json +1 -1
- package/src/mobs/index.js +163 -70
package/package.json
CHANGED
package/src/mobs/index.js
CHANGED
@@ -1,66 +1,193 @@
|
|
1
1
|
import m from "mithril"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
vnodes.push(v);
|
10
|
-
return prepare(ctx);
|
11
|
-
},
|
12
|
-
elseIf(newCondition) {
|
13
|
-
conditions.push(newCondition); // <- fixed
|
14
|
-
return prepare(ctx);
|
15
|
-
},
|
16
|
-
else(v) {
|
17
|
-
conditions.push(true); // the default branch
|
18
|
-
vnodes.push(v);
|
19
|
-
return prepare(ctx);
|
20
|
-
},
|
21
|
-
view() { // evaluate
|
22
|
-
for (let i = 0; i < conditions.length; i++) {
|
23
|
-
if (conditions[i]) return vnodes[i] || null;
|
24
|
-
}
|
25
|
-
return null;
|
26
|
-
}
|
27
|
-
};
|
3
|
+
/**
|
4
|
+
* Reactive Observable (obs)
|
5
|
+
*/
|
6
|
+
export function obs(initialValue) {
|
7
|
+
let _value = initialValue;
|
8
|
+
const listeners = new Set();
|
28
9
|
|
29
|
-
|
10
|
+
const api = {
|
11
|
+
get value() {
|
12
|
+
return _value;
|
13
|
+
},
|
14
|
+
set value(newValue) {
|
15
|
+
_value = newValue;
|
16
|
+
listeners.forEach(listener => listener(_value));
|
17
|
+
},
|
18
|
+
subscribe(listener) {
|
19
|
+
listeners.add(listener);
|
20
|
+
return () => listeners.delete(listener);
|
21
|
+
},
|
22
|
+
toJSON() {
|
23
|
+
return { __obs__: true, value: _value }; // Mark for serialization
|
24
|
+
},
|
25
|
+
_isObs: true
|
26
|
+
};
|
27
|
+
|
28
|
+
return api;
|
30
29
|
}
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
/**
|
32
|
+
* Helper to serialize state with obs() support
|
33
|
+
*/
|
34
|
+
function serializeState(state) {
|
35
|
+
const result = {};
|
36
|
+
for (const key in state) {
|
37
|
+
const val = state[key];
|
38
|
+
if (val && typeof val === 'object' && val._isObs) {
|
39
|
+
result[key] = val.toJSON();
|
40
|
+
} else {
|
41
|
+
result[key] = val;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
return JSON.stringify(result);
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Helper to deserialize and hydrate obs() in state
|
49
|
+
*/
|
50
|
+
function deserializeState(json, template = {}) {
|
51
|
+
const parsed = JSON.parse(json);
|
52
|
+
const result = {};
|
53
|
+
|
54
|
+
for (const key in parsed) {
|
55
|
+
const val = parsed[key];
|
56
|
+
|
57
|
+
if (val && typeof val === 'object' && val.__obs__) {
|
58
|
+
if (template[key] && template[key]._isObs) {
|
59
|
+
template[key].value = val.value;
|
60
|
+
result[key] = template[key];
|
61
|
+
} else {
|
62
|
+
result[key] = obs(val.value);
|
63
|
+
}
|
64
|
+
} else {
|
65
|
+
result[key] = val;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
return result;
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* defineState - reactive state with localStorage
|
74
|
+
*/
|
75
|
+
export function defineState(name, stateTemplate) {
|
76
|
+
const listeners = new Set();
|
77
|
+
let state = { ...stateTemplate };
|
78
|
+
|
79
|
+
try {
|
80
|
+
const saved = localStorage.getItem(name);
|
81
|
+
if (saved) {
|
82
|
+
state = deserializeState(saved, stateTemplate);
|
83
|
+
}
|
84
|
+
} catch (e) {
|
85
|
+
console.warn(`Failed to load state '${name}' from localStorage:`, e);
|
86
|
+
}
|
87
|
+
|
88
|
+
return {
|
89
|
+
...state,
|
90
|
+
set(setter, save) {
|
91
|
+
setter(state);
|
92
|
+
listeners.forEach(listener => listener(state));
|
93
|
+
if (save) this.save();
|
94
|
+
},
|
95
|
+
subscribe(listener) {
|
96
|
+
listeners.add(listener);
|
97
|
+
return () => listeners.delete(listener);
|
98
|
+
},
|
99
|
+
save() {
|
100
|
+
try {
|
101
|
+
localStorage.setItem(name, serializeState(state));
|
102
|
+
} catch (e) {
|
103
|
+
console.warn(`Failed to save state '${name}' to localStorage:`, e);
|
104
|
+
}
|
105
|
+
},
|
106
|
+
load() {
|
107
|
+
try {
|
108
|
+
const saved = localStorage.getItem(name);
|
109
|
+
if (saved) {
|
110
|
+
const newState = deserializeState(saved, state);
|
111
|
+
Object.assign(state, newState);
|
112
|
+
listeners.forEach(listener => listener(state));
|
113
|
+
}
|
114
|
+
} catch (e) {
|
115
|
+
console.warn(`Failed to reload state '${name}' from localStorage:`, e);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
};
|
39
119
|
}
|
40
120
|
|
121
|
+
/**
|
122
|
+
* Class Binding Utility
|
123
|
+
*/
|
124
|
+
export function classes(classes_) {
|
125
|
+
const result = [];
|
126
|
+
for (let key in classes_) {
|
127
|
+
if (classes_[key]) result.push(key);
|
128
|
+
}
|
129
|
+
return result.join(" ");
|
130
|
+
}
|
41
131
|
|
132
|
+
/**
|
133
|
+
* Conditional Rendering Helper
|
134
|
+
*/
|
135
|
+
export function if_(firstCondition) {
|
136
|
+
const conditions = [firstCondition];
|
137
|
+
const vnodes = [];
|
42
138
|
|
139
|
+
const ctx = {
|
140
|
+
then(v) {
|
141
|
+
vnodes.push(v);
|
142
|
+
return prepare(ctx);
|
143
|
+
},
|
144
|
+
elseIf(newCondition) {
|
145
|
+
conditions.push(newCondition);
|
146
|
+
return prepare(ctx);
|
147
|
+
},
|
148
|
+
else(v) {
|
149
|
+
conditions.push(true);
|
150
|
+
vnodes.push(v);
|
151
|
+
return prepare(ctx);
|
152
|
+
},
|
153
|
+
view() {
|
154
|
+
for (let i = 0; i < conditions.length; i++) {
|
155
|
+
if (conditions[i]) return vnodes[i] || null;
|
156
|
+
}
|
157
|
+
return null;
|
158
|
+
}
|
159
|
+
};
|
43
160
|
|
161
|
+
return prepare(ctx);
|
162
|
+
}
|
44
163
|
|
164
|
+
function prepare(ctx) {
|
165
|
+
const n = m(ctx);
|
166
|
+
n.then = ctx.then;
|
167
|
+
n.elseIf = ctx.elseIf;
|
168
|
+
n.else = ctx.else;
|
169
|
+
n.view = ctx.view;
|
170
|
+
return n;
|
171
|
+
}
|
45
172
|
|
173
|
+
/**
|
174
|
+
* Styled Component Creator
|
175
|
+
*/
|
46
176
|
const Component = {
|
47
177
|
_counter: 0,
|
48
178
|
|
49
179
|
create(def) {
|
50
180
|
const compId = `comp-${Component._counter++}`;
|
51
181
|
|
52
|
-
// Convert SASS-like staticStyles object to CSS string
|
53
182
|
function stylesToCss(styles, prefix = `[data-comp-id="${compId}"]`) {
|
54
183
|
let css = "";
|
55
184
|
let props = [];
|
56
185
|
|
57
186
|
for (const key in styles) {
|
58
187
|
if (key.startsWith("/")) {
|
59
|
-
// Nested selector, replace "/" with space for nesting
|
60
188
|
const selector = prefix + key.replace(/\//g, " ");
|
61
189
|
css += stylesToCss(styles[key], selector);
|
62
190
|
} else {
|
63
|
-
// Style property
|
64
191
|
props.push(`${key}: ${styles[key]};`);
|
65
192
|
}
|
66
193
|
}
|
@@ -72,7 +199,6 @@ const Component = {
|
|
72
199
|
return css;
|
73
200
|
}
|
74
201
|
|
75
|
-
// Automatically add <style id="component-styles"> if missing
|
76
202
|
let styleTag = document.getElementById("component-styles");
|
77
203
|
if (!styleTag) {
|
78
204
|
styleTag = document.createElement("style");
|
@@ -80,7 +206,6 @@ const Component = {
|
|
80
206
|
document.head.appendChild(styleTag);
|
81
207
|
}
|
82
208
|
|
83
|
-
// Use a Set to track injected components
|
84
209
|
styleTag.__injectedComponents = styleTag.__injectedComponents || new Set();
|
85
210
|
|
86
211
|
if (!styleTag.__injectedComponents.has(compId)) {
|
@@ -110,35 +235,3 @@ const Component = {
|
|
110
235
|
};
|
111
236
|
|
112
237
|
export const createComponent = (def) => Component.create(def);
|
113
|
-
|
114
|
-
|
115
|
-
export function obs(initialValue) {
|
116
|
-
let value = initialValue;
|
117
|
-
const listeners = new Set();
|
118
|
-
|
119
|
-
return {
|
120
|
-
get() {
|
121
|
-
return value;
|
122
|
-
},
|
123
|
-
set(newValue) {
|
124
|
-
value = newValue
|
125
|
-
listeners.forEach((listener) => listener(value))
|
126
|
-
},
|
127
|
-
subscribe(listener) {
|
128
|
-
listeners.add(listener);
|
129
|
-
// Return an unsubscribe function
|
130
|
-
return () => listeners.delete(listener);
|
131
|
-
}
|
132
|
-
};
|
133
|
-
}
|
134
|
-
|
135
|
-
|
136
|
-
export function classes(classes_) {
|
137
|
-
const classes__ = []
|
138
|
-
for(let class_ in classes_) {
|
139
|
-
if(classes_[class_]) {
|
140
|
-
classes__.push(class_)
|
141
|
-
}
|
142
|
-
}
|
143
|
-
return classes__.join(" ")
|
144
|
-
}
|