@zestl/zvolv-widgets 0.0.0 → 0.0.2

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.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import React13, { createContext, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
1
+ import React14, { createContext, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import dayjs8 from 'dayjs';
3
+ import dayjs4 from 'dayjs';
4
4
  import utc from 'dayjs/plugin/utc.js';
5
5
  import weekOfYear from 'dayjs/plugin/weekOfYear.js';
6
6
  import _ from 'lodash';
7
+ import timezone2 from 'dayjs/plugin/timezone.js';
7
8
 
8
9
  // src/core/ZvolvProvider.tsx
9
10
  var RuntimeContext = createContext(null);
@@ -36,9 +37,47 @@ function getRuntimeClient() {
36
37
  if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
37
38
  return c;
38
39
  }
40
+ var sdk = {};
41
+ try {
42
+ import('@zestl/zvolv-js-sdk').then((m) => {
43
+ sdk = m ?? {};
44
+ }).catch(() => {
45
+ });
46
+ } catch {
47
+ }
48
+ var AUTH_CRYPT_READY = "zvAuthCryptReady";
49
+ var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
50
+ var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
51
+ function stripUndecrypted(node) {
52
+ if (Array.isArray(node)) return node.map(stripUndecrypted);
53
+ if (node && typeof node === "object") {
54
+ const out = {};
55
+ for (const [k, v] of Object.entries(node)) {
56
+ if (v && typeof v === "object") out[k] = stripUndecrypted(v);
57
+ else if (!isEnc(v)) out[k] = v;
58
+ }
59
+ return out;
60
+ }
61
+ return isEnc(node) ? void 0 : node;
62
+ }
39
63
  function getStoredUser() {
40
64
  try {
41
- return userReader() ?? clientGetter()?.auth?.userInstance ?? null;
65
+ let u = userReader() ?? null;
66
+ if (!u) {
67
+ try {
68
+ u = sdk.readUserFromStorage?.() ?? null;
69
+ } catch {
70
+ u = null;
71
+ }
72
+ }
73
+ if (!u) u = clientGetter()?.auth?.userInstance ?? null;
74
+ if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
75
+ try {
76
+ u = sdk.decryptAuthObject(u);
77
+ } catch {
78
+ }
79
+ }
80
+ return u ? stripUndecrypted(u) : null;
42
81
  } catch {
43
82
  return null;
44
83
  }
@@ -79,8 +118,8 @@ var TASK_DATE_FIELDS = [
79
118
  "completeDate"
80
119
  ];
81
120
  new Set(TASK_DATE_FIELDS.map((f) => f.toLowerCase()));
82
- dayjs8.extend(utc);
83
- dayjs8.extend(weekOfYear);
121
+ dayjs4.extend(utc);
122
+ dayjs4.extend(weekOfYear);
84
123
  function humanizeDurationToMinutes(value) {
85
124
  if (!value) return 0;
86
125
  let total = 0;
@@ -124,60 +163,60 @@ function getDataType(elementType) {
124
163
  }
125
164
  function getDateRange(ops) {
126
165
  const values = [];
127
- const fmt = "YYYY-MM-DD";
128
- const today = dayjs8();
166
+ const fmt2 = "YYYY-MM-DD";
167
+ const today = dayjs4();
129
168
  switch (ops) {
130
169
  case "TODAY":
131
- values[0] = today.format(fmt);
132
- values[1] = today.format(fmt);
170
+ values[0] = today.format(fmt2);
171
+ values[1] = today.format(fmt2);
133
172
  break;
134
173
  case "LAST_SEVEN_DAYS":
135
- values[0] = today.subtract(7, "day").format(fmt);
136
- values[1] = today.add(1, "day").format(fmt);
174
+ values[0] = today.subtract(7, "day").format(fmt2);
175
+ values[1] = today.add(1, "day").format(fmt2);
137
176
  break;
138
177
  case "LAST_THIRTY_DAYS":
139
- values[0] = today.subtract(30, "day").format(fmt);
140
- values[1] = today.add(1, "day").format(fmt);
178
+ values[0] = today.subtract(30, "day").format(fmt2);
179
+ values[1] = today.add(1, "day").format(fmt2);
141
180
  break;
142
181
  case "THIS_WEEK":
143
- values[0] = today.startOf("week").format(fmt);
144
- values[1] = today.endOf("week").format(fmt);
182
+ values[0] = today.startOf("week").format(fmt2);
183
+ values[1] = today.endOf("week").format(fmt2);
145
184
  break;
146
185
  case "THIS_MONTH":
147
- values[0] = today.startOf("month").format(fmt);
148
- values[1] = today.endOf("month").format(fmt);
186
+ values[0] = today.startOf("month").format(fmt2);
187
+ values[1] = today.endOf("month").format(fmt2);
149
188
  break;
150
189
  case "THIS_YEAR":
151
- values[0] = today.startOf("year").format(fmt);
152
- values[1] = today.endOf("year").format(fmt);
190
+ values[0] = today.startOf("year").format(fmt2);
191
+ values[1] = today.endOf("year").format(fmt2);
153
192
  break;
154
193
  case "THIS_FINANCIAL_YEAR": {
155
194
  const m = today.month();
156
195
  const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
157
196
  const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
158
- values[0] = fiscalStart.format(fmt);
159
- values[1] = fiscalEnd.format(fmt);
197
+ values[0] = fiscalStart.format(fmt2);
198
+ values[1] = fiscalEnd.format(fmt2);
160
199
  break;
161
200
  }
162
201
  case "NEXT_THREE_DAYS":
163
- values[0] = today.format(fmt);
164
- values[1] = today.add(4, "day").format(fmt);
202
+ values[0] = today.format(fmt2);
203
+ values[1] = today.add(4, "day").format(fmt2);
165
204
  break;
166
205
  case "NEXT_SEVEN_DAYS":
167
- values[0] = today.format(fmt);
168
- values[1] = today.add(8, "day").format(fmt);
206
+ values[0] = today.format(fmt2);
207
+ values[1] = today.add(8, "day").format(fmt2);
169
208
  break;
170
209
  case "NEXT_WEEK":
171
- values[0] = today.add(1, "week").startOf("week").format(fmt);
172
- values[1] = today.add(1, "week").endOf("week").format(fmt);
210
+ values[0] = today.add(1, "week").startOf("week").format(fmt2);
211
+ values[1] = today.add(1, "week").endOf("week").format(fmt2);
173
212
  break;
174
213
  case "NEXT_FIFTEEN_DAYS":
175
- values[0] = today.format(fmt);
176
- values[1] = today.add(16, "day").format(fmt);
214
+ values[0] = today.format(fmt2);
215
+ values[1] = today.add(16, "day").format(fmt2);
177
216
  break;
178
217
  case "NEXT_THIRTY_DAYS":
179
- values[0] = today.format(fmt);
180
- values[1] = today.add(30, "day").format(fmt);
218
+ values[0] = today.format(fmt2);
219
+ values[1] = today.add(30, "day").format(fmt2);
181
220
  break;
182
221
  case "THIS_QUARTER": {
183
222
  const m = today.month();
@@ -195,8 +234,8 @@ function getDateRange(ops) {
195
234
  qStart = today.startOf("year").month(9).date(1);
196
235
  qEnd = today.startOf("year").month(11).date(31);
197
236
  }
198
- values[0] = qStart.format(fmt);
199
- values[1] = qEnd.format(fmt);
237
+ values[0] = qStart.format(fmt2);
238
+ values[1] = qEnd.format(fmt2);
200
239
  break;
201
240
  }
202
241
  }
@@ -277,7 +316,7 @@ var resolveQueryUser = () => {
277
316
  };
278
317
 
279
318
  // src/runtime/queryGenerator.ts
280
- dayjs8.extend(utc);
319
+ dayjs4.extend(utc);
281
320
  var mappingsCache = /* @__PURE__ */ new Map();
282
321
  function getCachedAnalyticsMappings(indexKey) {
283
322
  let pending2 = mappingsCache.get(indexKey);
@@ -429,7 +468,7 @@ function resolveDateMath(value) {
429
468
  if (typeof value !== "string") return null;
430
469
  const match = dateMathPattern.exec(value.trim());
431
470
  if (!match) return null;
432
- let d = dayjs8();
471
+ let d = dayjs4();
433
472
  const [, amountStr, amountUnit, , roundUnit] = match;
434
473
  if (amountStr && amountUnit) {
435
474
  d = d.add(parseInt(amountStr, 10), dateMathUnit[amountUnit]);
@@ -443,56 +482,56 @@ function isDateMath(value) {
443
482
  return typeof value === "string" && dateMathPattern.test(value.trim());
444
483
  }
445
484
  function getDate(dateFormula) {
446
- const fmt = "YYYY-MM-DDTHH:mm:ssZ";
447
- const d = dayjs8();
485
+ const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
486
+ const d = dayjs4();
448
487
  switch (dateFormula) {
449
488
  case "{@[TODAY]}":
450
- return d.startOf("day").format(fmt);
489
+ return d.startOf("day").format(fmt2);
451
490
  case "{@[3DAYSLATER]}":
452
- return d.add(3, "day").startOf("day").format(fmt);
491
+ return d.add(3, "day").startOf("day").format(fmt2);
453
492
  case "{@[7DAYSLATER]}":
454
- return d.add(7, "day").startOf("day").format(fmt);
493
+ return d.add(7, "day").startOf("day").format(fmt2);
455
494
  case "{@[14DAYSLATER]}":
456
- return d.add(14, "day").startOf("day").format(fmt);
495
+ return d.add(14, "day").startOf("day").format(fmt2);
457
496
  case "{@[15DAYSLATER]}":
458
- return d.add(15, "day").startOf("day").format(fmt);
497
+ return d.add(15, "day").startOf("day").format(fmt2);
459
498
  case "{@[21DAYSLATER]}":
460
- return d.add(21, "day").startOf("day").format(fmt);
499
+ return d.add(21, "day").startOf("day").format(fmt2);
461
500
  case "{@[28DAYSLATER]}":
462
- return d.add(28, "day").startOf("day").format(fmt);
501
+ return d.add(28, "day").startOf("day").format(fmt2);
463
502
  case "{@[30DAYSLATER]}":
464
- return d.add(30, "day").startOf("day").format(fmt);
503
+ return d.add(30, "day").startOf("day").format(fmt2);
465
504
  case "{@[60DAYSLATER]}":
466
- return d.add(60, "day").startOf("day").format(fmt);
505
+ return d.add(60, "day").startOf("day").format(fmt2);
467
506
  case "{@[90DAYSLATER]}":
468
- return d.add(90, "day").startOf("day").format(fmt);
507
+ return d.add(90, "day").startOf("day").format(fmt2);
469
508
  case "{@[180DAYSLATER]}":
470
- return d.add(180, "day").startOf("day").format(fmt);
509
+ return d.add(180, "day").startOf("day").format(fmt2);
471
510
  case "{@[365DAYSLATER]}":
472
- return d.add(365, "day").startOf("day").format(fmt);
511
+ return d.add(365, "day").startOf("day").format(fmt2);
473
512
  case "{@[3DAYSAGO]}":
474
- return d.subtract(3, "day").startOf("day").format(fmt);
513
+ return d.subtract(3, "day").startOf("day").format(fmt2);
475
514
  case "{@[7DAYSAGO]}":
476
- return d.subtract(7, "day").startOf("day").format(fmt);
515
+ return d.subtract(7, "day").startOf("day").format(fmt2);
477
516
  case "{@[14DAYSAGO]}":
478
- return d.subtract(14, "day").startOf("day").format(fmt);
517
+ return d.subtract(14, "day").startOf("day").format(fmt2);
479
518
  case "{@[15DAYSAGO]}":
480
- return d.subtract(15, "day").startOf("day").format(fmt);
519
+ return d.subtract(15, "day").startOf("day").format(fmt2);
481
520
  case "{@[21DAYSAGO]}":
482
- return d.subtract(21, "day").startOf("day").format(fmt);
521
+ return d.subtract(21, "day").startOf("day").format(fmt2);
483
522
  case "{@[28DAYSAGO]}":
484
- return d.subtract(28, "day").startOf("day").format(fmt);
523
+ return d.subtract(28, "day").startOf("day").format(fmt2);
485
524
  case "{@[30DAYSAGO]}":
486
- return d.subtract(20, "day").startOf("day").format(fmt);
525
+ return d.subtract(20, "day").startOf("day").format(fmt2);
487
526
  // note: original uses 20
488
527
  case "{@[60DAYSAGO]}":
489
- return d.subtract(60, "day").startOf("day").format(fmt);
528
+ return d.subtract(60, "day").startOf("day").format(fmt2);
490
529
  case "{@[90DAYSAGO]}":
491
- return d.subtract(90, "day").startOf("day").format(fmt);
530
+ return d.subtract(90, "day").startOf("day").format(fmt2);
492
531
  case "{@[180DAYSAGO]}":
493
- return d.subtract(180, "day").startOf("day").format(fmt);
532
+ return d.subtract(180, "day").startOf("day").format(fmt2);
494
533
  case "{@[365DAYSAGO]}":
495
- return d.subtract(365, "day").startOf("day").format(fmt);
534
+ return d.subtract(365, "day").startOf("day").format(fmt2);
496
535
  default:
497
536
  return dateFormula;
498
537
  }
@@ -572,8 +611,8 @@ function getFormula(formula, fMap, isFilter = false) {
572
611
  }
573
612
  }
574
613
  });
575
- formula.format.split("%s").forEach((text, i) => {
576
- textFormat += i === 0 ? `'${text}'` : ` + '${text}'`;
614
+ formula.format.split("%s").forEach((text2, i) => {
615
+ textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
577
616
  if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
578
617
  });
579
618
  script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
@@ -641,7 +680,7 @@ function getFilters(field2, type, loggedInUser = null, isTask = false) {
641
680
  field2.filter.operand = field2.filter.operand.map((oprd) => {
642
681
  if (isDateMath(oprd)) return oprd;
643
682
  const pattern = dateFieldPattern[field2.fieldFormat] ?? field2.fieldFormat;
644
- return dayjs8(oprd, pattern).format("YYYY-MM-DD");
683
+ return dayjs4(oprd, pattern).format("YYYY-MM-DD");
645
684
  });
646
685
  }
647
686
  if (field2["taskfield"] && !field2["masterFieldDataType"]) {
@@ -926,6 +965,142 @@ function getTaskElementSort(dataSource, fieldMetaByName) {
926
965
  }
927
966
  return null;
928
967
  }
968
+ function pivotEsField(entry) {
969
+ return entry?.masterField || entry?.title;
970
+ }
971
+ var HISTOGRAM_INTERVALS = {
972
+ day: "1d",
973
+ week: "1w",
974
+ month: "1M",
975
+ quarter: "1q",
976
+ year: "1y"
977
+ };
978
+ function getDateHistogramSource(row, esField, mfType, meta, sortOrder) {
979
+ const isTaskDate = taskDate.includes(esField) && !!(row.isTaskField || row.taskfield || meta?.isTaskField || meta?.taskfield);
980
+ if (!dateTypes.includes(mfType) && !isTaskDate) {
981
+ console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": interval "${row.interval}" ignored \u2014 "${esField}" is not a date field.`);
982
+ return null;
983
+ }
984
+ const calendarInterval = HISTOGRAM_INTERVALS[row.interval];
985
+ if (!calendarInterval) {
986
+ console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": unknown interval "${row.interval}" \u2014 use day | week | month | quarter | year.`);
987
+ return null;
988
+ }
989
+ const histogram = { field: esField, calendar_interval: calendarInterval, time_zone: row.timeZone || timezone };
990
+ if (sortOrder) histogram.order = sortOrder;
991
+ return { date_histogram: histogram };
992
+ }
993
+ var summaryFilterLists = /* @__PURE__ */ new Map();
994
+ var DATE_DIFF_DIVISOR = {
995
+ days: "86400000.0",
996
+ hours: "3600000.0",
997
+ minutes: "60000.0"
998
+ };
999
+ var LITERAL_DATE = /^\d{4}-\d{2}-\d{2}([T ][\d:.]+(Z|[+-]\d{2}:?\d{2})?)?$/;
1000
+ function getDateDiffFixedEnd(end) {
1001
+ if (typeof end !== "string") return null;
1002
+ if (dateFormulas.includes(end)) return dayjs4(getDate(end)).valueOf();
1003
+ if (LITERAL_DATE.test(end.trim())) {
1004
+ const d = dayjs4(end.trim());
1005
+ if (d.isValid()) return d.valueOf();
1006
+ }
1007
+ if (/^\{@\[.*\]\}$/.test(end)) {
1008
+ console.warn(`[@zestl/zvolv-widgets] dateDiff end "${end}" is not a known date token (${dateFormulas.join(", ")}); it is being queried as a field name.`);
1009
+ }
1010
+ return null;
1011
+ }
1012
+ function getDateDiffScript(dd, fixedEnd) {
1013
+ const divisor = DATE_DIFF_DIVISOR[dd.unit] ?? DATE_DIFF_DIVISOR.days;
1014
+ if (fixedEnd != null) {
1015
+ return {
1016
+ source: `if(doc['${dd.start}'].size()==0) { return null; } return (params.now - doc['${dd.start}'].value.millis) / ${divisor};`,
1017
+ lang: "painless",
1018
+ params: { now: fixedEnd }
1019
+ };
1020
+ }
1021
+ return {
1022
+ source: `if(doc['${dd.start}'].size()==0 || doc['${dd.end}'].size()==0) { return null; } return (doc['${dd.end}'].value.millis - doc['${dd.start}'].value.millis) / ${divisor};`,
1023
+ lang: "painless"
1024
+ };
1025
+ }
1026
+ function getSummaryFilterClause(sumary, f, loggedInUser) {
1027
+ if (f.operator === "_ALL" || f.operator === "MULTIPLE") return null;
1028
+ const fb = {
1029
+ header: sumary.title,
1030
+ masterfield: f.operand ?? f.masterField,
1031
+ masterFieldDataType: f.masterFieldDataType,
1032
+ masterFieldType: f.masterFieldType,
1033
+ filter: { operator: f.operator, operand: Array.isArray(f.value) ? [...f.value] : f.value }
1034
+ };
1035
+ return getFilters(fb, "PIVOT", loggedInUser) ?? null;
1036
+ }
1037
+ function buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys = {}) {
1038
+ const name = sumary.value;
1039
+ const list = summaryFilterLists.get(name) ?? [];
1040
+ const andGroup = list.length > 1 && list.some((f) => f?.combine === "AND");
1041
+ const isPercentiles = sumary.type === "percentiles";
1042
+ if (!sumary.dateDiff && !andGroup && !(isPercentiles && sumary.percents?.length)) return false;
1043
+ const must = [];
1044
+ const mustNot = [];
1045
+ const conditions = andGroup ? list : list.slice(-1);
1046
+ for (const f of conditions) {
1047
+ const clause = getSummaryFilterClause(sumary, f, loggedInUser);
1048
+ if (!clause) continue;
1049
+ if (mustNotOp.includes(f.operator)) mustNot.push({ ...clause });
1050
+ else must.push(clause);
1051
+ }
1052
+ let metric;
1053
+ if (sumary.dateDiff) {
1054
+ const dd = sumary.dateDiff;
1055
+ const fixedEnd = getDateDiffFixedEnd(dd.end);
1056
+ must.push({ exists: { field: dd.start } });
1057
+ if (fixedEnd == null) must.push({ exists: { field: dd.end } });
1058
+ metric = { script: getDateDiffScript(dd, fixedEnd) };
1059
+ } else {
1060
+ const esField = pivotEsField(sumary);
1061
+ let aggType = sumary.type;
1062
+ let aggField = esField;
1063
+ const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary);
1064
+ if (sumary.masterFieldType === "FORM_SEARCH" && (aggType === "value_count" || aggType === "cardinality")) {
1065
+ aggField = resolveFormSearchViewField(esField);
1066
+ } else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
1067
+ aggField = `${esField}.mediaKey.keyword`;
1068
+ } else if (isTextField) {
1069
+ if (["sum", "avg", "min", "max"].includes(aggType)) aggType = "value_count";
1070
+ aggField = `${esField}.keyword`;
1071
+ }
1072
+ metric = { field: aggField };
1073
+ finish(name, summaryAggs, must, mustNot, { [aggType]: metric });
1074
+ if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
1075
+ if (!isPercentiles && aggField === esField && sortKeys[esField]) {
1076
+ summaryAggs[`${name}_sort`] = { bucket_sort: { sort: [{ [name]: { order: sortKeys[esField] } }] } };
1077
+ }
1078
+ return true;
1079
+ }
1080
+ if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
1081
+ return finish(name, summaryAggs, must, mustNot, { [sumary.type]: metric });
1082
+ }
1083
+ function finish(name, summaryAggs, must, mustNot, metric) {
1084
+ if (must.length === 0 && mustNot.length === 0) {
1085
+ summaryAggs[name] = metric;
1086
+ return true;
1087
+ }
1088
+ const bool = {};
1089
+ if (must.length) bool.must = must;
1090
+ if (mustNot.length) bool.must_not = mustNot;
1091
+ summaryAggs[name] = { filter: { bool }, aggs: { [name]: metric } };
1092
+ return true;
1093
+ }
1094
+ function groupPivotFilters(pivotFilters) {
1095
+ const out = /* @__PURE__ */ new Map();
1096
+ (pivotFilters || []).forEach((f) => {
1097
+ if (!f) return;
1098
+ const list = out.get(f.field) ?? [];
1099
+ list.push(f);
1100
+ out.set(f.field, list);
1101
+ });
1102
+ return out;
1103
+ }
929
1104
  function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e4, scriptFields = {}, loggedInUser = null, sort = [], fieldMetaByName) {
930
1105
  const composite = { size, sources: [] };
931
1106
  pivotBody.rows.forEach((row) => {
@@ -947,17 +1122,21 @@ function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e
947
1122
  const meta = fieldMetaByName?.get(row.title) || fieldMetaByName?.get(row.masterField) || {};
948
1123
  const mfType = row.masterFieldType ?? meta.masterFieldType;
949
1124
  const mfDataType = row.masterFieldDataType ?? meta.masterFieldDataType ?? (mfType ? getDataType(mfType) : void 0);
1125
+ const esField = pivotEsField(row);
950
1126
  const sourceObj = {};
951
- if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
952
- sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(row.title) } };
1127
+ const histogram = row.interval ? getDateHistogramSource(row, esField, mfType, meta, sortOrder) : null;
1128
+ if (histogram) {
1129
+ sourceObj[row.title] = histogram;
1130
+ } else if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
1131
+ sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(esField) } };
953
1132
  } else if (mfType === "FILE_UPLOAD_V2") {
954
- sourceObj[row.title] = { terms: { field: `${row.title}.mediaKey.keyword` } };
1133
+ sourceObj[row.title] = { terms: { field: `${esField}.mediaKey.keyword` } };
955
1134
  } else if (mfDataType === "STRING" && !dateTypes.includes(mfType) && !isNumericTaskField(row, fieldMetaByName)) {
956
- sourceObj[row.title] = { terms: { field: `${row.title}.keyword` } };
1135
+ sourceObj[row.title] = { terms: { field: `${esField}.keyword` } };
957
1136
  } else if (!mfDataType && Object.keys(scriptFields).length > 0) {
958
1137
  sourceObj[row.title] = { terms: scriptFields[row.title] };
959
1138
  } else {
960
- sourceObj[row.title] = { terms: { field: row.title } };
1139
+ sourceObj[row.title] = { terms: { field: esField } };
961
1140
  if (sortOrder) sourceObj[row.title]["terms"]["order"] = sortOrder;
962
1141
  }
963
1142
  composite.sources.push(sourceObj);
@@ -975,7 +1154,7 @@ function getColumnAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser
975
1154
  const colFilter = getFilters(fb, "PIVOT", loggedInUser);
976
1155
  if (colFilter) colFilters.push(colFilter);
977
1156
  }
978
- colAggs[col.title] = { terms: { field: col.title } };
1157
+ colAggs[col.title] = { terms: { field: pivotEsField(col) } };
979
1158
  if (i === pivotBody.columns.length - 1) {
980
1159
  const summaryResult = getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser, fMap);
981
1160
  colAggs[col.title]["aggs"] = summaryResult.summaryAggs;
@@ -1008,6 +1187,7 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1008
1187
  pivotBody.summary.forEach((sumary) => {
1009
1188
  const filter = filterMap.get(sumary.value);
1010
1189
  aggDataPoints["summary"].push(sumary.value);
1190
+ if (buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys)) return;
1011
1191
  summaryAggs[sumary.value] = {};
1012
1192
  const addBucketSort = (key) => {
1013
1193
  if (sortKeys[key]) {
@@ -1015,17 +1195,18 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1015
1195
  }
1016
1196
  };
1017
1197
  const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary, fMap);
1198
+ const esField = pivotEsField(sumary);
1018
1199
  let aggType = sumary.type;
1019
- let aggField = sumary.title;
1200
+ let aggField = esField;
1020
1201
  if (sumary.masterFieldType === "FORM_SEARCH" && (sumary.type === "value_count" || sumary.type === "cardinality")) {
1021
- aggField = resolveFormSearchViewField(sumary.title);
1202
+ aggField = resolveFormSearchViewField(esField);
1022
1203
  } else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
1023
- aggField = `${sumary.title}.mediaKey.keyword`;
1204
+ aggField = `${esField}.mediaKey.keyword`;
1024
1205
  } else if (isTextField) {
1025
1206
  if (["sum", "avg", "min", "max"].includes(sumary.type)) aggType = "value_count";
1026
- aggField = `${sumary.title}.keyword`;
1207
+ aggField = `${esField}.keyword`;
1027
1208
  }
1028
- const usesRawField = aggField === sumary.title;
1209
+ const usesRawField = aggField === esField;
1029
1210
  if (filter) {
1030
1211
  const fb = {
1031
1212
  header: sumary.title,
@@ -1043,10 +1224,10 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1043
1224
  }
1044
1225
  summaryAggs[sumary.value]["aggs"] = { [sumary.value]: {} };
1045
1226
  summaryAggs[sumary.value]["aggs"][sumary.value][aggType] = { field: aggField };
1046
- if (usesRawField) addBucketSort(sumary.title);
1227
+ if (usesRawField) addBucketSort(esField);
1047
1228
  } else {
1048
1229
  summaryAggs[sumary.value][aggType] = { field: aggField };
1049
- if (usesRawField) addBucketSort(sumary.title);
1230
+ if (usesRawField) addBucketSort(esField);
1050
1231
  }
1051
1232
  });
1052
1233
  return { summaryAggs, aggDataPoints };
@@ -1056,6 +1237,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
1056
1237
  scriptFilters = [];
1057
1238
  fieldMap = /* @__PURE__ */ new Map();
1058
1239
  currentMappings = mappings;
1240
+ summaryFilterLists = /* @__PURE__ */ new Map();
1059
1241
  let staticQueryClause = null;
1060
1242
  const rawDataSourceQuery = dataSource?.query;
1061
1243
  if (rawDataSourceQuery && typeof rawDataSourceQuery === "string") {
@@ -1148,6 +1330,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
1148
1330
  if (dataSource?.pivot.filters?.length > 0) {
1149
1331
  dataSource?.pivot.filters.forEach((f) => filterMap2.set(f.field, f));
1150
1332
  }
1333
+ summaryFilterLists = groupPivotFilters(dataSource?.pivot.filters);
1151
1334
  if (!isDrilldownView() && dataSource?.displayRowLimit && dataSource?.displayRowLimit !== "") {
1152
1335
  size = dataSource?.displayRowLimit;
1153
1336
  }
@@ -1655,7 +1838,7 @@ var resolveUserLabels = async (values, kind) => {
1655
1838
  // src/runtime/index.ts
1656
1839
  function createZvolvRuntime({ client, readUser }) {
1657
1840
  const getClient = typeof client === "function" ? client : () => client;
1658
- setRuntimeClient(getClient, readUser ?? (() => getClient()?.auth?.userInstance));
1841
+ setRuntimeClient(getClient, readUser ?? (() => null));
1659
1842
  resetQueryUser();
1660
1843
  return {
1661
1844
  get client() {
@@ -1725,16 +1908,29 @@ function taskField(header, masterFieldType = "INPUT_TEXT", options = {}) {
1725
1908
  ...options
1726
1909
  };
1727
1910
  }
1728
- function groupBy(f, title = f.header) {
1911
+ function groupBy(f, title = f.header, options = {}) {
1729
1912
  return {
1730
1913
  title,
1731
1914
  masterField: f.masterfield ?? f.taskfield ?? f.header,
1732
1915
  masterFieldType: f.masterFieldType,
1733
- masterFieldDataType: f.masterFieldDataType
1916
+ masterFieldDataType: f.masterFieldDataType,
1917
+ ...options.interval ? { interval: options.interval } : {},
1918
+ ...options.timeZone ? { timeZone: options.timeZone } : {}
1734
1919
  };
1735
1920
  }
1736
- function aggregate(f, type = "sum", outputName = f.header) {
1921
+ function dateDiff(start, end, unit = "days") {
1922
+ const startField = start.masterfield ?? start.taskfield ?? start.header;
1923
+ const endField = typeof end === "string" ? end === "TODAY" ? "{@[TODAY]}" : end : end.masterfield ?? end.taskfield ?? end.header;
1924
+ const endLabel = typeof end === "string" ? end : end.header;
1737
1925
  return {
1926
+ header: `${start.header} \u2192 ${endLabel} (${unit})`,
1927
+ masterfield: startField,
1928
+ masterFieldDataType: "FLOAT",
1929
+ dateDiff: { start: startField, end: endField, unit }
1930
+ };
1931
+ }
1932
+ function aggregate(f, type = "sum", outputName = f.header, options = {}) {
1933
+ const summary = {
1738
1934
  title: f.header,
1739
1935
  masterField: f.masterfield ?? f.taskfield ?? f.header,
1740
1936
  masterFieldType: f.masterFieldType,
@@ -1742,6 +1938,36 @@ function aggregate(f, type = "sum", outputName = f.header) {
1742
1938
  value: outputName,
1743
1939
  type
1744
1940
  };
1941
+ const dd = f.dateDiff;
1942
+ if (dd) summary.dateDiff = { ...dd };
1943
+ if (options.percents?.length) summary.percents = [...options.percents];
1944
+ if (options.where?.length) {
1945
+ summary.filters = options.where.map(([wf, cond]) => {
1946
+ const mf = wf.masterfield ?? wf.taskfield ?? wf.header;
1947
+ return {
1948
+ field: outputName,
1949
+ operand: mf,
1950
+ masterField: mf,
1951
+ masterFieldType: wf.masterFieldType,
1952
+ masterFieldDataType: wf.masterFieldDataType,
1953
+ operator: cond.operator,
1954
+ value: [...cond.operand],
1955
+ combine: "AND"
1956
+ };
1957
+ });
1958
+ }
1959
+ return summary;
1960
+ }
1961
+ function hoistSummaryFilters(p) {
1962
+ if (!p?.summary?.some((s) => s.filters?.length)) return p;
1963
+ const hoisted = [];
1964
+ const summary = p.summary.map((s) => {
1965
+ if (!s.filters) return s;
1966
+ const { filters: filters2, ...rest } = s;
1967
+ hoisted.push(...filters2);
1968
+ return rest;
1969
+ });
1970
+ return { ...p, summary, filters: [...p.filters ?? [], ...hoisted] };
1745
1971
  }
1746
1972
  function derive(title, operator, operands) {
1747
1973
  return { title, operator, operands };
@@ -1754,7 +1980,7 @@ function formSource(formId, fields, options = {}) {
1754
1980
  formId,
1755
1981
  newFormFramework: true,
1756
1982
  fields: fields.map((f) => ({ ...f, formID: f.formID ?? formId })),
1757
- ...options.pivot ? { pivot: options.pivot } : {},
1983
+ ...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
1758
1984
  ...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
1759
1985
  };
1760
1986
  }
@@ -1763,7 +1989,7 @@ function taskSource(fields, options = {}) {
1763
1989
  isTask: true,
1764
1990
  newFormFramework: true,
1765
1991
  fields: fields.map((f) => ({ ...f, isTaskField: true })),
1766
- ...options.pivot ? { pivot: options.pivot } : {},
1992
+ ...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
1767
1993
  ...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
1768
1994
  };
1769
1995
  }
@@ -1771,13 +1997,13 @@ function automationSource(automationId, fields = []) {
1771
1997
  return { automation: { automationId, enabled: true }, fields };
1772
1998
  }
1773
1999
  function pivot(config) {
1774
- return {
2000
+ return hoistSummaryFilters({
1775
2001
  rows: config.rows ?? [],
1776
2002
  columns: config.columns ?? [],
1777
2003
  summary: config.summary ?? [],
1778
2004
  filters: config.filters ?? [],
1779
2005
  postOperations: config.postOperations ?? []
1780
- };
2006
+ });
1781
2007
  }
1782
2008
 
1783
2009
  // src/core/config.ts
@@ -1875,7 +2101,7 @@ function coerceForField(field2, values) {
1875
2101
  });
1876
2102
  }
1877
2103
  var FMT = "YYYY-MM-DD";
1878
- function resolveDateToken(token, now = dayjs8()) {
2104
+ function resolveDateToken(token, now = dayjs4()) {
1879
2105
  switch (String(token).toUpperCase()) {
1880
2106
  case "TODAY":
1881
2107
  return [now.format(FMT), now.format(FMT)];
@@ -1898,19 +2124,19 @@ function resolveDateToken(token, now = dayjs8()) {
1898
2124
  }
1899
2125
  }
1900
2126
  var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
1901
- function searchAsDate(text) {
1902
- const t = text.trim();
2127
+ function searchAsDate(text2) {
2128
+ const t = text2.trim();
1903
2129
  if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
1904
- for (const fmt of SEARCH_DATE_FORMATS) {
1905
- const [y, m, d] = parseStrict(t, fmt);
2130
+ for (const fmt2 of SEARCH_DATE_FORMATS) {
2131
+ const [y, m, d] = parseStrict(t, fmt2);
1906
2132
  if (y) return `${y}-${m}-${d}`;
1907
2133
  }
1908
2134
  return null;
1909
2135
  }
1910
- function parseStrict(text, fmt) {
1911
- const sep = fmt.includes("/") ? "/" : "-";
1912
- const parts = text.split(sep);
1913
- const order = fmt.split(sep);
2136
+ function parseStrict(text2, fmt2) {
2137
+ const sep = fmt2.includes("/") ? "/" : "-";
2138
+ const parts = text2.split(sep);
2139
+ const order = fmt2.split(sep);
1914
2140
  if (parts.length !== 3 || order.length !== 3) return [null, null, null];
1915
2141
  const map = {};
1916
2142
  order.forEach((k, i) => {
@@ -1919,15 +2145,15 @@ function parseStrict(text, fmt) {
1919
2145
  const y = map.YYYY, m = map.MM, d = map.DD;
1920
2146
  if (!y || y.length !== 4 || !m || !d) return [null, null, null];
1921
2147
  const mm = m.padStart(2, "0"), dd = d.padStart(2, "0");
1922
- const parsed = dayjs8(`${y}-${mm}-${dd}`);
2148
+ const parsed = dayjs4(`${y}-${mm}-${dd}`);
1923
2149
  if (!parsed.isValid() || parsed.format(FMT) !== `${y}-${mm}-${dd}`) return [null, null, null];
1924
2150
  return [y, mm, dd];
1925
2151
  }
1926
2152
  function buildSearchClause(search) {
1927
- const text = (search ?? "").trim();
1928
- if (!text) return null;
1929
- const iso = searchAsDate(text);
1930
- return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text } };
2153
+ const text2 = (search ?? "").trim();
2154
+ if (!text2) return null;
2155
+ const iso = searchAsDate(text2);
2156
+ return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
1931
2157
  }
1932
2158
  function buildWidgetFilterClauses(dataSource, filters2 = []) {
1933
2159
  const isTask = resolveIsTask2(dataSource);
@@ -2018,7 +2244,7 @@ function toComparable(v) {
2018
2244
  if (typeof v === "number") return v;
2019
2245
  const n = Number(v);
2020
2246
  if (!isNaN(n)) return n;
2021
- const d = dayjs8(v);
2247
+ const d = dayjs4(v);
2022
2248
  return d.isValid() ? d.valueOf() : null;
2023
2249
  }
2024
2250
  function applyLocalSearch(rows, search, toLabel) {
@@ -2086,7 +2312,7 @@ function matchesWidgetFilter(row, f) {
2086
2312
  case "DATE_IS": {
2087
2313
  const range = resolveDateToken(String(values[0] ?? ""));
2088
2314
  if (!range || raw2 == null || raw2 === "") return false;
2089
- const d = dayjs8(raw2).format(FMT);
2315
+ const d = dayjs4(raw2).format(FMT);
2090
2316
  return d >= range[0] && d <= range[1];
2091
2317
  }
2092
2318
  default:
@@ -2338,11 +2564,11 @@ var Delta = ({ vm }) => {
2338
2564
  if (!vm.delta) return null;
2339
2565
  const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
2340
2566
  const sign = vm.delta.value >= 0 ? "+" : "";
2341
- const text = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2567
+ const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2342
2568
  return /* @__PURE__ */ jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
2343
2569
  vm.delta.isPositive ? "\u25B2" : "\u25BC",
2344
2570
  " ",
2345
- text
2571
+ text2
2346
2572
  ] });
2347
2573
  };
2348
2574
  var Sparkline = ({
@@ -2505,7 +2731,7 @@ var KPI_DEFAULT_TEMPLATES = {
2505
2731
  hero_chart: HeroChart,
2506
2732
  icon: Icon
2507
2733
  };
2508
- var WidgetStateSlot = React13.createContext(null);
2734
+ var WidgetStateSlot = React14.createContext(null);
2509
2735
  var WidgetShell = ({
2510
2736
  widgetType,
2511
2737
  title,
@@ -2523,7 +2749,7 @@ var WidgetShell = ({
2523
2749
  }) => {
2524
2750
  const t = useTokens();
2525
2751
  const chrome = resolveChrome(t, widgetType);
2526
- const renderState = React13.useContext(WidgetStateSlot);
2752
+ const renderState = React14.useContext(WidgetStateSlot);
2527
2753
  const frame = {
2528
2754
  display: "flex",
2529
2755
  flexDirection: "column",
@@ -2628,24 +2854,91 @@ function alignSummaryToSeries(dataSource, series) {
2628
2854
  });
2629
2855
  return { ...dataSource, pivot: { ...dataSource.pivot, summary: fixedSummary } };
2630
2856
  }
2857
+ dayjs4.extend(utc);
2858
+ dayjs4.extend(timezone2);
2631
2859
  function isLikelyTimestamp(v) {
2632
2860
  if (typeof v !== "number" || !Number.isFinite(v)) return false;
2633
2861
  return v > 946e9 && v < 41e11;
2634
2862
  }
2635
- function fillDateGaps({ rows, xKey, series, unit, range }) {
2863
+ function wallStartOfPeriod(d, unit) {
2864
+ switch (unit) {
2865
+ case "week": {
2866
+ const day = d.startOf("day");
2867
+ return day.subtract((day.day() + 6) % 7, "day");
2868
+ }
2869
+ case "quarter": {
2870
+ const month = d.startOf("month");
2871
+ return month.month(Math.floor(month.month() / 3) * 3);
2872
+ }
2873
+ default:
2874
+ return d.startOf(unit);
2875
+ }
2876
+ }
2877
+ function wallAddPeriod(d, unit) {
2878
+ return unit === "quarter" ? d.add(3, "month") : d.add(1, unit);
2879
+ }
2880
+ function inZone(wall, d, timeZone) {
2881
+ const date = wall(dayjs4.utc(d.tz(timeZone).format("YYYY-MM-DD"))).format("YYYY-MM-DD");
2882
+ return dayjs4.tz(date, timeZone);
2883
+ }
2884
+ function startOfPeriod(d, unit, timeZone) {
2885
+ return timeZone ? inZone((w) => wallStartOfPeriod(w, unit), d, timeZone) : wallStartOfPeriod(d, unit);
2886
+ }
2887
+ function addPeriod(d, unit, timeZone) {
2888
+ return timeZone ? inZone((w) => wallAddPeriod(w, unit), d, timeZone) : wallAddPeriod(d, unit);
2889
+ }
2890
+ var HAS_OFFSET = /(Z|[+-]\d{2}:?\d{2})$/i;
2891
+ function parseDateValue(v, timeZone) {
2892
+ if (timeZone && typeof v === "string" && !HAS_OFFSET.test(v.trim())) {
2893
+ try {
2894
+ return dayjs4.tz(v, timeZone);
2895
+ } catch {
2896
+ return dayjs4(NaN);
2897
+ }
2898
+ }
2899
+ return dayjs4(v);
2900
+ }
2901
+ function sumInto(target, row, series) {
2902
+ series.forEach(({ valueField }) => {
2903
+ if (!(valueField in row) && !(valueField in target)) return;
2904
+ const a = Number(target[valueField]);
2905
+ const b = Number(row[valueField]);
2906
+ target[valueField] = (Number.isFinite(a) ? a : 0) + (Number.isFinite(b) ? b : 0);
2907
+ });
2908
+ }
2909
+ function fillDateGaps({
2910
+ rows,
2911
+ xKey,
2912
+ series,
2913
+ unit,
2914
+ range,
2915
+ merge = "sum",
2916
+ xFormat,
2917
+ timeZone
2918
+ }) {
2636
2919
  if (!range) return rows;
2637
- const keyFormat = unit === "month" ? "YYYY-MM" : "YYYY-MM-DD";
2638
2920
  const byPeriod = /* @__PURE__ */ new Map();
2921
+ const copied = /* @__PURE__ */ new Set();
2639
2922
  rows.forEach((row) => {
2640
- const d = dayjs8(row[xKey]);
2641
- if (d.isValid()) byPeriod.set(d.startOf(unit).format(keyFormat), row);
2923
+ const d = parseDateValue(row[xKey], timeZone);
2924
+ if (!d.isValid()) return;
2925
+ const key = startOfPeriod(d, unit, timeZone).valueOf();
2926
+ const existing = byPeriod.get(key);
2927
+ if (!existing || merge === "last") {
2928
+ byPeriod.set(key, row);
2929
+ return;
2930
+ }
2931
+ const merged = copied.has(key) ? existing : { ...existing };
2932
+ copied.add(key);
2933
+ sumInto(merged, row, series);
2934
+ byPeriod.set(key, merged);
2642
2935
  });
2643
2936
  const filled = [];
2644
- let cursor = range.start;
2645
- const useTimestamp = isLikelyTimestamp(rows[0]?.[xKey]);
2646
- while (cursor.isBefore(range.end) || cursor.isSame(range.end, unit)) {
2647
- const key = cursor.format(keyFormat);
2648
- const existing = byPeriod.get(key);
2937
+ const useTimestamp = rows.length ? isLikelyTimestamp(rows[0]?.[xKey]) : xFormat === "timestamp";
2938
+ const end = startOfPeriod(range.end, unit, timeZone);
2939
+ let cursor = startOfPeriod(range.start, unit, timeZone);
2940
+ while (!cursor.isAfter(end)) {
2941
+ const existing = byPeriod.get(cursor.valueOf());
2649
2942
  if (existing) {
2650
2943
  filled.push(existing);
2651
2944
  } else {
@@ -2657,10 +2950,33 @@ function fillDateGaps({ rows, xKey, series, unit, range }) {
2657
2950
  });
2658
2951
  filled.push(zeroRow);
2659
2952
  }
2660
- cursor = cursor.add(1, unit);
2953
+ cursor = addPeriod(cursor, unit, timeZone);
2661
2954
  }
2662
2955
  return filled;
2663
2956
  }
2957
+ function applyCumulative(rows, xKey, fields, baseline = {}) {
2958
+ if (!fields.length) return rows;
2959
+ const dated = rows.map((row, i) => ({ row, i, t: dayjs4(row?.[xKey]).valueOf() }));
2960
+ dated.sort((a, b) => {
2961
+ const at = Number.isFinite(a.t), bt = Number.isFinite(b.t);
2962
+ if (at && bt) return a.t - b.t || a.i - b.i;
2963
+ if (at !== bt) return at ? -1 : 1;
2964
+ return a.i - b.i;
2965
+ });
2966
+ const running = {};
2967
+ fields.forEach((f) => {
2968
+ running[f] = Number(baseline[f]) || 0;
2969
+ });
2970
+ return dated.map(({ row }) => {
2971
+ const next = { ...row };
2972
+ fields.forEach((f) => {
2973
+ const v = Number(row?.[f]);
2974
+ running[f] += Number.isFinite(v) ? v : 0;
2975
+ next[f] = running[f];
2976
+ });
2977
+ return next;
2978
+ });
2979
+ }
2664
2980
  function toNumber(v) {
2665
2981
  if (typeof v === "number") return Number.isFinite(v) ? v : 0;
2666
2982
  const n = Number(v);
@@ -2678,6 +2994,49 @@ function coerceSeriesNumbers(rows, series) {
2678
2994
  });
2679
2995
  }
2680
2996
 
2997
+ // src/transforms/percentiles.ts
2998
+ function percentileKey(name, percent) {
2999
+ return `${name}_p${String(Number(percent)).replace(".", "_")}`;
3000
+ }
3001
+ function percentileSummaries(summary) {
3002
+ return (summary || []).filter((s) => s?.type === "percentiles" && s.value).map((s) => s.value);
3003
+ }
3004
+ function readPercentiles(raw2) {
3005
+ if (raw2 == null || typeof raw2 !== "object") return null;
3006
+ const values = Array.isArray(raw2) ? raw2 : raw2.values ?? raw2;
3007
+ const out = {};
3008
+ if (Array.isArray(values)) {
3009
+ values.forEach((v) => {
3010
+ if (v && v.key != null) out[String(v.key)] = v.value;
3011
+ });
3012
+ } else if (typeof values === "object") {
3013
+ for (const [k, v] of Object.entries(values)) {
3014
+ if (k.endsWith("_as_string")) continue;
3015
+ if (Number.isFinite(Number(k))) out[k] = v;
3016
+ }
3017
+ }
3018
+ return Object.keys(out).length ? out : null;
3019
+ }
3020
+ function flattenPercentiles(rows, names) {
3021
+ if (!names.length) return rows;
3022
+ return rows.map((row) => {
3023
+ let next = row;
3024
+ for (const name of names) {
3025
+ const parsed = readPercentiles(row?.[name]);
3026
+ if (!parsed) continue;
3027
+ if (next === row) next = { ...row };
3028
+ let median;
3029
+ for (const [percent, value] of Object.entries(parsed)) {
3030
+ const n = value == null ? null : Number(value);
3031
+ next[percentileKey(name, percent)] = n;
3032
+ if (Number(percent) === 50 && n != null) median = n;
3033
+ }
3034
+ if (median !== void 0) next[name] = median;
3035
+ }
3036
+ return next;
3037
+ });
3038
+ }
3039
+
2681
3040
  // src/react/useWidgetData.ts
2682
3041
  var ALIGN_TYPES = /* @__PURE__ */ new Set(["BAR", "BAR_CHART", "PIE", "PIE_CHART", "DOUGHNUT", "DOUGHNUT_CHART"]);
2683
3042
  var formCache = /* @__PURE__ */ new WeakMap();
@@ -2698,6 +3057,27 @@ function getFormOnce(client, formId) {
2698
3057
  }
2699
3058
  return p;
2700
3059
  }
3060
+ function baselineClause(source, start) {
3061
+ const f = (source.dataSource?.fields || []).find(
3062
+ (x) => x?.header === source.dateField || x?.masterfield === source.dateField
3063
+ );
3064
+ return {
3065
+ header: f?.header ?? source.dateField,
3066
+ masterfield: f?.masterfield ?? source.dateField,
3067
+ masterFieldType: f?.masterFieldType ?? "DATE_PICKER",
3068
+ masterFieldDataType: f?.masterFieldDataType ?? "STRING",
3069
+ // Date-only: the query generator keeps "YYYY-MM-DD" operands as dates.
3070
+ filter: { operator: "LESS_THAN", operand: [start.format("YYYY-MM-DD")] }
3071
+ };
3072
+ }
3073
+ function readBaseline(rows) {
3074
+ const out = {};
3075
+ for (const [k, v] of Object.entries(rows?.[0] ?? {})) {
3076
+ const n = Number(v);
3077
+ if (Number.isFinite(n)) out[k] = n;
3078
+ }
3079
+ return out;
3080
+ }
2701
3081
  function useDebounced(value, ms, enabled) {
2702
3082
  const [v, setV] = useState(value);
2703
3083
  useEffect(() => {
@@ -2777,13 +3157,34 @@ function useWidgetData(args) {
2777
3157
  const localKey = JSON.stringify(localFilters ?? {});
2778
3158
  const dsKey = JSON.stringify(dataSource ?? {});
2779
3159
  const remoteKey = JSON.stringify(remote);
2780
- const normalize = useCallback((incoming) => {
2781
- const withPostOps = applyPostOperations(incoming, dataSource?.pivot?.postOperations);
3160
+ const dateGapKey = JSON.stringify(dateGap ?? null);
3161
+ const gapTimeZone = useMemo(() => {
3162
+ if (dateGap?.timeZone) return dateGap.timeZone;
3163
+ const row = (dataSource?.pivot?.rows || []).find((r) => r?.title === xKey && r?.interval);
3164
+ return row?.timeZone || void 0;
3165
+ }, [dateGap?.timeZone, dataSource, xKey]);
3166
+ const normalize = useCallback((incoming, fetchedBaseline) => {
3167
+ const percentiles = percentileSummaries(dataSource?.pivot?.summary);
3168
+ const flat = percentiles.length ? flattenPercentiles(incoming, percentiles) : incoming;
3169
+ const withPostOps = applyPostOperations(flat, dataSource?.pivot?.postOperations);
2782
3170
  const numeric = coerceSeriesNumbers(withPostOps, series);
2783
3171
  if (!dateGap) return numeric;
2784
- const range = dateGap.start && dateGap.end ? { start: dayjs8(dateGap.start).startOf(dateGap.unit), end: dayjs8(dateGap.end).startOf(dateGap.unit) } : null;
2785
- return fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range });
2786
- }, [dataSource, series, xKey, dateGap]);
3172
+ const tz = gapTimeZone;
3173
+ const period = (v) => startOfPeriod(parseDateValue(v, tz), dateGap.unit, tz);
3174
+ const range = dateGap.start && dateGap.end ? { start: period(dateGap.start), end: period(dateGap.end) } : null;
3175
+ const filled = fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range, merge: dateGap.merge, timeZone: tz });
3176
+ if (!dateGap.cumulative) return filled;
3177
+ let inRange = filled;
3178
+ if (dateGap.start && !range) {
3179
+ const from = period(dateGap.start).valueOf();
3180
+ inRange = filled.filter((row) => {
3181
+ const d = parseDateValue(row?.[xKey], tz);
3182
+ return !d.isValid() || startOfPeriod(d, dateGap.unit, tz).valueOf() >= from;
3183
+ });
3184
+ }
3185
+ const fields = dateGap.cumulative === true ? series.map((s) => s.valueField) : dateGap.cumulative;
3186
+ return applyCumulative(inRange, xKey, fields, { ...fetchedBaseline || {}, ...dateGap.baseline || {} });
3187
+ }, [dataSource, series, xKey, dateGap, gapTimeZone]);
2787
3188
  useEffect(() => {
2788
3189
  if (automationRows) {
2789
3190
  if (automationLoading) {
@@ -2805,53 +3206,67 @@ function useWidgetData(args) {
2805
3206
  let cancelled = false;
2806
3207
  setError(null);
2807
3208
  setLoading(true);
3209
+ const queryForm = async (ds, mode, extraClauses = []) => {
3210
+ const formId = ds.formId ?? ds.fields?.[0]?.formID;
3211
+ const withFormId = formId ? { ...ds, formId } : ds;
3212
+ const merged = mergeHostFilters(localFilters, dashFilters);
3213
+ const cfgWithDates = { ...config, cdateFilters: mode === "main" ? cdateFilters : [] };
3214
+ const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
3215
+ if (remote.widgetFilters?.length) {
3216
+ dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
3217
+ }
3218
+ if (mode === "main" && remote.columnFilters && Object.keys(remote.columnFilters).length) {
3219
+ dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
3220
+ }
3221
+ const searchClause = mode === "main" ? buildSearchClause(remote.search) : null;
3222
+ if (searchClause) dashboardFilters.push(searchClause);
3223
+ dashboardFilters.push(...extraClauses);
3224
+ const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, mode === "main" ? remote.sort : null);
3225
+ if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
3226
+ const form = await getFormOnce(rt.client, formId).catch((e) => {
3227
+ console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
3228
+ return true;
3229
+ });
3230
+ if (!form) throw new Error("You don't have access to this widget's form.");
3231
+ }
3232
+ const fixedDs = mode === "main" && ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
3233
+ const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
3234
+ const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
3235
+ if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
3236
+ const query = rt.generateQuery(
3237
+ queryConfig,
3238
+ fixedDs,
3239
+ fixedDs.formId,
3240
+ 0,
3241
+ rowLimit,
3242
+ [],
3243
+ dashboardFilters,
3244
+ [],
3245
+ loggedInUser,
3246
+ dashboardSort,
3247
+ null,
3248
+ fixedDs.isTask,
3249
+ mappings
3250
+ );
3251
+ const res = await rt.client.workflow.elasticSearch(query);
3252
+ return Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
3253
+ };
2808
3254
  (async () => {
2809
3255
  try {
2810
3256
  const ds = dataSource || {};
2811
3257
  let fetched = [];
3258
+ let fetchedBaseline;
2812
3259
  if (usesFormFramework(ds)) {
2813
- const formId = ds.formId ?? ds.fields?.[0]?.formID;
2814
- const withFormId = formId ? { ...ds, formId } : ds;
2815
- const merged = mergeHostFilters(localFilters, dashFilters);
2816
- const cfgWithDates = { ...config, cdateFilters };
2817
- const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
2818
- if (remote.widgetFilters?.length) {
2819
- dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
2820
- }
2821
- if (remote.columnFilters && Object.keys(remote.columnFilters).length) {
2822
- dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
2823
- }
2824
- const searchClause = buildSearchClause(remote.search);
2825
- if (searchClause) dashboardFilters.push(searchClause);
2826
- const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, remote.sort);
2827
- if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
2828
- const form = await getFormOnce(rt.client, formId).catch((e) => {
2829
- console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
2830
- return true;
2831
- });
2832
- if (!form) throw new Error("You don't have access to this widget's form.");
2833
- }
2834
- const fixedDs = ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
2835
- const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
2836
- const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
2837
- if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
2838
- const query = rt.generateQuery(
2839
- queryConfig,
2840
- fixedDs,
2841
- fixedDs.formId,
2842
- 0,
2843
- rowLimit,
2844
- [],
2845
- dashboardFilters,
2846
- [],
2847
- loggedInUser,
2848
- dashboardSort,
2849
- null,
2850
- fixedDs.isTask,
2851
- mappings
2852
- );
2853
- const res = await rt.client.workflow.elasticSearch(query);
2854
- fetched = Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
3260
+ const baselineReq = dateGap?.baselineSource && dateGap.start ? queryForm(
3261
+ dateGap.baselineSource.dataSource,
3262
+ "baseline",
3263
+ [baselineClause(dateGap.baselineSource, startOfPeriod(parseDateValue(dateGap.start, gapTimeZone), dateGap.unit, gapTimeZone))]
3264
+ ).catch((e) => {
3265
+ console.warn("[@zestl/zvolv-widgets] dateGap baseline fetch failed; starting from 0", e);
3266
+ return [];
3267
+ }) : null;
3268
+ fetched = await queryForm(ds, "main");
3269
+ if (baselineReq) fetchedBaseline = readBaseline(await baselineReq);
2855
3270
  } else {
2856
3271
  const wftype = config?.dataConfs?.WorkflowTypeID ?? workflowTypeId;
2857
3272
  const res = await rt.client.workflow.getDataSource(JSON.stringify({ widgetId, wftype }));
@@ -2859,7 +3274,7 @@ function useWidgetData(args) {
2859
3274
  fetched = payload?.table ?? (Array.isArray(payload) ? payload : []);
2860
3275
  }
2861
3276
  if (cancelled || seq !== reqSeq.current) return;
2862
- setSourceRows(normalize(fetched));
3277
+ setSourceRows(normalize(fetched, fetchedBaseline));
2863
3278
  } catch (err) {
2864
3279
  if (cancelled || seq !== reqSeq.current) return;
2865
3280
  console.error(`[@zestl/zvolv-widgets] ${widgetType ?? "widget"} fetch failed:`, err);
@@ -2875,7 +3290,7 @@ function useWidgetData(args) {
2875
3290
  return () => {
2876
3291
  cancelled = true;
2877
3292
  };
2878
- }, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, configured, nonce, automationRows, automationLoading]);
3293
+ }, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, dateGapKey, configured, nonce, automationRows, automationLoading]);
2879
3294
  const userKind = useMemo(() => {
2880
3295
  if (!rt.getUserFieldKind) return null;
2881
3296
  const field2 = (dataSource?.fields || []).find(
@@ -3227,7 +3642,7 @@ function useInteractiveWidgetData(props, options = {}) {
3227
3642
  tokens: t
3228
3643
  };
3229
3644
  parts.push(
3230
- /* @__PURE__ */ jsx(React13.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsx(DefaultSortIndicator, { ...args }) }, "sort")
3645
+ /* @__PURE__ */ jsx(React14.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsx(DefaultSortIndicator, { ...args }) }, "sort")
3231
3646
  );
3232
3647
  }
3233
3648
  if (flags.showColumnFilter) {
@@ -3241,7 +3656,7 @@ function useInteractiveWidgetData(props, options = {}) {
3241
3656
  tokens: t
3242
3657
  };
3243
3658
  parts.push(
3244
- /* @__PURE__ */ jsx(React13.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsx(DefaultColumnFilter, { ...args }) }, "filter")
3659
+ /* @__PURE__ */ jsx(React14.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsx(DefaultColumnFilter, { ...args }) }, "filter")
3245
3660
  );
3246
3661
  }
3247
3662
  return parts.length ? /* @__PURE__ */ jsx(Fragment, { children: parts }) : null;
@@ -3729,31 +4144,100 @@ function resolveDrillDown(config = {}) {
3729
4144
  }
3730
4145
  var WIDGET_TYPE = "KPI_CARD";
3731
4146
  var KpiWidget = (props) => {
3732
- const { title, description, emptyMessage, unconfiguredMessage, className, style, onItemClick, ...dataArgs } = props;
4147
+ const {
4148
+ title,
4149
+ description,
4150
+ emptyMessage,
4151
+ unconfiguredMessage,
4152
+ className,
4153
+ style,
4154
+ onItemClick,
4155
+ externalSeriesByIndex,
4156
+ ...dataArgs
4157
+ } = props;
3733
4158
  const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: WIDGET_TYPE });
3734
4159
  const { data, config, tokens: t } = ix;
3735
4160
  const chrome = resolveChromeConfig(config);
3736
4161
  const contents = config?.cardConfig?.contents ?? config?.contents ?? [];
3737
4162
  const cardTemplate = config?.cardConfig?.template ?? config?.template ?? "standard";
3738
- const vms = useKpiContents({ contents, rows: data.rows, cardTemplate });
4163
+ const [fetchedSeries, setFetchedSeries] = useState({});
4164
+ const onSeries = useCallback((index, values) => {
4165
+ setFetchedSeries((prev) => {
4166
+ if (JSON.stringify(prev[index]) === JSON.stringify(values)) return prev;
4167
+ const next = { ...prev };
4168
+ if (values) next[index] = values;
4169
+ else delete next[index];
4170
+ return next;
4171
+ });
4172
+ }, []);
4173
+ const seriesByIndex = useMemo(
4174
+ () => externalSeriesByIndex ? { ...fetchedSeries, ...externalSeriesByIndex } : fetchedSeries,
4175
+ [fetchedSeries, externalSeriesByIndex]
4176
+ );
4177
+ const vms = useKpiContents({ contents, rows: data.rows, cardTemplate, externalSeriesByIndex: seriesByIndex });
3739
4178
  const state = data.state === "empty" && contents.length > 0 ? "ready" : data.state;
3740
- return /* @__PURE__ */ jsx(
3741
- WidgetShell,
3742
- {
3743
- widgetType: WIDGET_TYPE,
3744
- title: chrome.showHeader ? title ?? chrome.title : void 0,
3745
- description: description ?? chrome.description,
3746
- state,
3747
- error: data.error,
3748
- emptyMessage: emptyMessage ?? chrome.emptyMessage,
3749
- unconfiguredMessage,
3750
- className,
3751
- style,
3752
- actions: ix.toolbar,
3753
- ...ix.shell,
3754
- children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
3755
- }
4179
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4180
+ contents.map(
4181
+ (content, i) => content?.seriesSource?.mode === "timeseries" && content.seriesSource.dataSource && !externalSeriesByIndex?.[i] ? /* @__PURE__ */ jsx(
4182
+ KpiSeriesFetcher,
4183
+ {
4184
+ index: i,
4185
+ source: content.seriesSource,
4186
+ onSeries,
4187
+ dashFilters: dataArgs.dashFilters,
4188
+ cdateFilters: dataArgs.cdateFilters,
4189
+ localFilters: dataArgs.localFilters,
4190
+ workflowTypeId: dataArgs.workflowTypeId
4191
+ },
4192
+ i
4193
+ ) : null
4194
+ ),
4195
+ /* @__PURE__ */ jsx(
4196
+ WidgetShell,
4197
+ {
4198
+ widgetType: WIDGET_TYPE,
4199
+ title: chrome.showHeader ? title ?? chrome.title : void 0,
4200
+ description: description ?? chrome.description,
4201
+ state,
4202
+ error: data.error,
4203
+ emptyMessage: emptyMessage ?? chrome.emptyMessage,
4204
+ unconfiguredMessage,
4205
+ className,
4206
+ style,
4207
+ actions: ix.toolbar,
4208
+ ...ix.shell,
4209
+ children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
4210
+ }
4211
+ )
4212
+ ] });
4213
+ };
4214
+ var KpiSeriesFetcher = ({ index, source, onSeries, dashFilters, cdateFilters, localFilters, workflowTypeId }) => {
4215
+ const { dataSource, xField, valueField, dateGap } = source;
4216
+ const configurations = useMemo(
4217
+ () => ({ series: [{ argumentField: xField, valueField }] }),
4218
+ [xField, valueField]
4219
+ );
4220
+ const data = useWidgetData({
4221
+ widgetType: "KPI_SERIES",
4222
+ dataSource,
4223
+ configurations,
4224
+ dateGap: dateGap ?? null,
4225
+ dashFilters,
4226
+ cdateFilters,
4227
+ localFilters,
4228
+ workflowTypeId
4229
+ });
4230
+ const values = useMemo(
4231
+ () => data.rows.map((r) => toNum(r?.[valueField])).filter((n) => n != null),
4232
+ [data.rows, valueField]
3756
4233
  );
4234
+ const ready = data.state === "ready" || data.state === "empty";
4235
+ const key = ready ? JSON.stringify(values) : "";
4236
+ useEffect(() => {
4237
+ onSeries(index, ready ? values : void 0);
4238
+ }, [index, key, ready, onSeries]);
4239
+ useEffect(() => () => onSeries(index, void 0), [index, onSeries]);
4240
+ return null;
3757
4241
  };
3758
4242
  var KpiItem = ({
3759
4243
  vm,
@@ -3810,7 +4294,7 @@ var ChartWidget = (props) => {
3810
4294
  const renderer = render ?? children;
3811
4295
  let body;
3812
4296
  if (renderer) body = renderer(args);
3813
- else if (override) body = React13.createElement(override, args);
4297
+ else if (override) body = React14.createElement(override, args);
3814
4298
  else body = /* @__PURE__ */ jsx(FallbackBars, { ...args });
3815
4299
  return /* @__PURE__ */ jsx(
3816
4300
  WidgetShell,
@@ -3920,7 +4404,7 @@ var RankingBarWidget = (props) => {
3920
4404
  style,
3921
4405
  actions: ix.toolbar,
3922
4406
  ...ix.shell,
3923
- children: render ? render(args) : override ? React13.createElement(override, args) : /* @__PURE__ */ jsx(DefaultRanking, { ...args })
4407
+ children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultRanking, { ...args })
3924
4408
  }
3925
4409
  );
3926
4410
  };
@@ -4208,7 +4692,7 @@ var CollectionWidget = (props) => {
4208
4692
  style,
4209
4693
  actions: ix.toolbar,
4210
4694
  ...ix.shell,
4211
- children: render ? render(args) : override ? React13.createElement(override, args) : /* @__PURE__ */ jsx(DefaultList, { ...args })
4695
+ children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultList, { ...args })
4212
4696
  }
4213
4697
  );
4214
4698
  };
@@ -4378,8 +4862,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
4378
4862
  if (column.kind === "number") {
4379
4863
  return /* @__PURE__ */ jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
4380
4864
  }
4381
- const text = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4382
- return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text) ?? text).replace(/<[^>]*>/g, "") });
4865
+ const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4866
+ return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
4383
4867
  };
4384
4868
  var DefaultTable = ({
4385
4869
  rows,
@@ -4458,7 +4942,7 @@ var PivotGridWidget = (props) => {
4458
4942
  };
4459
4943
  function toDate(v) {
4460
4944
  if (v === null || v === void 0 || v === "") return null;
4461
- const d = dayjs8(v);
4945
+ const d = dayjs4(v);
4462
4946
  return d.isValid() ? d.toDate() : null;
4463
4947
  }
4464
4948
  function useTemporalEvents(rows, mapping) {
@@ -4483,7 +4967,7 @@ function useTemporalEvents(rows, mapping) {
4483
4967
  start,
4484
4968
  end,
4485
4969
  // With no time component on either bound, treat it as an all-day span.
4486
- allDay: !!(start && !end) || !!start && dayjs8(start).startOf("day").isSame(start),
4970
+ allDay: !!(start && !end) || !!start && dayjs4(start).startOf("day").isSame(start),
4487
4971
  status: statusRaw !== void 0 ? { label: statusRaw, tone: statusTone(statusRaw) } : void 0,
4488
4972
  progress: Number.isFinite(rawProgress) ? Math.max(0, Math.min(1, rawProgress > 1 ? rawProgress / 100 : rawProgress)) : void 0,
4489
4973
  color: colorField ? row?.[colorField] : void 0,
@@ -4550,7 +5034,7 @@ var TemporalWidget = (props) => {
4550
5034
  style,
4551
5035
  actions: ix.toolbar,
4552
5036
  ...ix.shell,
4553
- children: render ? render(args) : override ? React13.createElement(override, args) : /* @__PURE__ */ jsx(DefaultTimeline, { ...args })
5037
+ children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultTimeline, { ...args })
4554
5038
  }
4555
5039
  );
4556
5040
  };
@@ -4577,8 +5061,8 @@ var DefaultTimeline = ({ events, tokens: t, onEventClick }) => /* @__PURE__ */ j
4577
5061
  /* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
4578
5062
  /* @__PURE__ */ jsx("div", { style: { fontSize: t.typography.body.fontSize, color: t.colors.textPrimary, fontWeight: 600 }, children: String(e.title ?? "") }),
4579
5063
  /* @__PURE__ */ jsxs("div", { style: { fontSize: t.typography.caption.fontSize, color: t.colors.textTertiary }, children: [
4580
- e.start ? dayjs8(e.start).format("DD MMM YYYY") : "",
4581
- e.end ? ` \u2192 ${dayjs8(e.end).format("DD MMM YYYY")}` : "",
5064
+ e.start ? dayjs4(e.start).format("DD MMM YYYY") : "",
5065
+ e.end ? ` \u2192 ${dayjs4(e.end).format("DD MMM YYYY")}` : "",
4582
5066
  e.group ? ` \xB7 ${String(e.group)}` : ""
4583
5067
  ] })
4584
5068
  ] }),
@@ -4673,7 +5157,7 @@ var StepperWidget = (props) => {
4673
5157
  style,
4674
5158
  actions: ix.toolbar,
4675
5159
  ...ix.shell,
4676
- children: render ? render(args) : override ? React13.createElement(override, args) : /* @__PURE__ */ jsx(DefaultStepper, { ...args })
5160
+ children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultStepper, { ...args })
4677
5161
  }
4678
5162
  );
4679
5163
  };
@@ -4745,19 +5229,19 @@ function renderHtmlVariables(template, rows, variables) {
4745
5229
  } else if (v.dataFormat === "AMOUNT" && typeof value === "number") {
4746
5230
  value = formatAmountValue(value, v.amountSuffix || "Rupees", v.precision ?? 2);
4747
5231
  } else if (v.dataFormat === "DATE" && value) {
4748
- const d = dayjs8(value);
5232
+ const d = dayjs4(value);
4749
5233
  if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
4750
5234
  }
4751
- let text;
5235
+ let text2;
4752
5236
  if (Array.isArray(value)) {
4753
- text = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
5237
+ text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
4754
5238
  } else if (value && typeof value === "object") {
4755
- text = String(value.view ?? value.title ?? value.name ?? "");
5239
+ text2 = String(value.view ?? value.title ?? value.name ?? "");
4756
5240
  } else {
4757
5241
  const n = typeof value === "number" ? value : Number(value);
4758
- text = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
5242
+ text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
4759
5243
  }
4760
- out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text);
5244
+ out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
4761
5245
  }
4762
5246
  return out;
4763
5247
  }
@@ -5230,6 +5714,7 @@ function resolveFieldType(el2) {
5230
5714
  if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
5231
5715
  if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
5232
5716
  if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
5717
+ if (sub && sub !== t && sub in KIND) return sub;
5233
5718
  if (t === "SCAN") return "INPUT_SCAN";
5234
5719
  if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
5235
5720
  if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
@@ -5292,7 +5777,11 @@ var KIND = {
5292
5777
  COMMENTS: "comments",
5293
5778
  ACTION_BUTTON: "action",
5294
5779
  INPUT_TRIGGER: "action",
5295
- CASCADED_ACTIONS: "action"
5780
+ CASCADED_ACTIONS: "action",
5781
+ // ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
5782
+ INPUT_TIME_SAMPLING: "trials",
5783
+ INPUT_PERCENT_CORRECT: "trials",
5784
+ INPUT_TASK_ANALYSIS: "trials"
5296
5785
  };
5297
5786
  function fieldKindOf(el2) {
5298
5787
  const t = resolveFieldType(el2);
@@ -5541,6 +6030,9 @@ function formulaScalar(v) {
5541
6030
  if (typeof v === "number" || typeof v === "boolean") return v;
5542
6031
  return String(v);
5543
6032
  }
6033
+ function toBraceFormula(formula, labels) {
6034
+ return braceLegacyLabels(formula, labels);
6035
+ }
5544
6036
  function braceLegacyLabels(formula, labels) {
5545
6037
  if (formula.includes("{")) return formula;
5546
6038
  const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
@@ -5576,6 +6068,31 @@ function formulaLabels(formula, labels = []) {
5576
6068
  const src = braceLegacyLabels(String(formula ?? ""), labels);
5577
6069
  return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
5578
6070
  }
6071
+ function isServerSafeFormula(formula, labels = []) {
6072
+ const f = String(formula ?? "");
6073
+ if (!f.trim()) return true;
6074
+ return braceLegacyLabels(f, labels) === f;
6075
+ }
6076
+ function computeRowFormulas(elements, row) {
6077
+ const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
6078
+ const cols = elements.filter((e) => formulaOf(e));
6079
+ if (!cols.length) return row;
6080
+ const labels = elements.map((e) => e.label).filter(Boolean);
6081
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
6082
+ const out = { ...row };
6083
+ for (let pass = 0; pass < 2; pass++) {
6084
+ cols.forEach((c) => {
6085
+ const refs = formulaLabels(formulaOf(c), labels);
6086
+ const empty = (v) => v === null || v === void 0 || v === "";
6087
+ if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
6088
+ out[c.elementId] = null;
6089
+ return;
6090
+ }
6091
+ out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
6092
+ });
6093
+ }
6094
+ return out;
6095
+ }
5579
6096
 
5580
6097
  // src/forms/validate.ts
5581
6098
  var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
@@ -5625,6 +6142,11 @@ function validateField(el2, value, required = el2.required) {
5625
6142
  const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
5626
6143
  if (required && blank) return `${name} is required`;
5627
6144
  if (blank) return void 0;
6145
+ if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
6146
+ const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
6147
+ const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
6148
+ if (min > 0 && n < min) return `Trials are less than ${min}`;
6149
+ }
5628
6150
  if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
5629
6151
  if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
5630
6152
  if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
@@ -5735,6 +6257,9 @@ function getEmptyValue(type) {
5735
6257
  case "SIGNATURE":
5736
6258
  case "TABLE_START":
5737
6259
  case "MATRIX":
6260
+ case "INPUT_TIME_SAMPLING":
6261
+ case "INPUT_PERCENT_CORRECT":
6262
+ case "INPUT_TASK_ANALYSIS":
5738
6263
  return null;
5739
6264
  default:
5740
6265
  return "";
@@ -5776,7 +6301,7 @@ var DATE_UNITS = {
5776
6301
  };
5777
6302
  function currentUserId(user) {
5778
6303
  const id = user?.userId ?? user?.UserID ?? user?.id;
5779
- return id != null && id !== "" ? String(id) : void 0;
6304
+ return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
5780
6305
  }
5781
6306
  function dynamicDefault(el2, user) {
5782
6307
  const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
@@ -5784,7 +6309,7 @@ function dynamicDefault(el2, user) {
5784
6309
  const t = resolveFieldType(el2);
5785
6310
  if ((dd.operator === "ADD" || dd.operator === "SUB") && (t === "DATE_PICKER" || t === "DATE_TIME_PICKER")) {
5786
6311
  const sign = dd.operator === "SUB" ? -1 : 1;
5787
- let d = dayjs8();
6312
+ let d = dayjs4();
5788
6313
  const u = dd.units;
5789
6314
  if (typeof u === "number" || typeof u === "string" && u.trim() !== "" && !isNaN(Number(u))) d = d.add(sign * Number(u), "day");
5790
6315
  else Object.entries(u ?? {}).forEach(([k, n]) => {
@@ -5824,7 +6349,7 @@ function buildInitialValues(elements, opts = {}) {
5824
6349
  }
5825
6350
  function formatDateish(t, value) {
5826
6351
  const isDayjs = (v) => v && typeof v === "object" && typeof v.isValid === "function" && typeof v.format === "function";
5827
- const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs8(v) : typeof v === "string" && v ? dayjs8(v) : null;
6352
+ const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs4(v) : typeof v === "string" && v ? dayjs4(v) : null;
5828
6353
  if (t === "DATE_PICKER") {
5829
6354
  const d = toD(value);
5830
6355
  return d?.isValid() ? d.format("YYYY-MM-DD") : value;
@@ -5939,9 +6464,9 @@ function formatFieldValue(el2, value) {
5939
6464
  const t = resolveFieldType(el2);
5940
6465
  if (t === "CHECK_BOX") return value ? "Yes" : "No";
5941
6466
  if (RELATIONAL_TYPES.has(t)) return (Array.isArray(value) ? value : [value]).map((v) => v?.view ?? v?.select ?? String(v)).join(", ");
5942
- if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs8(v).format("D MMM YYYY")).join(" \u2013 ");
5943
- if (t === "DATE_PICKER") return dayjs8(value).isValid() ? dayjs8(value).format("D MMM YYYY") : String(value);
5944
- if (t === "DATE_TIME_PICKER") return dayjs8(value).isValid() ? dayjs8(value).format("D MMM YYYY, HH:mm") : String(value);
6467
+ if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs4(v).format("D MMM YYYY")).join(" \u2013 ");
6468
+ if (t === "DATE_PICKER") return dayjs4(value).isValid() ? dayjs4(value).format("D MMM YYYY") : String(value);
6469
+ if (t === "DATE_TIME_PICKER") return dayjs4(value).isValid() ? dayjs4(value).format("D MMM YYYY, HH:mm") : String(value);
5945
6470
  if (t === "INPUT_PERCENT") return `${value}%`;
5946
6471
  if (Array.isArray(value)) return value.map((v) => v?.media_name ?? v?.view ?? String(v)).join(", ");
5947
6472
  return String(value);
@@ -6163,6 +6688,67 @@ async function invokeTrigger(client, triggerId, body) {
6163
6688
  }
6164
6689
  return res?.data ?? res;
6165
6690
  }
6691
+ async function invokeZeno(client, automationUuid, body) {
6692
+ const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
6693
+ const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
6694
+ if (typeof payload === "string") {
6695
+ try {
6696
+ return JSON.parse(payload);
6697
+ } catch {
6698
+ return payload;
6699
+ }
6700
+ }
6701
+ return payload;
6702
+ }
6703
+ async function invokeAgent(client, body, flowId, agentId) {
6704
+ const wf = wfOf(client);
6705
+ if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
6706
+ const headers = { ...await wf.getHeaders(true) };
6707
+ const post = async (url, data2) => {
6708
+ try {
6709
+ const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
6710
+ const d = res?.data;
6711
+ return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
6712
+ } catch (e) {
6713
+ const r = e?.response;
6714
+ const msg = r?.data?.message ?? r?.data?.error_message ?? r?.data?.detail ?? (typeof r?.data?.error === "string" ? r.data.error : null) ?? (typeof r?.data?.reply === "string" ? r.data.reply : null);
6715
+ throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
6716
+ }
6717
+ };
6718
+ const agent = encodeURIComponent(String(agentId ?? ""));
6719
+ let data = null;
6720
+ if (flowId) {
6721
+ data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
6722
+ const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
6723
+ if (!unknownFlow && data?.error !== true) return data;
6724
+ if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6725
+ }
6726
+ const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
6727
+ data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
6728
+ if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6729
+ return data;
6730
+ }
6731
+ var parseMaybeJson = (v) => {
6732
+ if (typeof v !== "string") return v;
6733
+ const t = v.trim();
6734
+ if (!t.startsWith("{") && !t.startsWith("[")) return v;
6735
+ try {
6736
+ return JSON.parse(t);
6737
+ } catch {
6738
+ return v;
6739
+ }
6740
+ };
6741
+ function extractAgentFormData(reply) {
6742
+ let node = parseMaybeJson(reply);
6743
+ for (let d = 0; d < 5; d++) {
6744
+ if (!node || typeof node !== "object" || Array.isArray(node)) return null;
6745
+ const fd = parseMaybeJson(node.formData);
6746
+ if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
6747
+ if (!("response" in node)) return Object.keys(node).length ? node : null;
6748
+ node = parseMaybeJson(node.response);
6749
+ }
6750
+ return null;
6751
+ }
6166
6752
  function formDataOf(result) {
6167
6753
  let node = result;
6168
6754
  for (let d = 0; d < 4 && node; d++) {
@@ -6339,45 +6925,383 @@ async function searchPeople(client, el2, query) {
6339
6925
  const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
6340
6926
  return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
6341
6927
  }
6342
-
6343
- // src/forms/useZvolvForm.ts
6344
- var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
6345
- if (o == null) return null;
6346
- if (typeof o !== "object") return { label: String(o), value: String(o) };
6347
- const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
6348
- const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
6349
- return { label: String(label ?? ""), value, color: o.color };
6350
- }).filter(Boolean);
6351
- var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
6352
- function toDayjsFormat(fmt, withTime) {
6353
- const f = typeof fmt === "string" && fmt.trim() ? fmt.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
6354
- return f.replace(/yyyy/g, "YYYY").replace(/yy/g, "YY").replace(/dd/g, "DD").replace(/(^|[^D])d(?!d)/g, "$1D").replace(/EEE+/g, "ddd").replace(/a/g, "A");
6355
- }
6356
- function acceptOf(el2) {
6357
- const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
6358
- if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6359
- const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
6360
- if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6361
- return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
6362
- }
6363
- var FormApiContext = React13.createContext(null);
6364
- function useFormApi() {
6365
- const f = React13.useContext(FormApiContext);
6366
- if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
6367
- return f;
6368
- }
6369
- function useZvolvForm(args) {
6370
- const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
6371
- const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
6372
- const rt = useZvolvRuntime();
6373
- const client = rt.client;
6374
- const argsRef = useRef(args);
6375
- argsRef.current = args;
6376
- const [definition, setDefinition] = useState(null);
6377
- const [status, setStatus] = useState("loading");
6928
+ var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6929
+ var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6930
+ function useFieldOptions(form, field2, opts = {}) {
6931
+ const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6932
+ const el2 = field2.element;
6933
+ const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6934
+ const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6935
+ const elements = form.definition?.elements ?? [];
6936
+ const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6937
+ const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6938
+ const depKey = JSON.stringify(dep.filters);
6939
+ const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6940
+ const single = maxCount === 1;
6941
+ const [search, setSearch] = useState("");
6942
+ const [q, setQ] = useState("");
6943
+ useEffect(() => {
6944
+ const t = setTimeout(() => setQ(search), debounceMs);
6945
+ return () => clearTimeout(t);
6946
+ }, [search, debounceMs]);
6947
+ const [remote, setRemote] = useState([]);
6948
+ const [loading, setLoading] = useState(false);
6378
6949
  const [error, setError] = useState(null);
6379
- const [values, setValues] = useState({});
6380
- const [serverErrors, setServerErrors] = useState({});
6950
+ const [opened, setOpened] = useState(loadOn === "mount");
6951
+ const [nonce, setNonce] = useState(0);
6952
+ const lastFetch = useRef(0);
6953
+ const maxAge = useRef(1e4);
6954
+ const waitingFor = useMemo(() => {
6955
+ if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6956
+ const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6957
+ const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6958
+ return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6959
+ }, [cfg, dep.satisfied, rules, form]);
6960
+ const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6961
+ const active = opened || depDriven || q !== "";
6962
+ useEffect(() => {
6963
+ if (!cfg && !people) return;
6964
+ if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6965
+ setRemote([]);
6966
+ return;
6967
+ }
6968
+ if (!active) return;
6969
+ let alive = true;
6970
+ setLoading(true);
6971
+ setError(null);
6972
+ lastFetch.current = Date.now();
6973
+ const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6974
+ maxAge.current = 1e4;
6975
+ p.then((o) => {
6976
+ if (!alive) return;
6977
+ setRemote(o);
6978
+ if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6979
+ }).catch((e) => {
6980
+ if (alive) setError(e?.message ?? "Couldn't load options");
6981
+ }).finally(() => {
6982
+ if (alive) setLoading(false);
6983
+ });
6984
+ return () => {
6985
+ alive = false;
6986
+ };
6987
+ }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6988
+ const refresh = useCallback(() => {
6989
+ maxAge.current = 0;
6990
+ setOpened(true);
6991
+ setNonce((n) => n + 1);
6992
+ }, []);
6993
+ const onFocus = useCallback(() => {
6994
+ if (!cfg && !people) return;
6995
+ if (!opened) {
6996
+ setOpened(true);
6997
+ return;
6998
+ }
6999
+ if (cfg && Date.now() - lastFetch.current > 2e3) {
7000
+ maxAge.current = 2e3;
7001
+ setNonce((n) => n + 1);
7002
+ } else if (people && !remote.length) setNonce((n) => n + 1);
7003
+ }, [cfg, people, opened, remote.length]);
7004
+ const prevDepKey = useRef(null);
7005
+ useEffect(() => {
7006
+ if (!cfg || !rules.length) return;
7007
+ if (prevDepKey.current === null) {
7008
+ prevDepKey.current = depKey;
7009
+ return;
7010
+ }
7011
+ if (prevDepKey.current === depKey) return;
7012
+ prevDepKey.current = depKey;
7013
+ if (form.readOnly) return;
7014
+ if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
7015
+ }, [depKey]);
7016
+ const staticFiltered = useMemo(() => {
7017
+ if (cfg || people) return remote;
7018
+ const needle = search.trim().toLowerCase();
7019
+ return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
7020
+ }, [cfg, people, remote, field2.options, search]);
7021
+ const pickedCache = useRef(/* @__PURE__ */ new Map());
7022
+ const current = field2.value;
7023
+ const selected = useMemo(() => {
7024
+ const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
7025
+ const pool = [...staticFiltered, ...field2.options];
7026
+ return arr.map((v) => {
7027
+ const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
7028
+ if (hit) return hit;
7029
+ if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
7030
+ return { label: String(v), value: v };
7031
+ });
7032
+ }, [current, staticFiltered, field2.options]);
7033
+ const store = useCallback((picked) => {
7034
+ picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
7035
+ const t = field2.type;
7036
+ if (field2.kind === "lookup") {
7037
+ const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
7038
+ form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
7039
+ if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
7040
+ const fill2 = {};
7041
+ cfg.extraLabels.forEach((label, i) => {
7042
+ const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
7043
+ const v = picked[0].extra?.[i];
7044
+ if (target && v !== void 0) fill2[target.elementId] = v;
7045
+ });
7046
+ if (Object.keys(fill2).length) form.applyValues(fill2);
7047
+ }
7048
+ return;
7049
+ }
7050
+ if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
7051
+ else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
7052
+ }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
7053
+ const autoPickRef = useRef(() => {
7054
+ });
7055
+ const lastAutoKey = useRef(null);
7056
+ autoPickRef.current = (o, key) => {
7057
+ if (form.readOnly || lastAutoKey.current === key) return;
7058
+ lastAutoKey.current = key;
7059
+ if (isFilled(form.values[field2.id])) return;
7060
+ store([o]);
7061
+ };
7062
+ const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
7063
+ const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
7064
+ const atLimit = field2.multiple && selected.length >= maxCount;
7065
+ const toggle = useCallback((o) => {
7066
+ const on = isSelected(o);
7067
+ if (!on && field2.multiple && selected.length >= maxCount) return;
7068
+ store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
7069
+ }, [isSelected, selected, store, field2.multiple, maxCount]);
7070
+ const addFreeTag = useCallback((text2) => {
7071
+ const t = text2.trim();
7072
+ if (!t || field2.type !== "INPUT_TAG") return;
7073
+ const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
7074
+ if (selected.some((s) => optionKey(s.value) === t)) return;
7075
+ if (field2.multiple) {
7076
+ if (selected.length < maxCount) store([...selected, o]);
7077
+ } else store([o]);
7078
+ setSearch("");
7079
+ }, [field2.type, field2.multiple, selected, maxCount, store]);
7080
+ return {
7081
+ options: staticFiltered,
7082
+ loading,
7083
+ error,
7084
+ search,
7085
+ setSearch,
7086
+ waitingFor,
7087
+ selected,
7088
+ select,
7089
+ toggle,
7090
+ isSelected,
7091
+ onFocus,
7092
+ refresh,
7093
+ maxCount,
7094
+ atLimit,
7095
+ addFreeTag
7096
+ };
7097
+ }
7098
+ var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
7099
+ function useFileField(form, field2, opts = {}) {
7100
+ const stored = Array.isArray(field2.value) ? field2.value : [];
7101
+ const [pending2, setPending] = useState([]);
7102
+ const [reject, setReject] = useState(null);
7103
+ const storedRef = useRef(stored);
7104
+ storedRef.current = stored;
7105
+ const items = [
7106
+ ...stored.map((r, i) => ({
7107
+ key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
7108
+ name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
7109
+ size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
7110
+ type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
7111
+ url: r.media ?? r.url,
7112
+ uploading: false,
7113
+ record: r
7114
+ })),
7115
+ ...pending2
7116
+ ];
7117
+ const max = field2.maxCount;
7118
+ const add = useCallback((list) => {
7119
+ setReject(null);
7120
+ const files = Array.from(list);
7121
+ const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
7122
+ if (!room) {
7123
+ setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
7124
+ return;
7125
+ }
7126
+ files.slice(0, room).forEach((file) => {
7127
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7128
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
7129
+ setReject(`Cannot upload files with the extension .${ext}`);
7130
+ return;
7131
+ }
7132
+ if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
7133
+ setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
7134
+ return;
7135
+ }
7136
+ const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
7137
+ if (accept?.length && !accept.some((a) => a.endsWith("/*") ? file.type.startsWith(a.slice(0, -1)) : a.startsWith(".") ? file.name.toLowerCase().endsWith(a) : file.type === a)) {
7138
+ setReject(`${file.name} isn't an allowed file type`);
7139
+ return;
7140
+ }
7141
+ const key = `p${Date.now()}${Math.random()}`;
7142
+ setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
7143
+ uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
7144
+ setPending((p) => p.filter((x) => x.key !== key));
7145
+ const next = [...storedRef.current, rec];
7146
+ storedRef.current = next;
7147
+ form.setValue(field2.id, next);
7148
+ }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
7149
+ });
7150
+ if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
7151
+ }, [form, field2, max, pending2, opts.zappId]);
7152
+ const remove = useCallback((key) => {
7153
+ if (key.startsWith("p")) {
7154
+ setPending((p) => p.filter((x) => x.key !== key));
7155
+ return;
7156
+ }
7157
+ const idx = items.findIndex((i) => i.key === key);
7158
+ if (idx < 0) return;
7159
+ const next = storedRef.current.filter((_2, i) => i !== idx);
7160
+ form.setValue(field2.id, next.length ? next : []);
7161
+ }, [items, form, field2.id]);
7162
+ return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
7163
+ }
7164
+ function sheetAllows(p, action) {
7165
+ const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
7166
+ const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
7167
+ if (!Array.isArray(list) || !list.length) return true;
7168
+ return list.map((x) => String(x).toUpperCase()).includes(action);
7169
+ }
7170
+ function useSheetField(form, field2) {
7171
+ const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
7172
+ const [child, setChild] = useState(null);
7173
+ const [loading, setLoading] = useState(!!childId);
7174
+ const [error, setError] = useState(childId ? null : "No child form configured");
7175
+ useEffect(() => {
7176
+ if (!childId) return;
7177
+ let alive = true;
7178
+ setLoading(true);
7179
+ fetchForm(form.client, childId).then(async (def) => {
7180
+ if (!alive) return;
7181
+ setChild(def);
7182
+ form.registerSheet(field2.id, def);
7183
+ const raw2 = Array.isArray(field2.value) ? field2.value : [];
7184
+ const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
7185
+ if (ids.length) {
7186
+ const rows2 = await fetchRows(form.client, childId, ids);
7187
+ if (alive) form.applyValues({ [field2.id]: rows2 });
7188
+ }
7189
+ }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
7190
+ return () => {
7191
+ alive = false;
7192
+ };
7193
+ }, [childId, form.client]);
7194
+ const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
7195
+ id: e.elementId,
7196
+ label: e.label,
7197
+ type: resolveFieldType(e),
7198
+ required: e.required,
7199
+ element: e,
7200
+ options: (Array.isArray(e.properties?.options) ? e.properties.options : []).map((o) => typeof o === "object" ? { label: String(o.view ?? o.label ?? o.select), value: o.select ?? o.value } : { label: String(o), value: o })
7201
+ })), [child]);
7202
+ const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
7203
+ const p = field2.element.properties ?? {};
7204
+ const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
7205
+ const [limitMessage, setLimitMessage] = useState(null);
7206
+ const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
7207
+ const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
7208
+ const addRow = useCallback((vals) => {
7209
+ if (!addAllowed) return;
7210
+ if (maxRows && rows.length >= maxRows) {
7211
+ setLimitMessage(`Maximum number of rows reached (${maxRows})`);
7212
+ return;
7213
+ }
7214
+ setLimitMessage(null);
7215
+ const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
7216
+ form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
7217
+ }, [child, rows, form, field2.id, addAllowed, maxRows]);
7218
+ const updateRow = useCallback((i, col, v) => {
7219
+ const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
7220
+ form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
7221
+ }, [rows, form, field2.id, child]);
7222
+ const removeRow = useCallback((i) => {
7223
+ if (!canDelete) return;
7224
+ setLimitMessage(null);
7225
+ form.setValue(field2.id, rows.filter((_2, j) => j !== i));
7226
+ }, [rows, form, field2.id, canDelete]);
7227
+ const rowErrors = useMemo(() => {
7228
+ const out = {};
7229
+ if (!form.submitAttempted) return out;
7230
+ rows.forEach((r, i) => {
7231
+ if (!sheetRowNeedsCheck(r)) return;
7232
+ columns.forEach((c) => {
7233
+ if (!sheetCellChecked(c.element)) return;
7234
+ const e = validateField(c.element, r[c.id]);
7235
+ if (e) out[`${i}:${c.id}`] = e;
7236
+ });
7237
+ });
7238
+ return out;
7239
+ }, [rows, columns, form.submitAttempted]);
7240
+ return {
7241
+ loading,
7242
+ error,
7243
+ columns,
7244
+ rows,
7245
+ addRow,
7246
+ updateRow,
7247
+ removeRow,
7248
+ rowErrors,
7249
+ childForm: child,
7250
+ canAdd: addAllowed && (!maxRows || rows.length < maxRows),
7251
+ canDelete,
7252
+ maxRows,
7253
+ limitMessage,
7254
+ display: (c, v) => formatFieldValue(c.element, v)
7255
+ };
7256
+ }
7257
+
7258
+ // src/forms/useZvolvForm.ts
7259
+ function submitElementsOf(elements) {
7260
+ const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
7261
+ return elements.filter((el2) => {
7262
+ const t = resolveFieldType(el2);
7263
+ return !NON_INPUT_TYPES.has(t) || always.has(t);
7264
+ });
7265
+ }
7266
+ var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
7267
+ if (o == null) return null;
7268
+ if (typeof o !== "object") return { label: String(o), value: String(o) };
7269
+ const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
7270
+ const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
7271
+ return { label: String(label ?? ""), value, color: o.color };
7272
+ }).filter(Boolean);
7273
+ var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
7274
+ var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
7275
+ var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
7276
+ function toDayjsFormat(fmt2, withTime) {
7277
+ const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
7278
+ return f.replace(/yyyy/g, "YYYY").replace(/yy/g, "YY").replace(/dd/g, "DD").replace(/(^|[^D])d(?!d)/g, "$1D").replace(/EEE+/g, "ddd").replace(/a/g, "A");
7279
+ }
7280
+ function acceptOf(el2) {
7281
+ const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
7282
+ if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
7283
+ const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
7284
+ if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
7285
+ return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
7286
+ }
7287
+ var FormApiContext = React14.createContext(null);
7288
+ function useFormApi() {
7289
+ const f = React14.useContext(FormApiContext);
7290
+ if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
7291
+ return f;
7292
+ }
7293
+ function useZvolvForm(args) {
7294
+ const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
7295
+ const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
7296
+ const rt = useZvolvRuntime();
7297
+ const client = rt.client;
7298
+ const argsRef = useRef(args);
7299
+ argsRef.current = args;
7300
+ const [definition, setDefinition] = useState(null);
7301
+ const [status, setStatus] = useState("loading");
7302
+ const [error, setError] = useState(null);
7303
+ const [values, setValues] = useState({});
7304
+ const [serverErrors, setServerErrors] = useState({});
6381
7305
  const [touched, setTouched] = useState(/* @__PURE__ */ new Set());
6382
7306
  const [submitAttempted, setSubmitAttempted] = useState(false);
6383
7307
  const [flagged, setFlagged] = useState(/* @__PURE__ */ new Set());
@@ -6390,20 +7314,41 @@ function useZvolvForm(args) {
6390
7314
  const valuesRef = useRef({});
6391
7315
  valuesRef.current = values;
6392
7316
  const sheetDefs = useRef(/* @__PURE__ */ new Map());
6393
- const user = useMemo(() => {
6394
- try {
6395
- return client?.auth?.userInstance ?? null;
6396
- } catch {
6397
- return null;
6398
- }
6399
- }, [client]);
7317
+ const [userTick, setUserTick] = useState(0);
7318
+ useEffect(() => {
7319
+ if (typeof window === "undefined") return;
7320
+ const bump = () => setUserTick((n) => n + 1);
7321
+ window.addEventListener(AUTH_CRYPT_READY, bump);
7322
+ return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
7323
+ }, []);
7324
+ const user = useMemo(() => getStoredUser() ?? null, [client, userTick]);
7325
+ useEffect(() => {
7326
+ if (status !== "ready" || mode !== "NEW") return;
7327
+ const uid = currentUserId(user);
7328
+ if (!uid) return;
7329
+ const fill2 = {};
7330
+ (definition?.elements ?? []).forEach((el2) => {
7331
+ const v = valuesRef.current[el2.elementId];
7332
+ if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
7333
+ });
7334
+ if (Object.keys(fill2).length) setValues((p) => {
7335
+ const m = { ...p, ...fill2 };
7336
+ valuesRef.current = m;
7337
+ return m;
7338
+ });
7339
+ }, [user, status]);
6400
7340
  const onBehalfName = useCallback((v) => {
6401
7341
  if (v == null || v === "") return "";
6402
7342
  if (String(v) === "3122") return "Super Admin";
6403
- if (user && String(v) === currentUserId(user)) return String(user.name ?? user.userName ?? user.fullName ?? user.email ?? v);
7343
+ if (isEncString(v)) return "";
7344
+ if (user && String(v) === currentUserId(user)) {
7345
+ const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
7346
+ return name ? String(name) : String(v);
7347
+ }
6404
7348
  return String(v);
6405
7349
  }, [user]);
6406
- const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef]);
7350
+ const [reloadTick, setReloadTick] = useState(0);
7351
+ const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
6407
7352
  useEffect(() => {
6408
7353
  let alive = true;
6409
7354
  setStatus("loading");
@@ -6630,7 +7575,7 @@ function useZvolvForm(args) {
6630
7575
  value: values[el2.elementId],
6631
7576
  error: showErr ? errors[el2.elementId] : void 0,
6632
7577
  touched: touched.has(el2.elementId),
6633
- options: normalizeOptions(p.options),
7578
+ options: kind === "action" && isActionDropdown(el2) ? (p.actionEleIds ?? []).map((id) => byId.get(String(id))).filter(Boolean).map((o) => ({ label: o.label, value: o.label })) : normalizeOptions(p.options),
6634
7579
  multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
6635
7580
  allowAddOptions: !!p.allowAddOptions,
6636
7581
  min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
@@ -6645,7 +7590,7 @@ function useZvolvForm(args) {
6645
7590
  childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
6646
7591
  pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
6647
7592
  html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
6648
- variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
7593
+ variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
6649
7594
  displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
6650
7595
  dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
6651
7596
  maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
@@ -6704,11 +7649,12 @@ function useZvolvForm(args) {
6704
7649
  saved.push(row);
6705
7650
  continue;
6706
7651
  }
6707
- const { _submissionId, _dirty, _rowKey, ...cells } = row;
7652
+ const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
6708
7653
  if (_submissionId && !_dirty) {
6709
7654
  saved.push(row);
6710
7655
  continue;
6711
7656
  }
7657
+ const cells = computeRowFormulas(child.elements, raw2);
6712
7658
  const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
6713
7659
  saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
6714
7660
  }
@@ -6739,7 +7685,7 @@ function useZvolvForm(args) {
6739
7685
  }
6740
7686
  const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
6741
7687
  const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
6742
- const submitEls = elements.filter((el2) => !NON_INPUT_TYPES.has(resolveFieldType(el2)) || resolveFieldType(el2) === "INPUT_FORMULA" || resolveFieldType(el2) === "ON_BEHALF_OF" || resolveFieldType(el2) === "INPUT_IP" || resolveFieldType(el2) === "INPUT_DEVICE_INFO");
7688
+ const submitEls = submitElementsOf(elements);
6743
7689
  const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
6744
7690
  const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
6745
7691
  setValues(vals);
@@ -6766,435 +7712,400 @@ function useZvolvForm(args) {
6766
7712
  if (definition) setStatus("ready");
6767
7713
  }, [definition]);
6768
7714
  const apiRef = useRef(null);
6769
- const runAction = useCallback(async (field2) => {
7715
+ const runAction = useCallback(async (field2, choice) => {
6770
7716
  const p = field2.element.properties ?? {};
6771
7717
  const subType = String(field2.element.attributes?.subType ?? "");
6772
- const rawType = p.actionType ?? p.subType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "");
6773
- const actionType = String(rawType ?? "").toUpperCase();
7718
+ const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
7719
+ const actionType = String(rawType || p.subType || "").toUpperCase();
7720
+ const value = valuesRef.current[field2.id] ?? field2.value;
7721
+ const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
7722
+ const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
7723
+ const browser = typeof window !== "undefined" && typeof document !== "undefined";
7724
+ if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
7725
+ const pick2 = choice ?? (typeof value === "string" ? value : "");
7726
+ const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
7727
+ if (!target) return { ok: false, message: "No actions found to perform!" };
7728
+ setValue(field2.id, pick2);
7729
+ return runAction(target);
7730
+ }
6774
7731
  if (field2.type === "CASCADED_ACTIONS") {
6775
7732
  for (const id of p.actionEleIds ?? []) {
6776
7733
  const f = fieldMap2.get(String(id));
6777
7734
  if (f) {
6778
- const r2 = await runAction(f);
6779
- if (!r2.ok) return r2;
6780
- }
6781
- }
6782
- if (p.submit) await submit();
6783
- return { ok: true };
6784
- }
6785
- const handled = await argsRef.current.onAction?.(field2, apiRef.current);
6786
- if (handled) return handled;
6787
- const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
6788
- const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
6789
- if (isTrigger) {
6790
- if (!trig) return { ok: false, message: "No automation is linked to this button" };
6791
- return withBusy(field2.id, async () => {
6792
- try {
6793
- const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
6794
- const fd = formDataOf(res);
6795
- if (fd) applyValues(fd);
6796
- const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
6797
- if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
6798
- return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
6799
- } catch (e) {
6800
- return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
6801
- }
6802
- });
6803
- }
6804
- if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
6805
- const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
6806
- if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
6807
- return { ok: !!url, message: url ? void 0 : "No link configured" };
6808
- }
6809
- if (actionType === "SUBMIT" || p.SubmitButton) {
6810
- const r2 = await submit();
6811
- return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
6812
- }
6813
- const HOST = [
6814
- "SAVE_SUBMISSION",
6815
- "CREATE_SUBMISSION",
6816
- "EDIT_SUBMISSION",
6817
- "OPEN_TASK",
6818
- "PRINT_PREVIEW",
6819
- "DOWNLOAD_FILE",
6820
- "UPLOAD_FILE",
6821
- "OPEN_PROJECT",
6822
- "INVOKE_AGENT",
6823
- "AGENT",
6824
- "FLOW"
6825
- ];
6826
- if (HOST.includes(actionType)) {
6827
- return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
6828
- }
6829
- setValue(field2.id, true);
6830
- const r = await submit();
6831
- return { ok: !!r, message: r ? p.successMessage : void 0 };
6832
- }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
6833
- const registerSheet = useCallback((id, def) => {
6834
- sheetDefs.current.set(id, def);
6835
- }, []);
6836
- const api = {
6837
- status,
6838
- error,
6839
- definition,
6840
- mode,
6841
- readOnly,
6842
- values,
6843
- errors,
6844
- fields,
6845
- rootFields,
6846
- field: (id) => fieldMap2.get(id),
6847
- childrenOf,
6848
- setValue,
6849
- applyValues,
6850
- dirty,
6851
- submitting,
6852
- submitAttempted,
6853
- submitError,
6854
- submittedId,
6855
- validate,
6856
- submit,
6857
- reset,
6858
- runAction,
6859
- busy,
6860
- client,
6861
- registerSheet
6862
- };
6863
- apiRef.current = api;
6864
- return api;
6865
- }
6866
- var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6867
- var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6868
- function useFieldOptions(form, field2, opts = {}) {
6869
- const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6870
- const el2 = field2.element;
6871
- const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6872
- const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6873
- const elements = form.definition?.elements ?? [];
6874
- const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6875
- const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6876
- const depKey = JSON.stringify(dep.filters);
6877
- const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6878
- const single = maxCount === 1;
6879
- const [search, setSearch] = useState("");
6880
- const [q, setQ] = useState("");
6881
- useEffect(() => {
6882
- const t = setTimeout(() => setQ(search), debounceMs);
6883
- return () => clearTimeout(t);
6884
- }, [search, debounceMs]);
6885
- const [remote, setRemote] = useState([]);
6886
- const [loading, setLoading] = useState(false);
6887
- const [error, setError] = useState(null);
6888
- const [opened, setOpened] = useState(loadOn === "mount");
6889
- const [nonce, setNonce] = useState(0);
6890
- const lastFetch = useRef(0);
6891
- const maxAge = useRef(1e4);
6892
- const waitingFor = useMemo(() => {
6893
- if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6894
- const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6895
- const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6896
- return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6897
- }, [cfg, dep.satisfied, rules, form]);
6898
- const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6899
- const active = opened || depDriven || q !== "";
6900
- useEffect(() => {
6901
- if (!cfg && !people) return;
6902
- if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6903
- setRemote([]);
6904
- return;
6905
- }
6906
- if (!active) return;
6907
- let alive = true;
6908
- setLoading(true);
6909
- setError(null);
6910
- lastFetch.current = Date.now();
6911
- const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6912
- maxAge.current = 1e4;
6913
- p.then((o) => {
6914
- if (!alive) return;
6915
- setRemote(o);
6916
- if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6917
- }).catch((e) => {
6918
- if (alive) setError(e?.message ?? "Couldn't load options");
6919
- }).finally(() => {
6920
- if (alive) setLoading(false);
6921
- });
6922
- return () => {
6923
- alive = false;
6924
- };
6925
- }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6926
- const refresh = useCallback(() => {
6927
- maxAge.current = 0;
6928
- setOpened(true);
6929
- setNonce((n) => n + 1);
6930
- }, []);
6931
- const onFocus = useCallback(() => {
6932
- if (!cfg && !people) return;
6933
- if (!opened) {
6934
- setOpened(true);
6935
- return;
6936
- }
6937
- if (cfg && Date.now() - lastFetch.current > 2e3) {
6938
- maxAge.current = 2e3;
6939
- setNonce((n) => n + 1);
6940
- } else if (people && !remote.length) setNonce((n) => n + 1);
6941
- }, [cfg, people, opened, remote.length]);
6942
- const prevDepKey = useRef(null);
6943
- useEffect(() => {
6944
- if (!cfg || !rules.length) return;
6945
- if (prevDepKey.current === null) {
6946
- prevDepKey.current = depKey;
6947
- return;
6948
- }
6949
- if (prevDepKey.current === depKey) return;
6950
- prevDepKey.current = depKey;
6951
- if (form.readOnly) return;
6952
- if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
6953
- }, [depKey]);
6954
- const staticFiltered = useMemo(() => {
6955
- if (cfg || people) return remote;
6956
- const needle = search.trim().toLowerCase();
6957
- return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
6958
- }, [cfg, people, remote, field2.options, search]);
6959
- const pickedCache = useRef(/* @__PURE__ */ new Map());
6960
- const current = field2.value;
6961
- const selected = useMemo(() => {
6962
- const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
6963
- const pool = [...staticFiltered, ...field2.options];
6964
- return arr.map((v) => {
6965
- const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
6966
- if (hit) return hit;
6967
- if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
6968
- return { label: String(v), value: v };
6969
- });
6970
- }, [current, staticFiltered, field2.options]);
6971
- const store = useCallback((picked) => {
6972
- picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
6973
- const t = field2.type;
6974
- if (field2.kind === "lookup") {
6975
- const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
6976
- form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
6977
- if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
6978
- const fill2 = {};
6979
- cfg.extraLabels.forEach((label, i) => {
6980
- const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
6981
- const v = picked[0].extra?.[i];
6982
- if (target && v !== void 0) fill2[target.elementId] = v;
6983
- });
6984
- if (Object.keys(fill2).length) form.applyValues(fill2);
6985
- }
6986
- return;
6987
- }
6988
- if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
6989
- else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
6990
- }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
6991
- const autoPickRef = useRef(() => {
6992
- });
6993
- const lastAutoKey = useRef(null);
6994
- autoPickRef.current = (o, key) => {
6995
- if (form.readOnly || lastAutoKey.current === key) return;
6996
- lastAutoKey.current = key;
6997
- if (isFilled(form.values[field2.id])) return;
6998
- store([o]);
6999
- };
7000
- const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
7001
- const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
7002
- const atLimit = field2.multiple && selected.length >= maxCount;
7003
- const toggle = useCallback((o) => {
7004
- const on = isSelected(o);
7005
- if (!on && field2.multiple && selected.length >= maxCount) return;
7006
- store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
7007
- }, [isSelected, selected, store, field2.multiple, maxCount]);
7008
- const addFreeTag = useCallback((text) => {
7009
- const t = text.trim();
7010
- if (!t || field2.type !== "INPUT_TAG") return;
7011
- const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
7012
- if (selected.some((s) => optionKey(s.value) === t)) return;
7013
- if (field2.multiple) {
7014
- if (selected.length < maxCount) store([...selected, o]);
7015
- } else store([o]);
7016
- setSearch("");
7017
- }, [field2.type, field2.multiple, selected, maxCount, store]);
7018
- return {
7019
- options: staticFiltered,
7020
- loading,
7021
- error,
7022
- search,
7023
- setSearch,
7024
- waitingFor,
7025
- selected,
7026
- select,
7027
- toggle,
7028
- isSelected,
7029
- onFocus,
7030
- refresh,
7031
- maxCount,
7032
- atLimit,
7033
- addFreeTag
7034
- };
7035
- }
7036
- var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
7037
- function useFileField(form, field2, opts = {}) {
7038
- const stored = Array.isArray(field2.value) ? field2.value : [];
7039
- const [pending2, setPending] = useState([]);
7040
- const [reject, setReject] = useState(null);
7041
- const storedRef = useRef(stored);
7042
- storedRef.current = stored;
7043
- const items = [
7044
- ...stored.map((r, i) => ({
7045
- key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
7046
- name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
7047
- size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
7048
- type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
7049
- url: r.media ?? r.url,
7050
- uploading: false,
7051
- record: r
7052
- })),
7053
- ...pending2
7054
- ];
7055
- const max = field2.maxCount;
7056
- const add = useCallback((list) => {
7057
- setReject(null);
7058
- const files = Array.from(list);
7059
- const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
7060
- if (!room) {
7061
- setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
7062
- return;
7735
+ const r = await runAction(f);
7736
+ if (!r.ok) return r;
7737
+ }
7738
+ }
7739
+ if (p.submit) await submit();
7740
+ return { ok: true };
7063
7741
  }
7064
- files.slice(0, room).forEach((file) => {
7065
- const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7066
- if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
7067
- setReject(`Cannot upload files with the extension .${ext}`);
7068
- return;
7742
+ const handled = await argsRef.current.onAction?.(field2, apiRef.current);
7743
+ if (handled) return handled;
7744
+ const afterAutomation = (res) => {
7745
+ const fd = formDataOf(res);
7746
+ if (fd) applyValues(fd);
7747
+ if (p.refreshForm && (submissionId || submittedId)) {
7748
+ const delay = Number(p.refreshFormDelay) || 0;
7749
+ setTimeout(() => setReloadTick((n) => n + 1), delay);
7069
7750
  }
7070
- if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
7071
- setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
7072
- return;
7751
+ };
7752
+ const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
7753
+ const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
7754
+ if (isTrigger) {
7755
+ const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
7756
+ if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
7757
+ if (!zeno && !trig) return fail("Automation ID missing from action");
7758
+ return withBusy(field2.id, async () => {
7759
+ try {
7760
+ const body = triggerBody(valuesRef.current);
7761
+ const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
7762
+ const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
7763
+ if (failed) {
7764
+ const fd = formDataOf(res);
7765
+ if (fd) applyValues(fd);
7766
+ return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
7767
+ }
7768
+ afterAutomation(res);
7769
+ return ok(res?.comment?.message ?? res?.message);
7770
+ } catch (e) {
7771
+ return fail(e?.message ?? `${field2.label} failed`);
7772
+ }
7773
+ });
7774
+ }
7775
+ switch (actionType) {
7776
+ case "":
7777
+ case "OPEN_URL":
7778
+ case "URL": {
7779
+ const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
7780
+ if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
7781
+ return url ? { ok: true } : fail("No link configured");
7073
7782
  }
7074
- const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
7075
- if (accept?.length && !accept.some((a) => a.endsWith("/*") ? file.type.startsWith(a.slice(0, -1)) : a.startsWith(".") ? file.name.toLowerCase().endsWith(a) : file.type === a)) {
7076
- setReject(`${file.name} isn't an allowed file type`);
7077
- return;
7783
+ case "SUBMIT": {
7784
+ const r = await submit();
7785
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7786
+ }
7787
+ case "INVOKE_AGENT": {
7788
+ if (!p.agentId) return fail("Agent ID missing from action");
7789
+ return withBusy(field2.id, async () => {
7790
+ try {
7791
+ const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
7792
+ const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
7793
+ if (!res) return fail(`${field2.label} failed`);
7794
+ const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
7795
+ if (fd) applyValues(fd);
7796
+ return p.successMessage ? ok() : { ok: true };
7797
+ } catch (e) {
7798
+ return fail(e?.message ?? `${field2.label} failed`);
7799
+ }
7800
+ });
7801
+ }
7802
+ case "DOWNLOAD_FILE": {
7803
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7804
+ if (!file) return fail("File URL missing from action");
7805
+ if (browser) {
7806
+ const a = document.createElement("a");
7807
+ a.href = String(file);
7808
+ a.download = p.fileName ?? "";
7809
+ a.target = "_blank";
7810
+ a.rel = "noopener";
7811
+ a.click();
7812
+ }
7813
+ return { ok: true };
7814
+ }
7815
+ case "PRINT_PREVIEW": {
7816
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7817
+ if (!file) return fail("File URL missing from action");
7818
+ if (!browser) return fail("Printing needs a browser");
7819
+ return withBusy(field2.id, async () => {
7820
+ try {
7821
+ const blob = await (await fetch(String(file))).blob();
7822
+ const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
7823
+ const frame = document.createElement("iframe");
7824
+ frame.style.display = "none";
7825
+ frame.src = url;
7826
+ document.body.appendChild(frame);
7827
+ frame.onload = () => frame.contentWindow?.print();
7828
+ return { ok: true };
7829
+ } catch {
7830
+ return fail("Failed to load file for printing");
7831
+ }
7832
+ });
7833
+ }
7834
+ case "UPLOAD_FILE": {
7835
+ if (!browser) return fail("Uploading needs a browser");
7836
+ const file = await new Promise((resolve) => {
7837
+ const input = document.createElement("input");
7838
+ input.type = "file";
7839
+ input.onchange = () => resolve(input.files?.[0] ?? null);
7840
+ input.click();
7841
+ });
7842
+ if (!file) return fail("File upload failed");
7843
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7844
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
7845
+ const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
7846
+ if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
7847
+ return withBusy(field2.id, async () => {
7848
+ try {
7849
+ const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
7850
+ setValue(field2.id, rec.media);
7851
+ return ok("File successfully uploaded");
7852
+ } catch (e) {
7853
+ return fail(e?.message ?? "File upload failed");
7854
+ }
7855
+ });
7856
+ }
7857
+ case "CREATE_SUBMISSION":
7858
+ case "EDIT_SUBMISSION":
7859
+ case "SAVE_SUBMISSION":
7860
+ case "OPEN_TASK":
7861
+ case "OPEN_PROJECT":
7862
+ case "AGENT":
7863
+ case "FLOW":
7864
+ return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
7865
+ default: {
7866
+ setValue(field2.id, true);
7867
+ const r = await submit();
7868
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7078
7869
  }
7079
- const key = `p${Date.now()}${Math.random()}`;
7080
- setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
7081
- uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
7082
- setPending((p) => p.filter((x) => x.key !== key));
7083
- const next = [...storedRef.current, rec];
7084
- storedRef.current = next;
7085
- form.setValue(field2.id, next);
7086
- }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
7087
- });
7088
- if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
7089
- }, [form, field2, max, pending2, opts.zappId]);
7090
- const remove = useCallback((key) => {
7091
- if (key.startsWith("p")) {
7092
- setPending((p) => p.filter((x) => x.key !== key));
7093
- return;
7094
7870
  }
7095
- const idx = items.findIndex((i) => i.key === key);
7096
- if (idx < 0) return;
7097
- const next = storedRef.current.filter((_2, i) => i !== idx);
7098
- form.setValue(field2.id, next.length ? next : []);
7099
- }, [items, form, field2.id]);
7100
- return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
7871
+ }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
7872
+ const reload = useCallback(() => setReloadTick((n) => n + 1), []);
7873
+ const [sheetVersion, setSheetVersion] = useState(0);
7874
+ const registerSheet = useCallback((id, def) => {
7875
+ if (sheetDefs.current.get(id) === def) return;
7876
+ sheetDefs.current.set(id, def);
7877
+ setSheetVersion((n) => n + 1);
7878
+ }, []);
7879
+ const sheetDefinition = useCallback((id) => sheetDefs.current.get(id), []);
7880
+ const api = {
7881
+ status,
7882
+ error,
7883
+ definition,
7884
+ mode,
7885
+ readOnly,
7886
+ values,
7887
+ errors,
7888
+ fields,
7889
+ rootFields,
7890
+ field: (id) => fieldMap2.get(id),
7891
+ childrenOf,
7892
+ setValue,
7893
+ applyValues,
7894
+ dirty,
7895
+ submitting,
7896
+ submitAttempted,
7897
+ submitError,
7898
+ submittedId,
7899
+ validate,
7900
+ submit,
7901
+ reset,
7902
+ runAction,
7903
+ reload,
7904
+ busy,
7905
+ client,
7906
+ registerSheet,
7907
+ sheetDefinition,
7908
+ sheetVersion
7909
+ };
7910
+ apiRef.current = api;
7911
+ return api;
7101
7912
  }
7102
- function sheetAllows(p, action) {
7103
- const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
7104
- const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
7105
- if (!Array.isArray(list) || !list.length) return true;
7106
- return list.map((x) => String(x).toUpperCase()).includes(action);
7913
+ function openFormRequest(field2, form) {
7914
+ const p = field2.element.properties ?? {};
7915
+ const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
7916
+ const value = form.values[field2.id] ?? field2.value;
7917
+ const has2 = value != null && value !== "" && value !== true;
7918
+ const formId = p.formId ?? p.formID;
7919
+ switch (t) {
7920
+ case "CREATE_SUBMISSION":
7921
+ return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
7922
+ case "EDIT_SUBMISSION":
7923
+ case "OPEN_TASK":
7924
+ return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
7925
+ case "OPEN_PROJECT":
7926
+ return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
7927
+ case "SAVE_SUBMISSION": {
7928
+ if (!formId) return null;
7929
+ const defaults = {};
7930
+ (Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
7931
+ if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
7932
+ });
7933
+ return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
7934
+ }
7935
+ default:
7936
+ return null;
7937
+ }
7107
7938
  }
7108
- function useSheetField(form, field2) {
7109
- const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
7110
- const [child, setChild] = useState(null);
7111
- const [loading, setLoading] = useState(!!childId);
7112
- const [error, setError] = useState(childId ? null : "No child form configured");
7939
+ var CORRECT = ["IND", "NR", "CHECK"];
7940
+ var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
7941
+ var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
7942
+ var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
7943
+ label,
7944
+ icon: c?.icon,
7945
+ color: c?.color,
7946
+ hideLabel: !!c?.hideLabel,
7947
+ disabled: !!c?.isDisabled,
7948
+ prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
7949
+ }));
7950
+ function useTrialField(form, field2) {
7951
+ const t = field2.type;
7952
+ const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
7953
+ const props = field2.element.properties ?? {};
7954
+ const value = field2.value && typeof field2.value === "object" ? field2.value : null;
7955
+ const conf = useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
7956
+ const disabled = field2.disabled || form.readOnly;
7957
+ const set = (v) => form.setValue(field2.id, v);
7958
+ const [active, setActive] = useState();
7959
+ const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
7960
+ const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7961
+ const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
7962
+ const linkedTimer = () => {
7963
+ const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
7964
+ return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
7965
+ };
7966
+ const timeLimit = Number(conf.timeLimit ?? 300);
7967
+ const interval = Number(conf.timeInterval ?? 30) || 30;
7968
+ const [timer, setTimer] = useState(Number(value?.lastStatus?.timer ?? 0));
7969
+ const [running, setRunning] = useState(false);
7970
+ const timerRef = useRef(timer);
7971
+ timerRef.current = timer;
7113
7972
  useEffect(() => {
7114
- if (!childId) return;
7115
- let alive = true;
7116
- setLoading(true);
7117
- fetchForm(form.client, childId).then(async (def) => {
7118
- if (!alive) return;
7119
- setChild(def);
7120
- form.registerSheet(field2.id, def);
7121
- const raw2 = Array.isArray(field2.value) ? field2.value : [];
7122
- const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
7123
- if (ids.length) {
7124
- const rows2 = await fetchRows(form.client, childId, ids);
7125
- if (alive) form.applyValues({ [field2.id]: rows2 });
7973
+ if (!running) return;
7974
+ const id = setInterval(() => setTimer((p) => {
7975
+ const n = p + 1;
7976
+ timerRef.current = n;
7977
+ if (n >= timeLimit) setRunning(false);
7978
+ return n;
7979
+ }), 1e3);
7980
+ return () => clearInterval(id);
7981
+ }, [running, timeLimit]);
7982
+ const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7983
+ const bound = (s) => {
7984
+ const i = Math.floor(s / interval);
7985
+ return `${i * interval}-${(i + 1) * interval}`;
7986
+ };
7987
+ const tsEmit = (resp, at) => set({
7988
+ conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
7989
+ buttonResp: resp,
7990
+ lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
7991
+ });
7992
+ const steps = useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
7993
+ const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
7994
+ const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
7995
+ const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
7996
+ const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
7997
+ const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
7998
+ const stepName = steps[taStep] ?? "";
7999
+ const taEmit = (resp, trial, step) => set({
8000
+ conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
8001
+ buttonResp: resp,
8002
+ currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
8003
+ });
8004
+ const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
8005
+ const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
8006
+ const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
8007
+ const buttons = useMemo(() => toButtons(rb), [rb]);
8008
+ const record = useCallback((response, promptLabel) => {
8009
+ if (disabled) return;
8010
+ if (type === "percent") {
8011
+ if (pcTrial >= maxTrials) return;
8012
+ const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
8013
+ const next = pcTrial + 1;
8014
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
8015
+ } else if (type === "time") {
8016
+ const at = timerRef.current;
8017
+ tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
8018
+ } else {
8019
+ if (taTrial >= maxTrials || !steps.length) return;
8020
+ const key = `Trial ${taTrial}`;
8021
+ const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
8022
+ let step = taStep, trial = taTrial;
8023
+ if (chain === "backward") {
8024
+ step -= 1;
8025
+ if (step < 0) {
8026
+ step = steps.length - 1;
8027
+ trial += 1;
8028
+ }
8029
+ } else {
8030
+ step += 1;
8031
+ if (step >= steps.length) {
8032
+ step = 0;
8033
+ trial += 1;
8034
+ }
7126
8035
  }
7127
- }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
7128
- return () => {
7129
- alive = false;
7130
- };
7131
- }, [childId, form.client]);
7132
- const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
7133
- id: e.elementId,
7134
- label: e.label,
7135
- type: resolveFieldType(e),
7136
- required: e.required,
7137
- element: e,
7138
- options: (Array.isArray(e.properties?.options) ? e.properties.options : []).map((o) => typeof o === "object" ? { label: String(o.view ?? o.label ?? o.select), value: o.select ?? o.value } : { label: String(o), value: o })
7139
- })), [child]);
7140
- const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
7141
- const p = field2.element.properties ?? {};
7142
- const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
7143
- const [limitMessage, setLimitMessage] = useState(null);
7144
- const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
7145
- const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
7146
- const addRow = useCallback((vals) => {
7147
- if (!addAllowed) return;
7148
- if (maxRows && rows.length >= maxRows) {
7149
- setLimitMessage(`Maximum number of rows reached (${maxRows})`);
7150
- return;
7151
- }
7152
- setLimitMessage(null);
7153
- const blank = child ? buildInitialValues(child.elements, { defaults: vals }) : { ...vals ?? {} };
7154
- form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
7155
- }, [child, rows, form, field2.id, addAllowed, maxRows]);
7156
- const updateRow = useCallback((i, col, v) => {
7157
- form.setValue(field2.id, rows.map((r, j) => j === i ? { ...r, [col]: v, _dirty: true } : r));
7158
- }, [rows, form, field2.id]);
7159
- const removeRow = useCallback((i) => {
7160
- if (!canDelete) return;
7161
- setLimitMessage(null);
7162
- form.setValue(field2.id, rows.filter((_2, j) => j !== i));
7163
- }, [rows, form, field2.id, canDelete]);
7164
- const rowErrors = useMemo(() => {
7165
- const out = {};
7166
- if (!form.submitAttempted) return out;
7167
- rows.forEach((r, i) => {
7168
- if (!sheetRowNeedsCheck(r)) return;
7169
- columns.forEach((c) => {
7170
- if (!sheetCellChecked(c.element)) return;
7171
- const e = validateField(c.element, r[c.id]);
7172
- if (e) out[`${i}:${c.id}`] = e;
7173
- });
7174
- });
7175
- return out;
7176
- }, [rows, columns, form.submitAttempted]);
8036
+ taEmit(resp, trial, step);
8037
+ }
8038
+ setActive(void 0);
8039
+ }, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
8040
+ const respond = useCallback((label) => {
8041
+ const b = buttons.find((x) => x.label === label);
8042
+ if (!b || b.disabled) return;
8043
+ if (b.prompts.length) setActive(label);
8044
+ else record(label);
8045
+ }, [buttons, record]);
8046
+ const prompt = useCallback((label) => {
8047
+ if (active) record(active, label);
8048
+ }, [active, record]);
8049
+ const prev = useCallback(() => {
8050
+ if (disabled) return;
8051
+ if (type === "percent" && pcTrial > 0) {
8052
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
8053
+ } else if (type === "task" && taTrial > 0) {
8054
+ const resp = { ...taResp };
8055
+ delete resp[`Trial ${taTrial}`];
8056
+ taEmit(resp, taTrial - 1, firstStep);
8057
+ }
8058
+ }, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
8059
+ const reset = useCallback(() => {
8060
+ if (type !== "time" || disabled) return;
8061
+ setTimer(0);
8062
+ setRunning(false);
8063
+ setActive(void 0);
8064
+ tsEmit([], 0);
8065
+ }, [type, disabled, value, conf]);
8066
+ const allResp = type === "task" ? Object.entries(taResp).flatMap(([trial, arr]) => (Array.isArray(arr) ? arr : []).map((r) => ({ r, trial }))) : (type === "time" ? tsResp : pcResp).map((r) => ({ r }));
8067
+ const log = allResp.map(({ r, trial }, i) => ({
8068
+ label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
8069
+ correct: isCorrect(r.responseButtonPressed),
8070
+ detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
8071
+ }));
8072
+ const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
7177
8073
  return {
7178
- loading,
7179
- error,
7180
- columns,
7181
- rows,
7182
- addRow,
7183
- updateRow,
7184
- removeRow,
7185
- rowErrors,
7186
- childForm: child,
7187
- canAdd: addAllowed && (!maxRows || rows.length < maxRows),
7188
- canDelete,
7189
- maxRows,
7190
- limitMessage,
7191
- display: (c, v) => formatFieldValue(c.element, v)
8074
+ type,
8075
+ buttons,
8076
+ activeResponse: active,
8077
+ prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
8078
+ respond,
8079
+ prompt,
8080
+ cancelPrompt: () => setActive(void 0),
8081
+ prev,
8082
+ currentTrial,
8083
+ maxTrials,
8084
+ complete,
8085
+ percentCorrect,
8086
+ log,
8087
+ disabled,
8088
+ timer,
8089
+ formattedTimer: fmt(timer),
8090
+ running,
8091
+ start: () => {
8092
+ if (!disabled && timer < timeLimit) setRunning(true);
8093
+ },
8094
+ pause: () => setRunning(false),
8095
+ reset,
8096
+ interval,
8097
+ intervalCount: Math.floor(timer / interval),
8098
+ timeLimit,
8099
+ steps,
8100
+ currentStep: taStep,
8101
+ stepName,
8102
+ chain
7192
8103
  };
7193
8104
  }
7194
- var FormKitSlot = React13.createContext(null);
8105
+ var FormKitSlot = React14.createContext(null);
7195
8106
  function useKit(local) {
7196
- const ctx = React13.useContext(FormKitSlot);
7197
- return React13.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
8107
+ const ctx = React14.useContext(FormKitSlot);
8108
+ return React14.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
7198
8109
  }
7199
8110
  var FormFields = ({ form, fields, kit: local }) => {
7200
8111
  const kit = useKit(local);
@@ -7332,6 +8243,33 @@ var DefaultSheet = ({ field: field2, form }) => {
7332
8243
  s.limitMessage && /* @__PURE__ */ jsx("div", { role: "alert", children: s.limitMessage })
7333
8244
  ] });
7334
8245
  };
8246
+ var DefaultTrials = ({ field: field2, form }) => {
8247
+ const t = useTrialField(form, field2);
8248
+ return /* @__PURE__ */ jsxs("div", { children: [
8249
+ /* @__PURE__ */ jsxs("div", { children: [
8250
+ t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
8251
+ t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
8252
+ " \xB7 ",
8253
+ t.percentCorrect,
8254
+ "% correct",
8255
+ t.complete ? " \xB7 complete" : ""
8256
+ ] }),
8257
+ t.type === "time" && /* @__PURE__ */ jsxs("span", { children: [
8258
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
8259
+ " ",
8260
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
8261
+ ] }),
8262
+ t.type !== "time" && /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
8263
+ /* @__PURE__ */ jsxs("div", { children: [
8264
+ (t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: () => t.activeResponse ? t.prompt(b.label) : t.respond(b.label), children: b.label }, b.label)),
8265
+ t.activeResponse && /* @__PURE__ */ jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
8266
+ ] }),
8267
+ /* @__PURE__ */ jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
8268
+ l.label,
8269
+ l.detail ? ` ${l.detail}` : ""
8270
+ ] }, i)) })
8271
+ ] });
8272
+ };
7335
8273
  var DefaultField = ({ field: field2, form }) => {
7336
8274
  const f = field2;
7337
8275
  const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
@@ -7360,7 +8298,20 @@ var DefaultField = ({ field: field2, form }) => {
7360
8298
  case "display":
7361
8299
  return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
7362
8300
  case "action":
8301
+ if (f.variant === "dropdown") {
8302
+ return /* @__PURE__ */ jsxs("label", { children: [
8303
+ f.label,
8304
+ " ",
8305
+ /* @__PURE__ */ jsxs("select", { disabled: f.disabled, value: typeof f.value === "string" ? f.value : "", onChange: (e) => e.target.value && void form.runAction(f, e.target.value), children: [
8306
+ /* @__PURE__ */ jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
8307
+ f.options.map((o) => /* @__PURE__ */ jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
8308
+ ] })
8309
+ ] });
8310
+ }
7363
8311
  return /* @__PURE__ */ jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
8312
+ case "trials":
8313
+ control = /* @__PURE__ */ jsx(DefaultTrials, { field: f, form });
8314
+ break;
7364
8315
  case "checkbox":
7365
8316
  control = /* @__PURE__ */ jsxs("label", { children: [
7366
8317
  /* @__PURE__ */ jsx("input", { id: f.id, type: "checkbox", role: f.variant === "switch" ? "switch" : void 0, disabled: f.disabled, checked: f.value === true, onChange: (e) => f.setValue(e.target.checked), onBlur: f.onBlur }),
@@ -7866,7 +8817,7 @@ function buildDashFilters(values = [], dateRange, operator = "AND") {
7866
8817
  }
7867
8818
  function dateRangeFor(token) {
7868
8819
  const f = (d) => d.format("YYYY-MM-DD");
7869
- const now = dayjs8();
8820
+ const now = dayjs4();
7870
8821
  switch (token) {
7871
8822
  case "TODAY":
7872
8823
  return { start: f(now), end: f(now) };
@@ -7938,6 +8889,232 @@ function buildDashboardTree(elements, isAdmin = false) {
7938
8889
  });
7939
8890
  return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
7940
8891
  }
8892
+ var text = (v) => {
8893
+ if (v === null || v === void 0 || v === "") return "";
8894
+ if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
8895
+ if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
8896
+ return String(v);
8897
+ };
8898
+ var effectOf = (action) => action.startsWith("SHOW") ? "show" : action.startsWith("HIDE") ? "hide" : action.startsWith("MARK_MANDATORY") ? "required" : action.startsWith("MARK_NON") ? "optional" : action === "EDITABLE" ? "editable" : action === "NON_EDITABLE" ? "read-only" : null;
8899
+ function expectedOf(dep, labelOf) {
8900
+ const conds = dep.conditions ?? dep.Conditions ?? [];
8901
+ if (conds.length) {
8902
+ const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
8903
+ return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
8904
+ }
8905
+ if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
8906
+ return `= ${text(dep.value) || "(empty)"}`;
8907
+ }
8908
+ function checksOf(el2, required) {
8909
+ const t = resolveFieldType(el2);
8910
+ const p = el2.properties ?? {};
8911
+ const out = [];
8912
+ if (required) out.push("required");
8913
+ if (t === "INPUT_EMAIL") out.push("email");
8914
+ if (t === "INPUT_URL") out.push("url");
8915
+ if (t === "INPUT_PHONE") out.push("phone");
8916
+ if (t === "INPUT_PERCENT") out.push("0\u2013100");
8917
+ const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
8918
+ if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
8919
+ if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
8920
+ const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
8921
+ if (len) out.push(`\u2264 ${len} chars`);
8922
+ if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
8923
+ (el2.validations ?? []).forEach((v) => {
8924
+ const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
8925
+ if (k === "regex") out.push("pattern");
8926
+ else if (k === "expression") out.push("expression");
8927
+ else if (k) out.push(`${k} (server)`);
8928
+ });
8929
+ return out;
8930
+ }
8931
+ var sameNumber = (a, b) => {
8932
+ if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
8933
+ const na = Number(a), nb = Number(b);
8934
+ return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
8935
+ };
8936
+ function formulaRows(elements, values, scope, extra = {}) {
8937
+ const labels = elements.map((e) => e.label).filter(Boolean);
8938
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
8939
+ return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
8940
+ const formula = String(e.properties.formula);
8941
+ const refs = formulaLabels(formula, labels);
8942
+ const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
8943
+ const expected = extra.expectedRow ? extra.expectedRow[e.elementId] : refs.length && inputs.every((i) => i.value === null || i.value === "") ? null : evaluateFormula(formula, (l) => values[idOf2.get(l) ?? l], labels);
8944
+ const current = values[e.elementId] ?? null;
8945
+ return {
8946
+ fieldId: e.elementId,
8947
+ label: e.label,
8948
+ scope,
8949
+ sheetLabel: extra.sheetLabel,
8950
+ row: extra.row,
8951
+ formula,
8952
+ serverFormula: toBraceFormula(formula, labels),
8953
+ serverSafe: isServerSafeFormula(formula, labels),
8954
+ inputs,
8955
+ expected,
8956
+ current,
8957
+ inSync: sameNumber(expected, current)
8958
+ };
8959
+ });
8960
+ }
8961
+ function inspectForm(form) {
8962
+ const def = form.definition;
8963
+ if (!def) {
8964
+ return {
8965
+ rules: [],
8966
+ filters: [],
8967
+ validation: [],
8968
+ formulas: [],
8969
+ defaults: [],
8970
+ payload: null,
8971
+ summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
8972
+ };
8973
+ }
8974
+ const elements = def.elements;
8975
+ const values = form.values;
8976
+ const byId = new Map(elements.map((e) => [e.elementId, e]));
8977
+ const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
8978
+ const rules = [];
8979
+ elements.forEach((el2) => {
8980
+ const origin = el2.properties?.triage ? "triage" : "dependencies";
8981
+ rulesOf(el2).forEach((dep) => {
8982
+ const action = String(dep.dependencyType ?? dep.type ?? "");
8983
+ const effect = effectOf(action);
8984
+ const target = String(dep.elementId ?? dep.targetElementId ?? "");
8985
+ if (!effect || !target) return;
8986
+ rules.push({
8987
+ sourceId: el2.elementId,
8988
+ sourceLabel: el2.label,
8989
+ sourceValue: text(values[el2.elementId]) || "(empty)",
8990
+ action,
8991
+ effect,
8992
+ targetId: target,
8993
+ targetLabel: labelOf(target),
8994
+ expected: expectedOf(dep, labelOf),
8995
+ matched: ruleMatches(el2, dep, values),
8996
+ origin
8997
+ });
8998
+ });
8999
+ });
9000
+ const filters2 = [];
9001
+ elements.forEach((el2) => {
9002
+ const cfg = lookupConfigOf(el2);
9003
+ if (!cfg) return;
9004
+ const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
9005
+ optionFilterRules(elements, el2.elementId).forEach((r) => {
9006
+ const v = text(values[r.elementId]);
9007
+ filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
9008
+ });
9009
+ cfg.dynamicFilters.forEach((df) => {
9010
+ const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
9011
+ const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
9012
+ if (!dep) return;
9013
+ const src = elements.find((e) => e.label === dep);
9014
+ const v = src ? text(values[src.elementId]) : "";
9015
+ filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
9016
+ });
9017
+ cfg.staticFilters.forEach((sf) => {
9018
+ const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
9019
+ filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
9020
+ });
9021
+ });
9022
+ const validation = [];
9023
+ form.fields.forEach((f) => {
9024
+ const t = resolveFieldType(f.element);
9025
+ if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
9026
+ validation.push({
9027
+ id: f.id,
9028
+ label: f.label,
9029
+ type: t,
9030
+ required: f.required,
9031
+ hidden: f.hidden,
9032
+ checks: checksOf(f.element, f.required),
9033
+ value: formatFieldValue(f.element, f.value),
9034
+ error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
9035
+ shownError: f.error
9036
+ });
9037
+ });
9038
+ const formulas = formulaRows(elements, values, "form");
9039
+ const sheets = [];
9040
+ elements.forEach((el2) => {
9041
+ const t = resolveFieldType(el2);
9042
+ if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
9043
+ const child = form.sheetDefinition(el2.elementId);
9044
+ if (!child) return;
9045
+ const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
9046
+ const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
9047
+ rows.forEach((row, i) => {
9048
+ const { _submissionId, _dirty, _rowKey, ...cells } = row;
9049
+ const scope = `${el2.label} \xB7 row ${i + 1}`;
9050
+ if (sheetRowNeedsCheck(row)) {
9051
+ child.elements.forEach((c) => {
9052
+ if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
9053
+ validation.push({
9054
+ id: `${el2.elementId}:${i}:${c.elementId}`,
9055
+ label: c.label,
9056
+ type: resolveFieldType(c),
9057
+ scope,
9058
+ required: !!c.required,
9059
+ hidden: false,
9060
+ checks: checksOf(c, !!c.required),
9061
+ value: formatFieldValue(c, cells[c.elementId]),
9062
+ error: validateField(c, cells[c.elementId])
9063
+ });
9064
+ });
9065
+ }
9066
+ const fresh = computeRowFormulas(child.elements, cells);
9067
+ formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
9068
+ sheet.rows.push(buildSubmissionElements(child.elements, fresh));
9069
+ });
9070
+ sheets.push(sheet);
9071
+ });
9072
+ const defaults = [];
9073
+ elements.forEach((el2) => {
9074
+ const dd = el2.attributes?.dynamicDefault;
9075
+ const ds = el2.properties?.dataSource ?? {};
9076
+ let source = "";
9077
+ if (dd && typeof dd === "object") {
9078
+ const u = dd.units ?? {};
9079
+ const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
9080
+ source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
9081
+ } else if (typeof dd === "string" && dd) source = dd;
9082
+ else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
9083
+ else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
9084
+ else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
9085
+ if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
9086
+ });
9087
+ const submitEls = submitElementsOf(elements);
9088
+ const sheetIds = new Set(sheets.map((s) => s.fieldId));
9089
+ const shown = { ...values };
9090
+ sheets.forEach((s) => {
9091
+ shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
9092
+ });
9093
+ const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
9094
+ const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
9095
+ const errors = validation.filter((v) => !v.hidden && v.error).length;
9096
+ const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
9097
+ return {
9098
+ rules,
9099
+ filters: filters2,
9100
+ validation,
9101
+ formulas,
9102
+ defaults,
9103
+ payload,
9104
+ summary: {
9105
+ rules: rules.length,
9106
+ matched: rules.filter((r) => r.matched).length,
9107
+ filters: filters2.length,
9108
+ activeFilters: filters2.filter((f) => f.active).length,
9109
+ errors,
9110
+ formulas: formulas.length,
9111
+ formulaIssues
9112
+ }
9113
+ };
9114
+ }
9115
+ function useFormInspector(form) {
9116
+ return useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
9117
+ }
7941
9118
 
7942
9119
  // src/forms/authoring.ts
7943
9120
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
@@ -8049,6 +9226,6 @@ function formWidget(o) {
8049
9226
  };
8050
9227
  }
8051
9228
 
8052
- export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, interpolate, invokeTrigger, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, taskField, taskSource, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
9229
+ export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BLOCKED_FILE_EXTENSIONS, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, addPeriod, aggregate, alignSummaryToSeries, applyCumulative, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, computeRowFormulas, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateDiff, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, extractAgentFormData, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, flattenPercentiles, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, inspectForm, interpolate, invokeAgent, invokeTrigger, invokeZeno, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, isServerSafeFormula, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, openFormRequest, operatorsForKind, optionFilterRules, parseConfig, parseDateValue, parseDeep, percentileKey, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, rulesOf, searchPeople, serializeElementValue, startOfPeriod, statusTone, submissionIdOf, submissionToValues, submitElementsOf, taskField, taskSource, toBraceFormula, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useFormInspector, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useTrialField, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
8053
9230
  //# sourceMappingURL=index.js.map
8054
9231
  //# sourceMappingURL=index.js.map