@olenbetong/appframe-core 2.15.2 → 2.16.1

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.
@@ -1,4 +1,4 @@
1
- //#region ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs
1
+ // ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs
2
2
  function mitt_default(n) {
3
3
  return {
4
4
  all: n = n || /* @__PURE__ */ new Map(),
@@ -21,8 +21,7 @@ function mitt_default(n) {
21
21
  };
22
22
  }
23
23
 
24
- //#endregion
25
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/typeof.js
24
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/typeof.js
26
25
  function _typeof(o) {
27
26
  "@babel/helpers - typeof";
28
27
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -32,28 +31,25 @@ function _typeof(o) {
32
31
  }, _typeof(o);
33
32
  }
34
33
 
35
- //#endregion
36
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/toPrimitive.js
37
- function toPrimitive(t, r) {
34
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/toPrimitive.js
35
+ function toPrimitive(t, e) {
38
36
  if ("object" != _typeof(t) || !t) return t;
39
- var e = t[Symbol.toPrimitive];
40
- if (void 0 !== e) {
41
- var i = e.call(t, r || "default");
37
+ var r;
38
+ if ("undefined" != typeof Symbol && void 0 !== (r = t[Symbol.toPrimitive])) {
39
+ var i = r.call(t, e || "default");
42
40
  if ("object" != _typeof(i)) return i;
43
41
  throw new TypeError("@@toPrimitive must return a primitive value.");
44
42
  }
45
- return ("string" === r ? String : Number)(t);
43
+ return ("string" === e ? String : Number)(t);
46
44
  }
47
45
 
48
- //#endregion
49
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/toPropertyKey.js
46
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/toPropertyKey.js
50
47
  function toPropertyKey(t) {
51
48
  var i = toPrimitive(t, "string");
52
49
  return "symbol" == _typeof(i) ? i : i + "";
53
50
  }
54
51
 
55
- //#endregion
56
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/defineProperty.js
52
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/defineProperty.js
57
53
  function _defineProperty(e, r, t) {
58
54
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
59
55
  value: t,
@@ -63,8 +59,7 @@ function _defineProperty(e, r, t) {
63
59
  }) : e[r] = t, e;
64
60
  }
65
61
 
66
- //#endregion
67
- //#region src/SessionHandler.ts
62
+ // src/SessionHandler.ts
68
63
  var SessionHandler = class {
69
64
  constructor() {
70
65
  _defineProperty(this, "eventHandler", mitt_default());
@@ -111,8 +106,7 @@ var SessionHandler = class {
111
106
  }
112
107
  };
113
108
 
114
- //#endregion
115
- //#region src/UserSession.ts
109
+ // src/UserSession.ts
116
110
  var UserSession = class extends SessionHandler {
117
111
  constructor(userData) {
118
112
  super();
@@ -184,8 +178,7 @@ var UserSession = class extends SessionHandler {
184
178
  }
185
179
  };
186
180
 
187
- //#endregion
188
- //#region src/log.ts
181
+ // src/log.ts
189
182
  const article = window.af?.article?.id ?? window.window?.location.pathname.substring(1).split("/")[0];
190
183
  /**
191
184
  * Logs an activity to the website activity log
@@ -330,15 +323,13 @@ function getSessionData() {
330
323
  };
331
324
  }
332
325
 
333
- //#endregion
334
- //#region src/errorHandler.ts
326
+ // src/errorHandler.ts
335
327
  window.window?.addEventListener("error", (event) => {
336
328
  if (typeof event === "object" && "error" in event) logError(event.error);
337
329
  else if (typeof event === "object" && "message" in event) logError(event);
338
330
  });
339
331
 
340
- //#endregion
341
- //#region src/localStorage.ts
332
+ // src/localStorage.ts
342
333
  function getLocalKey(key) {
343
334
  return [
344
335
  af?.article?.id || "",
@@ -375,8 +366,7 @@ var LocalStorage = class {
375
366
  };
376
367
  var localStorage_default = new LocalStorage();
377
368
 
378
- //#endregion
379
- //#region src/date.ts
369
+ // src/date.ts
380
370
  function toLocalISODate(date) {
381
371
  return date ? toLocalISODateTime(date).substring(0, 10) : null;
382
372
  }
@@ -399,8 +389,7 @@ function toLocalDateTime(date) {
399
389
  return date ? date.toISOString().substring(0, 16) : "";
400
390
  }
401
391
 
402
- //#endregion
403
- //#region src/downloadBlob.ts
392
+ // src/downloadBlob.ts
404
393
  function downloadBlob(blob, fileName) {
405
394
  return new Promise((resolve, reject) => {
406
395
  try {
@@ -422,8 +411,7 @@ function downloadBlob(blob, fileName) {
422
411
  });
423
412
  }
424
413
 
425
- //#endregion
426
- //#region src/invariant.ts
414
+ // src/invariant.ts
427
415
  /**
428
416
  * Throws an error if the condition is falsy. The provided message is used as the error message.
429
417
  * A function can be provided to lazily generate the message.
@@ -438,8 +426,7 @@ function invariant(condition, message) {
438
426
  throw new Error(provided ?? "Invariant failed");
439
427
  }
440
428
 
441
- //#endregion
442
- //#region src/downloadRequest.ts
429
+ // src/downloadRequest.ts
443
430
  async function downloadRequest(request, { fileName, signal, onProgress }) {
444
431
  let bytesReceived = 0;
445
432
  onProgress?.({ status: "requested" });
@@ -493,8 +480,7 @@ async function downloadRequest(request, { fileName, signal, onProgress }) {
493
480
  }
494
481
  }
495
482
 
496
- //#endregion
497
- //#region src/parseFunc.ts
483
+ // src/parseFunc.ts
498
484
  const funcRegex = /^function(?:\s+([a-zA-Z$_][a-zA-Z0-9$_]*))?\s*\(((?:(?:\s*[a-zA-Z$_][a-zA-Z0-9$_]*)(?:\s*,\s*[a-zA-Z$_][a-zA-Z0-9$_]*)*)?)\s*\)\s*\{((?:.|\n|\r)*)\}$/;
499
485
  const arrowBlockRegex = /^(?:\(([^)]*)\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\s*=>\s*\{([\s\S]*)\}$/;
500
486
  const arrowExpressionRegex = /^(?:\(([^)]*)\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\s*=>\s*([\s\S]+)$/;
@@ -528,13 +514,11 @@ function parseFunc(func) {
528
514
  };
529
515
  }
530
516
 
531
- //#endregion
532
- //#region src/Namespace.ts
517
+ // src/Namespace.ts
533
518
  function Namespace() {}
534
519
  Namespace.prototype = Object.create(null);
535
520
 
536
- //#endregion
537
- //#region src/requireNamespace.ts
521
+ // src/requireNamespace.ts
538
522
  function requireNamespace(root, namespace) {
539
523
  if (typeof root === "object") {
540
524
  if (typeof namespace === "string") return requireNamespace(root, namespace.split("."));
@@ -546,8 +530,7 @@ function requireNamespace(root, namespace) {
546
530
  }
547
531
  }
548
532
 
549
- //#endregion
550
- //#region src/expose.ts
533
+ // src/expose.ts
551
534
  /**
552
535
  * Expose a value to a potentially deeply nested global variable
553
536
  * @param name {string | object} Either a dot-separated path to the global variable, or an object to expose the value on
@@ -577,8 +560,7 @@ function expose(name, value, options = {}) {
577
560
  }
578
561
  }
579
562
 
580
- //#endregion
581
- //#region src/getLocalizedString.ts
563
+ // src/getLocalizedString.ts
582
564
  async function fetchLocalizedString(text, project) {
583
565
  try {
584
566
  let response = await fetch(`/api/user/localize/new/${encodeURIComponent(project)}`, {
@@ -667,8 +649,7 @@ function createLocalizer(project) {
667
649
  };
668
650
  }
669
651
 
670
- //#endregion
671
- //#region src/filter/operators.ts
652
+ // src/filter/operators.ts
672
653
  /**
673
654
  * Operators that take no value at all.
674
655
  *
@@ -682,6 +663,26 @@ const PARAMETERLESS_OPERATORS = [
682
663
  "isblank",
683
664
  "isnotblank"
684
665
  ];
666
+ /**
667
+ * Whole-period checks for dates, such as "Is last week", in the order of the
668
+ * Windows filter editor. They take no value. The framework has no operator for
669
+ * them, so a filter using one is sent as a filter string (`needsFilterString`).
670
+ */
671
+ const PERIOD_OPERATORS = [
672
+ "isbeyondthisyear",
673
+ "islaterthisyear",
674
+ "islaterthismonth",
675
+ "isnextweek",
676
+ "islaterthisweek",
677
+ "istomorrow",
678
+ "istoday",
679
+ "isyesterday",
680
+ "isearlierthisweek",
681
+ "islastweek",
682
+ "isearlierthismonth",
683
+ "isearlierthisyear",
684
+ "ispriorthisyear"
685
+ ];
685
686
  /** Operators whose value is a two element `[from, to]` array. */
686
687
  const PAIR_OPERATORS = [
687
688
  "between",
@@ -692,7 +693,7 @@ const PAIR_OPERATORS = [
692
693
  /** Operators whose value is an array of values. */
693
694
  const LIST_OPERATORS = ["inlist", "notinlist"];
694
695
  function getOperatorArity(operator) {
695
- if (PARAMETERLESS_OPERATORS.includes(operator)) return "none";
696
+ if (PARAMETERLESS_OPERATORS.includes(operator) || PERIOD_OPERATORS.includes(operator)) return "none";
696
697
  if (PAIR_OPERATORS.includes(operator)) return "pair";
697
698
  if (LIST_OPERATORS.includes(operator)) return "list";
698
699
  return "single";
@@ -735,7 +736,20 @@ const OPERATOR_LABELS = {
735
736
  datelessthan: "Before",
736
737
  datelessthanorequal: "Up to",
737
738
  datebetween: "Between",
738
- datenotbetween: "Not between"
739
+ datenotbetween: "Not between",
740
+ isbeyondthisyear: "Is beyond this year",
741
+ islaterthisyear: "Is later this year",
742
+ islaterthismonth: "Is later this month",
743
+ isnextweek: "Is next week",
744
+ islaterthisweek: "Is later this week",
745
+ istomorrow: "Is tomorrow",
746
+ istoday: "Is today",
747
+ isyesterday: "Is yesterday",
748
+ isearlierthisweek: "Is earlier this week",
749
+ islastweek: "Is last week",
750
+ isearlierthismonth: "Is earlier this month",
751
+ isearlierthisyear: "Is earlier this year",
752
+ ispriorthisyear: "Is prior to this year"
739
753
  };
740
754
  /** Comparison operator labels used for date and time fields. */
741
755
  const DATE_OPERATOR_LABELS = {
@@ -780,13 +794,15 @@ const OPERATORS_BY_TYPE = {
780
794
  ...EQUALITY,
781
795
  ...LIST_CHECKS,
782
796
  ...RANGE,
783
- ...COMPARISONS
797
+ ...COMPARISONS,
798
+ ...PERIOD_OPERATORS
784
799
  ],
785
800
  datetime: [
786
801
  ...EQUALITY,
787
802
  ...LIST_CHECKS,
788
803
  ...RANGE,
789
- ...COMPARISONS
804
+ ...COMPARISONS,
805
+ ...PERIOD_OPERATORS
790
806
  ],
791
807
  number: [
792
808
  ...EQUALITY,
@@ -859,7 +875,10 @@ function getValueType(field, operator) {
859
875
  const RELATIVE_DATE_VALUES = [
860
876
  "Yesterday",
861
877
  "Today",
878
+ "Now",
862
879
  "Tomorrow",
880
+ "DayAfterTomorrow",
881
+ "TwoWeeksAway",
863
882
  "ThisWeek",
864
883
  "ThisMonth",
865
884
  "ThisYear",
@@ -874,7 +893,10 @@ const DYNAMIC_VALUE_LABELS = {
874
893
  CurrentUser: "Current user",
875
894
  Yesterday: "Yesterday",
876
895
  Today: "Today",
896
+ Now: "Now",
877
897
  Tomorrow: "Tomorrow",
898
+ DayAfterTomorrow: "Day after tomorrow",
899
+ TwoWeeksAway: "Two weeks away",
878
900
  ThisWeek: "This week",
879
901
  ThisMonth: "This month",
880
902
  ThisYear: "This year",
@@ -910,6 +932,30 @@ function getDynamicValuesForField(field) {
910
932
  if (field.type === "string") return ["CurrentUser"];
911
933
  return [];
912
934
  }
935
+ /** Operators that can compare a field with another column. */
936
+ const COLUMN_OPERATORS = [
937
+ ...EQUALITY,
938
+ ...RANGE,
939
+ ...COMPARISONS
940
+ ];
941
+ /** Whether an operator can take another column as its value. */
942
+ function supportsColumnValue(operator) {
943
+ return COLUMN_OPERATORS.includes(operator);
944
+ }
945
+ /** Dates and datetimes compare with each other; other types only with their own. */
946
+ function getComparableType(type) {
947
+ return type === "datetime" ? "date" : type;
948
+ }
949
+ /**
950
+ * The columns a field can be compared with: the other fields of the same type,
951
+ * as in the Windows filter editor. Booleans have only true and false checks,
952
+ * so they get none.
953
+ */
954
+ function getColumnsForField(field, fields) {
955
+ if (field.type === "boolean") return [];
956
+ let type = getComparableType(field.type);
957
+ return fields.filter((other) => other.name !== field.name && !other.excludeFromFilter && getComparableType(other.type) === type);
958
+ }
913
959
  /**
914
960
  * Aliases for names `eOperators.ToString()` can produce that are not operators
915
961
  * in their own right. `Not` is the bare negation flag (i.e. "not equal to"),
@@ -933,8 +979,7 @@ function normalizeOperator(operator) {
933
979
  return OPERATOR_ALIASES[normalized] ?? normalized;
934
980
  }
935
981
 
936
- //#endregion
937
- //#region src/filter/tree.ts
982
+ // src/filter/tree.ts
938
983
  /**
939
984
  * Creates the default empty filter: a single `and` group with no conditions.
940
985
  */
@@ -1021,10 +1066,40 @@ function isCompleteExpression(item) {
1021
1066
  let arity = getOperatorArity(normalizeOperator(item.operator));
1022
1067
  if (arity === "none") return true;
1023
1068
  if (item.value === null || item.value === void 0 || item.value === "") return false;
1024
- if (arity === "pair") return Array.isArray(item.value) && item.value.length === 2 && item.value.every((v) => v !== "" && v != null);
1069
+ if (arity === "pair") return Array.isArray(item.value) && item.value.length === 2 && item.value.every((v) => {
1070
+ let end = isRangeEnd(v) ? v.value : v;
1071
+ return end !== "" && end != null;
1072
+ });
1025
1073
  if (arity === "list") return Array.isArray(item.value) && item.value.length > 0;
1026
1074
  return true;
1027
1075
  }
1076
+ /** Whether a range value end is a `FilterRangeEnd` rather than a plain value. */
1077
+ function isRangeEnd(value) {
1078
+ return typeof value === "object" && value !== null && !(value instanceof Date) && "valueType" in value;
1079
+ }
1080
+ /** Dynamic values the framework has no `eDynamicValue` member for. */
1081
+ const DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT = /* @__PURE__ */ new Set([
1082
+ "Now",
1083
+ "DayAfterTomorrow",
1084
+ "TwoWeeksAway"
1085
+ ]);
1086
+ function needsConversion(valueType, value) {
1087
+ if (valueType === "column") return true;
1088
+ return valueType === "special" && typeof value === "string" && DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT.has(value);
1089
+ }
1090
+ /**
1091
+ * True when the filter holds something the framework's data API cannot read
1092
+ * from a filter object: another column, a range end that is not a plain value,
1093
+ * one of the dates only the Windows editor offers, or a whole-period check. Such a filter must be
1094
+ * converted with `convertFilter` and sent as a filter string.
1095
+ */
1096
+ function needsFilterString(filter) {
1097
+ if (!filter) return false;
1098
+ if (isGroup(filter)) return filter.items.some(needsFilterString);
1099
+ if (PERIOD_OPERATORS.includes(normalizeOperator(filter.operator))) return true;
1100
+ if (needsConversion(filter.valueType, filter.value)) return true;
1101
+ return Array.isArray(filter.value) && filter.value.some((item) => isRangeEnd(item));
1102
+ }
1028
1103
  /**
1029
1104
  * Creates an expression for a field, using the field's default operator and an
1030
1105
  * empty value of the right shape.
@@ -1055,8 +1130,9 @@ function changeOperator(expression, operator, field) {
1055
1130
  let previousArity = getOperatorArity(normalizeOperator(expression.operator));
1056
1131
  let arity = getOperatorArity(operator);
1057
1132
  let value = expression.value;
1058
- if (arity !== previousArity) value = createEmptyValue(operator);
1059
- let valueType = expression.valueType === "special" && arity === previousArity ? "special" : getValueType(field, operator);
1133
+ let holdsColumn = expression.valueType === "column" || Array.isArray(value) && value.some((item) => isRangeEnd(item) && item.valueType === "column");
1134
+ if (arity !== previousArity || holdsColumn && !supportsColumnValue(operator)) value = createEmptyValue(operator);
1135
+ let valueType = (expression.valueType === "special" || expression.valueType === "column") && value === expression.value ? expression.valueType : getValueType(field, operator);
1060
1136
  return {
1061
1137
  ...expression,
1062
1138
  operator,
@@ -1122,8 +1198,7 @@ function addGroupAtPath(filter, path = []) {
1122
1198
  return insertNodeAtPath(filter, path, createEmptyGroup(parent && isGroup(parent) && parent.mode === "and" ? "or" : "and"));
1123
1199
  }
1124
1200
 
1125
- //#endregion
1126
- //#region src/filter/convertFilter.ts
1201
+ // src/filter/convertFilter.ts
1127
1202
  const CONVERT_URL = "/api/filter/convert";
1128
1203
  /**
1129
1204
  * Converts between a structured filter and a DevExpress filter string.
@@ -1148,8 +1223,7 @@ async function convertFilter(request, signal) {
1148
1223
  };
1149
1224
  }
1150
1225
 
1151
- //#endregion
1152
- //#region src/filter/describeFilter.ts
1226
+ // src/filter/describeFilter.ts
1153
1227
  /**
1154
1228
  * Builds a human-readable description of a filter, without contacting the
1155
1229
  * server. Use this for read-only summaries; the authoritative filter string is
@@ -1177,18 +1251,19 @@ function describeExpression(expression, fields) {
1177
1251
  let label = getOperatorLabel(operator, field?.type);
1178
1252
  let arity = getOperatorArity(operator);
1179
1253
  if (arity === "none") return `[${caption}] ${label}`;
1180
- return `[${caption}] ${label} ${formatValue(expression, arity)}`;
1254
+ return `[${caption}] ${label} ${formatValue(expression, arity, fields)}`;
1181
1255
  }
1182
- function formatValue(expression, arity) {
1256
+ function formatValue(expression, arity, fields) {
1183
1257
  let { value, valueType } = expression;
1184
- if (valueType === "special" && typeof value === "string") return getDynamicValueLabel(value);
1185
1258
  if (Array.isArray(value)) {
1186
- let formatted = value.map((item) => formatScalar(item, valueType));
1259
+ let formatted = value.map((item) => isRangeEnd(item) ? formatScalar(item.value, item.valueType, fields) : formatScalar(item, valueType, fields));
1187
1260
  return arity === "pair" ? formatted.join(" - ") : `(${formatted.join(", ")})`;
1188
1261
  }
1189
- return formatScalar(value, valueType);
1262
+ return formatScalar(value, valueType, fields);
1190
1263
  }
1191
- function formatScalar(value, valueType) {
1264
+ function formatScalar(value, valueType, fields) {
1265
+ if (valueType === "special" && typeof value === "string") return getDynamicValueLabel(value);
1266
+ if (valueType === "column" && typeof value === "string") return `[${fields.get(value.toLowerCase())?.caption ?? value}]`;
1192
1267
  if (value === null || value === void 0) return "";
1193
1268
  if (value instanceof Date) return value.toISOString().slice(0, 10);
1194
1269
  if (typeof value === "number" || typeof value === "boolean") return String(value);
@@ -1196,8 +1271,7 @@ function formatScalar(value, valueType) {
1196
1271
  return `'${String(value)}'`;
1197
1272
  }
1198
1273
 
1199
- //#endregion
1200
- //#region src/filter/fields.ts
1274
+ // src/filter/fields.ts
1201
1275
  const FIELD_TYPES = [
1202
1276
  "string",
1203
1277
  "number",
@@ -1232,8 +1306,7 @@ function toFieldMetadataList(records) {
1232
1306
  return records.map(toFieldMetadata).filter((field) => field.name !== "" && !field.excludeFromFilter).sort((a, b) => a.name.localeCompare(b.name));
1233
1307
  }
1234
1308
 
1235
- //#endregion
1236
- //#region src/filter/standardLayout.ts
1309
+ // src/filter/standardLayout.ts
1237
1310
  /**
1238
1311
  * The "standard layout" is the simplified shape the Windows filter editor can
1239
1312
  * render in its flow view: an outer `or` group whose items are `and` groups of
@@ -1315,8 +1388,7 @@ function collectExpressions(item, into) {
1315
1388
  for (let child of item.items) collectExpressions(child, into);
1316
1389
  }
1317
1390
 
1318
- //#endregion
1319
- //#region src/filter/savedFilters.ts
1391
+ // src/filter/savedFilters.ts
1320
1392
  /**
1321
1393
  * `sviw_Database_MyFilters` unions in a synthetic "No Filter" row so the Windows
1322
1394
  * tree can render a "no filter" entry. It is not a real filter and must be
@@ -1415,8 +1487,7 @@ const canDeleteSavedFilter = canRenameSavedFilter;
1415
1487
  */
1416
1488
  const canSetCrossDomain = canRenameSavedFilter;
1417
1489
 
1418
- //#endregion
1419
- //#region src/getUserImage.ts
1490
+ // src/getUserImage.ts
1420
1491
  const sizeMap = {
1421
1492
  xs: 64,
1422
1493
  sm: 128,
@@ -1431,8 +1502,7 @@ function getUserImage(primKeyOrOptions, login, hash, size = "xs") {
1431
1502
  return `/api/data/blob/EmployeesLookup/${primKeyOrOptions}/Image/${encodeURIComponent(login ?? "")}${login ? "." : ""}${encodeURIComponent(hash)}.jpg${sizePx ? `?width=${sizePx}` : ""}`;
1432
1503
  }
1433
1504
 
1434
- //#endregion
1435
- //#region src/localize.ts
1505
+ // src/localize.ts
1436
1506
  /**
1437
1507
  * Localization function for tagged templates. Replaces the arguments
1438
1508
  * with $n, where n is the index, before asking Appframe to translate
@@ -1456,8 +1526,7 @@ function localize(stringParts, ...args) {
1456
1526
  return localized;
1457
1527
  }
1458
1528
 
1459
- //#endregion
1460
- //#region src/report/recordSource.ts
1529
+ // src/report/recordSource.ts
1461
1530
  const XML_ENTITIES = {
1462
1531
  amp: "&",
1463
1532
  lt: "<",
@@ -1523,14 +1592,26 @@ function readSelectColumns(xml) {
1523
1592
  return columns;
1524
1593
  }
1525
1594
  /**
1595
+ * The record source a document holds. Parameter lookups store one `<afRecordSource>`; a report
1596
+ * stores its record sources as `<ArrayOfAfReportRecordSource>` with one `<afReportRecordSource>`
1597
+ * per data source, of which the one with the ID `Master` feeds the report (else the first).
1598
+ */
1599
+ function pickRecordSource(document) {
1600
+ if (!document) return null;
1601
+ let sources = [...document.matchAll(/<(afRecordSource|afReportRecordSource)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/g)].map((match) => match[2]);
1602
+ if (sources.length === 0) return null;
1603
+ return sources.find((source) => readElement(source, "ID") === "Master") ?? sources[0];
1604
+ }
1605
+ /**
1526
1606
  * Reads the parts of an `afRecordSource` a web client can act on: the view, the
1527
1607
  * selected columns and the design-time filter.
1528
1608
  *
1529
1609
  * Returns `null` for anything that is not a record source, so a missing or
1530
1610
  * empty `RecordSource` column simply means "no lookup".
1531
1611
  */
1532
- function parseRecordSource(xml) {
1533
- if (!xml || !xml.includes("<afRecordSource")) return null;
1612
+ function parseRecordSource(document) {
1613
+ let xml = pickRecordSource(document);
1614
+ if (!xml) return null;
1534
1615
  let viewName = readElement(xml, "ViewName");
1535
1616
  if (!viewName) return null;
1536
1617
  let maxRecords = readElement(xml, "MaxRecords");
@@ -1543,8 +1624,7 @@ function parseRecordSource(xml) {
1543
1624
  };
1544
1625
  }
1545
1626
 
1546
- //#endregion
1547
- //#region src/report/parameters.ts
1627
+ // src/report/parameters.ts
1548
1628
  /**
1549
1629
  * Report parameters: mapping the metadata records onto something renderable,
1550
1630
  * and the XML the parameter values are stored as on a saved filter.
@@ -1697,8 +1777,7 @@ function restoreParameterValues(parameters, xml) {
1697
1777
  return values;
1698
1778
  }
1699
1779
 
1700
- //#endregion
1701
- //#region src/report/parameterDefaults.ts
1780
+ // src/report/parameterDefaults.ts
1702
1781
  /**
1703
1782
  * Resolving report parameter defaults that name a stored procedure.
1704
1783
  *
@@ -1760,8 +1839,7 @@ function firstValue(result) {
1760
1839
  return row;
1761
1840
  }
1762
1841
 
1763
- //#endregion
1764
- //#region src/report/render.ts
1842
+ // src/report/render.ts
1765
1843
  /**
1766
1844
  * Rendering a report through the framework's report route.
1767
1845
  *
@@ -1965,8 +2043,7 @@ function toParameterEntries(parameters) {
1965
2043
  }));
1966
2044
  }
1967
2045
 
1968
- //#endregion
1969
- //#region src/report/report.ts
2046
+ // src/report/report.ts
1970
2047
  /**
1971
2048
  * Mapping a `sviw_WinClient_MyReports` record onto the report model, including
1972
2049
  * which output formats the report offers.
@@ -2010,8 +2087,7 @@ function toReport(record) {
2010
2087
  };
2011
2088
  }
2012
2089
 
2013
- //#endregion
2014
- //#region src/alert.ts
2090
+ // src/alert.ts
2015
2091
  const template = `
2016
2092
  <dialog class="alert-dialog">
2017
2093
  <form method="dialog">
@@ -2122,8 +2198,7 @@ async function alert(prompt, options = {}) {
2122
2198
  });
2123
2199
  }
2124
2200
 
2125
- //#endregion
2126
- //#region src/settings.ts
2201
+ // src/settings.ts
2127
2202
  function getKey(article) {
2128
2203
  return `${article ?? window.af?.article?.id ?? window.window?.location.pathname.substring(1).split("/")[0]}.${window.af?.userSession.login ?? "anonymous"}`;
2129
2204
  }
@@ -2143,9 +2218,7 @@ function saveSettings(setting, value, article) {
2143
2218
  localStorage.setItem(key, JSON.stringify(userSettings));
2144
2219
  }
2145
2220
 
2146
- //#endregion
2147
- //#region src/index.ts
2148
- const version = "2.15.2";
2221
+ // src/index.ts
2222
+ const version = "2.16.1";
2149
2223
 
2150
- //#endregion
2151
- export { ConfirmButtons, NO_FILTER_ID, RELATIVE_DATE_VALUES, SessionHandler, UserSession, addGroupAtPath, alert, canDeleteSavedFilter, canEditSavedFilter, canRenameSavedFilter, canSetCrossDomain, canShareSavedFilter, changeOperator, coerceParameterValue, coerceToStandardLayout, confirm, convertFilter, countExpressions, createEmptyFilter, createEmptyGroup, createExpression, createLocalizer, decodeXml, describeFilter, downloadBlob, downloadReport, downloadRequest, encodeXml, expose, fetchParameterDefault, findSavedFilter, getDefaultOperator, getDefaultParameterValues, getDynamicValueLabel, getDynamicValuesForField, getLocalizedString, getNodeAtPath, getOperatorArity, getOperatorLabel, getOperatorsForField, getReportExtension, getReportFileName, getReportFormats, getReportMediaType, getReportRequest, getSessionData, getSettings, getUserImage, getValueType, insertNodeAtPath, invariant, isCompleteExpression, isEmptyFilter, isExpression, isGroup, isProcedureDefault, isStandardLayout, localStorage_default as localStorage, localize, logActivity, logError, mitt_default as mitt, normalizeOperator, openAlertDialog, openReport, parseParameterItems, parseParameters, parseRecordSource, partitionSavedFilters, printReport, pruneFilter, readElement, readElements, removeNodeAtPath, renderReport, resolveParameterDefaults, restoreParameterValues, saveSettings, serializeParameters, setGroupMode, startActivity, time, toFieldMetadata, toFieldMetadataList, toFilterFieldType, toFilterTree, toLocalDateTime, toLocalISODate, toLocalISODateTime, toReport, toReportParameter, toReportParameters, toSavedFilter, toSavedFilters, unwrapSingleAndGroup, updateNodeAtPath, version, waitForDialog };
2224
+ export { ConfirmButtons, NO_FILTER_ID, PERIOD_OPERATORS, RELATIVE_DATE_VALUES, SessionHandler, UserSession, addGroupAtPath, alert, canDeleteSavedFilter, canEditSavedFilter, canRenameSavedFilter, canSetCrossDomain, canShareSavedFilter, changeOperator, coerceParameterValue, coerceToStandardLayout, confirm, convertFilter, countExpressions, createEmptyFilter, createEmptyGroup, createExpression, createLocalizer, decodeXml, describeFilter, downloadBlob, downloadReport, downloadRequest, encodeXml, expose, fetchParameterDefault, findSavedFilter, getColumnsForField, getDefaultOperator, getDefaultParameterValues, getDynamicValueLabel, getDynamicValuesForField, getLocalizedString, getNodeAtPath, getOperatorArity, getOperatorLabel, getOperatorsForField, getReportExtension, getReportFileName, getReportFormats, getReportMediaType, getReportRequest, getSessionData, getSettings, getUserImage, getValueType, insertNodeAtPath, invariant, isCompleteExpression, isEmptyFilter, isExpression, isGroup, isProcedureDefault, isRangeEnd, isStandardLayout, localStorage_default as localStorage, localize, logActivity, logError, mitt_default as mitt, needsFilterString, normalizeOperator, openAlertDialog, openReport, parseParameterItems, parseParameters, parseRecordSource, partitionSavedFilters, printReport, pruneFilter, readElement, readElements, removeNodeAtPath, renderReport, resolveParameterDefaults, restoreParameterValues, saveSettings, serializeParameters, setGroupMode, startActivity, supportsColumnValue, time, toFieldMetadata, toFieldMetadataList, toFilterFieldType, toFilterTree, toLocalDateTime, toLocalISODate, toLocalISODateTime, toReport, toReportParameter, toReportParameters, toSavedFilter, toSavedFilters, unwrapSingleAndGroup, updateNodeAtPath, version, waitForDialog };