@erickxavier/no-js 1.1.0 → 1.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 +2 -2
- package/dist/cjs/no.js +44 -5
- package/dist/cjs/no.js.map +4 -4
- package/dist/esm/no.js +44 -5
- package/dist/esm/no.js.map +4 -4
- package/dist/iife/no.js +44 -5
- package/dist/iife/no.js.map +4 -4
- package/package.json +1 -1
- package/src/animations.js +12 -9
- package/src/context.js +14 -1
- package/src/directives/binding.js +1 -1
- package/src/directives/conditionals.js +4 -4
- package/src/directives/dnd.js +1150 -0
- package/src/directives/events.js +2 -1
- package/src/directives/http.js +14 -11
- package/src/directives/i18n.js +5 -1
- package/src/directives/loops.js +21 -3
- package/src/directives/refs.js +4 -0
- package/src/directives/state.js +3 -3
- package/src/directives/styling.js +10 -7
- package/src/evaluate.js +9 -3
- package/src/i18n.js +3 -2
- package/src/index.js +2 -1
- package/src/router.js +3 -0
package/src/directives/events.js
CHANGED
|
@@ -55,7 +55,8 @@ registerDirective("on:*", {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
if (el.parentElement)
|
|
58
|
-
observer.observe(el.parentElement, { childList: true });
|
|
58
|
+
observer.observe(el.parentElement, { childList: true, subtree: true });
|
|
59
|
+
_onDispose(() => observer.disconnect());
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
|
package/src/directives/http.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
_config,
|
|
7
|
-
_log,
|
|
8
7
|
_warn,
|
|
9
8
|
_stores,
|
|
10
9
|
_notifyStoreWatchers,
|
|
@@ -124,16 +123,20 @@ for (const method of HTTP_METHODS) {
|
|
|
124
123
|
const savedRetryDelay = _config.retryDelay;
|
|
125
124
|
_config.retries = retryCount;
|
|
126
125
|
_config.retryDelay = retryDelay;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
let data;
|
|
127
|
+
try {
|
|
128
|
+
data = await _doFetch(
|
|
129
|
+
resolvedUrl,
|
|
130
|
+
method,
|
|
131
|
+
reqBody,
|
|
132
|
+
extraHeaders,
|
|
133
|
+
el,
|
|
134
|
+
_activeAbort.signal,
|
|
135
|
+
);
|
|
136
|
+
} finally {
|
|
137
|
+
_config.retries = savedRetries;
|
|
138
|
+
_config.retryDelay = savedRetryDelay;
|
|
139
|
+
}
|
|
137
140
|
|
|
138
141
|
// Cache response
|
|
139
142
|
if (method === "get") _cacheSet(cacheKey, data, cacheStrategy);
|
package/src/directives/i18n.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
6
|
import { _i18n, _watchI18n, _loadI18nNamespace, _notifyI18n } from "../i18n.js";
|
|
7
|
+
import { _watchExpr } from "../globals.js";
|
|
7
8
|
import { evaluate } from "../evaluate.js";
|
|
8
9
|
import { findContext } from "../dom.js";
|
|
9
10
|
import { registerDirective, processTree } from "../registry.js";
|
|
@@ -30,7 +31,7 @@ registerDirective("t", {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
ctx
|
|
34
|
+
_watchExpr(key, ctx, update);
|
|
34
35
|
_watchI18n(update);
|
|
35
36
|
update();
|
|
36
37
|
},
|
|
@@ -39,6 +40,9 @@ registerDirective("t", {
|
|
|
39
40
|
registerDirective("i18n-ns", {
|
|
40
41
|
priority: 1,
|
|
41
42
|
init(el, name, ns) {
|
|
43
|
+
// Empty ns = marker attribute (e.g. route-view); skip loading
|
|
44
|
+
if (!ns) return;
|
|
45
|
+
|
|
42
46
|
// Save children to prevent premature t resolution
|
|
43
47
|
const saved = document.createDocumentFragment();
|
|
44
48
|
while (el.firstChild) saved.appendChild(el.firstChild);
|
package/src/directives/loops.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4
4
|
|
|
5
5
|
import { createContext } from "../context.js";
|
|
6
|
+
import { _watchExpr } from "../globals.js";
|
|
6
7
|
import { evaluate, resolve } from "../evaluate.js";
|
|
7
8
|
import { findContext, _cloneTemplate } from "../dom.js";
|
|
8
9
|
import { registerDirective, processTree } from "../registry.js";
|
|
@@ -23,11 +24,26 @@ registerDirective("each", {
|
|
|
23
24
|
const animLeave = el.getAttribute("animate-leave");
|
|
24
25
|
const stagger = parseInt(el.getAttribute("animate-stagger")) || 0;
|
|
25
26
|
const animDuration = parseInt(el.getAttribute("animate-duration")) || 0;
|
|
27
|
+
let prevList = null;
|
|
26
28
|
|
|
27
29
|
function update() {
|
|
28
|
-
let list =
|
|
30
|
+
let list = /[\[\]()\s+\-*\/!?:&|]/.test(listPath)
|
|
31
|
+
? evaluate(listPath, ctx)
|
|
32
|
+
: resolve(listPath, ctx);
|
|
29
33
|
if (!Array.isArray(list)) return;
|
|
30
34
|
|
|
35
|
+
// If same list reference and items are rendered, skip re-render
|
|
36
|
+
// and just propagate the notification to child contexts so their
|
|
37
|
+
// watchers (bind, show, model, etc.) can react to parent changes
|
|
38
|
+
// without destroying/recreating the DOM (preserves input focus).
|
|
39
|
+
if (list === prevList && list.length > 0 && el.children.length > 0) {
|
|
40
|
+
for (const child of el.children) {
|
|
41
|
+
if (child.__ctx && child.__ctx.$notify) child.__ctx.$notify();
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
prevList = list;
|
|
46
|
+
|
|
31
47
|
// Empty state
|
|
32
48
|
if (list.length === 0 && elseTpl) {
|
|
33
49
|
const clone = _cloneTemplate(elseTpl);
|
|
@@ -90,6 +106,7 @@ registerDirective("each", {
|
|
|
90
106
|
const firstChild = wrapper.firstElementChild;
|
|
91
107
|
if (firstChild) {
|
|
92
108
|
firstChild.classList.add(animEnter);
|
|
109
|
+
firstChild.addEventListener("animationend", () => firstChild.classList.remove(animEnter), { once: true });
|
|
93
110
|
// Stagger animation — delay must be on the child, not the wrapper
|
|
94
111
|
if (stagger) {
|
|
95
112
|
firstChild.style.animationDelay = i * stagger + "ms";
|
|
@@ -99,7 +116,7 @@ registerDirective("each", {
|
|
|
99
116
|
});
|
|
100
117
|
}
|
|
101
118
|
|
|
102
|
-
ctx
|
|
119
|
+
_watchExpr(expr, ctx, update);
|
|
103
120
|
update();
|
|
104
121
|
},
|
|
105
122
|
});
|
|
@@ -204,6 +221,7 @@ registerDirective("foreach", {
|
|
|
204
221
|
const firstChild = wrapper.firstElementChild;
|
|
205
222
|
if (firstChild) {
|
|
206
223
|
firstChild.classList.add(animEnter);
|
|
224
|
+
firstChild.addEventListener("animationend", () => firstChild.classList.remove(animEnter), { once: true });
|
|
207
225
|
// Stagger animation — delay must be on the child, not the wrapper
|
|
208
226
|
if (stagger) {
|
|
209
227
|
firstChild.style.animationDelay = (i * stagger) + "ms";
|
|
@@ -233,7 +251,7 @@ registerDirective("foreach", {
|
|
|
233
251
|
}
|
|
234
252
|
}
|
|
235
253
|
|
|
236
|
-
ctx
|
|
254
|
+
_watchExpr(fromPath, ctx, update);
|
|
237
255
|
update();
|
|
238
256
|
},
|
|
239
257
|
});
|
package/src/directives/refs.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
_refs,
|
|
7
7
|
_stores,
|
|
8
8
|
_notifyStoreWatchers,
|
|
9
|
+
_onDispose,
|
|
9
10
|
} from "../globals.js";
|
|
10
11
|
import { createContext } from "../context.js";
|
|
11
12
|
import { evaluate, _execStatement, _interpolate } from "../evaluate.js";
|
|
@@ -17,6 +18,9 @@ registerDirective("ref", {
|
|
|
17
18
|
priority: 5,
|
|
18
19
|
init(el, name, refName) {
|
|
19
20
|
_refs[refName] = el;
|
|
21
|
+
_onDispose(() => {
|
|
22
|
+
if (_refs[refName] === el) delete _refs[refName];
|
|
23
|
+
});
|
|
20
24
|
},
|
|
21
25
|
});
|
|
22
26
|
|
package/src/directives/state.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// DIRECTIVES: state, store, computed, watch
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4
4
|
|
|
5
|
-
import { _stores, _log } from "../globals.js";
|
|
5
|
+
import { _stores, _log, _watchExpr } from "../globals.js";
|
|
6
6
|
import { createContext } from "../context.js";
|
|
7
7
|
import { evaluate, _execStatement } from "../evaluate.js";
|
|
8
8
|
import { findContext } from "../dom.js";
|
|
@@ -78,7 +78,7 @@ registerDirective("computed", {
|
|
|
78
78
|
const val = evaluate(expr, ctx);
|
|
79
79
|
ctx.$set(computedName, val);
|
|
80
80
|
}
|
|
81
|
-
ctx
|
|
81
|
+
_watchExpr(expr, ctx, update);
|
|
82
82
|
update();
|
|
83
83
|
},
|
|
84
84
|
});
|
|
@@ -89,7 +89,7 @@ registerDirective("watch", {
|
|
|
89
89
|
const ctx = findContext(el);
|
|
90
90
|
const onChange = el.getAttribute("on:change");
|
|
91
91
|
let lastVal = evaluate(watchExpr, ctx);
|
|
92
|
-
ctx
|
|
92
|
+
_watchExpr(watchExpr, ctx, () => {
|
|
93
93
|
const newVal = evaluate(watchExpr, ctx);
|
|
94
94
|
if (newVal !== lastVal) {
|
|
95
95
|
const oldVal = lastVal;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// DIRECTIVES: class-*, style-*
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4
4
|
|
|
5
|
+
import { _watchExpr } from "../globals.js";
|
|
5
6
|
import { evaluate } from "../evaluate.js";
|
|
6
7
|
import { findContext } from "../dom.js";
|
|
7
8
|
import { registerDirective } from "../registry.js";
|
|
@@ -14,16 +15,18 @@ registerDirective("class-*", {
|
|
|
14
15
|
const ctx = findContext(el);
|
|
15
16
|
|
|
16
17
|
// class-map="{ active: x, bold: y }"
|
|
18
|
+
// Supports space-separated keys: class-map="{ 'bg-sky-500 text-white': x }"
|
|
17
19
|
if (suffix === "map") {
|
|
18
20
|
function update() {
|
|
19
21
|
const obj = evaluate(expr, ctx);
|
|
20
22
|
if (obj && typeof obj === "object") {
|
|
21
23
|
for (const [cls, cond] of Object.entries(obj)) {
|
|
22
|
-
|
|
24
|
+
const parts = cls.split(/\s+/).filter(Boolean);
|
|
25
|
+
parts.forEach((c) => el.classList.toggle(c, !!cond));
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
ctx
|
|
29
|
+
_watchExpr(expr, ctx, update);
|
|
27
30
|
update();
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
@@ -42,7 +45,7 @@ registerDirective("class-*", {
|
|
|
42
45
|
prevClasses = next;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
ctx
|
|
48
|
+
_watchExpr(expr, ctx, update);
|
|
46
49
|
update();
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
@@ -51,8 +54,8 @@ registerDirective("class-*", {
|
|
|
51
54
|
function update() {
|
|
52
55
|
el.classList.toggle(suffix, !!evaluate(expr, ctx));
|
|
53
56
|
}
|
|
54
|
-
ctx
|
|
55
|
-
if (expr.includes("NoJS.locale")) _watchI18n(update);
|
|
57
|
+
_watchExpr(expr, ctx, update);
|
|
58
|
+
if (expr.includes("$i18n") || expr.includes("NoJS.locale")) _watchI18n(update);
|
|
56
59
|
update();
|
|
57
60
|
},
|
|
58
61
|
});
|
|
@@ -73,7 +76,7 @@ registerDirective("style-*", {
|
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
|
-
ctx
|
|
79
|
+
_watchExpr(expr, ctx, update);
|
|
77
80
|
update();
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
@@ -84,7 +87,7 @@ registerDirective("style-*", {
|
|
|
84
87
|
const val = evaluate(expr, ctx);
|
|
85
88
|
el.style[cssProp] = val != null ? String(val) : "";
|
|
86
89
|
}
|
|
87
|
-
ctx
|
|
90
|
+
_watchExpr(expr, ctx, update);
|
|
88
91
|
update();
|
|
89
92
|
},
|
|
90
93
|
});
|
package/src/evaluate.js
CHANGED
|
@@ -265,22 +265,28 @@ export function _execStatement(expr, ctx, extraVars = {}) {
|
|
|
265
265
|
// For each key in any ancestor context, find the owning context at runtime
|
|
266
266
|
// and call $set on it — so mutations inside `each` loops correctly
|
|
267
267
|
// propagate back to parent state (e.g. cart updated from a loop's on:click).
|
|
268
|
+
// Only write back values that actually changed locally, to avoid
|
|
269
|
+
// overwriting proxy mutations made by called functions.
|
|
268
270
|
const chainKeys = new Set();
|
|
269
271
|
let _wCtx = ctx;
|
|
270
272
|
while (_wCtx && _wCtx.__isProxy) {
|
|
271
273
|
for (const k of Object.keys(_wCtx.__raw)) chainKeys.add(k);
|
|
272
274
|
_wCtx = _wCtx.$parent;
|
|
273
275
|
}
|
|
276
|
+
const origObj = {};
|
|
277
|
+
for (const k of chainKeys) {
|
|
278
|
+
if (!k.startsWith("$") && k in vals) origObj[k] = vals[k];
|
|
279
|
+
}
|
|
274
280
|
const setters = [...chainKeys]
|
|
275
281
|
.filter((k) => !k.startsWith("$"))
|
|
276
282
|
.map(
|
|
277
283
|
(k) =>
|
|
278
|
-
`{let _c=__ctx;while(_c&&_c.__isProxy){if('${k}'in _c.__raw){
|
|
284
|
+
`{let _c=__ctx;while(_c&&_c.__isProxy){if('${k}'in _c.__raw){if(typeof ${k}!=='undefined'){if(${k}!==__orig['${k}'])_c.$set('${k}',${k});else if(typeof ${k}==='object'&&${k}!==null)_c.$notify();}break;}_c=_c.$parent;}}`,
|
|
279
285
|
)
|
|
280
286
|
.join("\n");
|
|
281
287
|
|
|
282
|
-
const fn = new Function("__ctx", ...keyArr, `${expr};\n${setters}`);
|
|
283
|
-
fn(ctx, ...valArr);
|
|
288
|
+
const fn = new Function("__ctx", "__orig", ...keyArr, `${expr};\n${setters}`);
|
|
289
|
+
fn(ctx, origObj, ...valArr);
|
|
284
290
|
|
|
285
291
|
// Notify global store watchers when expression touches $store
|
|
286
292
|
if (typeof expr === "string" && expr.includes("$store")) {
|
package/src/i18n.js
CHANGED
|
@@ -47,13 +47,14 @@ export async function _loadLocale(locale, ns) {
|
|
|
47
47
|
|
|
48
48
|
let url = _config.i18n.loadPath.replace("{locale}", locale);
|
|
49
49
|
if (ns) url = url.replace("{ns}", ns);
|
|
50
|
+
else if (url.includes("{ns}")) return; // no namespace to substitute
|
|
51
|
+
|
|
50
52
|
|
|
51
53
|
try {
|
|
52
54
|
const res = await fetch(url);
|
|
53
55
|
if (!res.ok) { _warn(`i18n: failed to load ${url} (${res.status})`); return; }
|
|
54
56
|
const data = await res.json();
|
|
55
|
-
|
|
56
|
-
_i18n.locales[locale] = _deepMerge(_i18n.locales[locale] || {}, toMerge);
|
|
57
|
+
_i18n.locales[locale] = _deepMerge(_i18n.locales[locale] || {}, data);
|
|
57
58
|
if (_config.i18n.cache) _i18nCache.set(cacheKey, data);
|
|
58
59
|
} catch (e) {
|
|
59
60
|
_warn(`i18n: error loading ${url}`, e);
|
package/src/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import "./directives/events.js";
|
|
|
37
37
|
import "./directives/refs.js";
|
|
38
38
|
import "./directives/validation.js";
|
|
39
39
|
import "./directives/i18n.js";
|
|
40
|
+
import "./directives/dnd.js";
|
|
40
41
|
|
|
41
42
|
// ═══════════════════════════════════════════════════════════════════════
|
|
42
43
|
// PUBLIC API
|
|
@@ -215,7 +216,7 @@ const NoJS = {
|
|
|
215
216
|
resolve,
|
|
216
217
|
|
|
217
218
|
// Version
|
|
218
|
-
version: "1.
|
|
219
|
+
version: "1.2.0",
|
|
219
220
|
};
|
|
220
221
|
|
|
221
222
|
export default NoJS;
|
package/src/router.js
CHANGED
|
@@ -315,6 +315,9 @@ export function _createRouter() {
|
|
|
315
315
|
}
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
|
+
// Skip if path unchanged (prevents double-processing from programmatic hash set)
|
|
319
|
+
const [p] = raw.split("?");
|
|
320
|
+
if (p === current.path) return;
|
|
318
321
|
navigate(raw, true);
|
|
319
322
|
});
|
|
320
323
|
// Initial route
|