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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/mobs/index.js +163 -70
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mithril-mobs",
3
3
  "private": false,
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
package/src/mobs/index.js CHANGED
@@ -1,66 +1,193 @@
1
1
  import m from "mithril"
2
2
 
3
- export function if_(firstCondition) {
4
- const conditions = [firstCondition];
5
- const vnodes = [];
6
-
7
- const ctx = {
8
- then(v) {
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
- return prepare(ctx)
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
- function prepare(ctx) {
33
- const n = m(ctx)
34
- n.then = ctx.then
35
- n.elseIf = ctx.elseIf
36
- n.else = ctx.else
37
- n.view = ctx.view
38
- return n
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
- }