@ai-matrx/office 0.8.26 → 0.8.28

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.
@@ -45,7 +45,7 @@ interface SheetRange {
45
45
  * column width, worksheet name. C5b: formula text in cells (`f`, array `ref`) and defined names. Shared
46
46
  * formulas (`si`) stay barriers (see transform.ts cellBarrier); ranged resources are C5c.
47
47
  */
48
- type SheetFamily = "cell" | "structure" | "merge" | "size" | "name" | "defname" | "numfmt" | "count" | "sheet" | "sort" | "anchor";
48
+ type SheetFamily = "cell" | "structure" | "merge" | "size" | "name" | "defname" | "numfmt" | "count" | "sheet" | "sort" | "anchor" | "cf" | "dv" | "filter";
49
49
  interface CoveredEntry {
50
50
  readonly kind: "covered";
51
51
  readonly family: SheetFamily;
@@ -103,7 +103,7 @@ declare function mapIndex(op: AxisOp, i: number): number | null;
103
103
  */
104
104
  declare function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null;
105
105
 
106
- declare const SHEET_TRANSFORMER_VERSION = "c5c-1";
106
+ declare const SHEET_TRANSFORMER_VERSION = "c5d-1";
107
107
  /**
108
108
  * Pairs the fuzz proved divergent (or that have no transform yet), as unordered mutation-id pairs, with the
109
109
  * failing seed. Each returns a Barrier until it has its own correct transform under a new proto.
@@ -208,6 +208,11 @@ type RefChange = {
208
208
  readonly kind: "rename";
209
209
  readonly sheet: string;
210
210
  readonly name: string;
211
+ }
212
+ /** C5d: the sheet was removed; every reference that names it becomes #REF! (Excel's rule). */
213
+ | {
214
+ readonly kind: "remove-sheet";
215
+ readonly sheet: string;
211
216
  } | {
212
217
  readonly kind: "defined-name";
213
218
  readonly from: string;
@@ -45,7 +45,7 @@ interface SheetRange {
45
45
  * column width, worksheet name. C5b: formula text in cells (`f`, array `ref`) and defined names. Shared
46
46
  * formulas (`si`) stay barriers (see transform.ts cellBarrier); ranged resources are C5c.
47
47
  */
48
- type SheetFamily = "cell" | "structure" | "merge" | "size" | "name" | "defname" | "numfmt" | "count" | "sheet" | "sort" | "anchor";
48
+ type SheetFamily = "cell" | "structure" | "merge" | "size" | "name" | "defname" | "numfmt" | "count" | "sheet" | "sort" | "anchor" | "cf" | "dv" | "filter";
49
49
  interface CoveredEntry {
50
50
  readonly kind: "covered";
51
51
  readonly family: SheetFamily;
@@ -103,7 +103,7 @@ declare function mapIndex(op: AxisOp, i: number): number | null;
103
103
  */
104
104
  declare function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null;
105
105
 
106
- declare const SHEET_TRANSFORMER_VERSION = "c5c-1";
106
+ declare const SHEET_TRANSFORMER_VERSION = "c5d-1";
107
107
  /**
108
108
  * Pairs the fuzz proved divergent (or that have no transform yet), as unordered mutation-id pairs, with the
109
109
  * failing seed. Each returns a Barrier until it has its own correct transform under a new proto.
@@ -208,6 +208,11 @@ type RefChange = {
208
208
  readonly kind: "rename";
209
209
  readonly sheet: string;
210
210
  readonly name: string;
211
+ }
212
+ /** C5d: the sheet was removed; every reference that names it becomes #REF! (Excel's rule). */
213
+ | {
214
+ readonly kind: "remove-sheet";
215
+ readonly sheet: string;
211
216
  } | {
212
217
  readonly kind: "defined-name";
213
218
  readonly from: string;
@@ -101,16 +101,17 @@ var SHEET_MUTATIONS = {
101
101
  "formula.mutation.remove-super-table": barrier("C5c", "table names in structured references belong to tables (C5c)"),
102
102
  "formula.mutation.set-super-table-option": barrier("C5c", "table names in structured references belong to tables (C5c)"),
103
103
  // ── barriers until C5c (ranged resources) ────────────────────────────────────────────────────────
104
- "sheet.mutation.add-conditional-rule": barrier("C5c", RESOURCE),
105
- "sheet.mutation.set-conditional-rule": barrier("C5c", RESOURCE),
106
- "sheet.mutation.delete-conditional-rule": barrier("C5c", RESOURCE),
104
+ // ── covered (C5d): ranged resources, transformed by ranged-resources.ts ──
105
+ "sheet.mutation.add-conditional-rule": covered("cf", "rule", "derived"),
106
+ "sheet.mutation.set-conditional-rule": covered("cf", "cfId", "rule", "derived"),
107
+ "sheet.mutation.delete-conditional-rule": covered("cf", "cfId", "derived"),
107
108
  "sheet.mutation.move-conditional-rule": barrier("C5c", "conditional-format priority (ordered list)"),
108
- "data-validation.mutation.addRule": barrier("C5c", RESOURCE),
109
- "data-validation.mutation.updateRule": barrier("C5c", RESOURCE),
110
- "data-validation.mutation.removeRule": barrier("C5c", RESOURCE),
111
- "sheet.mutation.set-filter-range": barrier("C5c", RESOURCE),
112
- "sheet.mutation.set-filter-criteria": barrier("C5c", RESOURCE),
113
- "sheet.mutation.remove-filter": barrier("C5c", RESOURCE),
109
+ "data-validation.mutation.addRule": covered("dv", "rule", "index", "source", "derived"),
110
+ "data-validation.mutation.updateRule": covered("dv", "ruleId", "payload", "source", "derived"),
111
+ "data-validation.mutation.removeRule": covered("dv", "ruleId", "source", "derived"),
112
+ "sheet.mutation.set-filter-range": covered("filter", "range", "derived"),
113
+ "sheet.mutation.set-filter-criteria": covered("filter", "col", "criteria", "reCalc", "derived"),
114
+ "sheet.mutation.remove-filter": covered("filter", "derived"),
114
115
  "sheet.mutation.add-table": barrier("C5c", RESOURCE),
115
116
  "sheet.mutation.delete-table": barrier("C5c", RESOURCE),
116
117
  "sheet.mutation.set-sheet-table": barrier("C5c", RESOURCE),
@@ -452,6 +453,9 @@ function rewriteFormula(text, ctx, change) {
452
453
  if (change.kind === "rename") {
453
454
  if (!sheetName) continue;
454
455
  token = serializeRangeToRefString({ range, sheetName: change.name, unitId });
456
+ } else if (change.kind === "remove-sheet") {
457
+ if (!sheetName) continue;
458
+ token = REF_ERROR;
455
459
  } else {
456
460
  const moved = moveRange(Rectangle.moveOffset(range, 0, 0), change.axis, change.op);
457
461
  if (moved === void 0) continue;
@@ -470,6 +474,10 @@ function rewriteFormula(text, ctx, change) {
470
474
  return { text: out, changed: out !== text };
471
475
  }
472
476
  function refSheetsAfter(refSheets, change) {
477
+ if (change.kind === "remove-sheet" && refSheets) {
478
+ if (!Object.values(refSheets).includes(change.sheet)) return refSheets;
479
+ return Object.fromEntries(Object.entries(refSheets).map(([n, id]) => [n, id === change.sheet ? null : id]));
480
+ }
473
481
  if (change.kind !== "rename" || !refSheets) return refSheets;
474
482
  const entries = Object.entries(refSheets);
475
483
  if (!entries.some(([name, id]) => id === change.sheet || name === change.name)) return refSheets;
@@ -553,8 +561,111 @@ function transformCommentAnchor(x, y, side) {
553
561
  return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);
554
562
  }
555
563
 
564
+ // src/collab/sheets/transform/ranged-resources.ts
565
+ var specs = /* @__PURE__ */ new Map();
566
+ var byId = /* @__PURE__ */ new Map();
567
+ function registerRangedResource(spec) {
568
+ specs.set(spec.family, spec);
569
+ for (const id of Object.keys(spec.ids)) byId.set(id, spec);
570
+ }
571
+ var rangedSpecOf = (id) => byId.get(id);
572
+ var cfRule = (m) => m.params.rule ?? {};
573
+ var CF_FORMULA = (r) => {
574
+ const rule = r.rule ?? {};
575
+ if (rule.type === "highlightCell" && rule.subType === "formula") return true;
576
+ return JSON.stringify(rule).includes('"type":"formula"');
577
+ };
578
+ registerRangedResource({
579
+ family: "cf",
580
+ ids: { "sheet.mutation.add-conditional-rule": "add", "sheet.mutation.set-conditional-rule": "set", "sheet.mutation.delete-conditional-rule": "remove" },
581
+ keys: (m) => [String(m.id === "sheet.mutation.add-conditional-rule" ? cfRule(m).cfId : m.params.cfId)],
582
+ rects: (m) => cfRule(m).ranges ?? null,
583
+ withRects: (m, rects) => ({ id: m.id, params: { ...m.params, rule: { ...cfRule(m), ranges: rects } } }),
584
+ setFor: (m, rects) => ({
585
+ id: "sheet.mutation.set-conditional-rule",
586
+ params: { subUnitId: m.params.subUnitId, cfId: cfRule(m).cfId ?? m.params.cfId, rule: { ...cfRule(m), ranges: rects }, derived: true }
587
+ }),
588
+ removeOf: (sheet, key) => ({ id: "sheet.mutation.delete-conditional-rule", params: { subUnitId: sheet, cfId: key, derived: true } }),
589
+ hasFormula: (m) => CF_FORMULA(cfRule(m)),
590
+ orderedAdds: true
591
+ });
592
+ var DV = { add: "data-validation.mutation.addRule", update: "data-validation.mutation.updateRule", remove: "data-validation.mutation.removeRule" };
593
+ var DV_RANGE = 1;
594
+ var DV_FACETS = { 0: "setting", 1: "range", 2: "options", 3: "all" };
595
+ var dvPayload = (m) => m.params.payload ?? {};
596
+ var dvFormula = (o) => !!o && [o.formula1, o.formula2].some((f) => typeof f === "string" && f.trim().startsWith("="));
597
+ registerRangedResource({
598
+ family: "dv",
599
+ ids: { [DV.add]: "add", [DV.update]: "set", [DV.remove]: "remove" },
600
+ keys: (m) => {
601
+ if (m.id === DV.add) return [String((m.params.rule ?? {}).uid)];
602
+ const id = m.params.ruleId;
603
+ return Array.isArray(id) ? id.map(String) : [String(id)];
604
+ },
605
+ facet: (m) => m.id === DV.update ? DV_FACETS[Number(dvPayload(m).type)] ?? "all" : "all",
606
+ rects: (m) => {
607
+ if (m.id === DV.add) return m.params.rule?.ranges ?? null;
608
+ if (m.id === DV.update && dvPayload(m).type === DV_RANGE) return dvPayload(m).payload ?? null;
609
+ if (m.id === DV.update && dvPayload(m).type === 3) return (dvPayload(m).payload ?? {}).ranges ?? null;
610
+ return null;
611
+ },
612
+ withRects: (m, rects) => {
613
+ if (m.id === DV.add) return { id: m.id, params: { ...m.params, rule: { ...m.params.rule, ranges: rects } } };
614
+ const p = dvPayload(m);
615
+ return { id: m.id, params: { ...m.params, payload: p.type === DV_RANGE ? { ...p, payload: rects } : { ...p, payload: { ...p.payload, ranges: rects } } } };
616
+ },
617
+ setFor: (m, rects) => ({
618
+ id: DV.update,
619
+ params: { subUnitId: m.params.subUnitId, ruleId: m.id === DV.add ? (m.params.rule ?? {}).uid : m.params.ruleId, payload: { type: DV_RANGE, payload: rects }, derived: true }
620
+ }),
621
+ removeOf: (sheet, key) => ({ id: DV.remove, params: { subUnitId: sheet, ruleId: key, derived: true } }),
622
+ withoutKeys: (m, keys) => {
623
+ const left = (Array.isArray(m.params.ruleId) ? m.params.ruleId : [m.params.ruleId]).filter((k) => !keys.includes(String(k)));
624
+ return left.length ? { id: m.id, params: { ...m.params, ruleId: Array.isArray(m.params.ruleId) ? left : left[0] } } : null;
625
+ },
626
+ hasFormula: (m) => {
627
+ if (m.id === DV.add) return dvFormula(m.params.rule);
628
+ const p = dvPayload(m);
629
+ return p.type === 0 || p.type === 3 ? dvFormula(p.payload) : false;
630
+ },
631
+ orderedAdds: true,
632
+ // an ALL update (the ref-range handler restates the whole rule) losing to a setting/options change keeps its ranges
633
+ narrow: (m, lost) => {
634
+ if (m.id !== DV.update || dvPayload(m).type !== 3 || lost === "range") return null;
635
+ const ranges = (dvPayload(m).payload ?? {}).ranges;
636
+ return Array.isArray(ranges) ? { id: m.id, params: { ...m.params, payload: { type: DV_RANGE, payload: ranges } } } : null;
637
+ }
638
+ });
639
+ var FILTER = { range: "sheet.mutation.set-filter-range", criteria: "sheet.mutation.set-filter-criteria", remove: "sheet.mutation.remove-filter" };
640
+ var hull = (rects) => ({
641
+ startRow: Math.min(...rects.map((r) => r.startRow)),
642
+ endRow: Math.max(...rects.map((r) => r.endRow)),
643
+ startColumn: Math.min(...rects.map((r) => r.startColumn)),
644
+ endColumn: Math.max(...rects.map((r) => r.endColumn))
645
+ });
646
+ var filterWith = (m, rects) => m.id === FILTER.range ? { id: m.id, params: { ...m.params, range: hull(rects) } } : { id: m.id, params: { ...m.params, col: rects[0].startColumn } };
647
+ var colRect = (col) => ({ startRow: 0, endRow: 1e9, startColumn: col, endColumn: col });
648
+ registerRangedResource({
649
+ family: "filter",
650
+ ids: { [FILTER.range]: "set", [FILTER.criteria]: "set", [FILTER.remove]: "remove" },
651
+ keys: () => ["filter"],
652
+ facet: (m) => m.id === FILTER.criteria ? `col:${String(m.params.col)}` : "range",
653
+ rects: (m) => m.id === FILTER.range ? [m.params.range] : m.id === FILTER.criteria ? [colRect(Number(m.params.col))] : null,
654
+ withRects: (m, rects) => filterWith(m, rects),
655
+ setFor: (m, rects) => {
656
+ const f = filterWith(m, rects);
657
+ return { id: f.id, params: { ...f.params, derived: true } };
658
+ },
659
+ removeOf: (sheet) => ({ id: FILTER.remove, params: { subUnitId: sheet, derived: true } }),
660
+ /** a criteria column that was removed takes only its criteria with it, never the filter */
661
+ emptyDrops: (m) => m.id === FILTER.criteria,
662
+ // a column move can carry a criteria column out of the filter's range (the model refuses it): not yet
663
+ // where a criteria column lands depends on how the filter's own range moved (Univer's rule at its edges): not yet
664
+ barrierOver: (m, axis) => m.id === FILTER.criteria && axis === "col" ? "filter: a column's criteria against a column insert, remove or move" : null
665
+ });
666
+
556
667
  // src/collab/sheets/transform/transform.ts
557
- var SHEET_TRANSFORMER_VERSION = "c5c-1";
668
+ var SHEET_TRANSFORMER_VERSION = "c5d-1";
558
669
  var ID = {
559
670
  values: "sheet.mutation.set-range-values",
560
671
  insertRow: "sheet.mutation.insert-row",
@@ -1329,19 +1440,44 @@ function sheetPair(x, y, side) {
1329
1440
  if (ys) {
1330
1441
  if (y.id !== ID.removeSheet) return ok2([x]);
1331
1442
  const gone2 = sheetOf(y);
1332
- if (namesSheet(x, gone2)) return barrier2("a formula names a sheet removed at the same time");
1333
1443
  if (sheetOf(x) === gone2 && x.id !== ID.setName && x.id !== ID.removeName)
1334
1444
  return ok2([], [], isDerived(x) ? [] : [`${gone2}: the sheet was removed`]);
1445
+ if (namesSheet(x, gone2)) {
1446
+ const ch = { kind: "remove-sheet", sheet: gone2 };
1447
+ if (x.id === ID.values) return cellsOverChange(x, ch);
1448
+ const r = definedNameThrough(x, ch);
1449
+ return typeof r === "string" ? barrier2(r) : ok2([r.op]);
1450
+ }
1335
1451
  return ok2([x]);
1336
1452
  }
1337
1453
  if (x.id !== ID.removeSheet) return ok2([x]);
1338
1454
  const gone = sheetOf(x);
1339
- if (namesSheet(y, gone)) return barrier2("a formula names a sheet removed at the same time");
1340
- return ok2([x], sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : []);
1455
+ const ry = rangedSpecOf(y.id);
1456
+ if (ry && sheetOf(y) === gone && ry.ids[y.id] !== "remove")
1457
+ return ok2([...ry.keys(y).map((k) => ({ id: ry.removeOf(gone, k).id, params: { ...ry.removeOf(gone, k).params } })), x]);
1458
+ const lost = sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : [];
1459
+ if (sheetOf(y) !== gone && namesSheet(y, gone)) {
1460
+ const ch = { kind: "remove-sheet", sheet: gone };
1461
+ if (y.id === ID.values) {
1462
+ const add = rewritesOf(y, ch);
1463
+ if (typeof add === "string") return barrier2(add);
1464
+ return ok2(add ? [x, add] : [x], lost);
1465
+ }
1466
+ const r = definedNameThrough(y, ch);
1467
+ if (typeof r === "string") return barrier2(r);
1468
+ return ok2(r.changed ? [x, asDerived(r.op)] : [x], lost);
1469
+ }
1470
+ return ok2([x], lost);
1341
1471
  }
1342
1472
  function annotateSheetOps(group, sheetOrder) {
1473
+ return cleanupsBeforeSheetRemoval(annotateSheetOpsInOrder(group, sheetOrder));
1474
+ }
1475
+ function annotateSheetOpsInOrder(group, sheetOrder) {
1343
1476
  const order = [...sheetOrder];
1344
- return group.map((m) => {
1477
+ let structural = false;
1478
+ return group.map((m0) => {
1479
+ if (structuralOf(m0)) structural = true;
1480
+ const m = structural && rangedSpecOf(m0.id) && !isDerived(m0) ? { id: m0.id, params: { ...m0.params, derived: true } } : m0;
1345
1481
  if (m.id === ID.insertSheet) {
1346
1482
  const index = typeof m.params.index === "number" ? Math.min(m.params.index, order.length) : order.length;
1347
1483
  order.splice(index, 0, sheetIdOf(m));
@@ -1360,6 +1496,16 @@ function annotateSheetOps(group, sheetOrder) {
1360
1496
  return m;
1361
1497
  });
1362
1498
  }
1499
+ function cleanupsBeforeSheetRemoval(group) {
1500
+ const at = group.findIndex((m) => m.id === ID.removeSheet);
1501
+ if (at < 0) return group;
1502
+ const gone = sheetOf(group[at]);
1503
+ const moved = group.filter((m, k2) => k2 > at && rangedSpecOf(m.id) && sheetOf(m) === gone);
1504
+ if (!moved.length) return group;
1505
+ const rest = group.filter((m) => !moved.includes(m));
1506
+ const k = rest.indexOf(group[at]);
1507
+ return [...rest.slice(0, k), ...moved, ...rest.slice(k)];
1508
+ }
1363
1509
  function inlineSheetStyles(sheet, styleOf) {
1364
1510
  const out = JSON.parse(JSON.stringify(sheet));
1365
1511
  const fix = (o) => {
@@ -1489,6 +1635,59 @@ function sortPair(x, y, sx, sy) {
1489
1635
  }
1490
1636
  return ok2([x]);
1491
1637
  }
1638
+ function rangedPair(x, y, side, sx, sy) {
1639
+ const px = rangedSpecOf(x.id);
1640
+ const py = rangedSpecOf(y.id);
1641
+ if (!px && !py) return null;
1642
+ const told = (m, what) => isDerived(m) ? [] : [`${sheetOf(m)}: ${what}`];
1643
+ if (px && sy) {
1644
+ const role = px.ids[x.id];
1645
+ const rects = role === "remove" ? null : px.rects(x);
1646
+ if (!rects) return ok2([x]);
1647
+ const why = px.barrierOver?.(x, sy.axis, sy.op.kind);
1648
+ if (why) return barrier2(why);
1649
+ if (px.hasFormula?.(x)) return barrier2(`${px.family}: a rule with formula text against a row or column change (its formulas do not move yet)`);
1650
+ const moved = rectsThrough(sy, rects);
1651
+ if (moved.length) return ok2([px.withRects(x, moved)]);
1652
+ if (role === "add" || px.emptyDrops?.(x)) return ok2([], [], told(x, `${px.family} ${px.keys(x).join(",")}: its cells were removed`));
1653
+ return ok2(px.keys(x).map((k) => px.removeOf(sheetOf(x), k)), [], told(x, `${px.family} ${px.keys(x).join(",")}: its cells were removed`));
1654
+ }
1655
+ if (py && sx) {
1656
+ const role = py.ids[y.id];
1657
+ const rects = role === "remove" ? null : py.rects(y);
1658
+ if (!rects) return ok2([x]);
1659
+ const why = py.barrierOver?.(y, sx.axis, sx.op.kind);
1660
+ if (why) return barrier2(why);
1661
+ if (py.hasFormula?.(y)) return barrier2(`${py.family}: a rule with formula text against a row or column change (its formulas do not move yet)`);
1662
+ const moved = rectsThrough(sx, rects);
1663
+ if (!moved.length && py.emptyDrops?.(y)) return ok2([x], told(y, `${py.family} ${py.keys(y).join(",")}: its cells were removed`));
1664
+ return ok2([x, ...moved.length ? [py.setFor(y, moved)] : py.keys(y).map((k) => py.removeOf(sheetOf(y), k))], moved.length ? [] : told(y, `${py.family} ${py.keys(y).join(",")}: its cells were removed`));
1665
+ }
1666
+ if (!px || !py || px !== py) return ok2([x]);
1667
+ const rx = px.ids[x.id];
1668
+ const ry = py.ids[y.id];
1669
+ const ky = py.keys(y);
1670
+ const common = px.keys(x).filter((k) => ky.includes(k));
1671
+ if (!common.length) {
1672
+ if (rx === "add" && ry === "add" && px.orderedAdds) return barrier2(`${px.family}: two added to one sheet at once (their order is state)`);
1673
+ return ok2([x]);
1674
+ }
1675
+ if (rx === "remove") {
1676
+ if (ry !== "remove") return ok2([x]);
1677
+ const rest = px.withoutKeys ? px.withoutKeys(x, common) : null;
1678
+ return ok2(rest ? [rest] : []);
1679
+ }
1680
+ if (ry === "remove") return ok2([], [], told(x, `${px.family} ${common.join(",")}: removed at the same time`));
1681
+ const fx = px.facet?.(x) ?? "all";
1682
+ const fy = py.facet?.(y) ?? "all";
1683
+ if (fx !== fy && fx !== "all" && fy !== "all") return ok2([x]);
1684
+ const dx = isDerived(x);
1685
+ const dy = isDerived(y);
1686
+ const xWins = dx !== dy ? !dx : side === "left";
1687
+ if (xWins) return ok2([x], told(y, `${py.family} ${common.join(",")}`));
1688
+ const narrowed = fx === "all" && fy !== "all" ? px.narrow?.(x, fy) : null;
1689
+ return ok2(narrowed ? [narrowed] : [], [], told(x, `${px.family} ${common.join(",")}`));
1690
+ }
1492
1691
  function familyOf(id) {
1493
1692
  const e = SHEET_MUTATIONS[id];
1494
1693
  return e && e.kind === "covered" ? e.family : "";
@@ -1516,6 +1715,8 @@ function transformMutation(x, y, side) {
1516
1715
  const fy = familyOf(y.id);
1517
1716
  const sy = structuralOf(y);
1518
1717
  const sx = structuralOf(x);
1718
+ const rp = rangedPair(x, y, side, sx, sy);
1719
+ if (rp) return rp;
1519
1720
  const sortp = sortPair(x, y, sx, sy);
1520
1721
  if (sortp) return sortp;
1521
1722
  const cp = countPair(x, y, sy);