formlift 0.1.0 → 0.2.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.
@@ -997,7 +997,7 @@ function headerRow(lines, first) {
997
997
  });
998
998
  return names.every(Boolean) ? names : null;
999
999
  }
1000
- function toQuestions(fields, textOf = () => void 0) {
1000
+ function toQuestions(fields, textOf = () => void 0, { categoryCode = CODE } = {}) {
1001
1001
  const sorted = [...fields].sort(
1002
1002
  (a, b) => a.position.page - b.position.page || a.position.y - b.position.y || a.position.x - b.position.x
1003
1003
  );
@@ -1165,7 +1165,7 @@ function toQuestions(fields, textOf = () => void 0) {
1165
1165
  });
1166
1166
  const at = (q) => top(q.parts.map((p) => p.field).filter((f) => f.position.page === q.page));
1167
1167
  out.sort((a, b) => a.page - b.page || at(a) - at(b));
1168
- setCategories(out, sorted, textOf);
1168
+ if (categoryCode) setCategories(out, sorted, textOf, categoryCode);
1169
1169
  setSections(out, sorted, textOf);
1170
1170
  setScales(out, textOf);
1171
1171
  return mergeBlocks(out);
@@ -1270,10 +1270,10 @@ function mergeBlocks(out) {
1270
1270
  return res;
1271
1271
  }
1272
1272
  var CODE = /^[A-Z]{2,4}$/;
1273
- function setCategories(out, fields, textOf) {
1273
+ function setCategories(out, fields, textOf, code) {
1274
1274
  const last = Math.max(0, ...fields.map((f) => f.position.page));
1275
1275
  const found = [];
1276
- const isCode = (t) => CODE.test(t.trim()) && !COLUMN.test(t.trim());
1276
+ const isCode = (t) => code.test(t.trim()) && !COLUMN.test(t.trim());
1277
1277
  for (let page = 1; page <= last; page++) {
1278
1278
  const lines = textOf(page) ?? [];
1279
1279
  for (const l of lines) {
@@ -1431,25 +1431,7 @@ function tagsOf(text) {
1431
1431
  if (pairs.some((m) => !m || m[1].trim().split(/\s+/).length > 3)) return null;
1432
1432
  return Object.fromEntries(pairs.map((m) => [m[1].trim(), m[2].trim()]));
1433
1433
  }
1434
- var NO_FILTER = {
1435
- search: "",
1436
- answered: "all",
1437
- remarks: "all",
1438
- comments: "all",
1439
- type: "all",
1440
- category: "all",
1441
- tags: {},
1442
- choices: "all",
1443
- picked: "all"
1444
- };
1445
1434
  var isFilled = (v) => v != null && v !== "" && v !== false;
1446
- var partsLike = (q, re) => q.parts.filter((p) => re.test(p.name) || re.test(p.field.label));
1447
- function check(parts, answers, want) {
1448
- if (want === "all") return true;
1449
- if (!parts.length) return false;
1450
- const any = parts.some((p) => isFilled(answers[p.field.key]));
1451
- return want === "filled" ? any : !any;
1452
- }
1453
1435
  function choiceParts(q) {
1454
1436
  if (q.columns) return [];
1455
1437
  const size = q.rows?.reduce((a, b) => a + b, 0) ?? 0;
@@ -1470,16 +1452,145 @@ function pickedOf(q, answers) {
1470
1452
  const v = radio && answers[radio.key];
1471
1453
  return typeof v === "string" && v ? [v] : [];
1472
1454
  }
1473
- function matches(q, answers, f) {
1474
- const text = f.search.trim().toLowerCase();
1475
- if (text && !`${q.section ?? ""} ${q.intro ?? ""} ${q.title} ${q.parts.map((p) => p.field.label).join(" ")}`.toLowerCase().includes(text))
1476
- return false;
1477
- if (f.type !== "all" && !q.parts.some((p) => p.field.type === f.type)) return false;
1478
- if (f.category !== "all" && q.category !== f.category) return false;
1479
- if (Object.entries(f.tags).some(([k, v]) => q.tags?.[k] !== v)) return false;
1480
- if (f.choices !== "all" && !choicesOf(q).includes(f.choices)) return false;
1481
- if (f.picked !== "all" && !pickedOf(q, answers).includes(f.picked)) return false;
1482
- return check(q.parts, answers, f.answered) && check(partsLike(q, /remark/i), answers, f.remarks) && check(partsLike(q, /comment/i), answers, f.comments);
1455
+
1456
+ // src/core/filters.ts
1457
+ var counted = (questions, values, label = (v) => v) => {
1458
+ const m = /* @__PURE__ */ new Map();
1459
+ for (const q of questions) for (const v of new Set(values(q))) m.set(v, (m.get(v) ?? 0) + 1);
1460
+ return [...m].map(([value, count]) => ({ value, label: label(value), count }));
1461
+ };
1462
+ var filledOptions = (yes, no) => [
1463
+ { value: "filled", label: yes },
1464
+ { value: "empty", label: no }
1465
+ ];
1466
+ function filledTest(parts, value, answers) {
1467
+ if (!parts.length) return false;
1468
+ const any = parts.some((p) => isFilled(answers[p.field.key]));
1469
+ return value === "filled" ? any : !any;
1470
+ }
1471
+ var answeredFilter = ({
1472
+ id = "answered",
1473
+ title = "Answered",
1474
+ yes = "Answered",
1475
+ no = "Not answered",
1476
+ section = "Progress"
1477
+ } = {}) => ({
1478
+ id,
1479
+ title,
1480
+ section,
1481
+ options: (qs) => qs.length ? filledOptions(yes, no) : [],
1482
+ test: (q, v, a) => filledTest(q.parts, v, a)
1483
+ });
1484
+ function partFilter({
1485
+ id,
1486
+ title,
1487
+ match,
1488
+ yes = "Written",
1489
+ no = "Empty",
1490
+ section = "Progress"
1491
+ }) {
1492
+ const parts = (q) => q.parts.filter((p) => match.test(p.name) || match.test(p.field.label));
1493
+ return {
1494
+ id,
1495
+ title,
1496
+ section,
1497
+ options: (qs) => qs.some((q) => parts(q).length) ? filledOptions(yes, no) : [],
1498
+ test: (q, v, a) => filledTest(parts(q), v, a)
1499
+ };
1500
+ }
1501
+ var choiceFilter = ({
1502
+ id = "choices",
1503
+ title = "Has option",
1504
+ section = "Answers",
1505
+ min = 2,
1506
+ max = 40
1507
+ } = {}) => ({
1508
+ id,
1509
+ title,
1510
+ section,
1511
+ options: (qs) => counted(qs, choicesOf).filter((o) => o.count >= min).slice(0, max),
1512
+ test: (q, v) => choicesOf(q).includes(v)
1513
+ });
1514
+ var pickedFilter = ({
1515
+ id = "picked",
1516
+ title = "Answered with",
1517
+ section = "Answers",
1518
+ min = 2,
1519
+ max = 40
1520
+ } = {}) => ({
1521
+ id,
1522
+ title,
1523
+ section,
1524
+ options: (qs, answers) => {
1525
+ const offered = new Set(
1526
+ choiceFilter({ min, max }).options(qs, answers).map((o) => o.value)
1527
+ );
1528
+ const picked = new Map(counted(qs, (q) => pickedOf(q, answers)).map((o) => [o.value, o.count]));
1529
+ return [...offered].map((c) => ({ value: c, label: c, count: picked.get(c) ?? 0 }));
1530
+ },
1531
+ test: (q, v, a) => pickedOf(q, a).includes(v)
1532
+ });
1533
+ var categoryFilter = ({ id = "category", title = "Category", section = "bar" } = {}) => ({
1534
+ id,
1535
+ title,
1536
+ section,
1537
+ options: (qs) => counted(qs, (q) => q.category ? [q.category] : []),
1538
+ test: (q, v) => q.category === v
1539
+ });
1540
+ var typeFilter = ({
1541
+ id = "type",
1542
+ title = "Answer type",
1543
+ section = "Answer type",
1544
+ label = (t) => t
1545
+ } = {}) => ({
1546
+ id,
1547
+ title,
1548
+ section,
1549
+ options: (qs) => {
1550
+ const opts = counted(
1551
+ qs,
1552
+ (q) => q.parts.map((p) => p.field.type),
1553
+ (t) => label(t)
1554
+ );
1555
+ return opts.length > 1 ? opts : [];
1556
+ },
1557
+ test: (q, v) => q.parts.some((p) => p.field.type === v)
1558
+ });
1559
+ var tagFilter = (key, { title = key, section = "Question details" } = {}) => ({
1560
+ id: `tag:${key}`,
1561
+ title,
1562
+ section,
1563
+ options: (qs) => counted(qs, (q) => q.tags?.[key] ? [q.tags[key]] : []).sort(
1564
+ (a, b) => a.value.localeCompare(b.value, void 0, { numeric: true })
1565
+ ),
1566
+ test: (q, v) => q.tags?.[key] === v
1567
+ });
1568
+ var tagFilters = ({ section = "Question details" } = {}) => (qs) => [...new Set(qs.flatMap((q) => Object.keys(q.tags ?? {})))].map((k) => tagFilter(k, { section }));
1569
+ var customFilter = (id, title, valuesOf, section) => ({
1570
+ id,
1571
+ title,
1572
+ section,
1573
+ options: (qs, answers) => counted(qs, (q) => valuesOf(q, answers)),
1574
+ test: (q, v, a) => valuesOf(q, a).includes(v)
1575
+ });
1576
+ var builtinFilters = (labels = {}) => [
1577
+ answeredFilter(),
1578
+ partFilter({ id: "remarks", title: "Remarks", match: /remark/i }),
1579
+ partFilter({ id: "comments", title: "Comments", match: /comment/i }),
1580
+ choiceFilter(),
1581
+ pickedFilter(),
1582
+ tagFilters(),
1583
+ typeFilter({ label: labels.type }),
1584
+ categoryFilter()
1585
+ ];
1586
+ var resolveFilters = (sources, questions) => sources.flatMap((s) => typeof s === "function" ? s(questions) : [s]);
1587
+ var searchText = (q) => `${q.section ?? ""} ${q.intro ?? ""} ${q.title} ${q.parts.map((p) => p.field.label).join(" ")}`;
1588
+ function filterQuestions(questions, answers, filters, values, search = "") {
1589
+ const text = search.trim().toLowerCase();
1590
+ const active = filters.filter((f) => values[f.id] != null);
1591
+ return questions.filter(
1592
+ (q) => (!text || searchText(q).toLowerCase().includes(text)) && active.every((f) => f.test(q, values[f.id], answers))
1593
+ );
1483
1594
  }
1484
1595
 
1485
1596
  export {
@@ -1508,10 +1619,21 @@ export {
1508
1619
  detectFields,
1509
1620
  detectDocument,
1510
1621
  toQuestions,
1511
- NO_FILTER,
1512
1622
  isFilled,
1513
1623
  choicesOf,
1514
1624
  pickedOf,
1515
- matches
1625
+ answeredFilter,
1626
+ partFilter,
1627
+ choiceFilter,
1628
+ pickedFilter,
1629
+ categoryFilter,
1630
+ typeFilter,
1631
+ tagFilter,
1632
+ tagFilters,
1633
+ customFilter,
1634
+ builtinFilters,
1635
+ resolveFilters,
1636
+ searchText,
1637
+ filterQuestions
1516
1638
  };
1517
- //# sourceMappingURL=chunk-BQAVPA7Q.js.map
1639
+ //# sourceMappingURL=chunk-NUKLGHKS.js.map