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.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
  "use strict";
3
3
  var Micra = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -116,56 +116,196 @@ var Micra = (() => {
116
116
  }
117
117
 
118
118
  // src/utils/expr.ts
119
- var exprCache = /* @__PURE__ */ new Map();
120
- var warnedRuntime = /* @__PURE__ */ new Set();
121
- var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
122
- var ALLOWED_GLOBALS = /* @__PURE__ */ new Set([
123
- "Math",
124
- "JSON",
125
- "Date",
126
- "String",
127
- "Number",
128
- "Boolean",
129
- "Array",
130
- "Object",
131
- "parseInt",
132
- "parseFloat",
133
- "isNaN",
134
- "isFinite",
135
- "NaN",
136
- "Infinity",
137
- "undefined"
138
- ]);
139
- var PARAM_S = "$s";
140
- var PARAM_SAFE = "$safe";
141
- var SAFE_OUTER = new Proxy(/* @__PURE__ */ Object.create(null), {
142
- has(_target, key) {
143
- if (typeof key !== "string") return false;
144
- if (key === PARAM_S || key === PARAM_SAFE) return false;
145
- return !ALLOWED_GLOBALS.has(key);
146
- },
147
- get() {
148
- return void 0;
149
- }
150
- });
151
- var safeWrapCache = /* @__PURE__ */ new WeakMap();
119
+ var ALLOWED_GLOBALS = new Set(
120
+ "Math,JSON,Date,String,Number,Boolean,Array,Object,parseInt,parseFloat,isNaN,isFinite,NaN,Infinity,undefined".split(",")
121
+ );
122
+ var BLOCKED_PROPS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
152
123
  var OBJ_PROTO_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
153
- function safeStateWrap(state) {
154
- const cached = safeWrapCache.get(state);
155
- if (cached) return cached;
156
- const wrapped = new Proxy(state, {
157
- has(target, key) {
158
- return safeStateHas(target, key);
159
- },
160
- get(target, key) {
161
- return Reflect.get(target, key);
124
+ var PUNCT = [
125
+ "===",
126
+ "!==",
127
+ "==",
128
+ "!=",
129
+ "<=",
130
+ ">=",
131
+ "&&",
132
+ "||",
133
+ "(",
134
+ ")",
135
+ ".",
136
+ ",",
137
+ "?",
138
+ ":",
139
+ "!",
140
+ "<",
141
+ ">",
142
+ "+",
143
+ "-",
144
+ "*",
145
+ "/",
146
+ "%"
147
+ ];
148
+ function tokenize(src) {
149
+ var _a;
150
+ const toks = [];
151
+ let i = 0;
152
+ const n = src.length;
153
+ while (i < n) {
154
+ const c = src[i];
155
+ if (c === " " || c === " " || c === "\n" || c === "\r" || c === "\f") {
156
+ i++;
157
+ continue;
162
158
  }
163
- });
164
- safeWrapCache.set(state, wrapped);
165
- return wrapped;
159
+ if (c === '"' || c === "'") {
160
+ let s = "";
161
+ i++;
162
+ while (i < n && src[i] !== c) {
163
+ if (src[i] === "\\") {
164
+ s += (_a = src[i + 1]) != null ? _a : "";
165
+ i += 2;
166
+ } else {
167
+ s += src[i];
168
+ i++;
169
+ }
170
+ }
171
+ if (src[i] !== c) throw 0;
172
+ i++;
173
+ toks.push({ t: "str", v: s });
174
+ continue;
175
+ }
176
+ if (c >= "0" && c <= "9") {
177
+ let s = "";
178
+ while (i < n && (src[i] >= "0" && src[i] <= "9" || src[i] === ".")) {
179
+ s += src[i];
180
+ i++;
181
+ }
182
+ toks.push({ t: "num", v: s });
183
+ continue;
184
+ }
185
+ if (/[A-Za-z_$]/.test(c)) {
186
+ let s = "";
187
+ while (i < n && /[A-Za-z0-9_$]/.test(src[i])) {
188
+ s += src[i];
189
+ i++;
190
+ }
191
+ toks.push({ t: "id", v: s });
192
+ continue;
193
+ }
194
+ const m = PUNCT.find((p) => src.startsWith(p, i));
195
+ if (!m) throw 0;
196
+ toks.push({ t: "p", v: m });
197
+ i += m.length;
198
+ }
199
+ return toks;
200
+ }
201
+ var BIN_PREC = {
202
+ "||": 1,
203
+ "&&": 2,
204
+ "==": 3,
205
+ "!=": 3,
206
+ "===": 3,
207
+ "!==": 3,
208
+ "<": 4,
209
+ "<=": 4,
210
+ ">": 4,
211
+ ">=": 4,
212
+ "+": 5,
213
+ "-": 5,
214
+ "*": 6,
215
+ "/": 6,
216
+ "%": 6
217
+ };
218
+ function parse(toks) {
219
+ let pos = 0;
220
+ const peek = () => toks[pos];
221
+ const next = () => toks[pos++];
222
+ const eat = (v) => {
223
+ var _a;
224
+ if (((_a = peek()) == null ? void 0 : _a.v) !== v) throw 0;
225
+ pos++;
226
+ };
227
+ function parseExpr() {
228
+ var _a;
229
+ const c = parseBin(1);
230
+ if (((_a = peek()) == null ? void 0 : _a.v) === "?") {
231
+ next();
232
+ const a = parseExpr();
233
+ eat(":");
234
+ const b = parseExpr();
235
+ return { k: "tern", c, a, b };
236
+ }
237
+ return c;
238
+ }
239
+ function parseBin(minPrec) {
240
+ let left = parseUnary();
241
+ for (; ; ) {
242
+ const t = peek();
243
+ const prec = t && t.t === "p" ? BIN_PREC[t.v] : void 0;
244
+ if (prec === void 0 || prec < minPrec) break;
245
+ next();
246
+ const right = parseBin(prec + 1);
247
+ left = { k: "bin", op: t.v, l: left, r: right };
248
+ }
249
+ return left;
250
+ }
251
+ function parseUnary() {
252
+ const t = peek();
253
+ if (t && t.t === "p" && (t.v === "!" || t.v === "-")) {
254
+ next();
255
+ return { k: "un", op: t.v, x: parseUnary() };
256
+ }
257
+ return parsePostfix();
258
+ }
259
+ function parsePostfix() {
260
+ var _a, _b;
261
+ let node = parsePrimary();
262
+ for (; ; ) {
263
+ const t = peek();
264
+ if ((t == null ? void 0 : t.v) === ".") {
265
+ next();
266
+ const id = next();
267
+ if (!id || id.t !== "id") throw 0;
268
+ node = { k: "mem", o: node, p: id.v };
269
+ } else if ((t == null ? void 0 : t.v) === "(") {
270
+ next();
271
+ const args = [];
272
+ if (((_a = peek()) == null ? void 0 : _a.v) !== ")") {
273
+ args.push(parseExpr());
274
+ while (((_b = peek()) == null ? void 0 : _b.v) === ",") {
275
+ next();
276
+ args.push(parseExpr());
277
+ }
278
+ }
279
+ eat(")");
280
+ node = { k: "call", c: node, a: args };
281
+ } else break;
282
+ }
283
+ return node;
284
+ }
285
+ function parsePrimary() {
286
+ const t = next();
287
+ if (!t) throw 0;
288
+ if (t.t === "num") return { k: "lit", v: Number(t.v) };
289
+ if (t.t === "str") return { k: "lit", v: t.v };
290
+ if (t.v === "(") {
291
+ const e = parseExpr();
292
+ eat(")");
293
+ return e;
294
+ }
295
+ if (t.t === "id") {
296
+ if (t.v === "true") return { k: "lit", v: true };
297
+ if (t.v === "false") return { k: "lit", v: false };
298
+ if (t.v === "null") return { k: "lit", v: null };
299
+ if (t.v === "undefined") return { k: "lit", v: void 0 };
300
+ return { k: "id", n: t.v };
301
+ }
302
+ throw 0;
303
+ }
304
+ const ast = parseExpr();
305
+ if (pos !== toks.length) throw 0;
306
+ return ast;
166
307
  }
167
308
  function safeStateHas(state, key) {
168
- if (typeof key !== "string") return false;
169
309
  if (!Reflect.has(state, key)) return false;
170
310
  if (!OBJ_PROTO_KEYS.has(key)) return true;
171
311
  let obj = state;
@@ -175,6 +315,87 @@ var Micra = (() => {
175
315
  }
176
316
  return false;
177
317
  }
318
+ function resolveIdent(name, scope) {
319
+ if (safeStateHas(scope, name)) return scope[name];
320
+ if (ALLOWED_GLOBALS.has(name)) return globalThis[name];
321
+ return void 0;
322
+ }
323
+ function evalNode(node, scope) {
324
+ switch (node.k) {
325
+ case "lit":
326
+ return node.v;
327
+ case "id":
328
+ return resolveIdent(node.n, scope);
329
+ case "mem": {
330
+ const o = evalNode(node.o, scope);
331
+ if (o == null || BLOCKED_PROPS.has(node.p)) return void 0;
332
+ return o[node.p];
333
+ }
334
+ case "un": {
335
+ const x = evalNode(node.x, scope);
336
+ return node.op === "!" ? !x : -x;
337
+ }
338
+ case "tern":
339
+ return evalNode(node.c, scope) ? evalNode(node.a, scope) : evalNode(node.b, scope);
340
+ case "bin": {
341
+ const op = node.op;
342
+ if (op === "&&") {
343
+ const l2 = evalNode(node.l, scope);
344
+ return l2 ? evalNode(node.r, scope) : l2;
345
+ }
346
+ if (op === "||") {
347
+ const l2 = evalNode(node.l, scope);
348
+ return l2 ? l2 : evalNode(node.r, scope);
349
+ }
350
+ const l = evalNode(node.l, scope);
351
+ const r = evalNode(node.r, scope);
352
+ switch (op) {
353
+ case "+":
354
+ return l + r;
355
+ case "-":
356
+ return l - r;
357
+ case "*":
358
+ return l * r;
359
+ case "/":
360
+ return l / r;
361
+ case "%":
362
+ return l % r;
363
+ case "<":
364
+ return l < r;
365
+ case "<=":
366
+ return l <= r;
367
+ case ">":
368
+ return l > r;
369
+ case ">=":
370
+ return l >= r;
371
+ case "==":
372
+ return l == r;
373
+ case "!=":
374
+ return l != r;
375
+ case "===":
376
+ return l === r;
377
+ case "!==":
378
+ return l !== r;
379
+ }
380
+ return void 0;
381
+ }
382
+ case "call": {
383
+ let fn;
384
+ let self;
385
+ if (node.c.k === "mem") {
386
+ self = evalNode(node.c.o, scope);
387
+ fn = self == null || BLOCKED_PROPS.has(node.c.p) ? void 0 : self[node.c.p];
388
+ } else {
389
+ fn = evalNode(node.c, scope);
390
+ }
391
+ if (typeof fn !== "function") throw new TypeError("not a function");
392
+ return fn.apply(self, node.a.map((x) => evalNode(x, scope)));
393
+ }
394
+ }
395
+ }
396
+ var exprCache = /* @__PURE__ */ new Map();
397
+ var warnedRuntime = /* @__PURE__ */ new Set();
398
+ var SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
178
399
  function evalExpr(expr, state) {
179
400
  let cached = exprCache.get(expr);
180
401
  if (!cached) {
@@ -182,26 +403,24 @@ var Micra = (() => {
182
403
  cached = { kind: "path", parts: expr.split(".") };
183
404
  } else {
184
405
  try {
185
- cached = {
186
- kind: "fn",
187
- fn: new Function("$s", "$safe", `with($safe){with($s){return (${expr})}}`)
188
- };
406
+ cached = { kind: "ast", ast: parse(tokenize(expr)) };
189
407
  } catch {
190
408
  warn(`invalid expression "${expr}"`);
191
- cached = { kind: "fn", fn: () => void 0 };
409
+ cached = { kind: "err" };
192
410
  }
193
411
  }
194
412
  exprCache.set(expr, cached);
195
413
  }
196
414
  if (cached.kind === "path") {
197
- if (!safeStateHas(state, cached.parts[0])) return void 0;
198
- return cached.parts.reduce(
199
- (obj, key) => obj != null ? obj[key] : void 0,
200
- state
201
- );
415
+ const parts = cached.parts;
416
+ if (!safeStateHas(state, parts[0])) return void 0;
417
+ let obj = state;
418
+ for (const key of parts) obj = obj != null ? obj[key] : void 0;
419
+ return obj;
202
420
  }
421
+ if (cached.kind === "err") return void 0;
203
422
  try {
204
- return cached.fn(safeStateWrap(state), SAFE_OUTER);
423
+ return evalNode(cached.ast, state);
205
424
  } catch (e) {
206
425
  if (!warnedRuntime.has(expr)) {
207
426
  warnedRuntime.add(expr);
@@ -361,6 +580,23 @@ var Micra = (() => {
361
580
  el.addEventListener(type, fn);
362
581
  ((_a = instance.__micraListeners) != null ? _a : instance.__micraListeners = []).push({ el, type, fn });
363
582
  }
583
+ function runHandler(instance, el, value, e) {
584
+ var _a;
585
+ if (value.includes("(")) {
586
+ let base;
587
+ for (let n = el; n && !base; n = n.parentElement) {
588
+ base = n._itemState;
589
+ }
590
+ const scope = Object.create((_a = base != null ? base : instance.__micraExpr) != null ? _a : null);
591
+ scope["$event"] = e;
592
+ scope["event"] = e;
593
+ evalExpr(value, scope);
594
+ return;
595
+ }
596
+ const fn = instance[value];
597
+ if (typeof fn === "function") fn.call(instance, e);
598
+ else warn(`method "${value}" not found`);
599
+ }
364
600
  function bindDataOn(els, instance) {
365
601
  var _a;
366
602
  for (const el of els) {
@@ -372,13 +608,12 @@ var Micra = (() => {
372
608
  const [evSpec, method] = part.trim().split(":");
373
609
  if (!evSpec || !method) continue;
374
610
  const [evName, ...mods] = evSpec.split(".");
611
+ const handler = method.trim();
375
612
  track(instance, el, evName, (e) => {
376
613
  if (mods.includes("prevent")) e.preventDefault();
377
614
  if (mods.includes("stop")) e.stopPropagation();
378
615
  if (mods.includes("self") && e.target !== el) return;
379
- const fn = instance[method.trim()];
380
- if (typeof fn === "function") fn.call(instance, e);
381
- else warn(`method "${method.trim()}" not found`);
616
+ runHandler(instance, el, handler, e);
382
617
  });
383
618
  }
384
619
  }
@@ -391,14 +626,12 @@ var Micra = (() => {
391
626
  for (const attr of Array.from(el.attributes)) {
392
627
  if (!attr.name.startsWith("@")) continue;
393
628
  const [evSpec, ...rest] = attr.name.slice(1).split(".");
394
- const method = attr.value.trim();
629
+ const handler = attr.value.trim();
395
630
  track(instance, el, evSpec, (e) => {
396
631
  if (rest.includes("prevent")) e.preventDefault();
397
632
  if (rest.includes("stop")) e.stopPropagation();
398
633
  if (rest.includes("self") && e.target !== el) return;
399
- const fn = instance[method];
400
- if (typeof fn === "function") fn.call(instance, e);
401
- else warn(`method "${method}" not found`);
634
+ runHandler(instance, el, handler, e);
402
635
  });
403
636
  bound = true;
404
637
  }
@@ -574,8 +807,13 @@ var Micra = (() => {
574
807
  function createRowNode(tmpl, state, instance) {
575
808
  const frag = tmpl.content.cloneNode(true);
576
809
  let node;
577
- if (frag.childNodes.length === 1) {
578
- node = frag.firstElementChild;
810
+ const first = frag.firstElementChild;
811
+ const single = !!first && !first.nextElementSibling && !Array.prototype.some.call(
812
+ frag.childNodes,
813
+ (c) => c.nodeType === 3 && /[^\x00- ]/.test(c.textContent)
814
+ );
815
+ if (single) {
816
+ node = first;
579
817
  } else {
580
818
  node = document.createElement("micra-each-item");
581
819
  node.style.display = "contents";
@@ -803,6 +1041,7 @@ var Micra = (() => {
803
1041
  return Object.prototype.hasOwnProperty.call(instance, key) && typeof instance[key] === "function";
804
1042
  }
805
1043
  });
1044
+ instance.__micraExpr = exprState;
806
1045
  let warnedReentry = false;
807
1046
  instance.render = function() {
808
1047
  var _a2;