micra.js 2.3.1 → 2.4.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/dist/micra.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v2.3.1 — https://github.com/micra-js/micra — MIT */
1
+ /* Micra.js v2.4.0 — https://github.com/micra-js/micra — MIT */
2
2
 
3
3
  // src/utils/fetch.ts
4
4
  function getCSRF() {
@@ -81,56 +81,196 @@ function debug() {
81
81
  }
82
82
 
83
83
  // src/utils/expr.ts
84
- var exprCache = /* @__PURE__ */ new Map();
85
- var warnedRuntime = /* @__PURE__ */ new Set();
86
- var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
87
- var ALLOWED_GLOBALS = /* @__PURE__ */ new Set([
88
- "Math",
89
- "JSON",
90
- "Date",
91
- "String",
92
- "Number",
93
- "Boolean",
94
- "Array",
95
- "Object",
96
- "parseInt",
97
- "parseFloat",
98
- "isNaN",
99
- "isFinite",
100
- "NaN",
101
- "Infinity",
102
- "undefined"
103
- ]);
104
- var PARAM_S = "$s";
105
- var PARAM_SAFE = "$safe";
106
- var SAFE_OUTER = new Proxy(/* @__PURE__ */ Object.create(null), {
107
- has(_target, key) {
108
- if (typeof key !== "string") return false;
109
- if (key === PARAM_S || key === PARAM_SAFE) return false;
110
- return !ALLOWED_GLOBALS.has(key);
111
- },
112
- get() {
113
- return void 0;
114
- }
115
- });
116
- var safeWrapCache = /* @__PURE__ */ new WeakMap();
84
+ var ALLOWED_GLOBALS = new Set(
85
+ "Math,JSON,Date,String,Number,Boolean,Array,Object,parseInt,parseFloat,isNaN,isFinite,NaN,Infinity,undefined".split(",")
86
+ );
87
+ var BLOCKED_PROPS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
117
88
  var OBJ_PROTO_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
118
- function safeStateWrap(state) {
119
- const cached = safeWrapCache.get(state);
120
- if (cached) return cached;
121
- const wrapped = new Proxy(state, {
122
- has(target, key) {
123
- return safeStateHas(target, key);
124
- },
125
- get(target, key) {
126
- return Reflect.get(target, key);
89
+ var PUNCT = [
90
+ "===",
91
+ "!==",
92
+ "==",
93
+ "!=",
94
+ "<=",
95
+ ">=",
96
+ "&&",
97
+ "||",
98
+ "(",
99
+ ")",
100
+ ".",
101
+ ",",
102
+ "?",
103
+ ":",
104
+ "!",
105
+ "<",
106
+ ">",
107
+ "+",
108
+ "-",
109
+ "*",
110
+ "/",
111
+ "%"
112
+ ];
113
+ function tokenize(src) {
114
+ var _a;
115
+ const toks = [];
116
+ let i = 0;
117
+ const n = src.length;
118
+ while (i < n) {
119
+ const c = src[i];
120
+ if (c === " " || c === " " || c === "\n" || c === "\r" || c === "\f") {
121
+ i++;
122
+ continue;
127
123
  }
128
- });
129
- safeWrapCache.set(state, wrapped);
130
- return wrapped;
124
+ if (c === '"' || c === "'") {
125
+ let s = "";
126
+ i++;
127
+ while (i < n && src[i] !== c) {
128
+ if (src[i] === "\\") {
129
+ s += (_a = src[i + 1]) != null ? _a : "";
130
+ i += 2;
131
+ } else {
132
+ s += src[i];
133
+ i++;
134
+ }
135
+ }
136
+ if (src[i] !== c) throw 0;
137
+ i++;
138
+ toks.push({ t: "str", v: s });
139
+ continue;
140
+ }
141
+ if (c >= "0" && c <= "9") {
142
+ let s = "";
143
+ while (i < n && (src[i] >= "0" && src[i] <= "9" || src[i] === ".")) {
144
+ s += src[i];
145
+ i++;
146
+ }
147
+ toks.push({ t: "num", v: s });
148
+ continue;
149
+ }
150
+ if (/[A-Za-z_$]/.test(c)) {
151
+ let s = "";
152
+ while (i < n && /[A-Za-z0-9_$]/.test(src[i])) {
153
+ s += src[i];
154
+ i++;
155
+ }
156
+ toks.push({ t: "id", v: s });
157
+ continue;
158
+ }
159
+ const m = PUNCT.find((p) => src.startsWith(p, i));
160
+ if (!m) throw 0;
161
+ toks.push({ t: "p", v: m });
162
+ i += m.length;
163
+ }
164
+ return toks;
165
+ }
166
+ var BIN_PREC = {
167
+ "||": 1,
168
+ "&&": 2,
169
+ "==": 3,
170
+ "!=": 3,
171
+ "===": 3,
172
+ "!==": 3,
173
+ "<": 4,
174
+ "<=": 4,
175
+ ">": 4,
176
+ ">=": 4,
177
+ "+": 5,
178
+ "-": 5,
179
+ "*": 6,
180
+ "/": 6,
181
+ "%": 6
182
+ };
183
+ function parse(toks) {
184
+ let pos = 0;
185
+ const peek = () => toks[pos];
186
+ const next = () => toks[pos++];
187
+ const eat = (v) => {
188
+ var _a;
189
+ if (((_a = peek()) == null ? void 0 : _a.v) !== v) throw 0;
190
+ pos++;
191
+ };
192
+ function parseExpr() {
193
+ var _a;
194
+ const c = parseBin(1);
195
+ if (((_a = peek()) == null ? void 0 : _a.v) === "?") {
196
+ next();
197
+ const a = parseExpr();
198
+ eat(":");
199
+ const b = parseExpr();
200
+ return { k: "tern", c, a, b };
201
+ }
202
+ return c;
203
+ }
204
+ function parseBin(minPrec) {
205
+ let left = parseUnary();
206
+ for (; ; ) {
207
+ const t = peek();
208
+ const prec = t && t.t === "p" ? BIN_PREC[t.v] : void 0;
209
+ if (prec === void 0 || prec < minPrec) break;
210
+ next();
211
+ const right = parseBin(prec + 1);
212
+ left = { k: "bin", op: t.v, l: left, r: right };
213
+ }
214
+ return left;
215
+ }
216
+ function parseUnary() {
217
+ const t = peek();
218
+ if (t && t.t === "p" && (t.v === "!" || t.v === "-")) {
219
+ next();
220
+ return { k: "un", op: t.v, x: parseUnary() };
221
+ }
222
+ return parsePostfix();
223
+ }
224
+ function parsePostfix() {
225
+ var _a, _b;
226
+ let node = parsePrimary();
227
+ for (; ; ) {
228
+ const t = peek();
229
+ if ((t == null ? void 0 : t.v) === ".") {
230
+ next();
231
+ const id = next();
232
+ if (!id || id.t !== "id") throw 0;
233
+ node = { k: "mem", o: node, p: id.v };
234
+ } else if ((t == null ? void 0 : t.v) === "(") {
235
+ next();
236
+ const args = [];
237
+ if (((_a = peek()) == null ? void 0 : _a.v) !== ")") {
238
+ args.push(parseExpr());
239
+ while (((_b = peek()) == null ? void 0 : _b.v) === ",") {
240
+ next();
241
+ args.push(parseExpr());
242
+ }
243
+ }
244
+ eat(")");
245
+ node = { k: "call", c: node, a: args };
246
+ } else break;
247
+ }
248
+ return node;
249
+ }
250
+ function parsePrimary() {
251
+ const t = next();
252
+ if (!t) throw 0;
253
+ if (t.t === "num") return { k: "lit", v: Number(t.v) };
254
+ if (t.t === "str") return { k: "lit", v: t.v };
255
+ if (t.v === "(") {
256
+ const e = parseExpr();
257
+ eat(")");
258
+ return e;
259
+ }
260
+ if (t.t === "id") {
261
+ if (t.v === "true") return { k: "lit", v: true };
262
+ if (t.v === "false") return { k: "lit", v: false };
263
+ if (t.v === "null") return { k: "lit", v: null };
264
+ if (t.v === "undefined") return { k: "lit", v: void 0 };
265
+ return { k: "id", n: t.v };
266
+ }
267
+ throw 0;
268
+ }
269
+ const ast = parseExpr();
270
+ if (pos !== toks.length) throw 0;
271
+ return ast;
131
272
  }
132
273
  function safeStateHas(state, key) {
133
- if (typeof key !== "string") return false;
134
274
  if (!Reflect.has(state, key)) return false;
135
275
  if (!OBJ_PROTO_KEYS.has(key)) return true;
136
276
  let obj = state;
@@ -140,6 +280,87 @@ function safeStateHas(state, key) {
140
280
  }
141
281
  return false;
142
282
  }
283
+ function resolveIdent(name, scope) {
284
+ if (safeStateHas(scope, name)) return scope[name];
285
+ if (ALLOWED_GLOBALS.has(name)) return globalThis[name];
286
+ return void 0;
287
+ }
288
+ function evalNode(node, scope) {
289
+ switch (node.k) {
290
+ case "lit":
291
+ return node.v;
292
+ case "id":
293
+ return resolveIdent(node.n, scope);
294
+ case "mem": {
295
+ const o = evalNode(node.o, scope);
296
+ if (o == null || BLOCKED_PROPS.has(node.p)) return void 0;
297
+ return o[node.p];
298
+ }
299
+ case "un": {
300
+ const x = evalNode(node.x, scope);
301
+ return node.op === "!" ? !x : -x;
302
+ }
303
+ case "tern":
304
+ return evalNode(node.c, scope) ? evalNode(node.a, scope) : evalNode(node.b, scope);
305
+ case "bin": {
306
+ const op = node.op;
307
+ if (op === "&&") {
308
+ const l2 = evalNode(node.l, scope);
309
+ return l2 ? evalNode(node.r, scope) : l2;
310
+ }
311
+ if (op === "||") {
312
+ const l2 = evalNode(node.l, scope);
313
+ return l2 ? l2 : evalNode(node.r, scope);
314
+ }
315
+ const l = evalNode(node.l, scope);
316
+ const r = evalNode(node.r, scope);
317
+ switch (op) {
318
+ case "+":
319
+ return l + r;
320
+ case "-":
321
+ return l - r;
322
+ case "*":
323
+ return l * r;
324
+ case "/":
325
+ return l / r;
326
+ case "%":
327
+ return l % r;
328
+ case "<":
329
+ return l < r;
330
+ case "<=":
331
+ return l <= r;
332
+ case ">":
333
+ return l > r;
334
+ case ">=":
335
+ return l >= r;
336
+ case "==":
337
+ return l == r;
338
+ case "!=":
339
+ return l != r;
340
+ case "===":
341
+ return l === r;
342
+ case "!==":
343
+ return l !== r;
344
+ }
345
+ return void 0;
346
+ }
347
+ case "call": {
348
+ let fn;
349
+ let self;
350
+ if (node.c.k === "mem") {
351
+ self = evalNode(node.c.o, scope);
352
+ fn = self == null || BLOCKED_PROPS.has(node.c.p) ? void 0 : self[node.c.p];
353
+ } else {
354
+ fn = evalNode(node.c, scope);
355
+ }
356
+ if (typeof fn !== "function") throw new TypeError("not a function");
357
+ return fn.apply(self, node.a.map((x) => evalNode(x, scope)));
358
+ }
359
+ }
360
+ }
361
+ var exprCache = /* @__PURE__ */ new Map();
362
+ var warnedRuntime = /* @__PURE__ */ new Set();
363
+ var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
143
364
  function evalExpr(expr, state) {
144
365
  let cached = exprCache.get(expr);
145
366
  if (!cached) {
@@ -147,26 +368,24 @@ function evalExpr(expr, state) {
147
368
  cached = { kind: "path", parts: expr.split(".") };
148
369
  } else {
149
370
  try {
150
- cached = {
151
- kind: "fn",
152
- fn: new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
153
- };
371
+ cached = { kind: "ast", ast: parse(tokenize(expr)) };
154
372
  } catch {
155
373
  warn(`invalid expression "${expr}"`);
156
- cached = { kind: "fn", fn: () => void 0 };
374
+ cached = { kind: "err" };
157
375
  }
158
376
  }
159
377
  exprCache.set(expr, cached);
160
378
  }
161
379
  if (cached.kind === "path") {
162
- if (!safeStateHas(state, cached.parts[0])) return void 0;
163
- return cached.parts.reduce(
164
- (obj, key) => obj != null ? obj[key] : void 0,
165
- state
166
- );
380
+ const parts = cached.parts;
381
+ if (!safeStateHas(state, parts[0])) return void 0;
382
+ let obj = state;
383
+ for (const key of parts) obj = obj != null ? obj[key] : void 0;
384
+ return obj;
167
385
  }
386
+ if (cached.kind === "err") return void 0;
168
387
  try {
169
- return cached.fn(safeStateWrap(state), SAFE_OUTER);
388
+ return evalNode(cached.ast, state);
170
389
  } catch (e) {
171
390
  if (!warnedRuntime.has(expr)) {
172
391
  warnedRuntime.add(expr);
@@ -326,6 +545,23 @@ function track(instance, el, type, fn) {
326
545
  el.addEventListener(type, fn);
327
546
  ((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
328
547
  }
548
+ function runHandler(instance, el, value, e) {
549
+ var _a;
550
+ if (value.includes("(")) {
551
+ let base;
552
+ for (let n = el; n && !base; n = n.parentElement) {
553
+ base = n._itemState;
554
+ }
555
+ const scope = Object.create((_a = base != null ? base : instance.__micraExpr) != null ? _a : null);
556
+ scope["$event"] = e;
557
+ scope["event"] = e;
558
+ evalExpr(value, scope);
559
+ return;
560
+ }
561
+ const fn = instance[value];
562
+ if (typeof fn === "function") fn.call(instance, e);
563
+ else warn(`method "${value}" not found`);
564
+ }
329
565
  function bindDataOn(els, instance) {
330
566
  var _a;
331
567
  for (const el of els) {
@@ -337,13 +573,12 @@ function bindDataOn(els, instance) {
337
573
  const [evSpec, method] = part.trim().split(":");
338
574
  if (!evSpec || !method) continue;
339
575
  const [evName, ...mods] = evSpec.split(".");
576
+ const handler = method.trim();
340
577
  track(instance, el, evName, (e) => {
341
578
  if (mods.includes("prevent")) e.preventDefault();
342
579
  if (mods.includes("stop")) e.stopPropagation();
343
580
  if (mods.includes("self") && e.target !== el) return;
344
- const fn = instance[method.trim()];
345
- if (typeof fn === "function") fn.call(instance, e);
346
- else warn(`method "${method.trim()}" not found`);
581
+ runHandler(instance, el, handler, e);
347
582
  });
348
583
  }
349
584
  }
@@ -356,14 +591,12 @@ function bindAtEvents(els, instance) {
356
591
  for (const attr of Array.from(el.attributes)) {
357
592
  if (!attr.name.startsWith("@")) continue;
358
593
  const [evSpec, ...rest] = attr.name.slice(1).split(".");
359
- const method = attr.value.trim();
594
+ const handler = attr.value.trim();
360
595
  track(instance, el, evSpec, (e) => {
361
596
  if (rest.includes("prevent")) e.preventDefault();
362
597
  if (rest.includes("stop")) e.stopPropagation();
363
598
  if (rest.includes("self") && e.target !== el) return;
364
- const fn = instance[method];
365
- if (typeof fn === "function") fn.call(instance, e);
366
- else warn(`method "${method}" not found`);
599
+ runHandler(instance, el, handler, e);
367
600
  });
368
601
  bound = true;
369
602
  }
@@ -539,8 +772,13 @@ function renderList(templates, state, rawState, instance, triggerKey) {
539
772
  function createRowNode(tmpl, state, instance) {
540
773
  const frag = tmpl.content.cloneNode(true);
541
774
  let node;
542
- if (frag.childNodes.length === 1) {
543
- node = frag.firstElementChild;
775
+ const first = frag.firstElementChild;
776
+ const single = !!first && !first.nextElementSibling && !Array.prototype.some.call(
777
+ frag.childNodes,
778
+ (c) => c.nodeType === 3 && /[^\x00- ]/.test(c.textContent)
779
+ );
780
+ if (single) {
781
+ node = first;
544
782
  } else {
545
783
  node = document.createElement("micra-each-item");
546
784
  node.style.display = "contents";
@@ -768,6 +1006,7 @@ function mount(selector, definition) {
768
1006
  return Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function";
769
1007
  }
770
1008
  });
1009
+ instance.__micraExpr = exprState;
771
1010
  let warnedReentry = false;
772
1011
  instance.render = function() {
773
1012
  var _a2;