@getforma/core 1.2.0 → 1.3.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.
@@ -679,14 +679,18 @@ var FormaRuntime = (function (exports) {
679
679
  }
680
680
  const oldKeyMap = /* @__PURE__ */ new Map();
681
681
  for (let i = 0; i < oldLen; i++) {
682
- oldKeyMap.set(keyFn(oldItems[i]), i);
682
+ const k = keyFn(oldItems[i]);
683
+ const bucket = oldKeyMap.get(k);
684
+ if (bucket) bucket.push(i);
685
+ else oldKeyMap.set(k, [i]);
683
686
  }
684
687
  const oldIndices = new Array(newLen);
685
688
  const oldUsed = new Uint8Array(oldLen);
686
689
  for (let i = 0; i < newLen; i++) {
687
690
  const key = keyFn(newItems[i]);
688
- const oldIdx = oldKeyMap.get(key);
689
- if (oldIdx !== void 0) {
691
+ const bucket = oldKeyMap.get(key);
692
+ if (bucket && bucket.length > 0) {
693
+ const oldIdx = bucket.shift();
690
694
  oldIndices[i] = oldIdx;
691
695
  oldUsed[oldIdx] = 1;
692
696
  } else {
@@ -1035,12 +1039,12 @@ var FormaRuntime = (function (exports) {
1035
1039
  return null;
1036
1040
  }
1037
1041
  function createReconciler(config) {
1038
- let _lastHtml = "";
1042
+ const lastHtmlByContainer = /* @__PURE__ */ new WeakMap();
1039
1043
  return function reconcile2(container, html) {
1040
1044
  const trimmed = html.trim();
1041
1045
  if (!trimmed) return;
1042
- if (trimmed === _lastHtml && container.hasChildNodes()) return;
1043
- _lastHtml = trimmed;
1046
+ if (lastHtmlByContainer.get(container) === trimmed && container.hasChildNodes()) return;
1047
+ lastHtmlByContainer.set(container, trimmed);
1044
1048
  config.disconnectObserver();
1045
1049
  try {
1046
1050
  if (!container.hasChildNodes() || container.children.length === 0) {
@@ -1354,9 +1358,97 @@ var FormaRuntime = (function (exports) {
1354
1358
  var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
1355
1359
  var RE_AND = /^(.+?)\s*&&\s*(.+)$/;
1356
1360
  var RE_OR = /^(.+?)\s*\|\|\s*(.+)$/;
1357
- var RE_COMPARISON = /^(.+?)\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/;
1358
- var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
1359
- var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
1361
+ var UNARY_PRECEDERS = "+-*/%<>=!&|^~(,[{?:";
1362
+ function matchBinaryOp(expr, ops) {
1363
+ const SQ = String.fromCharCode(39);
1364
+ const DQ = String.fromCharCode(34);
1365
+ const BT = String.fromCharCode(96);
1366
+ const BS = String.fromCharCode(92);
1367
+ let depth = 0;
1368
+ let inSingle = false, inDouble = false, inTemplate = false, escaped = false;
1369
+ let prevSig = "";
1370
+ let bestIdx = -1;
1371
+ let bestOp = "";
1372
+ for (let i = 0; i < expr.length; i++) {
1373
+ const ch = expr[i];
1374
+ if (escaped) {
1375
+ escaped = false;
1376
+ continue;
1377
+ }
1378
+ if (ch === BS && (inSingle || inDouble || inTemplate)) {
1379
+ escaped = true;
1380
+ continue;
1381
+ }
1382
+ if (inSingle) {
1383
+ if (ch === SQ) {
1384
+ inSingle = false;
1385
+ prevSig = DQ;
1386
+ }
1387
+ continue;
1388
+ }
1389
+ if (inDouble) {
1390
+ if (ch === DQ) {
1391
+ inDouble = false;
1392
+ prevSig = DQ;
1393
+ }
1394
+ continue;
1395
+ }
1396
+ if (inTemplate) {
1397
+ if (ch === BT) {
1398
+ inTemplate = false;
1399
+ prevSig = DQ;
1400
+ }
1401
+ continue;
1402
+ }
1403
+ if (ch === SQ) {
1404
+ inSingle = true;
1405
+ continue;
1406
+ }
1407
+ if (ch === DQ) {
1408
+ inDouble = true;
1409
+ continue;
1410
+ }
1411
+ if (ch === BT) {
1412
+ inTemplate = true;
1413
+ continue;
1414
+ }
1415
+ if (ch === "(" || ch === "[" || ch === "{") {
1416
+ depth++;
1417
+ prevSig = ch;
1418
+ continue;
1419
+ }
1420
+ if (ch === ")" || ch === "]" || ch === "}") {
1421
+ if (depth > 0) depth--;
1422
+ prevSig = ch;
1423
+ continue;
1424
+ }
1425
+ if (depth !== 0) continue;
1426
+ if (ch === " " || ch === " " || ch === "\n") continue;
1427
+ if (i > 0) {
1428
+ let matchedOp = "";
1429
+ for (const op of ops) {
1430
+ if (expr.startsWith(op, i)) {
1431
+ matchedOp = op;
1432
+ break;
1433
+ }
1434
+ }
1435
+ if (matchedOp) {
1436
+ const isPlusMinus = matchedOp === "+" || matchedOp === "-";
1437
+ const unary = isPlusMinus && (prevSig === "" || UNARY_PRECEDERS.includes(prevSig));
1438
+ if (!unary) {
1439
+ bestIdx = i;
1440
+ bestOp = matchedOp;
1441
+ }
1442
+ i += matchedOp.length - 1;
1443
+ prevSig = matchedOp[matchedOp.length - 1];
1444
+ continue;
1445
+ }
1446
+ }
1447
+ prevSig = ch;
1448
+ }
1449
+ if (bestIdx === -1) return null;
1450
+ return { left: expr.slice(0, bestIdx).trim(), op: bestOp, right: expr.slice(bestIdx + bestOp.length).trim() };
1451
+ }
1360
1452
  var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
1361
1453
  var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
1362
1454
  var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
@@ -2313,12 +2405,12 @@ var FormaRuntime = (function (exports) {
2313
2405
  return () => left() && right();
2314
2406
  }
2315
2407
  }
2316
- const compMatch = expr.match(RE_COMPARISON);
2408
+ const compMatch = matchBinaryOp(expr, ["===", "!==", "==", "!=", ">=", "<=", ">", "<"]);
2317
2409
  if (compMatch) {
2318
- const left = parseExpression(compMatch[1].trim(), scope);
2319
- const right = parseExpression(compMatch[3].trim(), scope);
2410
+ const left = parseExpression(compMatch.left, scope);
2411
+ const right = parseExpression(compMatch.right, scope);
2320
2412
  if (left && right) {
2321
- const op = compMatch[2];
2413
+ const op = compMatch.op;
2322
2414
  return () => {
2323
2415
  const l = left(), r = right();
2324
2416
  switch (op) {
@@ -2342,12 +2434,12 @@ var FormaRuntime = (function (exports) {
2342
2434
  };
2343
2435
  }
2344
2436
  }
2345
- const addMatch = expr.match(RE_ADD);
2437
+ const addMatch = matchBinaryOp(expr, ["+", "-"]);
2346
2438
  if (addMatch) {
2347
- const left = parseExpression(addMatch[1].trim(), scope);
2348
- const right = parseExpression(addMatch[3].trim(), scope);
2439
+ const left = parseExpression(addMatch.left, scope);
2440
+ const right = parseExpression(addMatch.right, scope);
2349
2441
  if (left && right) {
2350
- const op = addMatch[2];
2442
+ const op = addMatch.op;
2351
2443
  return () => {
2352
2444
  const l = left(), r = right();
2353
2445
  if (op === "+") return l + r;
@@ -2355,12 +2447,12 @@ var FormaRuntime = (function (exports) {
2355
2447
  };
2356
2448
  }
2357
2449
  }
2358
- const mulMatch = expr.match(RE_MUL);
2450
+ const mulMatch = matchBinaryOp(expr, ["*", "/", "%"]);
2359
2451
  if (mulMatch) {
2360
- const left = parseExpression(mulMatch[1].trim(), scope);
2361
- const right = parseExpression(mulMatch[3].trim(), scope);
2452
+ const left = parseExpression(mulMatch.left, scope);
2453
+ const right = parseExpression(mulMatch.right, scope);
2362
2454
  if (left && right) {
2363
- const op = mulMatch[2];
2455
+ const op = mulMatch.op;
2364
2456
  return () => {
2365
2457
  const l = left(), r = right();
2366
2458
  switch (op) {
@@ -2785,25 +2877,61 @@ var FormaRuntime = (function (exports) {
2785
2877
  const modelExpr = !known || known.has("data-model") ? el.getAttribute("data-model") : null;
2786
2878
  if (modelExpr) {
2787
2879
  const prop = modelExpr.replace(RE_STRIP_BRACES, "").trim();
2788
- const getter = scope.getters[prop];
2789
- const setter = scope.setters[prop];
2880
+ let getter = scope.getters[prop];
2881
+ let setter = scope.setters[prop];
2882
+ if ((!getter || !setter) && prop.includes(".")) {
2883
+ getter = buildEvaluator(prop, scope);
2884
+ const lastDot = prop.lastIndexOf(".");
2885
+ const basePath = prop.slice(0, lastDot);
2886
+ const key = prop.slice(lastDot + 1);
2887
+ const baseGet = buildEvaluator(basePath, scope);
2888
+ setter = (v) => {
2889
+ const base = baseGet();
2890
+ if (base != null && typeof base === "object") base[key] = v;
2891
+ };
2892
+ }
2790
2893
  if (getter && setter) {
2791
2894
  const input = el;
2895
+ const tag = input.tagName;
2792
2896
  const dispose = internalEffect(() => {
2793
2897
  const val = getter();
2794
- if (input.type === "checkbox") {
2898
+ const type = input.type;
2899
+ if (type === "checkbox") {
2795
2900
  input.checked = !!val;
2901
+ const indetExpr = el.getAttribute("data-model-indeterminate");
2902
+ if (indetExpr) {
2903
+ const indetGetter = scope.getters[indetExpr.replace(RE_STRIP_BRACES, "").trim()];
2904
+ if (indetGetter) input.indeterminate = !!indetGetter();
2905
+ }
2906
+ } else if (type === "radio") {
2907
+ input.checked = String(val) === input.value;
2908
+ } else if (tag === "SELECT" && input.multiple) {
2909
+ const sel = input;
2910
+ const arr = Array.isArray(val) ? val.map(String) : [];
2911
+ for (const opt of Array.from(sel.options)) opt.selected = arr.includes(opt.value);
2796
2912
  } else {
2797
2913
  input.value = String(val ?? "");
2798
2914
  }
2799
2915
  });
2800
2916
  disposers.push(dispose);
2801
- const event = input.type === "checkbox" ? "change" : "input";
2917
+ const event = input.type === "checkbox" || input.type === "radio" || tag === "SELECT" ? "change" : "input";
2802
2918
  const onModelInput = () => {
2803
- if (input.type === "checkbox") {
2919
+ const type = input.type;
2920
+ if (type === "checkbox") {
2804
2921
  setter(input.checked);
2805
- } else if (input.type === "number" || input.type === "range") {
2806
- setter(Number(input.value));
2922
+ } else if (type === "radio") {
2923
+ if (input.checked) setter(input.value);
2924
+ } else if (tag === "SELECT" && input.multiple) {
2925
+ const sel = input;
2926
+ setter(Array.from(sel.selectedOptions).map((o) => o.value));
2927
+ } else if (type === "number" || type === "range") {
2928
+ const raw = input.value;
2929
+ if (raw === "") {
2930
+ setter(null);
2931
+ } else {
2932
+ const n = Number(raw);
2933
+ if (!Number.isNaN(n)) setter(n);
2934
+ }
2807
2935
  } else {
2808
2936
  setter(input.value);
2809
2937
  }
@@ -2995,59 +3123,30 @@ var FormaRuntime = (function (exports) {
2995
3123
  }
2996
3124
  }
2997
3125
  const prevNodes = new Set(oldNodes);
2998
- if (keyProp) {
2999
- const result = reconcileList(
3000
- el,
3001
- oldItems,
3002
- rawItems,
3003
- oldNodes,
3004
- (item) => String(item?.[keyProp] ?? ""),
3005
- (item) => {
3006
- const idx = rawItems.indexOf(item);
3007
- return createBoundClone2(item, idx);
3008
- },
3009
- (node, item) => {
3010
- const idx = rawItems.indexOf(item);
3011
- updateBoundClone2(node, item, idx);
3012
- },
3013
- void 0,
3014
- // beforeNode
3015
- listHooks
3016
- );
3017
- const nextNodes = new Set(result.nodes);
3018
- for (const n of prevNodes) {
3019
- if (!nextNodes.has(n)) {
3020
- if (n.hasAttribute?.("data-forma-leaving")) continue;
3021
- disposeCloneBindings2(n);
3022
- }
3023
- }
3024
- oldItems = result.items;
3025
- oldNodes = result.nodes;
3026
- } else {
3027
- const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
3028
- const oldWrapped = oldItems;
3029
- const result = reconcileList(
3030
- el,
3031
- oldWrapped,
3032
- wrapped,
3033
- oldNodes,
3034
- (w) => w.__idx,
3035
- (w) => createBoundClone2(w.__item, w.__idx),
3036
- (node, w) => updateBoundClone2(node, w.__item, w.__idx),
3037
- void 0,
3038
- // beforeNode
3039
- listHooks
3040
- );
3041
- const nextNodes = new Set(result.nodes);
3042
- for (const n of prevNodes) {
3043
- if (!nextNodes.has(n)) {
3044
- if (n.hasAttribute?.("data-forma-leaving")) continue;
3045
- disposeCloneBindings2(n);
3046
- }
3126
+ const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
3127
+ const oldWrapped = oldItems;
3128
+ const keyFn = keyProp ? (w) => String(w.__item?.[keyProp] ?? "") : (w) => w.__idx;
3129
+ const result = reconcileList(
3130
+ el,
3131
+ oldWrapped,
3132
+ wrapped,
3133
+ oldNodes,
3134
+ keyFn,
3135
+ (w) => createBoundClone2(w.__item, w.__idx),
3136
+ (node, w) => updateBoundClone2(node, w.__item, w.__idx),
3137
+ void 0,
3138
+ // beforeNode
3139
+ listHooks
3140
+ );
3141
+ const nextNodes = new Set(result.nodes);
3142
+ for (const n of prevNodes) {
3143
+ if (!nextNodes.has(n)) {
3144
+ if (n.hasAttribute?.("data-forma-leaving")) continue;
3145
+ disposeCloneBindings2(n);
3047
3146
  }
3048
- oldItems = result.items;
3049
- oldNodes = result.nodes;
3050
3147
  }
3148
+ oldItems = result.items;
3149
+ oldNodes = result.nodes;
3051
3150
  });
3052
3151
  disposers.push(dispose);
3053
3152
  }