@jsswift/core 1.0.41
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/dist/cms.js +5280 -0
- package/dist/cms.min.js +1 -0
- package/dist/min-cms.js +1 -0
- package/package.json +27 -0
- package/src/00-bootstrap.js +393 -0
- package/src/09-overlay-shared.js +162 -0
- package/src/10-overlay.js +213 -0
- package/src/15-dom-props.js +211 -0
- package/src/16-renderer-shared.js +308 -0
- package/src/17-renderer-children.js +134 -0
- package/src/18-renderer-args.js +73 -0
- package/src/20-renderer.js +575 -0
- package/src/30-rod-core.js +270 -0
- package/src/31-rod-model.js +157 -0
- package/src/32-lifecycle-helpers.js +84 -0
- package/src/32-lifecycle.js +131 -0
- package/src/33-debug-perf.js +176 -0
- package/src/34-rod-devtools.js +43 -0
- package/src/39-store-shared.js +60 -0
- package/src/40-store-core.js +203 -0
- package/src/41-store-extensions.js +194 -0
- package/src/42-plugins.js +163 -0
- package/src/42a-auth-shared.js +148 -0
- package/src/43-auth.js +171 -0
- package/src/43a-http-shared.js +234 -0
- package/src/44-http.js +157 -0
- package/src/45-route-hooks.js +55 -0
- package/src/49-router-shared.js +99 -0
- package/src/49a-ui-meta-shared.js +116 -0
- package/src/50-ui-meta.js +189 -0
- package/src/51-can.js +20 -0
- package/src/52-router.js +399 -0
- package/src/53-footer.js +142 -0
- package/src/README.md +58 -0
- package/src/modules.json +31 -0
package/src/44-http.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ===============================
|
|
2
|
+
// HTTP (fetch wrapper)
|
|
3
|
+
// ===============================
|
|
4
|
+
const {
|
|
5
|
+
createReactiveState: createHttpReactiveState,
|
|
6
|
+
sleep,
|
|
7
|
+
isRetryable,
|
|
8
|
+
makeAbort,
|
|
9
|
+
normalizeRequest,
|
|
10
|
+
runHooks,
|
|
11
|
+
wrapResponse,
|
|
12
|
+
withJSON
|
|
13
|
+
} = JSswift._httpShared;
|
|
14
|
+
const JSswiftHttpSetting =
|
|
15
|
+
typeof globalThis !== "undefined" && globalThis.JSswift_setting
|
|
16
|
+
? globalThis.JSswift_setting
|
|
17
|
+
: {};
|
|
18
|
+
const configHTTP = {
|
|
19
|
+
baseURL: JSswiftHttpSetting.http?.baseURL || "",
|
|
20
|
+
timeout: JSswiftHttpSetting.http?.timeout ?? 0, // ms, 0 = no timeout
|
|
21
|
+
retry: JSswiftHttpSetting.http?.retry || { attempts: 0, delay: 250, factor: 2 }, // attempts extra
|
|
22
|
+
headers: JSswiftHttpSetting.http?.headers || {},
|
|
23
|
+
credentials: JSswiftHttpSetting.http?.credentials, // "include" etc (optional)
|
|
24
|
+
debug: JSswiftHttpSetting.debug ?? false
|
|
25
|
+
};
|
|
26
|
+
const httpState = createHttpReactiveState(JSswift);
|
|
27
|
+
const now = () => (typeof performance !== "undefined" && performance.now ? performance.now() : Date.now());
|
|
28
|
+
|
|
29
|
+
const hooksHTTP = {
|
|
30
|
+
beforeRequest: new Set(), // (req) => req
|
|
31
|
+
afterResponse: new Set(), // (res, req) => res
|
|
32
|
+
onError: new Set() // (err, req) => void
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function coreFetch(req) {
|
|
36
|
+
// Auth integration: se esiste auth.fetch usa quello
|
|
37
|
+
const f = (JSswift.auth && typeof JSswift.auth.fetch === "function")
|
|
38
|
+
? JSswift.auth.fetch.bind(JSswift.auth)
|
|
39
|
+
: fetch;
|
|
40
|
+
const init = {
|
|
41
|
+
method: req.method,
|
|
42
|
+
headers: req.headers,
|
|
43
|
+
body: req.body,
|
|
44
|
+
credentials: req.credentials,
|
|
45
|
+
signal: req.signal
|
|
46
|
+
};
|
|
47
|
+
return f(req.url, init);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function request(input, init = {}) {
|
|
51
|
+
let req = normalizeRequest(configHTTP, input, init);
|
|
52
|
+
|
|
53
|
+
// hooksHTTP pre
|
|
54
|
+
req = await runHooks(hooksHTTP.beforeRequest, req);
|
|
55
|
+
const requestId = httpState.markStart(req);
|
|
56
|
+
const startAt = now();
|
|
57
|
+
|
|
58
|
+
const retryCfg = req.retry || { attempts: 0, delay: 250, factor: 2 };
|
|
59
|
+
const maxAttempts = Math.max(0, Number(retryCfg.attempts || 0));
|
|
60
|
+
let delay = Math.max(0, Number(retryCfg.delay || 0));
|
|
61
|
+
const factor = Math.max(1, Number(retryCfg.factor || 2));
|
|
62
|
+
|
|
63
|
+
let lastErr = null;
|
|
64
|
+
let lastRes = null;
|
|
65
|
+
let finalRes = null;
|
|
66
|
+
let finalErr = null;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
70
|
+
lastErr = null;
|
|
71
|
+
lastRes = null;
|
|
72
|
+
|
|
73
|
+
const { signal, cancel } = makeAbort(req.timeout, init.signal);
|
|
74
|
+
const effectiveReq = { ...req, signal };
|
|
75
|
+
const attemptAt = now();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (configHTTP.debug) console.log("[http.request]", effectiveReq.method, effectiveReq.url);
|
|
79
|
+
|
|
80
|
+
JSswift.perf?.inc("httpRequests");
|
|
81
|
+
JSswift.perf?.mark("http:req", { url: req.url, method: req.method });
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
const res = await coreFetch(effectiveReq);
|
|
85
|
+
lastRes = res;
|
|
86
|
+
|
|
87
|
+
// hooksHTTP post
|
|
88
|
+
const outRes = await runHooks(hooksHTTP.afterResponse, res, effectiveReq);
|
|
89
|
+
const dt = now() - attemptAt;
|
|
90
|
+
|
|
91
|
+
JSswift.perf?.tick("http:res", dt, { status: res.status, url: req.url });
|
|
92
|
+
|
|
93
|
+
// retryable status?
|
|
94
|
+
if (attempt < maxAttempts && isRetryable(null, outRes)) {
|
|
95
|
+
cancel();
|
|
96
|
+
await sleep(delay);
|
|
97
|
+
delay = Math.round(delay * factor);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
cancel();
|
|
102
|
+
finalRes = outRes;
|
|
103
|
+
return wrapResponse(outRes, effectiveReq);
|
|
104
|
+
|
|
105
|
+
} catch (err) {
|
|
106
|
+
lastErr = err;
|
|
107
|
+
cancel();
|
|
108
|
+
|
|
109
|
+
// hooksHTTP error
|
|
110
|
+
for (const fn of hooksHTTP.onError) {
|
|
111
|
+
try { fn(err, effectiveReq); } catch { }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// retry?
|
|
115
|
+
if (attempt < maxAttempts && isRetryable(err, null)) {
|
|
116
|
+
await sleep(delay);
|
|
117
|
+
delay = Math.round(delay * factor);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
finalErr = err;
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
const totalDt = now() - startAt;
|
|
127
|
+
httpState.markEnd(requestId, finalRes, finalErr, totalDt);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// fallback (non dovrebbe arrivare qui)
|
|
131
|
+
if (lastErr) throw lastErr;
|
|
132
|
+
return wrapResponse(lastRes, req);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// shortcuts
|
|
136
|
+
JSswift.http = {};
|
|
137
|
+
JSswift.http.request = request;
|
|
138
|
+
JSswift.http.state = () => httpState.state;
|
|
139
|
+
JSswift.http.get = (url, init) => request(url, { ...init, method: "GET" });
|
|
140
|
+
JSswift.http.del = (url, init) => request(url, { ...init, method: "DELETE" });
|
|
141
|
+
JSswift.http.post = (url, body, init) => withJSON(request, "POST", url, body, init);
|
|
142
|
+
JSswift.http.put = (url, body, init) => withJSON(request, "PUT", url, body, init);
|
|
143
|
+
JSswift.http.patch = (url, body, init) => withJSON(request, "PATCH", url, body, init);
|
|
144
|
+
|
|
145
|
+
JSswift.http.getJSON = async (url, init) => (await request(url, { ...init, method: "GET" })).jsonStrict();
|
|
146
|
+
JSswift.http.delJSON = async (url, init) => (await request(url, { ...init, method: "DELETE" })).jsonStrict();
|
|
147
|
+
JSswift.http.postJSON = async (url, body, init) => (await withJSON(request, "POST", url, body, init)).jsonStrict();
|
|
148
|
+
JSswift.http.putJSON = async (url, body, init) => (await withJSON(request, "PUT", url, body, init)).jsonStrict();
|
|
149
|
+
JSswift.http.patchJSON = async (url, body, init) => (await withJSON(request, "PATCH", url, body, init)).jsonStrict();
|
|
150
|
+
|
|
151
|
+
JSswift.http.onBefore = function (fn) { hooksHTTP.beforeRequest.add(fn); return () => hooksHTTP.beforeRequest.delete(fn); };
|
|
152
|
+
JSswift.http.onAfter = function (fn) { hooksHTTP.afterResponse.add(fn); return () => hooksHTTP.afterResponse.delete(fn); };
|
|
153
|
+
JSswift.http.onError = function (fn) { hooksHTTP.onError.add(fn); return () => hooksHTTP.onError.delete(fn); };
|
|
154
|
+
|
|
155
|
+
// shortcuts per browser global
|
|
156
|
+
window._http = JSswift.http;
|
|
157
|
+
// ===============================
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// useRouter (hook-style)
|
|
2
|
+
// ===============================
|
|
3
|
+
JSswift.useRouter = function (ctx) {
|
|
4
|
+
const router = JSswift.router;
|
|
5
|
+
|
|
6
|
+
// nessun cleanup necessario ora, ma pronto per future estensioni
|
|
7
|
+
if (ctx && typeof ctx.onDispose === "function") {
|
|
8
|
+
// placeholder per future listener
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
navigate: router.navigate,
|
|
13
|
+
back: () => history.back(),
|
|
14
|
+
forward: () => history.forward(),
|
|
15
|
+
get current() {
|
|
16
|
+
return router;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
// ===============================
|
|
21
|
+
// useRoute (hook-style, reattivo)
|
|
22
|
+
// ===============================
|
|
23
|
+
JSswift.useRoute = function (ctx) {
|
|
24
|
+
const [getPath, setPath] = JSswift.reactive.signal("");
|
|
25
|
+
const [getParams, setParams] = JSswift.reactive.signal({});
|
|
26
|
+
const [getQuery, setQuery] = JSswift.reactive.signal({});
|
|
27
|
+
const [getHash, setHash] = JSswift.reactive.signal("");
|
|
28
|
+
|
|
29
|
+
// handler aggiornamento
|
|
30
|
+
const update = (routeCtx) => {
|
|
31
|
+
setPath(routeCtx.path || "");
|
|
32
|
+
setParams(routeCtx.params || {});
|
|
33
|
+
setQuery(routeCtx.query || {});
|
|
34
|
+
setHash(routeCtx.hash || "");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// subscribe router
|
|
38
|
+
const unsubscribe = JSswift.router.subscribe(update);
|
|
39
|
+
|
|
40
|
+
// cleanup automatico
|
|
41
|
+
if (ctx && typeof ctx.onDispose === "function") {
|
|
42
|
+
ctx.onDispose(unsubscribe);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
path: getPath,
|
|
47
|
+
params: getParams,
|
|
48
|
+
query: getQuery,
|
|
49
|
+
hash: getHash
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ===============================
|
|
54
|
+
// Permission-based rendering helpers
|
|
55
|
+
// ===============================
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ===============================
|
|
2
|
+
// Router shared helpers
|
|
3
|
+
// ===============================
|
|
4
|
+
JSswift._routerShared = (() => {
|
|
5
|
+
function normalizePath(path) {
|
|
6
|
+
if (!path) return "/";
|
|
7
|
+
if (!path.startsWith("/")) path = "/" + path;
|
|
8
|
+
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function stripBase(path, base = "") {
|
|
13
|
+
if (base && path.startsWith(base)) {
|
|
14
|
+
const next = path.slice(base.length) || "/";
|
|
15
|
+
return normalizePath(next);
|
|
16
|
+
}
|
|
17
|
+
return normalizePath(path);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function compilePattern(pattern) {
|
|
21
|
+
pattern = normalizePath(pattern);
|
|
22
|
+
const keys = [];
|
|
23
|
+
const regexStr = pattern
|
|
24
|
+
.replace(/([.+*?=^!${}()[\]|/\\])/g, "\\$1")
|
|
25
|
+
.replace(/\\\/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
26
|
+
keys.push(key);
|
|
27
|
+
return "\\/([^\\/]+)";
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
pattern,
|
|
31
|
+
regex: new RegExp("^" + regexStr + "$"),
|
|
32
|
+
keys
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseQuery(search) {
|
|
37
|
+
const query = {};
|
|
38
|
+
const params = new URLSearchParams(search || "");
|
|
39
|
+
params.forEach((value, key) => {
|
|
40
|
+
if (query[key] === undefined) query[key] = value;
|
|
41
|
+
else if (Array.isArray(query[key])) query[key].push(value);
|
|
42
|
+
else query[key] = [query[key], value];
|
|
43
|
+
});
|
|
44
|
+
return query;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function flattenRoutes(routes) {
|
|
48
|
+
const all = [];
|
|
49
|
+
for (const route of routes) {
|
|
50
|
+
all.push({ ...route, _parent: null });
|
|
51
|
+
if (route.children && route.children.length) {
|
|
52
|
+
for (const child of route.children) {
|
|
53
|
+
all.push({ ...child, _parent: route });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
all.sort((a, b) => b.path.length - a.path.length);
|
|
58
|
+
return all;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function matchRoute(pathname, routes) {
|
|
62
|
+
const path = normalizePath(pathname);
|
|
63
|
+
const all = flattenRoutes(routes);
|
|
64
|
+
|
|
65
|
+
for (const route of all) {
|
|
66
|
+
const match = route._compiled.regex.exec(path);
|
|
67
|
+
if (!match) continue;
|
|
68
|
+
|
|
69
|
+
const params = {};
|
|
70
|
+
route._compiled.keys.forEach((key, index) => {
|
|
71
|
+
params[key] = decodeURIComponent(match[index + 1]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return { route, params, parent: route._parent || null };
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pushHistoryEntry(history, ctx, limit = 50) {
|
|
80
|
+
history.push({
|
|
81
|
+
at: Date.now(),
|
|
82
|
+
path: ctx.path,
|
|
83
|
+
params: ctx.params,
|
|
84
|
+
query: ctx.query,
|
|
85
|
+
hash: ctx.hash
|
|
86
|
+
});
|
|
87
|
+
if (history.length > limit) history.shift();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
normalizePath,
|
|
92
|
+
stripBase,
|
|
93
|
+
compilePattern,
|
|
94
|
+
parseQuery,
|
|
95
|
+
flattenRoutes,
|
|
96
|
+
matchRoute,
|
|
97
|
+
pushHistoryEntry
|
|
98
|
+
};
|
|
99
|
+
})();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ===============================
|
|
2
|
+
// UI meta shared helpers
|
|
3
|
+
// ===============================
|
|
4
|
+
JSswift._uiMetaShared = (() => {
|
|
5
|
+
function resolveDocComponents(_) {
|
|
6
|
+
return {
|
|
7
|
+
hasTabPanel: typeof _.TabPanel === "function",
|
|
8
|
+
Card: typeof _.Card === "function"
|
|
9
|
+
? _.Card
|
|
10
|
+
: (...children) => _.div({ class: "cms-doc-card" }, ...children),
|
|
11
|
+
Chip: typeof _.Chip === "function"
|
|
12
|
+
? _.Chip
|
|
13
|
+
: (_props, label) => _.span({ class: "cms-chip cms-chip-fallback" }, label)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatMetaValues(values) {
|
|
18
|
+
if (!values) return "—";
|
|
19
|
+
if (Array.isArray(values)) return values.join(" | ");
|
|
20
|
+
return String(values);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function renderMetaItem(_, item, Chip) {
|
|
24
|
+
return _.div({ class: "cms-doc-meta" },
|
|
25
|
+
_.div({ class: "cms-doc-meta-head" },
|
|
26
|
+
_.div({ class: "cms-doc-meta-name" }, item.name),
|
|
27
|
+
_.div({ class: "cms-doc-meta-types" },
|
|
28
|
+
...(item.type
|
|
29
|
+
? String(item.type).split("|").map((token) => Chip({ color: "secondary", dense: true, outline: true }, token))
|
|
30
|
+
: [_.span("—")])
|
|
31
|
+
)
|
|
32
|
+
),
|
|
33
|
+
_.div({ class: "cms-doc-meta-grid" },
|
|
34
|
+
_.div({ class: "cms-doc-meta-block" },
|
|
35
|
+
_.div({ class: "cms-doc-meta-label" }, "Default"),
|
|
36
|
+
_.div({ class: "cms-doc-meta-value" }, item.default == null ? "—" : String(item.default))
|
|
37
|
+
),
|
|
38
|
+
_.div({ class: "cms-doc-meta-block" },
|
|
39
|
+
_.div({ class: "cms-doc-meta-label" }, "Values"),
|
|
40
|
+
_.div({ class: "cms-doc-meta-value" }, formatMetaValues(item.values))
|
|
41
|
+
)
|
|
42
|
+
),
|
|
43
|
+
_.div({ class: "cms-doc-meta-block" },
|
|
44
|
+
_.div({ class: "cms-doc-meta-label" }, "Description"),
|
|
45
|
+
_.div({ class: "cms-doc-meta-value" }, item.description || "—")
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderTabGroupFallback(_, rows) {
|
|
51
|
+
return _.div({ class: "cms-doc-fallback" },
|
|
52
|
+
rows.map((row) => _.div({ class: "cms-doc-fallback-item" },
|
|
53
|
+
_.h4({ class: "cms-doc-fallback-title" }, row.label || row.name),
|
|
54
|
+
row.content
|
|
55
|
+
))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeEventRows(_, events) {
|
|
60
|
+
if (!events) return [];
|
|
61
|
+
if (Array.isArray(events)) {
|
|
62
|
+
return events.map((eventItem) => ({
|
|
63
|
+
name: eventItem.name,
|
|
64
|
+
wrap: true,
|
|
65
|
+
label: eventItem.name,
|
|
66
|
+
content: _.div({ class: "cms-p-md" }, eventItem.description)
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return Object.entries(events).map(([key, value]) => ({
|
|
70
|
+
name: key,
|
|
71
|
+
wrap: true,
|
|
72
|
+
label: key,
|
|
73
|
+
content: _.div({ class: "cms-p-md" }, value)
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeSlotRows(_, slots) {
|
|
78
|
+
if (!slots) return [];
|
|
79
|
+
if (Array.isArray(slots)) {
|
|
80
|
+
return slots.map((slot) => ({
|
|
81
|
+
name: slot.name,
|
|
82
|
+
wrap: true,
|
|
83
|
+
label: slot.type,
|
|
84
|
+
content: slot.description
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
return Object.entries(slots).map(([key, value]) => ({
|
|
88
|
+
name: key,
|
|
89
|
+
wrap: true,
|
|
90
|
+
label: key,
|
|
91
|
+
content: _.div({ class: "cms-doc-meta" },
|
|
92
|
+
_.div({ class: "cms-doc-meta-head" },
|
|
93
|
+
_.div({ class: "cms-doc-meta-name" }, key)
|
|
94
|
+
),
|
|
95
|
+
_.div({ class: "cms-doc-meta-grid" },
|
|
96
|
+
_.div({ class: "cms-doc-meta-block" },
|
|
97
|
+
_.div({ class: "cms-doc-meta-label" }, "Type"),
|
|
98
|
+
_.div({ class: "cms-doc-meta-value" }, value.type || "—")
|
|
99
|
+
)
|
|
100
|
+
),
|
|
101
|
+
_.div({ class: "cms-doc-meta-block" },
|
|
102
|
+
_.div({ class: "cms-doc-meta-label" }, "Description"),
|
|
103
|
+
_.div({ class: "cms-doc-meta-value" }, value.description || "—")
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
resolveDocComponents,
|
|
111
|
+
renderMetaItem,
|
|
112
|
+
renderTabGroupFallback,
|
|
113
|
+
normalizeEventRows,
|
|
114
|
+
normalizeSlotRows
|
|
115
|
+
};
|
|
116
|
+
})();
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
JSswift.ui = JSswift.ui || {};
|
|
2
|
+
JSswift.ui.meta = JSswift.ui.meta || {};
|
|
3
|
+
|
|
4
|
+
JSswift.ui.slot = function slot(value, opts = {}) {
|
|
5
|
+
const UI = JSswift.ui;
|
|
6
|
+
const {
|
|
7
|
+
as = "node", // "node" | "text" | "icon"
|
|
8
|
+
wrap = "span", // "span" | "text" | null
|
|
9
|
+
iconProps = null, // props extra per UI.Icon
|
|
10
|
+
filter = true // filtra null/false
|
|
11
|
+
} = opts;
|
|
12
|
+
|
|
13
|
+
const makeTextNode = (v) => document.createTextNode(String(v));
|
|
14
|
+
const makeSpan = (v) => _.span(String(v));
|
|
15
|
+
|
|
16
|
+
const normalizeOne = (v) => {
|
|
17
|
+
if (v == null || v === false) return null;
|
|
18
|
+
|
|
19
|
+
// function: IMPORTANT -> non eseguire qui (serve reattività)
|
|
20
|
+
if (typeof v === "function") return v;
|
|
21
|
+
|
|
22
|
+
// array: flatten
|
|
23
|
+
if (Array.isArray(v)) return v.flatMap(normalizeOne).filter(Boolean);
|
|
24
|
+
|
|
25
|
+
// DOM Node
|
|
26
|
+
if (v && typeof v === "object" && v.nodeType) return v;
|
|
27
|
+
|
|
28
|
+
// primitive
|
|
29
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
30
|
+
const s = String(v);
|
|
31
|
+
|
|
32
|
+
if (as === "icon" && UI.Icon) {
|
|
33
|
+
// se vuoi: allow "tabler:plus" o simili
|
|
34
|
+
return UI.Icon(s, { class: "cms-slot-icon", ...(iconProps || {}) });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (wrap === "text") return makeTextNode(s);
|
|
38
|
+
if (wrap === "span") return makeSpan(s);
|
|
39
|
+
return s; // lascia stringa nativa (se il tuo _h la gestisce)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// fallback: stringify
|
|
43
|
+
if (wrap === "text") return makeTextNode(v);
|
|
44
|
+
if (wrap === "span") return makeSpan(v);
|
|
45
|
+
return String(v);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const out = normalizeOne(value);
|
|
49
|
+
if (!filter) return out;
|
|
50
|
+
|
|
51
|
+
// out può essere null, function, node, string, oppure array
|
|
52
|
+
if (Array.isArray(out)) return out.filter(Boolean);
|
|
53
|
+
return out;
|
|
54
|
+
};
|
|
55
|
+
JSswift.ui.slots = function slots(...values) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const v of values) {
|
|
58
|
+
const r = JSswift.ui.slot(v);
|
|
59
|
+
if (!r) continue;
|
|
60
|
+
if (Array.isArray(r)) out.push(...r);
|
|
61
|
+
else out.push(r);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// docTable genera una tabella di documentazione
|
|
67
|
+
JSswift.docTable = (name) => {
|
|
68
|
+
if (!JSswift.isDev()) return _.div(); // non fa niente in prod
|
|
69
|
+
|
|
70
|
+
const {
|
|
71
|
+
resolveDocComponents,
|
|
72
|
+
renderMetaItem,
|
|
73
|
+
renderTabGroupFallback,
|
|
74
|
+
normalizeEventRows,
|
|
75
|
+
normalizeSlotRows
|
|
76
|
+
} = JSswift._uiMetaShared;
|
|
77
|
+
const meta = JSswift.ui.meta?.[name];
|
|
78
|
+
if (!meta) return _.div({ class: "cms-muted" }, `Meta non trovata: ${name}`);
|
|
79
|
+
const { hasTabPanel, Card, Chip } = resolveDocComponents(_);
|
|
80
|
+
|
|
81
|
+
const list = {};
|
|
82
|
+
Object.entries(meta.props || {}).forEach(([k, v]) => {
|
|
83
|
+
const cat = v.category || "general";
|
|
84
|
+
if (!list[cat]) list[cat] = [];
|
|
85
|
+
list[cat].push({
|
|
86
|
+
name: k,
|
|
87
|
+
...v
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
const propsTab = Object.keys(list).map((v) => {
|
|
91
|
+
const rows = list[v].map((p) => {
|
|
92
|
+
return {
|
|
93
|
+
name: p.name,
|
|
94
|
+
wrap: true,
|
|
95
|
+
label: p.name,
|
|
96
|
+
content: renderMetaItem(_, p, Chip)
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
name: v, wrap: true, label: v, content: hasTabPanel ? _.TabPanel({
|
|
101
|
+
orientation: "vertical",
|
|
102
|
+
animated: true,
|
|
103
|
+
radius: "0 0 0 var(--cms-r-default)",
|
|
104
|
+
tabs: rows
|
|
105
|
+
}) : renderTabGroupFallback(_, rows)
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const eventsRows = normalizeEventRows(_, meta.events);
|
|
110
|
+
const slotsRows = normalizeSlotRows(_, meta.slots);
|
|
111
|
+
const tabPanelModel = _.rod(null);
|
|
112
|
+
const taps = [];
|
|
113
|
+
if (slotsRows.length) taps.push({
|
|
114
|
+
name: "Slots",
|
|
115
|
+
wrap: true,
|
|
116
|
+
label: "Slots",
|
|
117
|
+
content: hasTabPanel ? _.TabPanel({
|
|
118
|
+
animated: true,
|
|
119
|
+
radius: "0 0 0 var(--cms-r-default)",
|
|
120
|
+
orientation: "vertical",
|
|
121
|
+
tabs: slotsRows
|
|
122
|
+
}) : renderTabGroupFallback(_, slotsRows)
|
|
123
|
+
});
|
|
124
|
+
if (propsTab.length) taps.push(...propsTab);
|
|
125
|
+
if (eventsRows.length) taps.push({
|
|
126
|
+
name: "Events",
|
|
127
|
+
wrap: true,
|
|
128
|
+
label: "Events",
|
|
129
|
+
content: hasTabPanel ? _.TabPanel({
|
|
130
|
+
animated: true,
|
|
131
|
+
radius: "0 0 0 var(--cms-r-default)",
|
|
132
|
+
orientation: "vertical",
|
|
133
|
+
tabs: eventsRows
|
|
134
|
+
}) : renderTabGroupFallback(_, eventsRows)
|
|
135
|
+
});
|
|
136
|
+
const first = "general";
|
|
137
|
+
taps.sort((a, b) => {
|
|
138
|
+
if (a.name === first) return -1;
|
|
139
|
+
if (b.name === first) return 1;
|
|
140
|
+
return a.name.localeCompare(b.name);
|
|
141
|
+
})
|
|
142
|
+
return Card({ class: "cms-doc-table" },
|
|
143
|
+
_.div({ class: "cms-doc-table-header" },
|
|
144
|
+
_.div({ class: "cms-doc-table-title" }, `_.${name}`),
|
|
145
|
+
meta.signature ? _.div({ class: "cms-doc-table-signature" }, String(meta.signature).replaceAll("UI.", "_.")) : null
|
|
146
|
+
),
|
|
147
|
+
taps.length
|
|
148
|
+
? _.h4({ class: "cms-doc-table-section-title" }, propsTab.length ? "Props" : "Documentation")
|
|
149
|
+
: _.p({ class: "cms-muted" }, "Nessuna documentazione strutturata disponibile."),
|
|
150
|
+
taps.length
|
|
151
|
+
? (hasTabPanel ? _.TabPanel({
|
|
152
|
+
border: true,
|
|
153
|
+
animated: true,
|
|
154
|
+
orientation: "horizontal",
|
|
155
|
+
tabs: taps, model: tabPanelModel
|
|
156
|
+
}) : renderTabGroupFallback(_, taps))
|
|
157
|
+
: null,
|
|
158
|
+
meta.returns ? _.p({ class: "cms-muted", style: { marginTop: "14px" } }, `Returns: ${meta.returns}`) : null
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
JSswift.ui.inspect = (name) => console.log(JSswift.ui.meta?.[name] || "meta not found");
|
|
163
|
+
|
|
164
|
+
JSswift.ui.can = function (ctx, permOrRole, render, elseRender = null) {
|
|
165
|
+
const auth = JSswift.useAuth ? JSswift.useAuth(ctx) : JSswift.auth;
|
|
166
|
+
if (!auth) return typeof elseRender === "function" ? elseRender() : null;
|
|
167
|
+
|
|
168
|
+
const ok =
|
|
169
|
+
typeof permOrRole === "function"
|
|
170
|
+
? !!permOrRole(auth)
|
|
171
|
+
: (auth.can?.(permOrRole) || auth.hasRole?.(permOrRole));
|
|
172
|
+
|
|
173
|
+
if (ok) return typeof render === "function" ? render() : render;
|
|
174
|
+
return typeof elseRender === "function" ? elseRender() : elseRender;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
JSswift.ui.canAny = function (ctx, list, render, elseRender = null) {
|
|
178
|
+
const auth = JSswift.useAuth ? JSswift.useAuth(ctx) : JSswift.auth;
|
|
179
|
+
const ok = !!auth?.canAny?.(list);
|
|
180
|
+
return ok ? (typeof render === "function" ? render() : render)
|
|
181
|
+
: (typeof elseRender === "function" ? elseRender() : elseRender);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
JSswift.ui.canAll = function (ctx, list, render, elseRender = null) {
|
|
185
|
+
const auth = JSswift.useAuth ? JSswift.useAuth(ctx) : JSswift.auth;
|
|
186
|
+
const ok = !!auth?.canAll?.(list);
|
|
187
|
+
return ok ? (typeof render === "function" ? render() : render)
|
|
188
|
+
: (typeof elseRender === "function" ? elseRender() : elseRender);
|
|
189
|
+
};
|
package/src/51-can.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ===============================
|
|
2
|
+
// Can component (hyperscript-friendly)
|
|
3
|
+
// ===============================
|
|
4
|
+
JSswift.Can = function Can(props, ctx) {
|
|
5
|
+
const auth = JSswift.useAuth ? JSswift.useAuth(ctx) : JSswift.auth;
|
|
6
|
+
|
|
7
|
+
const pred = () => {
|
|
8
|
+
if (!auth) return false;
|
|
9
|
+
if (props.when && typeof props.when === "function") return !!props.when(auth);
|
|
10
|
+
if (props.perm) return !!auth.can?.(props.perm);
|
|
11
|
+
if (props.role) return !!auth.hasRole?.(props.role);
|
|
12
|
+
if (props.any) return !!auth.canAny?.(props.any);
|
|
13
|
+
if (props.all) return !!auth.canAll?.(props.all);
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return pred()
|
|
18
|
+
? (typeof props.then === "function" ? props.then() : props.then ?? null)
|
|
19
|
+
: (typeof props.else === "function" ? props.else() : props.else ?? null);
|
|
20
|
+
};
|