kreftjs 0.0.20-beta → 0.0.22-beta
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 +10 -1
- package/bin/artisan.js +4 -2
- package/bin/backend/cache.js +31 -5
- package/bin/backend/gc.js +193 -43
- package/bin/backend/http.js +6 -2
- package/bin/backend/index.js +6 -0
- package/bin/backend/mcp-client.js +133 -0
- package/bin/backend/mcp.js +17 -0
- package/bin/backend/model.js +15 -1
- package/bin/backend/shield.js +266 -0
- package/bin/backend/stream.js +74 -0
- package/bin/build.js +12 -5
- package/bin/db-api.js +9 -1
- package/bin/kreftjs.js +50 -12
- package/book/ar/16-virtual-list.md +131 -0
- package/book/ar/17-combobox.md +169 -0
- package/book/ar/18-tabs-offline.md +168 -0
- package/book/ar/19-wizard.md +196 -0
- package/book/ar/20-pipes-match-props.md +75 -0
- package/book/ar/README.md +12 -1
- package/book/kreft-book-ar.docx +0 -0
- package/docs/README.md +209 -13
- package/editors/sublime/Comments.tmPreferences +26 -0
- package/editors/sublime/Component.sublime-snippet +20 -0
- package/editors/sublime/Kreft.sublime-syntax +39 -0
- package/editors/sublime/LICENSE +95 -0
- package/editors/sublime/README.md +7 -0
- package/editors/sublime/action.sublime-snippet +10 -0
- package/editors/sublime/derive.sublime-snippet +8 -0
- package/editors/sublime/for.sublime-snippet +12 -0
- package/editors/sublime/k-case.sublime-snippet +8 -0
- package/editors/sublime/k-if.sublime-snippet +8 -0
- package/editors/sublime/k-match.sublime-snippet +12 -0
- package/editors/sublime/kreft-wait.sublime-snippet +12 -0
- package/editors/sublime/layout.sublime-snippet +8 -0
- package/editors/sublime/load.sublime-snippet +8 -0
- package/editors/sublime/on.sublime-snippet +10 -0
- package/editors/sublime/pipe.sublime-snippet +8 -0
- package/editors/sublime/prop-typed.sublime-snippet +8 -0
- package/editors/sublime/saved.sublime-snippet +8 -0
- package/editors/sublime/section.sublime-snippet +10 -0
- package/editors/sublime/state.sublime-snippet +8 -0
- package/editors/sublime/watch.sublime-snippet +10 -0
- package/editors/sublime/when.sublime-snippet +10 -0
- package/editors/sublime/yield.sublime-snippet +8 -0
- package/editors/vscode/snippets/kreftjs.json +143 -15
- package/editors/zed/LICENSE +95 -0
- package/editors/zed/README.md +17 -0
- package/editors/zed/extension.toml +11 -0
- package/editors/zed/languages/kreft/brackets.scm +4 -0
- package/editors/zed/languages/kreft/config.toml +17 -0
- package/editors/zed/languages/kreft/highlights.scm +18 -0
- package/editors/zed/languages/kreft/indents.scm +5 -0
- package/editors/zed/languages/kreft/injections.scm +14 -0
- package/editors/zed/languages/kreft/outline.scm +3 -0
- package/kreftjs.d.ts +1 -1
- package/kreftjs.js +219 -14
- package/package.json +3 -2
- package/server.d.ts +44 -0
- package/templates/app/backend/app/Auth/Session.js +8 -4
- package/templates/app/backend/app/Http/Middleware/Authenticate.js +7 -4
- package/templates/app/backend/database/seeders/DatabaseSeeder.js +4 -0
- package/templates/app/frontend/kreftjs.d.ts +1 -1
- package/templates/app/frontend/kreftjs.js +219 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Kreft v0.0.
|
|
2
|
+
* Kreft v0.0.22-beta — reactive by nature
|
|
3
3
|
* The .kreft component language (state · derive · action · when · watch · on),
|
|
4
4
|
* { } views with k-if / k-each / k-model, components, slots, router, stores,
|
|
5
5
|
* share / receive, event bus and a template engine.
|
|
@@ -1026,6 +1026,7 @@
|
|
|
1026
1026
|
onceDepth++;
|
|
1027
1027
|
try { return compile(el, scope, inst); } finally { onceDepth--; }
|
|
1028
1028
|
}
|
|
1029
|
+
if (el.hasAttribute('k-match')) return compileMatch(el, scope, inst); // becomes k-if / k-elif / k-else on its children
|
|
1029
1030
|
if (el.hasAttribute('k-if')) return compileIf(el, scope, inst); // k-if is checked before k-each
|
|
1030
1031
|
if (el.hasAttribute('k-elif') || el.hasAttribute('k-else')) {
|
|
1031
1032
|
warn('k-elif / k-else has no k-if right before it.', el);
|
|
@@ -1060,6 +1061,157 @@
|
|
|
1060
1061
|
compileElement(el, scope, inst, needAttrs, hasAnim, info);
|
|
1061
1062
|
}
|
|
1062
1063
|
|
|
1064
|
+
/* ---------- pipes: { price | currency:'EUR' | upper } ---------- */
|
|
1065
|
+
// positions of `want` at the top level of an expression: not inside (), [], {}, strings or regular expressions.
|
|
1066
|
+
// For '|' a lone bar only: `a || b` and `a |= b` are not pipes.
|
|
1067
|
+
function topLevel(s, want) {
|
|
1068
|
+
const hits = [];
|
|
1069
|
+
let depth = 0, prev = '';
|
|
1070
|
+
for (let i = 0; i < s.length; i++) {
|
|
1071
|
+
const c = s[i];
|
|
1072
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
1073
|
+
for (i++; i < s.length && s[i] !== c; i++) if (s[i] === '\\') i++;
|
|
1074
|
+
prev = c;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (c === '/' && s[i + 1] !== '/' && s[i + 1] !== '*' && (prev === '' || '(,=:?!&|{[;+-*%<>~^'.includes(prev))) { // a regular expression
|
|
1078
|
+
let cls = false;
|
|
1079
|
+
for (i++; i < s.length; i++) {
|
|
1080
|
+
if (s[i] === '\\') i++;
|
|
1081
|
+
else if (s[i] === '[') cls = true;
|
|
1082
|
+
else if (s[i] === ']') cls = false;
|
|
1083
|
+
else if (s[i] === '/' && !cls) break;
|
|
1084
|
+
}
|
|
1085
|
+
prev = '/';
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (c === '(' || c === '[' || c === '{') depth++;
|
|
1089
|
+
else if (c === ')' || c === ']' || c === '}') depth--;
|
|
1090
|
+
else if (depth === 0 && c === want && (want !== '|' || (s[i + 1] !== '|' && s[i - 1] !== '|' && s[i + 1] !== '='))) hits.push(i);
|
|
1091
|
+
if (!/\s/.test(c)) prev = c;
|
|
1092
|
+
}
|
|
1093
|
+
return hits;
|
|
1094
|
+
}
|
|
1095
|
+
const cutAt = (s, cuts) => { const out = []; let from = 0; for (const i of cuts) { out.push(s.slice(from, i)); from = i + 1; } out.push(s.slice(from)); return out; };
|
|
1096
|
+
const pipeCache = new Map();
|
|
1097
|
+
// "price | currency:'EUR' | upper" → Kreftjs.__pipe("upper")(Kreftjs.__pipe("currency")((price), 'EUR'))
|
|
1098
|
+
// A pipe is a function in your component (a local function wins) or one added with Kreftjs.pipe(name, fn).
|
|
1099
|
+
function applyPipes(expr) {
|
|
1100
|
+
if (typeof expr !== 'string' || expr.indexOf('|') < 0) return expr;
|
|
1101
|
+
let r = pipeCache.get(expr);
|
|
1102
|
+
if (r !== undefined) return r;
|
|
1103
|
+
r = expr;
|
|
1104
|
+
const cuts = topLevel(expr, '|');
|
|
1105
|
+
if (cuts.length) {
|
|
1106
|
+
const segs = cutAt(expr, cuts);
|
|
1107
|
+
let code = '(' + segs[0].trim() + ')', ok = true;
|
|
1108
|
+
for (const seg of segs.slice(1)) {
|
|
1109
|
+
const m = /^\s*([A-Za-z_$][\w$]*)\s*([\s\S]*)$/.exec(seg);
|
|
1110
|
+
if (!m) { ok = false; break; }
|
|
1111
|
+
const name = m[1];
|
|
1112
|
+
const rest = m[2].trim();
|
|
1113
|
+
let args = '';
|
|
1114
|
+
if (rest[0] === ':') args = cutAt(rest.slice(1), topLevel(rest.slice(1), ':')).map((a) => a.trim()).join(', ');
|
|
1115
|
+
else if (rest[0] === '(' && rest[rest.length - 1] === ')') args = rest.slice(1, -1).trim();
|
|
1116
|
+
else if (rest) { ok = false; break; }
|
|
1117
|
+
code = `Kreftjs.__pipe(${JSON.stringify(name)}, typeof ${name} === "function" ? ${name} : null)(${code}${args ? ', ' + args : ''})`;
|
|
1118
|
+
}
|
|
1119
|
+
if (ok) r = code;
|
|
1120
|
+
}
|
|
1121
|
+
if (pipeCache.size > 5000) pipeCache.clear();
|
|
1122
|
+
pipeCache.set(expr, r);
|
|
1123
|
+
return r;
|
|
1124
|
+
}
|
|
1125
|
+
const pipes = Object.create(null);
|
|
1126
|
+
const asText = (v) => (v == null ? '' : String(v));
|
|
1127
|
+
// "2026-11-05 15:39:58" (a date from the Kreft database) has no time zone: the database stores UTC, so read it as UTC
|
|
1128
|
+
const toDate = (v) => (v instanceof Date ? v : typeof v === 'string' && /^\d{4}-\d\d-\d\d \d\d:\d\d(:\d\d)?$/.test(v) ? new Date(v.replace(' ', 'T') + 'Z') : new Date(v));
|
|
1129
|
+
Object.assign(pipes, {
|
|
1130
|
+
upper: (v) => asText(v).toUpperCase(),
|
|
1131
|
+
lower: (v) => asText(v).toLowerCase(),
|
|
1132
|
+
capitalize: (v) => { const t = asText(v); return t.charAt(0).toUpperCase() + t.slice(1); },
|
|
1133
|
+
trim: (v) => asText(v).trim(),
|
|
1134
|
+
truncate: (v, n = 50, tail = '…') => { const t = asText(v); return t.length > n ? t.slice(0, Math.max(0, n)).trimEnd() + tail : t; },
|
|
1135
|
+
currency: (v, code = 'USD') => (v == null || v === '' ? '' : formatCurrency(Number(v), code)),
|
|
1136
|
+
number: (v, digits) => (v == null || v === '' ? '' : formatNumber(Number(v), digits && typeof digits === 'object' ? digits : digits == null ? undefined : { maximumFractionDigits: digits, minimumFractionDigits: digits })),
|
|
1137
|
+
percent: (v, digits = 0) => (v == null || v === '' ? '' : formatNumber(Number(v), { style: 'percent', maximumFractionDigits: digits, minimumFractionDigits: digits })),
|
|
1138
|
+
date: (v, style = 'medium') => (v == null || v === '' ? '' : formatDate(toDate(v), typeof style === 'object' ? style : { dateStyle: style })),
|
|
1139
|
+
time: (v, style = 'short') => (v == null || v === '' ? '' : formatDate(toDate(v), typeof style === 'object' ? style : { timeStyle: style })),
|
|
1140
|
+
ago: (v) => {
|
|
1141
|
+
if (v == null || v === '') return '';
|
|
1142
|
+
const secs = Math.round((toDate(v).getTime() - Date.now()) / 1000);
|
|
1143
|
+
if (isNaN(secs)) return '';
|
|
1144
|
+
const rtf = new Intl.RelativeTimeFormat(currentLocaleRef.value, { numeric: 'auto' });
|
|
1145
|
+
const steps = [['year', 31536000], ['month', 2592000], ['day', 86400], ['hour', 3600], ['minute', 60]];
|
|
1146
|
+
for (const [unit, size] of steps) if (Math.abs(secs) >= size) return rtf.format(Math.round(secs / size), unit);
|
|
1147
|
+
return rtf.format(secs, 'second');
|
|
1148
|
+
},
|
|
1149
|
+
json: (v, indent = 2) => { try { return JSON.stringify(v, null, indent); } catch (_) { return String(v); } },
|
|
1150
|
+
fallback: (v, d = '') => (v == null || v === '' ? d : v),
|
|
1151
|
+
join: (v, sep = ', ') => (isArray(v) ? v.join(sep) : asText(v)),
|
|
1152
|
+
first: (v, n) => (n == null ? (v == null ? v : v[0]) : (v || []).slice(0, n)),
|
|
1153
|
+
last: (v, n) => (n == null ? (v == null ? v : v[v.length - 1]) : (v || []).slice(-n)),
|
|
1154
|
+
slice: (v, a, b) => (v == null ? v : v.slice(a, b)),
|
|
1155
|
+
reverse: (v) => (isArray(v) ? [...v].reverse() : [...asText(v)].reverse().join('')),
|
|
1156
|
+
sortBy: (v, key, dir = 'asc') => (isArray(v) ? [...v].sort((x, y) => { const a = key ? x[key] : x, b = key ? y[key] : y; return (a > b ? 1 : a < b ? -1 : 0) * (dir === 'desc' ? -1 : 1); }) : v),
|
|
1157
|
+
count: (v) => (v == null ? 0 : typeof v === 'object' && !isArray(v) ? Object.keys(v).length : v.length || 0),
|
|
1158
|
+
bytes: (v) => {
|
|
1159
|
+
let n = Number(v);
|
|
1160
|
+
if (!isFinite(n)) return '';
|
|
1161
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
1162
|
+
let u = 0;
|
|
1163
|
+
while (Math.abs(n) >= 1024 && u < units.length - 1) { n /= 1024; u++; }
|
|
1164
|
+
return (u ? n.toFixed(n < 10 ? 1 : 0) : n) + ' ' + units[u];
|
|
1165
|
+
},
|
|
1166
|
+
});
|
|
1167
|
+
const definePipe = (name, fn) => { if (!isFunction(fn)) throw new Error(`Kreftjs.pipe("${name}", fn): fn must be a function.`); pipes[name] = fn; };
|
|
1168
|
+
function lookupPipe(name, local) {
|
|
1169
|
+
const f = local || pipes[name];
|
|
1170
|
+
if (f) return f;
|
|
1171
|
+
warn(`Unknown pipe "${name}". Use a function of your own named ${name}, or Kreftjs.pipe('${name}', fn).`);
|
|
1172
|
+
return (v) => v;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* ---------- prop types: prop size: 'sm' | 'lg' = 'sm' ---------- */
|
|
1176
|
+
// TypeScript type text → (value) => boolean, or null when it can't be checked (your own type names, any)
|
|
1177
|
+
function typeCheck(text) {
|
|
1178
|
+
const members = cutAt(String(text), topLevel(String(text), '|')).map((t) => t.trim()).filter(Boolean);
|
|
1179
|
+
if (!members.length) return null;
|
|
1180
|
+
const checks = [];
|
|
1181
|
+
for (const m of members) {
|
|
1182
|
+
let c;
|
|
1183
|
+
if (m === 'string') c = (v) => typeof v === 'string';
|
|
1184
|
+
else if (m === 'number') c = (v) => typeof v === 'number' && !isNaN(v);
|
|
1185
|
+
else if (m === 'boolean') c = (v) => typeof v === 'boolean';
|
|
1186
|
+
else if (m === 'null') c = (v) => v === null;
|
|
1187
|
+
else if (m === 'undefined' || m === 'void') c = (v) => v === undefined;
|
|
1188
|
+
else if (m === 'true' || m === 'false') c = (v) => v === (m === 'true');
|
|
1189
|
+
else if (m === 'object') c = (v) => v !== null && typeof v === 'object';
|
|
1190
|
+
else if (m === 'Date') c = (v) => v instanceof Date;
|
|
1191
|
+
else if (m === 'Function' || /=>/.test(m)) c = isFunction;
|
|
1192
|
+
else if (/\[\]$/.test(m) || /^(?:readonly\s+)?Array\s*</.test(m) || /^\[/.test(m)) c = isArray;
|
|
1193
|
+
else if (/^(?:Record|Partial|Map|Set)\s*</.test(m) || m[0] === '{') c = (v) => v !== null && typeof v === 'object';
|
|
1194
|
+
else if (/^(['"`])[\s\S]*\1$/.test(m)) { const lit = m.slice(1, -1); c = (v) => v === lit; }
|
|
1195
|
+
else if (/^-?\d+(?:\.\d+)?$/.test(m)) { const n = Number(m); c = (v) => v === n; }
|
|
1196
|
+
else return null; // any, unknown, User, … : not checked
|
|
1197
|
+
checks.push(c);
|
|
1198
|
+
}
|
|
1199
|
+
return (v) => checks.some((c) => c(v));
|
|
1200
|
+
}
|
|
1201
|
+
const simpleType = (t) => ({ string: String, number: Number, boolean: Boolean }[String(t).trim()]);
|
|
1202
|
+
const describeValue = (v) => (v === null ? 'null' : isArray(v) ? 'an array' : typeof v === 'string' ? `"${v.length > 20 ? v.slice(0, 20) + '…' : v}"` : typeof v === 'object' ? 'an object' : typeof v + (typeof v === 'number' || typeof v === 'boolean' ? ' ' + v : ''));
|
|
1203
|
+
function checkProp(comp, k, opt, v) {
|
|
1204
|
+
if (opt.check && v !== undefined && !opt.check(v)) warn(`<${comp}> prop "${k}" should be ${opt.typeText}, but got ${describeValue(v)}.`);
|
|
1205
|
+
}
|
|
1206
|
+
function typedProp(opt, info) {
|
|
1207
|
+
opt.typeText = info.type;
|
|
1208
|
+
const chk = typeCheck(info.type);
|
|
1209
|
+
if (chk) opt.check = chk;
|
|
1210
|
+
if (info.required) opt.required = true;
|
|
1211
|
+
if (!opt.type) { const t = simpleType(info.type); if (t) opt.type = t; }
|
|
1212
|
+
return opt;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1063
1215
|
/* ---------- { expr } interpolation ---------- */
|
|
1064
1216
|
// "Hi {name}!" → [{ text: 'Hi ' }, { expr: 'name' }, { text: '!' }]; write \{ for a literal brace
|
|
1065
1217
|
const interpCache = new Map();
|
|
@@ -1087,7 +1239,7 @@
|
|
|
1087
1239
|
if (!expr) { buf += str.slice(i, end); i = end; continue; }
|
|
1088
1240
|
if (buf) parts.push({ text: buf });
|
|
1089
1241
|
buf = '';
|
|
1090
|
-
parts.push({ expr });
|
|
1242
|
+
parts.push({ expr: applyPipes(expr) });
|
|
1091
1243
|
i = end;
|
|
1092
1244
|
continue;
|
|
1093
1245
|
}
|
|
@@ -1108,7 +1260,7 @@
|
|
|
1108
1260
|
function exactBraceRaw(v) {
|
|
1109
1261
|
const t = (v || '').trim();
|
|
1110
1262
|
if (t[0] !== '{') return null;
|
|
1111
|
-
try { return matchBrace(t, 0) === t.length ? t.slice(1, -1).trim() : null; } catch (_) { return null; }
|
|
1263
|
+
try { return matchBrace(t, 0) === t.length ? applyPipes(t.slice(1, -1).trim()) : null; } catch (_) { return null; }
|
|
1112
1264
|
}
|
|
1113
1265
|
const stripBrace = (v) => { const e = exactBrace(v); return e != null ? e : v; };
|
|
1114
1266
|
// "btn {active ? 'on' : ''}" → "`btn ${…}`"; null when there is nothing to interpolate
|
|
@@ -1791,6 +1943,41 @@
|
|
|
1791
1943
|
};
|
|
1792
1944
|
}
|
|
1793
1945
|
|
|
1946
|
+
/* ---------- k-match / k-case / k-default ---------- */
|
|
1947
|
+
// <div k-match="status">
|
|
1948
|
+
// <b k-case="'ok'">Fine</b>
|
|
1949
|
+
// <i k-case="'wait', 'queued'">Waiting</i> several values: one of them
|
|
1950
|
+
// <u k-default>Unknown</u>
|
|
1951
|
+
// </div>
|
|
1952
|
+
// The children turn into k-if / k-elif / k-else, so animations, components and templates work as they do there.
|
|
1953
|
+
function caseCond(subject, raw) {
|
|
1954
|
+
const list = cutAt(raw, topLevel(raw, ',')).map((v) => v.trim()).filter(Boolean);
|
|
1955
|
+
return list.length > 1 ? `[${list.join(', ')}].includes(${subject})` : `(${subject}) === (${list[0] || 'undefined'})`;
|
|
1956
|
+
}
|
|
1957
|
+
function compileMatch(el, scope, inst) {
|
|
1958
|
+
const subject = stripBrace(el.getAttribute('k-match'));
|
|
1959
|
+
el.removeAttribute('k-match');
|
|
1960
|
+
let started = false, fallback = null;
|
|
1961
|
+
for (const child of [...el.children]) {
|
|
1962
|
+
if (child.hasAttribute('k-case')) {
|
|
1963
|
+
const raw = stripBrace(child.getAttribute('k-case'));
|
|
1964
|
+
child.removeAttribute('k-case');
|
|
1965
|
+
child.setAttribute(started ? 'k-elif' : 'k-if', caseCond(subject, raw));
|
|
1966
|
+
started = true;
|
|
1967
|
+
} else if (child.hasAttribute('k-default')) {
|
|
1968
|
+
child.removeAttribute('k-default');
|
|
1969
|
+
if (fallback) warn('k-match has more than one k-default; only the first is used.', child);
|
|
1970
|
+
else fallback = child;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
if (fallback) {
|
|
1974
|
+
if (fallback.nextElementSibling) warn('k-default belongs last in k-match.', fallback);
|
|
1975
|
+
if (started) { fallback.setAttribute('k-else', ''); el.appendChild(fallback); } // right after the last case
|
|
1976
|
+
else fallback.setAttribute('k-if', 'true');
|
|
1977
|
+
}
|
|
1978
|
+
return compile(el, scope, inst);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1794
1981
|
/* ---------- k-if / k-elif / k-else ---------- */
|
|
1795
1982
|
function compileIf(el, scope, inst) {
|
|
1796
1983
|
const anchor = text();
|
|
@@ -2811,12 +2998,13 @@
|
|
|
2811
2998
|
if (hasType(opt, Number) && v !== '' && !isNaN(v)) v = Number(v);
|
|
2812
2999
|
} else if (hasOwn(extra, k)) { v = extra[k]; present = true; }
|
|
2813
3000
|
props[k] = resolvePropValue(opt, v, present);
|
|
3001
|
+
checkProp(def.__name || def.name || 'component', k, opt, props[k]);
|
|
2814
3002
|
if (opt.required && !present) warn(`Missing required prop: "${k}"`);
|
|
2815
3003
|
}
|
|
2816
3004
|
for (const k in extra) if (!hasOwn(propsOptions, k)) attrs[k] = extra[k];
|
|
2817
3005
|
dynamicProps.forEach(([k, expr]) => {
|
|
2818
3006
|
if (!hasOwn(propsOptions, k)) return void dynamicAttrs.push([k, expr]);
|
|
2819
|
-
renderEffect(() => { props[k] = resolvePropValue(propsOptions[k], evaluate(parentScope, expr, el), true); });
|
|
3007
|
+
renderEffect(() => { props[k] = resolvePropValue(propsOptions[k], evaluate(parentScope, expr, el), true); checkProp(def.__name || def.name || 'component', k, propsOptions[k], props[k]); });
|
|
2820
3008
|
});
|
|
2821
3009
|
dynamicAttrs.forEach(([k, expr]) => renderEffect(() => { attrs[k] = evaluate(parentScope, expr, el); }));
|
|
2822
3010
|
if (options.liveProps) renderEffect(() => { // props that follow the route (<kreft-view>)
|
|
@@ -3993,7 +4181,17 @@
|
|
|
3993
4181
|
try { return parseKreftScriptInner(src, url); } finally { scanScript = false; }
|
|
3994
4182
|
}
|
|
3995
4183
|
function parseKreftScriptInner(src, url) {
|
|
3996
|
-
const out = [], imports = [], props = {}, events = [];
|
|
4184
|
+
const out = [], imports = [], props = {}, events = [], propTypes = {};
|
|
4185
|
+
// `prop size: 'sm' | 'lg' = 'sm'` keeps its type, to warn when a parent sends something else
|
|
4186
|
+
const notePropType = (lhs, hasDefault) => {
|
|
4187
|
+
const c = lhs.indexOf(':');
|
|
4188
|
+
if (c < 0) return;
|
|
4189
|
+
const raw = lhs.slice(0, c).trim();
|
|
4190
|
+
const name = raw.replace(/\?$/, '').trim();
|
|
4191
|
+
const type = lhs.slice(c + 1).trim();
|
|
4192
|
+
if (!type || !IDENT.test(name)) return;
|
|
4193
|
+
propTypes[name] = { type, required: !hasDefault && !/\?$/.test(raw) && !/(?:^|[|\s])(?:null|undefined|any|unknown)(?:[|\s]|$)/.test(type) };
|
|
4194
|
+
};
|
|
3997
4195
|
const kw = /(prop|state|derive|derived|action|when|watch|on|use|share|receive|event|import|saved|load|layout|interface|type)(?![\w$])/y;
|
|
3998
4196
|
let layout;
|
|
3999
4197
|
// `saved` and `load` are keywords when followed by `name =` or `name: Type =`, so `load(…)` stays a normal call
|
|
@@ -4089,6 +4287,7 @@
|
|
|
4089
4287
|
if (word === 'prop' && eq > 0) {
|
|
4090
4288
|
const { name, value } = nameValue(j, end, 'prop');
|
|
4091
4289
|
props[name] = value;
|
|
4290
|
+
notePropType(text.slice(0, eq), true);
|
|
4092
4291
|
} else if (word === 'use' && eq > 0) { // use mouse = trackMouse() or use mouse: MousePos = trackMouse()
|
|
4093
4292
|
const { name, value } = nameValue(j, end, 'use');
|
|
4094
4293
|
out.push(`$h.use(${JSON.stringify(name)}, () => (${value}));`);
|
|
@@ -4097,7 +4296,7 @@
|
|
|
4097
4296
|
const colon = text.indexOf(':');
|
|
4098
4297
|
const name = text.slice(0, colon).replace(/\?\s*$/, '').trim();
|
|
4099
4298
|
if (!IDENT.test(name) && word !== 'event') fail(j, `Invalid name "${name}"`);
|
|
4100
|
-
if (word === 'prop') props[name] = null;
|
|
4299
|
+
if (word === 'prop') { props[name] = null; notePropType(text, false); }
|
|
4101
4300
|
else events.push(name);
|
|
4102
4301
|
} else {
|
|
4103
4302
|
text.split(',').map((s) => s.trim()).filter(Boolean).forEach((rawN) => {
|
|
@@ -4194,7 +4393,7 @@
|
|
|
4194
4393
|
fail(i, e.message);
|
|
4195
4394
|
}
|
|
4196
4395
|
}
|
|
4197
|
-
return { code: out.join('\n'), imports, props, events, layout };
|
|
4396
|
+
return { code: out.join('\n'), imports, props, propTypes, events, layout };
|
|
4198
4397
|
}
|
|
4199
4398
|
|
|
4200
4399
|
// a CSS file imported by a .kreft file: one <link> for the whole page, before the components' own styles
|
|
@@ -4451,6 +4650,7 @@
|
|
|
4451
4650
|
code: parsed.code,
|
|
4452
4651
|
imports: parsed.imports,
|
|
4453
4652
|
props: parsed.props, // name → default expression (or null)
|
|
4653
|
+
propTypes: parsed.propTypes, // name → { type, required } from `prop name: Type`
|
|
4454
4654
|
events: parsed.events,
|
|
4455
4655
|
layout: parsed.layout, // undefined: not said; false: none; or a name / path
|
|
4456
4656
|
styles: styles.map((m) => ({ css: m[2], scoped: /\bscoped\b/.test(m[1] || '') })),
|
|
@@ -4483,11 +4683,14 @@
|
|
|
4483
4683
|
const props = {};
|
|
4484
4684
|
for (const p in sp.props) {
|
|
4485
4685
|
const expr = sp.props[p];
|
|
4486
|
-
if (expr == null)
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4686
|
+
if (expr == null) props[p] = {};
|
|
4687
|
+
else {
|
|
4688
|
+
const make = new Function(`return (${expr})`);
|
|
4689
|
+
let sample;
|
|
4690
|
+
try { sample = make(); } catch (_) { /* default uses something only known later */ }
|
|
4691
|
+
props[p] = isFunction(sample) ? { type: Function, default: sample } : { type: propType(sample), default: make };
|
|
4692
|
+
}
|
|
4693
|
+
if (sp.propTypes && sp.propTypes[p]) typedProp(props[p], sp.propTypes[p]);
|
|
4491
4694
|
}
|
|
4492
4695
|
|
|
4493
4696
|
let scopeId = null;
|
|
@@ -5398,7 +5601,7 @@
|
|
|
5398
5601
|
stores, storeLog, busLog,
|
|
5399
5602
|
};
|
|
5400
5603
|
|
|
5401
|
-
const VERSION = '0.0.
|
|
5604
|
+
const VERSION = '0.0.22-beta';
|
|
5402
5605
|
const Kreftjs = {
|
|
5403
5606
|
version: VERSION,
|
|
5404
5607
|
// PUBLIC_ values from .env (kreft dev and kreft build put them here); never secrets
|
|
@@ -5450,6 +5653,8 @@
|
|
|
5450
5653
|
choice: choiceTranslation, transChoice: choiceTranslation, trans_choice: choiceTranslation,
|
|
5451
5654
|
hasTranslation,
|
|
5452
5655
|
formatNumber, formatCurrency, formatDate,
|
|
5656
|
+
// pipes: { price | currency }; add yours with Kreftjs.pipe('name', fn)
|
|
5657
|
+
pipe: definePipe, pipes, __pipe: lookupPipe, __typeCheck: typeCheck,
|
|
5453
5658
|
// reacting to changes
|
|
5454
5659
|
watch, effect: watchEffect, tick: nextTick, onCleanup: onScopeDispose,
|
|
5455
5660
|
// sharing data
|
|
@@ -5464,7 +5669,7 @@
|
|
|
5464
5669
|
|
|
5465
5670
|
// the text-only helpers the build tool reuses, so built code matches the browser exactly
|
|
5466
5671
|
Object.defineProperty(Kreftjs, '__internal', {
|
|
5467
|
-
value: { expandBlocks, prepareTemplate, splitKreft, splitInterp, exactBrace, stripBrace, interpToExpr, quoteBraceAttrs, expandSelfClosing, scopeCss, hashString, propType, simplePathRE, fnExpRE, forAliasRE, makeKreftDef, compileFn, evaluate, stripTypeScript, stripTsParams },
|
|
5672
|
+
value: { expandBlocks, prepareTemplate, splitKreft, splitInterp, exactBrace, stripBrace, interpToExpr, quoteBraceAttrs, expandSelfClosing, scopeCss, hashString, propType, typeCheck, caseCond, simplePathRE, fnExpRE, forAliasRE, makeKreftDef, compileFn, evaluate, stripTypeScript, stripTsParams },
|
|
5468
5673
|
});
|
|
5469
5674
|
|
|
5470
5675
|
if (typeof module !== 'undefined' && module.exports) module.exports = Kreftjs;
|