@erickxavier/no-js 1.0.2 → 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 +2 -2
- package/src/animations.js +12 -9
- package/src/context.js +28 -5
- 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 +8 -3
- package/src/directives/http.js +17 -12
- package/src/directives/i18n.js +31 -5
- 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 +11 -6
- package/src/directives/validation.js +5 -3
- package/src/evaluate.js +9 -3
- package/src/globals.js +27 -3
- package/src/i18n.js +80 -2
- package/src/index.js +37 -4
- package/src/registry.js +30 -0
- package/src/router.js +47 -7
package/src/registry.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// DIRECTIVE REGISTRY & DOM PROCESSING
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4
4
|
|
|
5
|
+
import { _currentEl, _setCurrentEl, _storeWatchers } from "./globals.js";
|
|
6
|
+
import { _i18nListeners } from "./i18n.js";
|
|
7
|
+
|
|
5
8
|
const _directives = new Map();
|
|
6
9
|
|
|
7
10
|
export function registerDirective(name, handler) {
|
|
@@ -43,9 +46,12 @@ export function processElement(el) {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
matched.sort((a, b) => a.priority - b.priority);
|
|
49
|
+
const prev = _currentEl;
|
|
46
50
|
for (const m of matched) {
|
|
51
|
+
_setCurrentEl(el);
|
|
47
52
|
m.init(el, m.name, m.value);
|
|
48
53
|
}
|
|
54
|
+
_setCurrentEl(prev);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export function processTree(root) {
|
|
@@ -58,3 +64,27 @@ export function processTree(root) {
|
|
|
58
64
|
if (!node.__declared) processElement(node);
|
|
59
65
|
}
|
|
60
66
|
}
|
|
67
|
+
|
|
68
|
+
// ─── Disposal: proactive cleanup of watchers/listeners/disposers ────────
|
|
69
|
+
|
|
70
|
+
function _disposeElement(node) {
|
|
71
|
+
if (node.__ctx && node.__ctx.__listeners) {
|
|
72
|
+
for (const fn of node.__ctx.__listeners) {
|
|
73
|
+
_storeWatchers.delete(fn);
|
|
74
|
+
_i18nListeners.delete(fn);
|
|
75
|
+
}
|
|
76
|
+
node.__ctx.__listeners.clear();
|
|
77
|
+
}
|
|
78
|
+
if (node.__disposers) {
|
|
79
|
+
node.__disposers.forEach((fn) => fn());
|
|
80
|
+
node.__disposers = null;
|
|
81
|
+
}
|
|
82
|
+
node.__declared = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function _disposeTree(root) {
|
|
86
|
+
if (!root) return;
|
|
87
|
+
_disposeElement(root);
|
|
88
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
89
|
+
while (walker.nextNode()) _disposeElement(walker.currentNode);
|
|
90
|
+
}
|
package/src/router.js
CHANGED
|
@@ -6,13 +6,14 @@ import { _config, _stores, _log } from "./globals.js";
|
|
|
6
6
|
import { createContext } from "./context.js";
|
|
7
7
|
import { evaluate } from "./evaluate.js";
|
|
8
8
|
import { findContext, _clearDeclared, _loadTemplateElement, _processTemplateIncludes } from "./dom.js";
|
|
9
|
-
import { processTree } from "./registry.js";
|
|
9
|
+
import { processTree, _disposeTree } from "./registry.js";
|
|
10
10
|
import { _animateIn } from "./animations.js";
|
|
11
11
|
|
|
12
12
|
export function _createRouter() {
|
|
13
13
|
const routes = [];
|
|
14
14
|
let current = { path: "", params: {}, query: {}, hash: "" };
|
|
15
15
|
const listeners = new Set();
|
|
16
|
+
const _autoTemplateCache = new Map();
|
|
16
17
|
|
|
17
18
|
function _getOrCreateEntry(path) {
|
|
18
19
|
let entry = routes.find((r) => r.path === path);
|
|
@@ -118,9 +119,38 @@ export function _createRouter() {
|
|
|
118
119
|
const outletName = outletAttr && outletAttr.trim() !== "" ? outletAttr.trim() : "default";
|
|
119
120
|
|
|
120
121
|
// Find the template for this outlet in the matched route
|
|
121
|
-
|
|
122
|
+
let tpl = matched?.route?.outlets?.[outletName];
|
|
123
|
+
|
|
124
|
+
// ── File-based routing: auto-resolve from route-view[src] or config ──
|
|
125
|
+
const configTemplates = _config.router.templates || "";
|
|
126
|
+
if (!tpl && (outletEl.hasAttribute("src") || configTemplates)) {
|
|
127
|
+
const rawSrc = outletEl.getAttribute("src") || configTemplates;
|
|
128
|
+
const baseSrc = rawSrc.replace(/\/?$/, "/");
|
|
129
|
+
const ext = outletEl.getAttribute("ext") || _config.router.ext || ".html";
|
|
130
|
+
const indexName = outletEl.getAttribute("route-index") || "index";
|
|
131
|
+
const segment = current.path === "/" ? indexName : current.path.replace(/^\//, "");
|
|
132
|
+
const fullSrc = baseSrc + segment + ext;
|
|
133
|
+
const cacheKey = outletName + ":" + fullSrc;
|
|
134
|
+
|
|
135
|
+
if (_autoTemplateCache.has(cacheKey)) {
|
|
136
|
+
tpl = _autoTemplateCache.get(cacheKey);
|
|
137
|
+
} else {
|
|
138
|
+
tpl = document.createElement("template");
|
|
139
|
+
tpl.setAttribute("src", fullSrc);
|
|
140
|
+
tpl.setAttribute("route", current.path);
|
|
141
|
+
document.body.appendChild(tpl);
|
|
142
|
+
_autoTemplateCache.set(cacheKey, tpl);
|
|
143
|
+
_log("[ROUTER] File-based route:", current.path, "→", fullSrc);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Auto i18n namespace (convention: filename = namespace)
|
|
147
|
+
if (outletEl.hasAttribute("i18n-ns") && !tpl.getAttribute("i18n-ns")) {
|
|
148
|
+
tpl.setAttribute("i18n-ns", segment);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
122
151
|
|
|
123
|
-
// Always clear first
|
|
152
|
+
// Always clear first — dispose watchers/listeners before wiping DOM
|
|
153
|
+
_disposeTree(outletEl);
|
|
124
154
|
outletEl.innerHTML = "";
|
|
125
155
|
|
|
126
156
|
if (tpl) {
|
|
@@ -130,6 +160,13 @@ export function _createRouter() {
|
|
|
130
160
|
await _loadTemplateElement(tpl);
|
|
131
161
|
}
|
|
132
162
|
|
|
163
|
+
// i18n namespace loading for route template
|
|
164
|
+
const i18nNs = tpl.getAttribute("i18n-ns");
|
|
165
|
+
if (i18nNs) {
|
|
166
|
+
const { _loadI18nNamespace } = await import("./i18n.js");
|
|
167
|
+
await _loadI18nNamespace(i18nNs);
|
|
168
|
+
}
|
|
169
|
+
|
|
133
170
|
const clone = tpl.content.cloneNode(true);
|
|
134
171
|
|
|
135
172
|
const routeCtx = createContext(
|
|
@@ -176,10 +213,10 @@ export function _createRouter() {
|
|
|
176
213
|
if (exactClass) {
|
|
177
214
|
link.classList.toggle(exactClass, current.path === routePath);
|
|
178
215
|
} else if (activeClass && !link.hasAttribute("route-active-exact")) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
current.path.startsWith(routePath)
|
|
182
|
-
);
|
|
216
|
+
const isActive = routePath === "/"
|
|
217
|
+
? current.path === "/"
|
|
218
|
+
: current.path.startsWith(routePath);
|
|
219
|
+
link.classList.toggle(activeClass, isActive);
|
|
183
220
|
}
|
|
184
221
|
});
|
|
185
222
|
|
|
@@ -278,6 +315,9 @@ export function _createRouter() {
|
|
|
278
315
|
}
|
|
279
316
|
return;
|
|
280
317
|
}
|
|
318
|
+
// Skip if path unchanged (prevents double-processing from programmatic hash set)
|
|
319
|
+
const [p] = raw.split("?");
|
|
320
|
+
if (p === current.path) return;
|
|
281
321
|
navigate(raw, true);
|
|
282
322
|
});
|
|
283
323
|
// Initial route
|