@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/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
+ };