@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.
@@ -788,14 +788,18 @@ var FormaRuntime = (() => {
788
788
  }
789
789
  const oldKeyMap = /* @__PURE__ */ new Map();
790
790
  for (let i = 0; i < oldLen; i++) {
791
- oldKeyMap.set(keyFn(oldItems[i]), i);
791
+ const k = keyFn(oldItems[i]);
792
+ const bucket = oldKeyMap.get(k);
793
+ if (bucket) bucket.push(i);
794
+ else oldKeyMap.set(k, [i]);
792
795
  }
793
796
  const oldIndices = new Array(newLen);
794
797
  const oldUsed = new Uint8Array(oldLen);
795
798
  for (let i = 0; i < newLen; i++) {
796
799
  const key = keyFn(newItems[i]);
797
- const oldIdx = oldKeyMap.get(key);
798
- if (oldIdx !== void 0) {
800
+ const bucket = oldKeyMap.get(key);
801
+ if (bucket && bucket.length > 0) {
802
+ const oldIdx = bucket.shift();
799
803
  oldIndices[i] = oldIdx;
800
804
  oldUsed[oldIdx] = 1;
801
805
  } else {
@@ -1144,12 +1148,12 @@ var FormaRuntime = (() => {
1144
1148
  return null;
1145
1149
  }
1146
1150
  function createReconciler(config) {
1147
- let _lastHtml = "";
1151
+ const lastHtmlByContainer = /* @__PURE__ */ new WeakMap();
1148
1152
  return function reconcile2(container, html) {
1149
1153
  const trimmed = html.trim();
1150
1154
  if (!trimmed) return;
1151
- if (trimmed === _lastHtml && container.hasChildNodes()) return;
1152
- _lastHtml = trimmed;
1155
+ if (lastHtmlByContainer.get(container) === trimmed && container.hasChildNodes()) return;
1156
+ lastHtmlByContainer.set(container, trimmed);
1153
1157
  config.disconnectObserver();
1154
1158
  try {
1155
1159
  if (!container.hasChildNodes() || container.children.length === 0) {
@@ -1464,9 +1468,97 @@ var FormaRuntime = (() => {
1464
1468
  var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
1465
1469
  var RE_AND = /^(.+?)\s*&&\s*(.+)$/;
1466
1470
  var RE_OR = /^(.+?)\s*\|\|\s*(.+)$/;
1467
- var RE_COMPARISON = /^(.+?)\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/;
1468
- var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
1469
- var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
1471
+ var UNARY_PRECEDERS = "+-*/%<>=!&|^~(,[{?:";
1472
+ function matchBinaryOp(expr, ops) {
1473
+ const SQ = String.fromCharCode(39);
1474
+ const DQ = String.fromCharCode(34);
1475
+ const BT = String.fromCharCode(96);
1476
+ const BS = String.fromCharCode(92);
1477
+ let depth = 0;
1478
+ let inSingle = false, inDouble = false, inTemplate = false, escaped = false;
1479
+ let prevSig = "";
1480
+ let bestIdx = -1;
1481
+ let bestOp = "";
1482
+ for (let i = 0; i < expr.length; i++) {
1483
+ const ch = expr[i];
1484
+ if (escaped) {
1485
+ escaped = false;
1486
+ continue;
1487
+ }
1488
+ if (ch === BS && (inSingle || inDouble || inTemplate)) {
1489
+ escaped = true;
1490
+ continue;
1491
+ }
1492
+ if (inSingle) {
1493
+ if (ch === SQ) {
1494
+ inSingle = false;
1495
+ prevSig = DQ;
1496
+ }
1497
+ continue;
1498
+ }
1499
+ if (inDouble) {
1500
+ if (ch === DQ) {
1501
+ inDouble = false;
1502
+ prevSig = DQ;
1503
+ }
1504
+ continue;
1505
+ }
1506
+ if (inTemplate) {
1507
+ if (ch === BT) {
1508
+ inTemplate = false;
1509
+ prevSig = DQ;
1510
+ }
1511
+ continue;
1512
+ }
1513
+ if (ch === SQ) {
1514
+ inSingle = true;
1515
+ continue;
1516
+ }
1517
+ if (ch === DQ) {
1518
+ inDouble = true;
1519
+ continue;
1520
+ }
1521
+ if (ch === BT) {
1522
+ inTemplate = true;
1523
+ continue;
1524
+ }
1525
+ if (ch === "(" || ch === "[" || ch === "{") {
1526
+ depth++;
1527
+ prevSig = ch;
1528
+ continue;
1529
+ }
1530
+ if (ch === ")" || ch === "]" || ch === "}") {
1531
+ if (depth > 0) depth--;
1532
+ prevSig = ch;
1533
+ continue;
1534
+ }
1535
+ if (depth !== 0) continue;
1536
+ if (ch === " " || ch === " " || ch === "\n") continue;
1537
+ if (i > 0) {
1538
+ let matchedOp = "";
1539
+ for (const op of ops) {
1540
+ if (expr.startsWith(op, i)) {
1541
+ matchedOp = op;
1542
+ break;
1543
+ }
1544
+ }
1545
+ if (matchedOp) {
1546
+ const isPlusMinus = matchedOp === "+" || matchedOp === "-";
1547
+ const unary = isPlusMinus && (prevSig === "" || UNARY_PRECEDERS.includes(prevSig));
1548
+ if (!unary) {
1549
+ bestIdx = i;
1550
+ bestOp = matchedOp;
1551
+ }
1552
+ i += matchedOp.length - 1;
1553
+ prevSig = matchedOp[matchedOp.length - 1];
1554
+ continue;
1555
+ }
1556
+ }
1557
+ prevSig = ch;
1558
+ }
1559
+ if (bestIdx === -1) return null;
1560
+ return { left: expr.slice(0, bestIdx).trim(), op: bestOp, right: expr.slice(bestIdx + bestOp.length).trim() };
1561
+ }
1470
1562
  var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
1471
1563
  var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
1472
1564
  var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
@@ -2461,12 +2553,12 @@ var FormaRuntime = (() => {
2461
2553
  return () => left() && right();
2462
2554
  }
2463
2555
  }
2464
- const compMatch = expr.match(RE_COMPARISON);
2556
+ const compMatch = matchBinaryOp(expr, ["===", "!==", "==", "!=", ">=", "<=", ">", "<"]);
2465
2557
  if (compMatch) {
2466
- const left = parseExpression(compMatch[1].trim(), scope);
2467
- const right = parseExpression(compMatch[3].trim(), scope);
2558
+ const left = parseExpression(compMatch.left, scope);
2559
+ const right = parseExpression(compMatch.right, scope);
2468
2560
  if (left && right) {
2469
- const op = compMatch[2];
2561
+ const op = compMatch.op;
2470
2562
  return () => {
2471
2563
  const l = left(), r = right();
2472
2564
  switch (op) {
@@ -2490,12 +2582,12 @@ var FormaRuntime = (() => {
2490
2582
  };
2491
2583
  }
2492
2584
  }
2493
- const addMatch = expr.match(RE_ADD);
2585
+ const addMatch = matchBinaryOp(expr, ["+", "-"]);
2494
2586
  if (addMatch) {
2495
- const left = parseExpression(addMatch[1].trim(), scope);
2496
- const right = parseExpression(addMatch[3].trim(), scope);
2587
+ const left = parseExpression(addMatch.left, scope);
2588
+ const right = parseExpression(addMatch.right, scope);
2497
2589
  if (left && right) {
2498
- const op = addMatch[2];
2590
+ const op = addMatch.op;
2499
2591
  return () => {
2500
2592
  const l = left(), r = right();
2501
2593
  if (op === "+") return l + r;
@@ -2503,12 +2595,12 @@ var FormaRuntime = (() => {
2503
2595
  };
2504
2596
  }
2505
2597
  }
2506
- const mulMatch = expr.match(RE_MUL);
2598
+ const mulMatch = matchBinaryOp(expr, ["*", "/", "%"]);
2507
2599
  if (mulMatch) {
2508
- const left = parseExpression(mulMatch[1].trim(), scope);
2509
- const right = parseExpression(mulMatch[3].trim(), scope);
2600
+ const left = parseExpression(mulMatch.left, scope);
2601
+ const right = parseExpression(mulMatch.right, scope);
2510
2602
  if (left && right) {
2511
- const op = mulMatch[2];
2603
+ const op = mulMatch.op;
2512
2604
  return () => {
2513
2605
  const l = left(), r = right();
2514
2606
  switch (op) {
@@ -3003,25 +3095,61 @@ var FormaRuntime = (() => {
3003
3095
  const modelExpr = !known || known.has("data-model") ? el.getAttribute("data-model") : null;
3004
3096
  if (modelExpr) {
3005
3097
  const prop = modelExpr.replace(RE_STRIP_BRACES, "").trim();
3006
- const getter = scope.getters[prop];
3007
- const setter = scope.setters[prop];
3098
+ let getter = scope.getters[prop];
3099
+ let setter = scope.setters[prop];
3100
+ if ((!getter || !setter) && prop.includes(".")) {
3101
+ getter = buildEvaluator(prop, scope);
3102
+ const lastDot = prop.lastIndexOf(".");
3103
+ const basePath = prop.slice(0, lastDot);
3104
+ const key = prop.slice(lastDot + 1);
3105
+ const baseGet = buildEvaluator(basePath, scope);
3106
+ setter = (v) => {
3107
+ const base = baseGet();
3108
+ if (base != null && typeof base === "object") base[key] = v;
3109
+ };
3110
+ }
3008
3111
  if (getter && setter) {
3009
3112
  const input = el;
3113
+ const tag = input.tagName;
3010
3114
  const dispose = internalEffect(() => {
3011
3115
  const val = getter();
3012
- if (input.type === "checkbox") {
3116
+ const type = input.type;
3117
+ if (type === "checkbox") {
3013
3118
  input.checked = !!val;
3119
+ const indetExpr = el.getAttribute("data-model-indeterminate");
3120
+ if (indetExpr) {
3121
+ const indetGetter = scope.getters[indetExpr.replace(RE_STRIP_BRACES, "").trim()];
3122
+ if (indetGetter) input.indeterminate = !!indetGetter();
3123
+ }
3124
+ } else if (type === "radio") {
3125
+ input.checked = String(val) === input.value;
3126
+ } else if (tag === "SELECT" && input.multiple) {
3127
+ const sel = input;
3128
+ const arr = Array.isArray(val) ? val.map(String) : [];
3129
+ for (const opt of Array.from(sel.options)) opt.selected = arr.includes(opt.value);
3014
3130
  } else {
3015
3131
  input.value = String(val ?? "");
3016
3132
  }
3017
3133
  });
3018
3134
  disposers.push(dispose);
3019
- const event = input.type === "checkbox" ? "change" : "input";
3135
+ const event = input.type === "checkbox" || input.type === "radio" || tag === "SELECT" ? "change" : "input";
3020
3136
  const onModelInput = () => {
3021
- if (input.type === "checkbox") {
3137
+ const type = input.type;
3138
+ if (type === "checkbox") {
3022
3139
  setter(input.checked);
3023
- } else if (input.type === "number" || input.type === "range") {
3024
- setter(Number(input.value));
3140
+ } else if (type === "radio") {
3141
+ if (input.checked) setter(input.value);
3142
+ } else if (tag === "SELECT" && input.multiple) {
3143
+ const sel = input;
3144
+ setter(Array.from(sel.selectedOptions).map((o) => o.value));
3145
+ } else if (type === "number" || type === "range") {
3146
+ const raw = input.value;
3147
+ if (raw === "") {
3148
+ setter(null);
3149
+ } else {
3150
+ const n = Number(raw);
3151
+ if (!Number.isNaN(n)) setter(n);
3152
+ }
3025
3153
  } else {
3026
3154
  setter(input.value);
3027
3155
  }
@@ -3214,59 +3342,30 @@ var FormaRuntime = (() => {
3214
3342
  }
3215
3343
  }
3216
3344
  const prevNodes = new Set(oldNodes);
3217
- if (keyProp) {
3218
- const result = reconcileList(
3219
- el,
3220
- oldItems,
3221
- rawItems,
3222
- oldNodes,
3223
- (item) => String(item?.[keyProp] ?? ""),
3224
- (item) => {
3225
- const idx = rawItems.indexOf(item);
3226
- return createBoundClone2(item, idx);
3227
- },
3228
- (node, item) => {
3229
- const idx = rawItems.indexOf(item);
3230
- updateBoundClone2(node, item, idx);
3231
- },
3232
- void 0,
3233
- // beforeNode
3234
- listHooks
3235
- );
3236
- const nextNodes = new Set(result.nodes);
3237
- for (const n of prevNodes) {
3238
- if (!nextNodes.has(n)) {
3239
- if (n.hasAttribute?.("data-forma-leaving")) continue;
3240
- disposeCloneBindings2(n);
3241
- }
3242
- }
3243
- oldItems = result.items;
3244
- oldNodes = result.nodes;
3245
- } else {
3246
- const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
3247
- const oldWrapped = oldItems;
3248
- const result = reconcileList(
3249
- el,
3250
- oldWrapped,
3251
- wrapped,
3252
- oldNodes,
3253
- (w) => w.__idx,
3254
- (w) => createBoundClone2(w.__item, w.__idx),
3255
- (node, w) => updateBoundClone2(node, w.__item, w.__idx),
3256
- void 0,
3257
- // beforeNode
3258
- listHooks
3259
- );
3260
- const nextNodes = new Set(result.nodes);
3261
- for (const n of prevNodes) {
3262
- if (!nextNodes.has(n)) {
3263
- if (n.hasAttribute?.("data-forma-leaving")) continue;
3264
- disposeCloneBindings2(n);
3265
- }
3345
+ const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
3346
+ const oldWrapped = oldItems;
3347
+ const keyFn = keyProp ? (w) => String(w.__item?.[keyProp] ?? "") : (w) => w.__idx;
3348
+ const result = reconcileList(
3349
+ el,
3350
+ oldWrapped,
3351
+ wrapped,
3352
+ oldNodes,
3353
+ keyFn,
3354
+ (w) => createBoundClone2(w.__item, w.__idx),
3355
+ (node, w) => updateBoundClone2(node, w.__item, w.__idx),
3356
+ void 0,
3357
+ // beforeNode
3358
+ listHooks
3359
+ );
3360
+ const nextNodes = new Set(result.nodes);
3361
+ for (const n of prevNodes) {
3362
+ if (!nextNodes.has(n)) {
3363
+ if (n.hasAttribute?.("data-forma-leaving")) continue;
3364
+ disposeCloneBindings2(n);
3266
3365
  }
3267
- oldItems = result.items;
3268
- oldNodes = result.nodes;
3269
3366
  }
3367
+ oldItems = result.items;
3368
+ oldNodes = result.nodes;
3270
3369
  });
3271
3370
  disposers.push(dispose);
3272
3371
  }