@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.cjs CHANGED
@@ -1,28 +1,30 @@
1
1
  'use strict';
2
2
 
3
- var React13 = require('react');
3
+ var React14 = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var dayjs8 = require('dayjs');
5
+ var dayjs4 = require('dayjs');
6
6
  var utc = require('dayjs/plugin/utc.js');
7
7
  var weekOfYear = require('dayjs/plugin/weekOfYear.js');
8
8
  var _ = require('lodash');
9
+ var timezone2 = require('dayjs/plugin/timezone.js');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
12
- var React13__default = /*#__PURE__*/_interopDefault(React13);
13
- var dayjs8__default = /*#__PURE__*/_interopDefault(dayjs8);
13
+ var React14__default = /*#__PURE__*/_interopDefault(React14);
14
+ var dayjs4__default = /*#__PURE__*/_interopDefault(dayjs4);
14
15
  var utc__default = /*#__PURE__*/_interopDefault(utc);
15
16
  var weekOfYear__default = /*#__PURE__*/_interopDefault(weekOfYear);
16
17
  var ___default = /*#__PURE__*/_interopDefault(_);
18
+ var timezone2__default = /*#__PURE__*/_interopDefault(timezone2);
17
19
 
18
20
  // src/core/ZvolvProvider.tsx
19
- var RuntimeContext = React13.createContext(null);
21
+ var RuntimeContext = React14.createContext(null);
20
22
  var ZvolvProvider = ({ runtime, children }) => {
21
- const value = React13.useMemo(() => runtime, [runtime]);
23
+ const value = React14.useMemo(() => runtime, [runtime]);
22
24
  return /* @__PURE__ */ jsxRuntime.jsx(RuntimeContext.Provider, { value, children });
23
25
  };
24
26
  function useZvolvRuntime() {
25
- const ctx = React13.useContext(RuntimeContext);
27
+ const ctx = React14.useContext(RuntimeContext);
26
28
  if (!ctx) {
27
29
  throw new Error(
28
30
  "@zestl/zvolv-widgets: no ZvolvProvider found. Wrap your app in <ZvolvProvider runtime={...}> before rendering widgets or calling data hooks."
@@ -46,9 +48,47 @@ function getRuntimeClient() {
46
48
  if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
47
49
  return c;
48
50
  }
51
+ var sdk = {};
52
+ try {
53
+ import('@zestl/zvolv-js-sdk').then((m) => {
54
+ sdk = m ?? {};
55
+ }).catch(() => {
56
+ });
57
+ } catch {
58
+ }
59
+ var AUTH_CRYPT_READY = "zvAuthCryptReady";
60
+ var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
61
+ var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
62
+ function stripUndecrypted(node) {
63
+ if (Array.isArray(node)) return node.map(stripUndecrypted);
64
+ if (node && typeof node === "object") {
65
+ const out = {};
66
+ for (const [k, v] of Object.entries(node)) {
67
+ if (v && typeof v === "object") out[k] = stripUndecrypted(v);
68
+ else if (!isEnc(v)) out[k] = v;
69
+ }
70
+ return out;
71
+ }
72
+ return isEnc(node) ? void 0 : node;
73
+ }
49
74
  function getStoredUser() {
50
75
  try {
51
- return userReader() ?? clientGetter()?.auth?.userInstance ?? null;
76
+ let u = userReader() ?? null;
77
+ if (!u) {
78
+ try {
79
+ u = sdk.readUserFromStorage?.() ?? null;
80
+ } catch {
81
+ u = null;
82
+ }
83
+ }
84
+ if (!u) u = clientGetter()?.auth?.userInstance ?? null;
85
+ if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
86
+ try {
87
+ u = sdk.decryptAuthObject(u);
88
+ } catch {
89
+ }
90
+ }
91
+ return u ? stripUndecrypted(u) : null;
52
92
  } catch {
53
93
  return null;
54
94
  }
@@ -89,8 +129,8 @@ var TASK_DATE_FIELDS = [
89
129
  "completeDate"
90
130
  ];
91
131
  new Set(TASK_DATE_FIELDS.map((f) => f.toLowerCase()));
92
- dayjs8__default.default.extend(utc__default.default);
93
- dayjs8__default.default.extend(weekOfYear__default.default);
132
+ dayjs4__default.default.extend(utc__default.default);
133
+ dayjs4__default.default.extend(weekOfYear__default.default);
94
134
  function humanizeDurationToMinutes(value) {
95
135
  if (!value) return 0;
96
136
  let total = 0;
@@ -134,60 +174,60 @@ function getDataType(elementType) {
134
174
  }
135
175
  function getDateRange(ops) {
136
176
  const values = [];
137
- const fmt = "YYYY-MM-DD";
138
- const today = dayjs8__default.default();
177
+ const fmt2 = "YYYY-MM-DD";
178
+ const today = dayjs4__default.default();
139
179
  switch (ops) {
140
180
  case "TODAY":
141
- values[0] = today.format(fmt);
142
- values[1] = today.format(fmt);
181
+ values[0] = today.format(fmt2);
182
+ values[1] = today.format(fmt2);
143
183
  break;
144
184
  case "LAST_SEVEN_DAYS":
145
- values[0] = today.subtract(7, "day").format(fmt);
146
- values[1] = today.add(1, "day").format(fmt);
185
+ values[0] = today.subtract(7, "day").format(fmt2);
186
+ values[1] = today.add(1, "day").format(fmt2);
147
187
  break;
148
188
  case "LAST_THIRTY_DAYS":
149
- values[0] = today.subtract(30, "day").format(fmt);
150
- values[1] = today.add(1, "day").format(fmt);
189
+ values[0] = today.subtract(30, "day").format(fmt2);
190
+ values[1] = today.add(1, "day").format(fmt2);
151
191
  break;
152
192
  case "THIS_WEEK":
153
- values[0] = today.startOf("week").format(fmt);
154
- values[1] = today.endOf("week").format(fmt);
193
+ values[0] = today.startOf("week").format(fmt2);
194
+ values[1] = today.endOf("week").format(fmt2);
155
195
  break;
156
196
  case "THIS_MONTH":
157
- values[0] = today.startOf("month").format(fmt);
158
- values[1] = today.endOf("month").format(fmt);
197
+ values[0] = today.startOf("month").format(fmt2);
198
+ values[1] = today.endOf("month").format(fmt2);
159
199
  break;
160
200
  case "THIS_YEAR":
161
- values[0] = today.startOf("year").format(fmt);
162
- values[1] = today.endOf("year").format(fmt);
201
+ values[0] = today.startOf("year").format(fmt2);
202
+ values[1] = today.endOf("year").format(fmt2);
163
203
  break;
164
204
  case "THIS_FINANCIAL_YEAR": {
165
205
  const m = today.month();
166
206
  const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
167
207
  const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
168
- values[0] = fiscalStart.format(fmt);
169
- values[1] = fiscalEnd.format(fmt);
208
+ values[0] = fiscalStart.format(fmt2);
209
+ values[1] = fiscalEnd.format(fmt2);
170
210
  break;
171
211
  }
172
212
  case "NEXT_THREE_DAYS":
173
- values[0] = today.format(fmt);
174
- values[1] = today.add(4, "day").format(fmt);
213
+ values[0] = today.format(fmt2);
214
+ values[1] = today.add(4, "day").format(fmt2);
175
215
  break;
176
216
  case "NEXT_SEVEN_DAYS":
177
- values[0] = today.format(fmt);
178
- values[1] = today.add(8, "day").format(fmt);
217
+ values[0] = today.format(fmt2);
218
+ values[1] = today.add(8, "day").format(fmt2);
179
219
  break;
180
220
  case "NEXT_WEEK":
181
- values[0] = today.add(1, "week").startOf("week").format(fmt);
182
- values[1] = today.add(1, "week").endOf("week").format(fmt);
221
+ values[0] = today.add(1, "week").startOf("week").format(fmt2);
222
+ values[1] = today.add(1, "week").endOf("week").format(fmt2);
183
223
  break;
184
224
  case "NEXT_FIFTEEN_DAYS":
185
- values[0] = today.format(fmt);
186
- values[1] = today.add(16, "day").format(fmt);
225
+ values[0] = today.format(fmt2);
226
+ values[1] = today.add(16, "day").format(fmt2);
187
227
  break;
188
228
  case "NEXT_THIRTY_DAYS":
189
- values[0] = today.format(fmt);
190
- values[1] = today.add(30, "day").format(fmt);
229
+ values[0] = today.format(fmt2);
230
+ values[1] = today.add(30, "day").format(fmt2);
191
231
  break;
192
232
  case "THIS_QUARTER": {
193
233
  const m = today.month();
@@ -205,8 +245,8 @@ function getDateRange(ops) {
205
245
  qStart = today.startOf("year").month(9).date(1);
206
246
  qEnd = today.startOf("year").month(11).date(31);
207
247
  }
208
- values[0] = qStart.format(fmt);
209
- values[1] = qEnd.format(fmt);
248
+ values[0] = qStart.format(fmt2);
249
+ values[1] = qEnd.format(fmt2);
210
250
  break;
211
251
  }
212
252
  }
@@ -287,7 +327,7 @@ var resolveQueryUser = () => {
287
327
  };
288
328
 
289
329
  // src/runtime/queryGenerator.ts
290
- dayjs8__default.default.extend(utc__default.default);
330
+ dayjs4__default.default.extend(utc__default.default);
291
331
  var mappingsCache = /* @__PURE__ */ new Map();
292
332
  function getCachedAnalyticsMappings(indexKey) {
293
333
  let pending2 = mappingsCache.get(indexKey);
@@ -439,7 +479,7 @@ function resolveDateMath(value) {
439
479
  if (typeof value !== "string") return null;
440
480
  const match = dateMathPattern.exec(value.trim());
441
481
  if (!match) return null;
442
- let d = dayjs8__default.default();
482
+ let d = dayjs4__default.default();
443
483
  const [, amountStr, amountUnit, , roundUnit] = match;
444
484
  if (amountStr && amountUnit) {
445
485
  d = d.add(parseInt(amountStr, 10), dateMathUnit[amountUnit]);
@@ -453,56 +493,56 @@ function isDateMath(value) {
453
493
  return typeof value === "string" && dateMathPattern.test(value.trim());
454
494
  }
455
495
  function getDate(dateFormula) {
456
- const fmt = "YYYY-MM-DDTHH:mm:ssZ";
457
- const d = dayjs8__default.default();
496
+ const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
497
+ const d = dayjs4__default.default();
458
498
  switch (dateFormula) {
459
499
  case "{@[TODAY]}":
460
- return d.startOf("day").format(fmt);
500
+ return d.startOf("day").format(fmt2);
461
501
  case "{@[3DAYSLATER]}":
462
- return d.add(3, "day").startOf("day").format(fmt);
502
+ return d.add(3, "day").startOf("day").format(fmt2);
463
503
  case "{@[7DAYSLATER]}":
464
- return d.add(7, "day").startOf("day").format(fmt);
504
+ return d.add(7, "day").startOf("day").format(fmt2);
465
505
  case "{@[14DAYSLATER]}":
466
- return d.add(14, "day").startOf("day").format(fmt);
506
+ return d.add(14, "day").startOf("day").format(fmt2);
467
507
  case "{@[15DAYSLATER]}":
468
- return d.add(15, "day").startOf("day").format(fmt);
508
+ return d.add(15, "day").startOf("day").format(fmt2);
469
509
  case "{@[21DAYSLATER]}":
470
- return d.add(21, "day").startOf("day").format(fmt);
510
+ return d.add(21, "day").startOf("day").format(fmt2);
471
511
  case "{@[28DAYSLATER]}":
472
- return d.add(28, "day").startOf("day").format(fmt);
512
+ return d.add(28, "day").startOf("day").format(fmt2);
473
513
  case "{@[30DAYSLATER]}":
474
- return d.add(30, "day").startOf("day").format(fmt);
514
+ return d.add(30, "day").startOf("day").format(fmt2);
475
515
  case "{@[60DAYSLATER]}":
476
- return d.add(60, "day").startOf("day").format(fmt);
516
+ return d.add(60, "day").startOf("day").format(fmt2);
477
517
  case "{@[90DAYSLATER]}":
478
- return d.add(90, "day").startOf("day").format(fmt);
518
+ return d.add(90, "day").startOf("day").format(fmt2);
479
519
  case "{@[180DAYSLATER]}":
480
- return d.add(180, "day").startOf("day").format(fmt);
520
+ return d.add(180, "day").startOf("day").format(fmt2);
481
521
  case "{@[365DAYSLATER]}":
482
- return d.add(365, "day").startOf("day").format(fmt);
522
+ return d.add(365, "day").startOf("day").format(fmt2);
483
523
  case "{@[3DAYSAGO]}":
484
- return d.subtract(3, "day").startOf("day").format(fmt);
524
+ return d.subtract(3, "day").startOf("day").format(fmt2);
485
525
  case "{@[7DAYSAGO]}":
486
- return d.subtract(7, "day").startOf("day").format(fmt);
526
+ return d.subtract(7, "day").startOf("day").format(fmt2);
487
527
  case "{@[14DAYSAGO]}":
488
- return d.subtract(14, "day").startOf("day").format(fmt);
528
+ return d.subtract(14, "day").startOf("day").format(fmt2);
489
529
  case "{@[15DAYSAGO]}":
490
- return d.subtract(15, "day").startOf("day").format(fmt);
530
+ return d.subtract(15, "day").startOf("day").format(fmt2);
491
531
  case "{@[21DAYSAGO]}":
492
- return d.subtract(21, "day").startOf("day").format(fmt);
532
+ return d.subtract(21, "day").startOf("day").format(fmt2);
493
533
  case "{@[28DAYSAGO]}":
494
- return d.subtract(28, "day").startOf("day").format(fmt);
534
+ return d.subtract(28, "day").startOf("day").format(fmt2);
495
535
  case "{@[30DAYSAGO]}":
496
- return d.subtract(20, "day").startOf("day").format(fmt);
536
+ return d.subtract(20, "day").startOf("day").format(fmt2);
497
537
  // note: original uses 20
498
538
  case "{@[60DAYSAGO]}":
499
- return d.subtract(60, "day").startOf("day").format(fmt);
539
+ return d.subtract(60, "day").startOf("day").format(fmt2);
500
540
  case "{@[90DAYSAGO]}":
501
- return d.subtract(90, "day").startOf("day").format(fmt);
541
+ return d.subtract(90, "day").startOf("day").format(fmt2);
502
542
  case "{@[180DAYSAGO]}":
503
- return d.subtract(180, "day").startOf("day").format(fmt);
543
+ return d.subtract(180, "day").startOf("day").format(fmt2);
504
544
  case "{@[365DAYSAGO]}":
505
- return d.subtract(365, "day").startOf("day").format(fmt);
545
+ return d.subtract(365, "day").startOf("day").format(fmt2);
506
546
  default:
507
547
  return dateFormula;
508
548
  }
@@ -582,8 +622,8 @@ function getFormula(formula, fMap, isFilter = false) {
582
622
  }
583
623
  }
584
624
  });
585
- formula.format.split("%s").forEach((text, i) => {
586
- textFormat += i === 0 ? `'${text}'` : ` + '${text}'`;
625
+ formula.format.split("%s").forEach((text2, i) => {
626
+ textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
587
627
  if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
588
628
  });
589
629
  script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
@@ -651,7 +691,7 @@ function getFilters(field2, type, loggedInUser = null, isTask = false) {
651
691
  field2.filter.operand = field2.filter.operand.map((oprd) => {
652
692
  if (isDateMath(oprd)) return oprd;
653
693
  const pattern = dateFieldPattern[field2.fieldFormat] ?? field2.fieldFormat;
654
- return dayjs8__default.default(oprd, pattern).format("YYYY-MM-DD");
694
+ return dayjs4__default.default(oprd, pattern).format("YYYY-MM-DD");
655
695
  });
656
696
  }
657
697
  if (field2["taskfield"] && !field2["masterFieldDataType"]) {
@@ -936,6 +976,142 @@ function getTaskElementSort(dataSource, fieldMetaByName) {
936
976
  }
937
977
  return null;
938
978
  }
979
+ function pivotEsField(entry) {
980
+ return entry?.masterField || entry?.title;
981
+ }
982
+ var HISTOGRAM_INTERVALS = {
983
+ day: "1d",
984
+ week: "1w",
985
+ month: "1M",
986
+ quarter: "1q",
987
+ year: "1y"
988
+ };
989
+ function getDateHistogramSource(row, esField, mfType, meta, sortOrder) {
990
+ const isTaskDate = taskDate.includes(esField) && !!(row.isTaskField || row.taskfield || meta?.isTaskField || meta?.taskfield);
991
+ if (!dateTypes.includes(mfType) && !isTaskDate) {
992
+ console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": interval "${row.interval}" ignored \u2014 "${esField}" is not a date field.`);
993
+ return null;
994
+ }
995
+ const calendarInterval = HISTOGRAM_INTERVALS[row.interval];
996
+ if (!calendarInterval) {
997
+ console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": unknown interval "${row.interval}" \u2014 use day | week | month | quarter | year.`);
998
+ return null;
999
+ }
1000
+ const histogram = { field: esField, calendar_interval: calendarInterval, time_zone: row.timeZone || timezone };
1001
+ if (sortOrder) histogram.order = sortOrder;
1002
+ return { date_histogram: histogram };
1003
+ }
1004
+ var summaryFilterLists = /* @__PURE__ */ new Map();
1005
+ var DATE_DIFF_DIVISOR = {
1006
+ days: "86400000.0",
1007
+ hours: "3600000.0",
1008
+ minutes: "60000.0"
1009
+ };
1010
+ var LITERAL_DATE = /^\d{4}-\d{2}-\d{2}([T ][\d:.]+(Z|[+-]\d{2}:?\d{2})?)?$/;
1011
+ function getDateDiffFixedEnd(end) {
1012
+ if (typeof end !== "string") return null;
1013
+ if (dateFormulas.includes(end)) return dayjs4__default.default(getDate(end)).valueOf();
1014
+ if (LITERAL_DATE.test(end.trim())) {
1015
+ const d = dayjs4__default.default(end.trim());
1016
+ if (d.isValid()) return d.valueOf();
1017
+ }
1018
+ if (/^\{@\[.*\]\}$/.test(end)) {
1019
+ console.warn(`[@zestl/zvolv-widgets] dateDiff end "${end}" is not a known date token (${dateFormulas.join(", ")}); it is being queried as a field name.`);
1020
+ }
1021
+ return null;
1022
+ }
1023
+ function getDateDiffScript(dd, fixedEnd) {
1024
+ const divisor = DATE_DIFF_DIVISOR[dd.unit] ?? DATE_DIFF_DIVISOR.days;
1025
+ if (fixedEnd != null) {
1026
+ return {
1027
+ source: `if(doc['${dd.start}'].size()==0) { return null; } return (params.now - doc['${dd.start}'].value.millis) / ${divisor};`,
1028
+ lang: "painless",
1029
+ params: { now: fixedEnd }
1030
+ };
1031
+ }
1032
+ return {
1033
+ 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};`,
1034
+ lang: "painless"
1035
+ };
1036
+ }
1037
+ function getSummaryFilterClause(sumary, f, loggedInUser) {
1038
+ if (f.operator === "_ALL" || f.operator === "MULTIPLE") return null;
1039
+ const fb = {
1040
+ header: sumary.title,
1041
+ masterfield: f.operand ?? f.masterField,
1042
+ masterFieldDataType: f.masterFieldDataType,
1043
+ masterFieldType: f.masterFieldType,
1044
+ filter: { operator: f.operator, operand: Array.isArray(f.value) ? [...f.value] : f.value }
1045
+ };
1046
+ return getFilters(fb, "PIVOT", loggedInUser) ?? null;
1047
+ }
1048
+ function buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys = {}) {
1049
+ const name = sumary.value;
1050
+ const list = summaryFilterLists.get(name) ?? [];
1051
+ const andGroup = list.length > 1 && list.some((f) => f?.combine === "AND");
1052
+ const isPercentiles = sumary.type === "percentiles";
1053
+ if (!sumary.dateDiff && !andGroup && !(isPercentiles && sumary.percents?.length)) return false;
1054
+ const must = [];
1055
+ const mustNot = [];
1056
+ const conditions = andGroup ? list : list.slice(-1);
1057
+ for (const f of conditions) {
1058
+ const clause = getSummaryFilterClause(sumary, f, loggedInUser);
1059
+ if (!clause) continue;
1060
+ if (mustNotOp.includes(f.operator)) mustNot.push({ ...clause });
1061
+ else must.push(clause);
1062
+ }
1063
+ let metric;
1064
+ if (sumary.dateDiff) {
1065
+ const dd = sumary.dateDiff;
1066
+ const fixedEnd = getDateDiffFixedEnd(dd.end);
1067
+ must.push({ exists: { field: dd.start } });
1068
+ if (fixedEnd == null) must.push({ exists: { field: dd.end } });
1069
+ metric = { script: getDateDiffScript(dd, fixedEnd) };
1070
+ } else {
1071
+ const esField = pivotEsField(sumary);
1072
+ let aggType = sumary.type;
1073
+ let aggField = esField;
1074
+ const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary);
1075
+ if (sumary.masterFieldType === "FORM_SEARCH" && (aggType === "value_count" || aggType === "cardinality")) {
1076
+ aggField = resolveFormSearchViewField(esField);
1077
+ } else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
1078
+ aggField = `${esField}.mediaKey.keyword`;
1079
+ } else if (isTextField) {
1080
+ if (["sum", "avg", "min", "max"].includes(aggType)) aggType = "value_count";
1081
+ aggField = `${esField}.keyword`;
1082
+ }
1083
+ metric = { field: aggField };
1084
+ finish(name, summaryAggs, must, mustNot, { [aggType]: metric });
1085
+ if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
1086
+ if (!isPercentiles && aggField === esField && sortKeys[esField]) {
1087
+ summaryAggs[`${name}_sort`] = { bucket_sort: { sort: [{ [name]: { order: sortKeys[esField] } }] } };
1088
+ }
1089
+ return true;
1090
+ }
1091
+ if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
1092
+ return finish(name, summaryAggs, must, mustNot, { [sumary.type]: metric });
1093
+ }
1094
+ function finish(name, summaryAggs, must, mustNot, metric) {
1095
+ if (must.length === 0 && mustNot.length === 0) {
1096
+ summaryAggs[name] = metric;
1097
+ return true;
1098
+ }
1099
+ const bool = {};
1100
+ if (must.length) bool.must = must;
1101
+ if (mustNot.length) bool.must_not = mustNot;
1102
+ summaryAggs[name] = { filter: { bool }, aggs: { [name]: metric } };
1103
+ return true;
1104
+ }
1105
+ function groupPivotFilters(pivotFilters) {
1106
+ const out = /* @__PURE__ */ new Map();
1107
+ (pivotFilters || []).forEach((f) => {
1108
+ if (!f) return;
1109
+ const list = out.get(f.field) ?? [];
1110
+ list.push(f);
1111
+ out.set(f.field, list);
1112
+ });
1113
+ return out;
1114
+ }
939
1115
  function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e4, scriptFields = {}, loggedInUser = null, sort = [], fieldMetaByName) {
940
1116
  const composite = { size, sources: [] };
941
1117
  pivotBody.rows.forEach((row) => {
@@ -957,17 +1133,21 @@ function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e
957
1133
  const meta = fieldMetaByName?.get(row.title) || fieldMetaByName?.get(row.masterField) || {};
958
1134
  const mfType = row.masterFieldType ?? meta.masterFieldType;
959
1135
  const mfDataType = row.masterFieldDataType ?? meta.masterFieldDataType ?? (mfType ? getDataType(mfType) : void 0);
1136
+ const esField = pivotEsField(row);
960
1137
  const sourceObj = {};
961
- if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
962
- sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(row.title) } };
1138
+ const histogram = row.interval ? getDateHistogramSource(row, esField, mfType, meta, sortOrder) : null;
1139
+ if (histogram) {
1140
+ sourceObj[row.title] = histogram;
1141
+ } else if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
1142
+ sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(esField) } };
963
1143
  } else if (mfType === "FILE_UPLOAD_V2") {
964
- sourceObj[row.title] = { terms: { field: `${row.title}.mediaKey.keyword` } };
1144
+ sourceObj[row.title] = { terms: { field: `${esField}.mediaKey.keyword` } };
965
1145
  } else if (mfDataType === "STRING" && !dateTypes.includes(mfType) && !isNumericTaskField(row, fieldMetaByName)) {
966
- sourceObj[row.title] = { terms: { field: `${row.title}.keyword` } };
1146
+ sourceObj[row.title] = { terms: { field: `${esField}.keyword` } };
967
1147
  } else if (!mfDataType && Object.keys(scriptFields).length > 0) {
968
1148
  sourceObj[row.title] = { terms: scriptFields[row.title] };
969
1149
  } else {
970
- sourceObj[row.title] = { terms: { field: row.title } };
1150
+ sourceObj[row.title] = { terms: { field: esField } };
971
1151
  if (sortOrder) sourceObj[row.title]["terms"]["order"] = sortOrder;
972
1152
  }
973
1153
  composite.sources.push(sourceObj);
@@ -985,7 +1165,7 @@ function getColumnAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser
985
1165
  const colFilter = getFilters(fb, "PIVOT", loggedInUser);
986
1166
  if (colFilter) colFilters.push(colFilter);
987
1167
  }
988
- colAggs[col.title] = { terms: { field: col.title } };
1168
+ colAggs[col.title] = { terms: { field: pivotEsField(col) } };
989
1169
  if (i === pivotBody.columns.length - 1) {
990
1170
  const summaryResult = getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser, fMap);
991
1171
  colAggs[col.title]["aggs"] = summaryResult.summaryAggs;
@@ -1018,6 +1198,7 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1018
1198
  pivotBody.summary.forEach((sumary) => {
1019
1199
  const filter = filterMap.get(sumary.value);
1020
1200
  aggDataPoints["summary"].push(sumary.value);
1201
+ if (buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys)) return;
1021
1202
  summaryAggs[sumary.value] = {};
1022
1203
  const addBucketSort = (key) => {
1023
1204
  if (sortKeys[key]) {
@@ -1025,17 +1206,18 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1025
1206
  }
1026
1207
  };
1027
1208
  const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary, fMap);
1209
+ const esField = pivotEsField(sumary);
1028
1210
  let aggType = sumary.type;
1029
- let aggField = sumary.title;
1211
+ let aggField = esField;
1030
1212
  if (sumary.masterFieldType === "FORM_SEARCH" && (sumary.type === "value_count" || sumary.type === "cardinality")) {
1031
- aggField = resolveFormSearchViewField(sumary.title);
1213
+ aggField = resolveFormSearchViewField(esField);
1032
1214
  } else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
1033
- aggField = `${sumary.title}.mediaKey.keyword`;
1215
+ aggField = `${esField}.mediaKey.keyword`;
1034
1216
  } else if (isTextField) {
1035
1217
  if (["sum", "avg", "min", "max"].includes(sumary.type)) aggType = "value_count";
1036
- aggField = `${sumary.title}.keyword`;
1218
+ aggField = `${esField}.keyword`;
1037
1219
  }
1038
- const usesRawField = aggField === sumary.title;
1220
+ const usesRawField = aggField === esField;
1039
1221
  if (filter) {
1040
1222
  const fb = {
1041
1223
  header: sumary.title,
@@ -1053,10 +1235,10 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
1053
1235
  }
1054
1236
  summaryAggs[sumary.value]["aggs"] = { [sumary.value]: {} };
1055
1237
  summaryAggs[sumary.value]["aggs"][sumary.value][aggType] = { field: aggField };
1056
- if (usesRawField) addBucketSort(sumary.title);
1238
+ if (usesRawField) addBucketSort(esField);
1057
1239
  } else {
1058
1240
  summaryAggs[sumary.value][aggType] = { field: aggField };
1059
- if (usesRawField) addBucketSort(sumary.title);
1241
+ if (usesRawField) addBucketSort(esField);
1060
1242
  }
1061
1243
  });
1062
1244
  return { summaryAggs, aggDataPoints };
@@ -1066,6 +1248,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
1066
1248
  scriptFilters = [];
1067
1249
  fieldMap = /* @__PURE__ */ new Map();
1068
1250
  currentMappings = mappings;
1251
+ summaryFilterLists = /* @__PURE__ */ new Map();
1069
1252
  let staticQueryClause = null;
1070
1253
  const rawDataSourceQuery = dataSource?.query;
1071
1254
  if (rawDataSourceQuery && typeof rawDataSourceQuery === "string") {
@@ -1158,6 +1341,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
1158
1341
  if (dataSource?.pivot.filters?.length > 0) {
1159
1342
  dataSource?.pivot.filters.forEach((f) => filterMap2.set(f.field, f));
1160
1343
  }
1344
+ summaryFilterLists = groupPivotFilters(dataSource?.pivot.filters);
1161
1345
  if (!isDrilldownView() && dataSource?.displayRowLimit && dataSource?.displayRowLimit !== "") {
1162
1346
  size = dataSource?.displayRowLimit;
1163
1347
  }
@@ -1665,7 +1849,7 @@ var resolveUserLabels = async (values, kind) => {
1665
1849
  // src/runtime/index.ts
1666
1850
  function createZvolvRuntime({ client, readUser }) {
1667
1851
  const getClient = typeof client === "function" ? client : () => client;
1668
- setRuntimeClient(getClient, readUser ?? (() => getClient()?.auth?.userInstance));
1852
+ setRuntimeClient(getClient, readUser ?? (() => null));
1669
1853
  resetQueryUser();
1670
1854
  return {
1671
1855
  get client() {
@@ -1735,16 +1919,29 @@ function taskField(header, masterFieldType = "INPUT_TEXT", options = {}) {
1735
1919
  ...options
1736
1920
  };
1737
1921
  }
1738
- function groupBy(f, title = f.header) {
1922
+ function groupBy(f, title = f.header, options = {}) {
1739
1923
  return {
1740
1924
  title,
1741
1925
  masterField: f.masterfield ?? f.taskfield ?? f.header,
1742
1926
  masterFieldType: f.masterFieldType,
1743
- masterFieldDataType: f.masterFieldDataType
1927
+ masterFieldDataType: f.masterFieldDataType,
1928
+ ...options.interval ? { interval: options.interval } : {},
1929
+ ...options.timeZone ? { timeZone: options.timeZone } : {}
1744
1930
  };
1745
1931
  }
1746
- function aggregate(f, type = "sum", outputName = f.header) {
1932
+ function dateDiff(start, end, unit = "days") {
1933
+ const startField = start.masterfield ?? start.taskfield ?? start.header;
1934
+ const endField = typeof end === "string" ? end === "TODAY" ? "{@[TODAY]}" : end : end.masterfield ?? end.taskfield ?? end.header;
1935
+ const endLabel = typeof end === "string" ? end : end.header;
1747
1936
  return {
1937
+ header: `${start.header} \u2192 ${endLabel} (${unit})`,
1938
+ masterfield: startField,
1939
+ masterFieldDataType: "FLOAT",
1940
+ dateDiff: { start: startField, end: endField, unit }
1941
+ };
1942
+ }
1943
+ function aggregate(f, type = "sum", outputName = f.header, options = {}) {
1944
+ const summary = {
1748
1945
  title: f.header,
1749
1946
  masterField: f.masterfield ?? f.taskfield ?? f.header,
1750
1947
  masterFieldType: f.masterFieldType,
@@ -1752,6 +1949,36 @@ function aggregate(f, type = "sum", outputName = f.header) {
1752
1949
  value: outputName,
1753
1950
  type
1754
1951
  };
1952
+ const dd = f.dateDiff;
1953
+ if (dd) summary.dateDiff = { ...dd };
1954
+ if (options.percents?.length) summary.percents = [...options.percents];
1955
+ if (options.where?.length) {
1956
+ summary.filters = options.where.map(([wf, cond]) => {
1957
+ const mf = wf.masterfield ?? wf.taskfield ?? wf.header;
1958
+ return {
1959
+ field: outputName,
1960
+ operand: mf,
1961
+ masterField: mf,
1962
+ masterFieldType: wf.masterFieldType,
1963
+ masterFieldDataType: wf.masterFieldDataType,
1964
+ operator: cond.operator,
1965
+ value: [...cond.operand],
1966
+ combine: "AND"
1967
+ };
1968
+ });
1969
+ }
1970
+ return summary;
1971
+ }
1972
+ function hoistSummaryFilters(p) {
1973
+ if (!p?.summary?.some((s) => s.filters?.length)) return p;
1974
+ const hoisted = [];
1975
+ const summary = p.summary.map((s) => {
1976
+ if (!s.filters) return s;
1977
+ const { filters: filters2, ...rest } = s;
1978
+ hoisted.push(...filters2);
1979
+ return rest;
1980
+ });
1981
+ return { ...p, summary, filters: [...p.filters ?? [], ...hoisted] };
1755
1982
  }
1756
1983
  function derive(title, operator, operands) {
1757
1984
  return { title, operator, operands };
@@ -1764,7 +1991,7 @@ function formSource(formId, fields, options = {}) {
1764
1991
  formId,
1765
1992
  newFormFramework: true,
1766
1993
  fields: fields.map((f) => ({ ...f, formID: f.formID ?? formId })),
1767
- ...options.pivot ? { pivot: options.pivot } : {},
1994
+ ...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
1768
1995
  ...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
1769
1996
  };
1770
1997
  }
@@ -1773,7 +2000,7 @@ function taskSource(fields, options = {}) {
1773
2000
  isTask: true,
1774
2001
  newFormFramework: true,
1775
2002
  fields: fields.map((f) => ({ ...f, isTaskField: true })),
1776
- ...options.pivot ? { pivot: options.pivot } : {},
2003
+ ...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
1777
2004
  ...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
1778
2005
  };
1779
2006
  }
@@ -1781,13 +2008,13 @@ function automationSource(automationId, fields = []) {
1781
2008
  return { automation: { automationId, enabled: true }, fields };
1782
2009
  }
1783
2010
  function pivot(config) {
1784
- return {
2011
+ return hoistSummaryFilters({
1785
2012
  rows: config.rows ?? [],
1786
2013
  columns: config.columns ?? [],
1787
2014
  summary: config.summary ?? [],
1788
2015
  filters: config.filters ?? [],
1789
2016
  postOperations: config.postOperations ?? []
1790
- };
2017
+ });
1791
2018
  }
1792
2019
 
1793
2020
  // src/core/config.ts
@@ -1885,7 +2112,7 @@ function coerceForField(field2, values) {
1885
2112
  });
1886
2113
  }
1887
2114
  var FMT = "YYYY-MM-DD";
1888
- function resolveDateToken(token, now = dayjs8__default.default()) {
2115
+ function resolveDateToken(token, now = dayjs4__default.default()) {
1889
2116
  switch (String(token).toUpperCase()) {
1890
2117
  case "TODAY":
1891
2118
  return [now.format(FMT), now.format(FMT)];
@@ -1908,19 +2135,19 @@ function resolveDateToken(token, now = dayjs8__default.default()) {
1908
2135
  }
1909
2136
  }
1910
2137
  var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
1911
- function searchAsDate(text) {
1912
- const t = text.trim();
2138
+ function searchAsDate(text2) {
2139
+ const t = text2.trim();
1913
2140
  if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
1914
- for (const fmt of SEARCH_DATE_FORMATS) {
1915
- const [y, m, d] = parseStrict(t, fmt);
2141
+ for (const fmt2 of SEARCH_DATE_FORMATS) {
2142
+ const [y, m, d] = parseStrict(t, fmt2);
1916
2143
  if (y) return `${y}-${m}-${d}`;
1917
2144
  }
1918
2145
  return null;
1919
2146
  }
1920
- function parseStrict(text, fmt) {
1921
- const sep = fmt.includes("/") ? "/" : "-";
1922
- const parts = text.split(sep);
1923
- const order = fmt.split(sep);
2147
+ function parseStrict(text2, fmt2) {
2148
+ const sep = fmt2.includes("/") ? "/" : "-";
2149
+ const parts = text2.split(sep);
2150
+ const order = fmt2.split(sep);
1924
2151
  if (parts.length !== 3 || order.length !== 3) return [null, null, null];
1925
2152
  const map = {};
1926
2153
  order.forEach((k, i) => {
@@ -1929,15 +2156,15 @@ function parseStrict(text, fmt) {
1929
2156
  const y = map.YYYY, m = map.MM, d = map.DD;
1930
2157
  if (!y || y.length !== 4 || !m || !d) return [null, null, null];
1931
2158
  const mm = m.padStart(2, "0"), dd = d.padStart(2, "0");
1932
- const parsed = dayjs8__default.default(`${y}-${mm}-${dd}`);
2159
+ const parsed = dayjs4__default.default(`${y}-${mm}-${dd}`);
1933
2160
  if (!parsed.isValid() || parsed.format(FMT) !== `${y}-${mm}-${dd}`) return [null, null, null];
1934
2161
  return [y, mm, dd];
1935
2162
  }
1936
2163
  function buildSearchClause(search) {
1937
- const text = (search ?? "").trim();
1938
- if (!text) return null;
1939
- const iso = searchAsDate(text);
1940
- return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text } };
2164
+ const text2 = (search ?? "").trim();
2165
+ if (!text2) return null;
2166
+ const iso = searchAsDate(text2);
2167
+ return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
1941
2168
  }
1942
2169
  function buildWidgetFilterClauses(dataSource, filters2 = []) {
1943
2170
  const isTask = resolveIsTask2(dataSource);
@@ -2028,7 +2255,7 @@ function toComparable(v) {
2028
2255
  if (typeof v === "number") return v;
2029
2256
  const n = Number(v);
2030
2257
  if (!isNaN(n)) return n;
2031
- const d = dayjs8__default.default(v);
2258
+ const d = dayjs4__default.default(v);
2032
2259
  return d.isValid() ? d.valueOf() : null;
2033
2260
  }
2034
2261
  function applyLocalSearch(rows, search, toLabel) {
@@ -2096,7 +2323,7 @@ function matchesWidgetFilter(row, f) {
2096
2323
  case "DATE_IS": {
2097
2324
  const range = resolveDateToken(String(values[0] ?? ""));
2098
2325
  if (!range || raw2 == null || raw2 === "") return false;
2099
- const d = dayjs8__default.default(raw2).format(FMT);
2326
+ const d = dayjs4__default.default(raw2).format(FMT);
2100
2327
  return d >= range[0] && d <= range[1];
2101
2328
  }
2102
2329
  default:
@@ -2300,22 +2527,22 @@ function resolveWidgetDefaults(tokens, widgetType, config) {
2300
2527
  const typeDefaults = widgetType && (defaults[widgetType] ?? defaults[widgetType.toUpperCase()]) || {};
2301
2528
  return { ...typeDefaults, ...config || {} };
2302
2529
  }
2303
- var TokensContext = React13.createContext(defaultTokens);
2530
+ var TokensContext = React14.createContext(defaultTokens);
2304
2531
  var ZvolvThemeProvider = ({ tokens, children }) => {
2305
- const value = React13.useMemo(() => mergeTokens(defaultTokens, tokens), [tokens]);
2532
+ const value = React14.useMemo(() => mergeTokens(defaultTokens, tokens), [tokens]);
2306
2533
  return /* @__PURE__ */ jsxRuntime.jsx(TokensContext.Provider, { value, children });
2307
2534
  };
2308
2535
  function useTokens() {
2309
- return React13.useContext(TokensContext);
2536
+ return React14.useContext(TokensContext);
2310
2537
  }
2311
- var TemplateContext = React13.createContext({});
2538
+ var TemplateContext = React14.createContext({});
2312
2539
  var TemplateProvider = ({ templates, children }) => {
2313
- const parent = React13.useContext(TemplateContext);
2314
- const value = React13.useMemo(() => ({ ...parent, ...templates || {} }), [parent, templates]);
2540
+ const parent = React14.useContext(TemplateContext);
2541
+ const value = React14.useMemo(() => ({ ...parent, ...templates || {} }), [parent, templates]);
2315
2542
  return /* @__PURE__ */ jsxRuntime.jsx(TemplateContext.Provider, { value, children });
2316
2543
  };
2317
2544
  function useTemplateOverride(widgetType, templateId) {
2318
- const map = React13.useContext(TemplateContext);
2545
+ const map = React14.useContext(TemplateContext);
2319
2546
  return map[`${widgetType}:${templateId}`] ?? map[`${widgetType}:*`] ?? null;
2320
2547
  }
2321
2548
  var Label = ({ children }) => {
@@ -2348,11 +2575,11 @@ var Delta = ({ vm }) => {
2348
2575
  if (!vm.delta) return null;
2349
2576
  const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
2350
2577
  const sign = vm.delta.value >= 0 ? "+" : "";
2351
- const text = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2578
+ const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2352
2579
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
2353
2580
  vm.delta.isPositive ? "\u25B2" : "\u25BC",
2354
2581
  " ",
2355
- text
2582
+ text2
2356
2583
  ] });
2357
2584
  };
2358
2585
  var Sparkline = ({
@@ -2515,7 +2742,7 @@ var KPI_DEFAULT_TEMPLATES = {
2515
2742
  hero_chart: HeroChart,
2516
2743
  icon: Icon
2517
2744
  };
2518
- var WidgetStateSlot = React13__default.default.createContext(null);
2745
+ var WidgetStateSlot = React14__default.default.createContext(null);
2519
2746
  var WidgetShell = ({
2520
2747
  widgetType,
2521
2748
  title,
@@ -2533,7 +2760,7 @@ var WidgetShell = ({
2533
2760
  }) => {
2534
2761
  const t = useTokens();
2535
2762
  const chrome = resolveChrome(t, widgetType);
2536
- const renderState = React13__default.default.useContext(WidgetStateSlot);
2763
+ const renderState = React14__default.default.useContext(WidgetStateSlot);
2537
2764
  const frame = {
2538
2765
  display: "flex",
2539
2766
  flexDirection: "column",
@@ -2638,24 +2865,91 @@ function alignSummaryToSeries(dataSource, series) {
2638
2865
  });
2639
2866
  return { ...dataSource, pivot: { ...dataSource.pivot, summary: fixedSummary } };
2640
2867
  }
2868
+ dayjs4__default.default.extend(utc__default.default);
2869
+ dayjs4__default.default.extend(timezone2__default.default);
2641
2870
  function isLikelyTimestamp(v) {
2642
2871
  if (typeof v !== "number" || !Number.isFinite(v)) return false;
2643
2872
  return v > 946e9 && v < 41e11;
2644
2873
  }
2645
- function fillDateGaps({ rows, xKey, series, unit, range }) {
2874
+ function wallStartOfPeriod(d, unit) {
2875
+ switch (unit) {
2876
+ case "week": {
2877
+ const day = d.startOf("day");
2878
+ return day.subtract((day.day() + 6) % 7, "day");
2879
+ }
2880
+ case "quarter": {
2881
+ const month = d.startOf("month");
2882
+ return month.month(Math.floor(month.month() / 3) * 3);
2883
+ }
2884
+ default:
2885
+ return d.startOf(unit);
2886
+ }
2887
+ }
2888
+ function wallAddPeriod(d, unit) {
2889
+ return unit === "quarter" ? d.add(3, "month") : d.add(1, unit);
2890
+ }
2891
+ function inZone(wall, d, timeZone) {
2892
+ const date = wall(dayjs4__default.default.utc(d.tz(timeZone).format("YYYY-MM-DD"))).format("YYYY-MM-DD");
2893
+ return dayjs4__default.default.tz(date, timeZone);
2894
+ }
2895
+ function startOfPeriod(d, unit, timeZone) {
2896
+ return timeZone ? inZone((w) => wallStartOfPeriod(w, unit), d, timeZone) : wallStartOfPeriod(d, unit);
2897
+ }
2898
+ function addPeriod(d, unit, timeZone) {
2899
+ return timeZone ? inZone((w) => wallAddPeriod(w, unit), d, timeZone) : wallAddPeriod(d, unit);
2900
+ }
2901
+ var HAS_OFFSET = /(Z|[+-]\d{2}:?\d{2})$/i;
2902
+ function parseDateValue(v, timeZone) {
2903
+ if (timeZone && typeof v === "string" && !HAS_OFFSET.test(v.trim())) {
2904
+ try {
2905
+ return dayjs4__default.default.tz(v, timeZone);
2906
+ } catch {
2907
+ return dayjs4__default.default(NaN);
2908
+ }
2909
+ }
2910
+ return dayjs4__default.default(v);
2911
+ }
2912
+ function sumInto(target, row, series) {
2913
+ series.forEach(({ valueField }) => {
2914
+ if (!(valueField in row) && !(valueField in target)) return;
2915
+ const a = Number(target[valueField]);
2916
+ const b = Number(row[valueField]);
2917
+ target[valueField] = (Number.isFinite(a) ? a : 0) + (Number.isFinite(b) ? b : 0);
2918
+ });
2919
+ }
2920
+ function fillDateGaps({
2921
+ rows,
2922
+ xKey,
2923
+ series,
2924
+ unit,
2925
+ range,
2926
+ merge = "sum",
2927
+ xFormat,
2928
+ timeZone
2929
+ }) {
2646
2930
  if (!range) return rows;
2647
- const keyFormat = unit === "month" ? "YYYY-MM" : "YYYY-MM-DD";
2648
2931
  const byPeriod = /* @__PURE__ */ new Map();
2932
+ const copied = /* @__PURE__ */ new Set();
2649
2933
  rows.forEach((row) => {
2650
- const d = dayjs8__default.default(row[xKey]);
2651
- if (d.isValid()) byPeriod.set(d.startOf(unit).format(keyFormat), row);
2934
+ const d = parseDateValue(row[xKey], timeZone);
2935
+ if (!d.isValid()) return;
2936
+ const key = startOfPeriod(d, unit, timeZone).valueOf();
2937
+ const existing = byPeriod.get(key);
2938
+ if (!existing || merge === "last") {
2939
+ byPeriod.set(key, row);
2940
+ return;
2941
+ }
2942
+ const merged = copied.has(key) ? existing : { ...existing };
2943
+ copied.add(key);
2944
+ sumInto(merged, row, series);
2945
+ byPeriod.set(key, merged);
2652
2946
  });
2653
2947
  const filled = [];
2654
- let cursor = range.start;
2655
- const useTimestamp = isLikelyTimestamp(rows[0]?.[xKey]);
2656
- while (cursor.isBefore(range.end) || cursor.isSame(range.end, unit)) {
2657
- const key = cursor.format(keyFormat);
2658
- const existing = byPeriod.get(key);
2948
+ const useTimestamp = rows.length ? isLikelyTimestamp(rows[0]?.[xKey]) : xFormat === "timestamp";
2949
+ const end = startOfPeriod(range.end, unit, timeZone);
2950
+ let cursor = startOfPeriod(range.start, unit, timeZone);
2951
+ while (!cursor.isAfter(end)) {
2952
+ const existing = byPeriod.get(cursor.valueOf());
2659
2953
  if (existing) {
2660
2954
  filled.push(existing);
2661
2955
  } else {
@@ -2667,10 +2961,33 @@ function fillDateGaps({ rows, xKey, series, unit, range }) {
2667
2961
  });
2668
2962
  filled.push(zeroRow);
2669
2963
  }
2670
- cursor = cursor.add(1, unit);
2964
+ cursor = addPeriod(cursor, unit, timeZone);
2671
2965
  }
2672
2966
  return filled;
2673
2967
  }
2968
+ function applyCumulative(rows, xKey, fields, baseline = {}) {
2969
+ if (!fields.length) return rows;
2970
+ const dated = rows.map((row, i) => ({ row, i, t: dayjs4__default.default(row?.[xKey]).valueOf() }));
2971
+ dated.sort((a, b) => {
2972
+ const at = Number.isFinite(a.t), bt = Number.isFinite(b.t);
2973
+ if (at && bt) return a.t - b.t || a.i - b.i;
2974
+ if (at !== bt) return at ? -1 : 1;
2975
+ return a.i - b.i;
2976
+ });
2977
+ const running = {};
2978
+ fields.forEach((f) => {
2979
+ running[f] = Number(baseline[f]) || 0;
2980
+ });
2981
+ return dated.map(({ row }) => {
2982
+ const next = { ...row };
2983
+ fields.forEach((f) => {
2984
+ const v = Number(row?.[f]);
2985
+ running[f] += Number.isFinite(v) ? v : 0;
2986
+ next[f] = running[f];
2987
+ });
2988
+ return next;
2989
+ });
2990
+ }
2674
2991
  function toNumber(v) {
2675
2992
  if (typeof v === "number") return Number.isFinite(v) ? v : 0;
2676
2993
  const n = Number(v);
@@ -2688,6 +3005,49 @@ function coerceSeriesNumbers(rows, series) {
2688
3005
  });
2689
3006
  }
2690
3007
 
3008
+ // src/transforms/percentiles.ts
3009
+ function percentileKey(name, percent) {
3010
+ return `${name}_p${String(Number(percent)).replace(".", "_")}`;
3011
+ }
3012
+ function percentileSummaries(summary) {
3013
+ return (summary || []).filter((s) => s?.type === "percentiles" && s.value).map((s) => s.value);
3014
+ }
3015
+ function readPercentiles(raw2) {
3016
+ if (raw2 == null || typeof raw2 !== "object") return null;
3017
+ const values = Array.isArray(raw2) ? raw2 : raw2.values ?? raw2;
3018
+ const out = {};
3019
+ if (Array.isArray(values)) {
3020
+ values.forEach((v) => {
3021
+ if (v && v.key != null) out[String(v.key)] = v.value;
3022
+ });
3023
+ } else if (typeof values === "object") {
3024
+ for (const [k, v] of Object.entries(values)) {
3025
+ if (k.endsWith("_as_string")) continue;
3026
+ if (Number.isFinite(Number(k))) out[k] = v;
3027
+ }
3028
+ }
3029
+ return Object.keys(out).length ? out : null;
3030
+ }
3031
+ function flattenPercentiles(rows, names) {
3032
+ if (!names.length) return rows;
3033
+ return rows.map((row) => {
3034
+ let next = row;
3035
+ for (const name of names) {
3036
+ const parsed = readPercentiles(row?.[name]);
3037
+ if (!parsed) continue;
3038
+ if (next === row) next = { ...row };
3039
+ let median;
3040
+ for (const [percent, value] of Object.entries(parsed)) {
3041
+ const n = value == null ? null : Number(value);
3042
+ next[percentileKey(name, percent)] = n;
3043
+ if (Number(percent) === 50 && n != null) median = n;
3044
+ }
3045
+ if (median !== void 0) next[name] = median;
3046
+ }
3047
+ return next;
3048
+ });
3049
+ }
3050
+
2691
3051
  // src/react/useWidgetData.ts
2692
3052
  var ALIGN_TYPES = /* @__PURE__ */ new Set(["BAR", "BAR_CHART", "PIE", "PIE_CHART", "DOUGHNUT", "DOUGHNUT_CHART"]);
2693
3053
  var formCache = /* @__PURE__ */ new WeakMap();
@@ -2708,9 +3068,30 @@ function getFormOnce(client, formId) {
2708
3068
  }
2709
3069
  return p;
2710
3070
  }
3071
+ function baselineClause(source, start) {
3072
+ const f = (source.dataSource?.fields || []).find(
3073
+ (x) => x?.header === source.dateField || x?.masterfield === source.dateField
3074
+ );
3075
+ return {
3076
+ header: f?.header ?? source.dateField,
3077
+ masterfield: f?.masterfield ?? source.dateField,
3078
+ masterFieldType: f?.masterFieldType ?? "DATE_PICKER",
3079
+ masterFieldDataType: f?.masterFieldDataType ?? "STRING",
3080
+ // Date-only: the query generator keeps "YYYY-MM-DD" operands as dates.
3081
+ filter: { operator: "LESS_THAN", operand: [start.format("YYYY-MM-DD")] }
3082
+ };
3083
+ }
3084
+ function readBaseline(rows) {
3085
+ const out = {};
3086
+ for (const [k, v] of Object.entries(rows?.[0] ?? {})) {
3087
+ const n = Number(v);
3088
+ if (Number.isFinite(n)) out[k] = n;
3089
+ }
3090
+ return out;
3091
+ }
2711
3092
  function useDebounced(value, ms, enabled) {
2712
- const [v, setV] = React13.useState(value);
2713
- React13.useEffect(() => {
3093
+ const [v, setV] = React14.useState(value);
3094
+ React14.useEffect(() => {
2714
3095
  if (!enabled) {
2715
3096
  setV(value);
2716
3097
  return;
@@ -2759,23 +3140,23 @@ function useWidgetData(args) {
2759
3140
  searchDebounceMs = 400
2760
3141
  } = args;
2761
3142
  const rt = useZvolvRuntime();
2762
- const [sourceRows, setSourceRows] = React13.useState([]);
2763
- const [userLabels, setUserLabels] = React13.useState({});
2764
- const [initialLoading, setInitialLoading] = React13.useState(true);
2765
- const [loading, setLoading] = React13.useState(false);
2766
- const [error, setError] = React13.useState(null);
2767
- const [nonce, setNonce] = React13.useState(0);
2768
- const requestedUserIds = React13.useRef(/* @__PURE__ */ new Set());
2769
- const reqSeq = React13.useRef(0);
2770
- const config = React13.useMemo(() => parseConfig(configurations), [configurations]);
2771
- const series = React13.useMemo(() => resolveSeries(config), [config]);
2772
- const xKey = React13.useMemo(() => resolveXKey(series), [series]);
2773
- const configured = React13.useMemo(() => isDataSourceConfigured(dataSource), [dataSource]);
3143
+ const [sourceRows, setSourceRows] = React14.useState([]);
3144
+ const [userLabels, setUserLabels] = React14.useState({});
3145
+ const [initialLoading, setInitialLoading] = React14.useState(true);
3146
+ const [loading, setLoading] = React14.useState(false);
3147
+ const [error, setError] = React14.useState(null);
3148
+ const [nonce, setNonce] = React14.useState(0);
3149
+ const requestedUserIds = React14.useRef(/* @__PURE__ */ new Set());
3150
+ const reqSeq = React14.useRef(0);
3151
+ const config = React14.useMemo(() => parseConfig(configurations), [configurations]);
3152
+ const series = React14.useMemo(() => resolveSeries(config), [config]);
3153
+ const xKey = React14.useMemo(() => resolveXKey(series), [series]);
3154
+ const configured = React14.useMemo(() => isDataSourceConfigured(dataSource), [dataSource]);
2774
3155
  const hasAutomation = !!automationRows;
2775
3156
  const tableMode = resolveTableMode(dataSource, widgetType, hasAutomation);
2776
3157
  const filterMode = resolveFilterMode(dataSource, hasAutomation);
2777
3158
  const debouncedSearch = useDebounced(search, searchDebounceMs, tableMode === "remote");
2778
- const remote = React13.useMemo(() => ({
3159
+ const remote = React14.useMemo(() => ({
2779
3160
  search: tableMode === "remote" ? debouncedSearch : "",
2780
3161
  columnFilters: tableMode === "remote" ? columnFilters : void 0,
2781
3162
  sort: tableMode === "remote" ? sort : null,
@@ -2787,14 +3168,35 @@ function useWidgetData(args) {
2787
3168
  const localKey = JSON.stringify(localFilters ?? {});
2788
3169
  const dsKey = JSON.stringify(dataSource ?? {});
2789
3170
  const remoteKey = JSON.stringify(remote);
2790
- const normalize = React13.useCallback((incoming) => {
2791
- const withPostOps = applyPostOperations(incoming, dataSource?.pivot?.postOperations);
3171
+ const dateGapKey = JSON.stringify(dateGap ?? null);
3172
+ const gapTimeZone = React14.useMemo(() => {
3173
+ if (dateGap?.timeZone) return dateGap.timeZone;
3174
+ const row = (dataSource?.pivot?.rows || []).find((r) => r?.title === xKey && r?.interval);
3175
+ return row?.timeZone || void 0;
3176
+ }, [dateGap?.timeZone, dataSource, xKey]);
3177
+ const normalize = React14.useCallback((incoming, fetchedBaseline) => {
3178
+ const percentiles = percentileSummaries(dataSource?.pivot?.summary);
3179
+ const flat = percentiles.length ? flattenPercentiles(incoming, percentiles) : incoming;
3180
+ const withPostOps = applyPostOperations(flat, dataSource?.pivot?.postOperations);
2792
3181
  const numeric = coerceSeriesNumbers(withPostOps, series);
2793
3182
  if (!dateGap) return numeric;
2794
- const range = dateGap.start && dateGap.end ? { start: dayjs8__default.default(dateGap.start).startOf(dateGap.unit), end: dayjs8__default.default(dateGap.end).startOf(dateGap.unit) } : null;
2795
- return fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range });
2796
- }, [dataSource, series, xKey, dateGap]);
2797
- React13.useEffect(() => {
3183
+ const tz = gapTimeZone;
3184
+ const period = (v) => startOfPeriod(parseDateValue(v, tz), dateGap.unit, tz);
3185
+ const range = dateGap.start && dateGap.end ? { start: period(dateGap.start), end: period(dateGap.end) } : null;
3186
+ const filled = fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range, merge: dateGap.merge, timeZone: tz });
3187
+ if (!dateGap.cumulative) return filled;
3188
+ let inRange = filled;
3189
+ if (dateGap.start && !range) {
3190
+ const from = period(dateGap.start).valueOf();
3191
+ inRange = filled.filter((row) => {
3192
+ const d = parseDateValue(row?.[xKey], tz);
3193
+ return !d.isValid() || startOfPeriod(d, dateGap.unit, tz).valueOf() >= from;
3194
+ });
3195
+ }
3196
+ const fields = dateGap.cumulative === true ? series.map((s) => s.valueField) : dateGap.cumulative;
3197
+ return applyCumulative(inRange, xKey, fields, { ...fetchedBaseline || {}, ...dateGap.baseline || {} });
3198
+ }, [dataSource, series, xKey, dateGap, gapTimeZone]);
3199
+ React14.useEffect(() => {
2798
3200
  if (automationRows) {
2799
3201
  if (automationLoading) {
2800
3202
  setLoading(true);
@@ -2815,53 +3217,67 @@ function useWidgetData(args) {
2815
3217
  let cancelled = false;
2816
3218
  setError(null);
2817
3219
  setLoading(true);
3220
+ const queryForm = async (ds, mode, extraClauses = []) => {
3221
+ const formId = ds.formId ?? ds.fields?.[0]?.formID;
3222
+ const withFormId = formId ? { ...ds, formId } : ds;
3223
+ const merged = mergeHostFilters(localFilters, dashFilters);
3224
+ const cfgWithDates = { ...config, cdateFilters: mode === "main" ? cdateFilters : [] };
3225
+ const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
3226
+ if (remote.widgetFilters?.length) {
3227
+ dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
3228
+ }
3229
+ if (mode === "main" && remote.columnFilters && Object.keys(remote.columnFilters).length) {
3230
+ dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
3231
+ }
3232
+ const searchClause = mode === "main" ? buildSearchClause(remote.search) : null;
3233
+ if (searchClause) dashboardFilters.push(searchClause);
3234
+ dashboardFilters.push(...extraClauses);
3235
+ const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, mode === "main" ? remote.sort : null);
3236
+ if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
3237
+ const form = await getFormOnce(rt.client, formId).catch((e) => {
3238
+ console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
3239
+ return true;
3240
+ });
3241
+ if (!form) throw new Error("You don't have access to this widget's form.");
3242
+ }
3243
+ const fixedDs = mode === "main" && ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
3244
+ const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
3245
+ const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
3246
+ if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
3247
+ const query = rt.generateQuery(
3248
+ queryConfig,
3249
+ fixedDs,
3250
+ fixedDs.formId,
3251
+ 0,
3252
+ rowLimit,
3253
+ [],
3254
+ dashboardFilters,
3255
+ [],
3256
+ loggedInUser,
3257
+ dashboardSort,
3258
+ null,
3259
+ fixedDs.isTask,
3260
+ mappings
3261
+ );
3262
+ const res = await rt.client.workflow.elasticSearch(query);
3263
+ return Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
3264
+ };
2818
3265
  (async () => {
2819
3266
  try {
2820
3267
  const ds = dataSource || {};
2821
3268
  let fetched = [];
3269
+ let fetchedBaseline;
2822
3270
  if (usesFormFramework(ds)) {
2823
- const formId = ds.formId ?? ds.fields?.[0]?.formID;
2824
- const withFormId = formId ? { ...ds, formId } : ds;
2825
- const merged = mergeHostFilters(localFilters, dashFilters);
2826
- const cfgWithDates = { ...config, cdateFilters };
2827
- const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
2828
- if (remote.widgetFilters?.length) {
2829
- dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
2830
- }
2831
- if (remote.columnFilters && Object.keys(remote.columnFilters).length) {
2832
- dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
2833
- }
2834
- const searchClause = buildSearchClause(remote.search);
2835
- if (searchClause) dashboardFilters.push(searchClause);
2836
- const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, remote.sort);
2837
- if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
2838
- const form = await getFormOnce(rt.client, formId).catch((e) => {
2839
- console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
2840
- return true;
2841
- });
2842
- if (!form) throw new Error("You don't have access to this widget's form.");
2843
- }
2844
- const fixedDs = ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
2845
- const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
2846
- const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
2847
- if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
2848
- const query = rt.generateQuery(
2849
- queryConfig,
2850
- fixedDs,
2851
- fixedDs.formId,
2852
- 0,
2853
- rowLimit,
2854
- [],
2855
- dashboardFilters,
2856
- [],
2857
- loggedInUser,
2858
- dashboardSort,
2859
- null,
2860
- fixedDs.isTask,
2861
- mappings
2862
- );
2863
- const res = await rt.client.workflow.elasticSearch(query);
2864
- fetched = Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
3271
+ const baselineReq = dateGap?.baselineSource && dateGap.start ? queryForm(
3272
+ dateGap.baselineSource.dataSource,
3273
+ "baseline",
3274
+ [baselineClause(dateGap.baselineSource, startOfPeriod(parseDateValue(dateGap.start, gapTimeZone), dateGap.unit, gapTimeZone))]
3275
+ ).catch((e) => {
3276
+ console.warn("[@zestl/zvolv-widgets] dateGap baseline fetch failed; starting from 0", e);
3277
+ return [];
3278
+ }) : null;
3279
+ fetched = await queryForm(ds, "main");
3280
+ if (baselineReq) fetchedBaseline = readBaseline(await baselineReq);
2865
3281
  } else {
2866
3282
  const wftype = config?.dataConfs?.WorkflowTypeID ?? workflowTypeId;
2867
3283
  const res = await rt.client.workflow.getDataSource(JSON.stringify({ widgetId, wftype }));
@@ -2869,7 +3285,7 @@ function useWidgetData(args) {
2869
3285
  fetched = payload?.table ?? (Array.isArray(payload) ? payload : []);
2870
3286
  }
2871
3287
  if (cancelled || seq !== reqSeq.current) return;
2872
- setSourceRows(normalize(fetched));
3288
+ setSourceRows(normalize(fetched, fetchedBaseline));
2873
3289
  } catch (err) {
2874
3290
  if (cancelled || seq !== reqSeq.current) return;
2875
3291
  console.error(`[@zestl/zvolv-widgets] ${widgetType ?? "widget"} fetch failed:`, err);
@@ -2885,8 +3301,8 @@ function useWidgetData(args) {
2885
3301
  return () => {
2886
3302
  cancelled = true;
2887
3303
  };
2888
- }, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, configured, nonce, automationRows, automationLoading]);
2889
- const userKind = React13.useMemo(() => {
3304
+ }, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, dateGapKey, configured, nonce, automationRows, automationLoading]);
3305
+ const userKind = React14.useMemo(() => {
2890
3306
  if (!rt.getUserFieldKind) return null;
2891
3307
  const field2 = (dataSource?.fields || []).find(
2892
3308
  (f) => f?.masterfield === xKey || f?.header === xKey
@@ -2896,7 +3312,7 @@ function useWidgetData(args) {
2896
3312
  );
2897
3313
  return rt.getUserFieldKind(field2?.masterFieldType ?? pivotRow?.masterFieldType);
2898
3314
  }, [rt, dataSource, xKey]);
2899
- React13.useEffect(() => {
3315
+ React14.useEffect(() => {
2900
3316
  if (!userKind || !rt.resolveUserLabels || sourceRows.length === 0) return;
2901
3317
  const pending2 = sourceRows.flatMap((row) => {
2902
3318
  const v = row?.[xKey];
@@ -2922,14 +3338,14 @@ function useWidgetData(args) {
2922
3338
  cancelled = true;
2923
3339
  };
2924
3340
  }, [sourceRows, userKind, xKey, rt]);
2925
- const toLabel = React13.useCallback((value) => {
3341
+ const toLabel = React14.useCallback((value) => {
2926
3342
  if (!userKind || value === null || value === void 0 || value === "") return value;
2927
3343
  if (Array.isArray(value)) {
2928
3344
  return value.map((v) => userLabels[String(v)] ?? v).filter((v) => v !== "" && v != null).join(", ");
2929
3345
  }
2930
3346
  return userLabels[String(value)] ?? value;
2931
3347
  }, [userKind, userLabels]);
2932
- const rows = React13.useMemo(() => {
3348
+ const rows = React14.useMemo(() => {
2933
3349
  let out = sourceRows;
2934
3350
  if (filterMode === "local" && widgetFilters?.length) out = applyLocalWidgetFilters(out, widgetFilters);
2935
3351
  if (tableMode === "local") {
@@ -2948,7 +3364,7 @@ function useWidgetData(args) {
2948
3364
  loading,
2949
3365
  initialLoading,
2950
3366
  error,
2951
- refresh: React13.useCallback(() => setNonce((n) => n + 1), []),
3367
+ refresh: React14.useCallback(() => setNonce((n) => n + 1), []),
2952
3368
  meta: {
2953
3369
  xKey,
2954
3370
  series,
@@ -2962,10 +3378,10 @@ function useWidgetData(args) {
2962
3378
  };
2963
3379
  }
2964
3380
  function useControllable(controlled, initial, onChange) {
2965
- const [inner, setInner] = React13.useState(initial);
3381
+ const [inner, setInner] = React14.useState(initial);
2966
3382
  const isControlled = controlled !== void 0;
2967
3383
  const value = isControlled ? controlled : inner;
2968
- const set = React13.useCallback((v) => {
3384
+ const set = React14.useCallback((v) => {
2969
3385
  if (!isControlled) setInner(v);
2970
3386
  onChange?.(v);
2971
3387
  }, [isControlled, onChange]);
@@ -2992,56 +3408,56 @@ function useWidgetInteractions(props = {}) {
2992
3408
  props.defaultWidgetFilters ?? EMPTY_INTERACTIONS.widgetFilters,
2993
3409
  props.onWidgetFiltersChange
2994
3410
  );
2995
- const latest = React13.useRef({ search, columnFilters, sort, widgetFilters });
3411
+ const latest = React14.useRef({ search, columnFilters, sort, widgetFilters });
2996
3412
  latest.current = { search, columnFilters, sort, widgetFilters };
2997
3413
  const onAny = props.onInteractionsChange;
2998
- const report = React13.useCallback((patch) => {
3414
+ const report = React14.useCallback((patch) => {
2999
3415
  const next = { ...latest.current, ...patch };
3000
3416
  latest.current = next;
3001
3417
  onAny?.(next);
3002
3418
  }, [onAny]);
3003
- const setSearch = React13.useCallback((v) => {
3419
+ const setSearch = React14.useCallback((v) => {
3004
3420
  setSearchRaw(v);
3005
3421
  report({ search: v });
3006
3422
  }, [setSearchRaw, report]);
3007
- const setColumnFilters = React13.useCallback((v) => {
3423
+ const setColumnFilters = React14.useCallback((v) => {
3008
3424
  setColumnFiltersRaw(v);
3009
3425
  report({ columnFilters: v });
3010
3426
  }, [setColumnFiltersRaw, report]);
3011
- const setColumnFilter = React13.useCallback((key, values) => {
3427
+ const setColumnFilter = React14.useCallback((key, values) => {
3012
3428
  const next = { ...latest.current.columnFilters };
3013
3429
  if (values && values.length) next[key] = values;
3014
3430
  else delete next[key];
3015
3431
  setColumnFilters(next);
3016
3432
  }, [setColumnFilters]);
3017
- const setSort = React13.useCallback((v) => {
3433
+ const setSort = React14.useCallback((v) => {
3018
3434
  setSortRaw(v);
3019
3435
  report({ sort: v });
3020
3436
  }, [setSortRaw, report]);
3021
- const toggleSort = React13.useCallback((key) => {
3437
+ const toggleSort = React14.useCallback((key) => {
3022
3438
  const cur = latest.current.sort;
3023
3439
  if (!cur || cur.key !== key) setSort({ key, direction: "asc" });
3024
3440
  else if (cur.direction === "asc") setSort({ key, direction: "desc" });
3025
3441
  else setSort(null);
3026
3442
  }, [setSort]);
3027
- const setWidgetFilters = React13.useCallback((v) => {
3443
+ const setWidgetFilters = React14.useCallback((v) => {
3028
3444
  setWidgetFiltersRaw(v);
3029
3445
  report({ widgetFilters: v });
3030
3446
  }, [setWidgetFiltersRaw, report]);
3031
- const addWidgetFilter = React13.useCallback((f) => {
3447
+ const addWidgetFilter = React14.useCallback((f) => {
3032
3448
  setWidgetFilters([...latest.current.widgetFilters, f]);
3033
3449
  }, [setWidgetFilters]);
3034
- const removeWidgetFilter = React13.useCallback((index) => {
3450
+ const removeWidgetFilter = React14.useCallback((index) => {
3035
3451
  setWidgetFilters(latest.current.widgetFilters.filter((_2, i) => i !== index));
3036
3452
  }, [setWidgetFilters]);
3037
- const clearAll = React13.useCallback(() => {
3453
+ const clearAll = React14.useCallback(() => {
3038
3454
  setSearchRaw("");
3039
3455
  setColumnFiltersRaw({});
3040
3456
  setSortRaw(null);
3041
3457
  setWidgetFiltersRaw([]);
3042
3458
  report({ ...EMPTY_INTERACTIONS });
3043
3459
  }, [setSearchRaw, setColumnFiltersRaw, setSortRaw, setWidgetFiltersRaw, report]);
3044
- return React13.useMemo(() => ({
3460
+ return React14.useMemo(() => ({
3045
3461
  search,
3046
3462
  columnFilters,
3047
3463
  sort,
@@ -3154,7 +3570,7 @@ function describeWidgetFilter(f) {
3154
3570
  function useInteractiveWidgetData(props, options = {}) {
3155
3571
  const t = useTokens();
3156
3572
  const interactions = useWidgetInteractions(props);
3157
- const dataArgs = React13.useMemo(() => {
3573
+ const dataArgs = React14.useMemo(() => {
3158
3574
  const rest = { ...props };
3159
3575
  for (const k of UI_KEYS) delete rest[k];
3160
3576
  return rest;
@@ -3166,7 +3582,7 @@ function useInteractiveWidgetData(props, options = {}) {
3166
3582
  sort: interactions.sort,
3167
3583
  widgetFilters: interactions.widgetFilters
3168
3584
  });
3169
- const config = React13.useMemo(
3585
+ const config = React14.useMemo(
3170
3586
  () => resolveWidgetDefaults(t, props.widgetType, parseConfig(props.configurations)),
3171
3587
  [t, props.widgetType, props.configurations]
3172
3588
  );
@@ -3176,7 +3592,7 @@ function useInteractiveWidgetData(props, options = {}) {
3176
3592
  showColumnFilter: !!options.table && (props.showColumnFilter ?? !!(config?.showColumnFilter ?? config?.styling?.showColumnFilter ?? props.renderColumnFilter)),
3177
3593
  enableSort: !!options.table && (props.enableSort ?? config?.enableSort !== false)
3178
3594
  };
3179
- const seen = React13.useRef({ ds: "", values: /* @__PURE__ */ new Map() });
3595
+ const seen = React14.useRef({ ds: "", values: /* @__PURE__ */ new Map() });
3180
3596
  const dsKey = JSON.stringify(props.dataSource ?? {});
3181
3597
  if (seen.current.ds !== dsKey) seen.current = { ds: dsKey, values: /* @__PURE__ */ new Map() };
3182
3598
  const getColumnOptions = (key) => {
@@ -3194,7 +3610,7 @@ function useInteractiveWidgetData(props, options = {}) {
3194
3610
  }
3195
3611
  return [...byValue.values()];
3196
3612
  };
3197
- const fields = React13.useMemo(() => filterableFields(props.dataSource), [dsKey]);
3613
+ const fields = React14.useMemo(() => filterableFields(props.dataSource), [dsKey]);
3198
3614
  const searchNode = flags.showSearch ? (() => {
3199
3615
  const args = {
3200
3616
  value: interactions.search,
@@ -3237,7 +3653,7 @@ function useInteractiveWidgetData(props, options = {}) {
3237
3653
  tokens: t
3238
3654
  };
3239
3655
  parts.push(
3240
- /* @__PURE__ */ jsxRuntime.jsx(React13__default.default.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultSortIndicator, { ...args }) }, "sort")
3656
+ /* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultSortIndicator, { ...args }) }, "sort")
3241
3657
  );
3242
3658
  }
3243
3659
  if (flags.showColumnFilter) {
@@ -3251,7 +3667,7 @@ function useInteractiveWidgetData(props, options = {}) {
3251
3667
  tokens: t
3252
3668
  };
3253
3669
  parts.push(
3254
- /* @__PURE__ */ jsxRuntime.jsx(React13__default.default.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultColumnFilter, { ...args }) }, "filter")
3670
+ /* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultColumnFilter, { ...args }) }, "filter")
3255
3671
  );
3256
3672
  }
3257
3673
  return parts.length ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : null;
@@ -3293,9 +3709,9 @@ var DefaultSearch = ({ value, setValue, placeholder, tokens: t }) => /* @__PURE_
3293
3709
  }
3294
3710
  );
3295
3711
  var Popover = ({ trigger, children, tokens: t, align = "right" }) => {
3296
- const [open, setOpen] = React13.useState(false);
3297
- const ref = React13.useRef(null);
3298
- React13.useEffect(() => {
3712
+ const [open, setOpen] = React14.useState(false);
3713
+ const ref = React14.useRef(null);
3714
+ React14.useEffect(() => {
3299
3715
  if (!open) return;
3300
3716
  const onDown = (e) => {
3301
3717
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -3357,7 +3773,7 @@ var DefaultSortIndicator = ({ direction, toggle, column, tokens: t }) => /* @__P
3357
3773
  }
3358
3774
  );
3359
3775
  var DefaultColumnFilter = ({ column, selected, setSelected, clear, options, tokens: t }) => {
3360
- const [q, setQ] = React13.useState("");
3776
+ const [q, setQ] = React14.useState("");
3361
3777
  const shown = options.filter((o) => !q || o.label.toLowerCase().includes(q.toLowerCase()));
3362
3778
  const isOn = (v) => selected.some((s) => String(s) === String(v));
3363
3779
  return /* @__PURE__ */ jsxRuntime.jsx(Popover, { tokens: t, align: "left", trigger: (_2, toggle) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -3385,10 +3801,10 @@ var DefaultColumnFilter = ({ column, selected, setSelected, clear, options, toke
3385
3801
  };
3386
3802
  var DATE_TOKENS = ["TODAY", "YESTERDAY", "LAST_SEVEN_DAYS", "LAST_THIRTY_DAYS", "THIS_WEEK", "THIS_MONTH", "THIS_YEAR"];
3387
3803
  var DefaultWidgetFilter = ({ filters: filters2, addFilter, removeFilter, clear, fields, tokens: t }) => {
3388
- const [field2, setField] = React13.useState("");
3389
- const [operator, setOperator] = React13.useState("");
3390
- const [value, setValue] = React13.useState("");
3391
- const [value2, setValue2] = React13.useState("");
3804
+ const [field2, setField] = React14.useState("");
3805
+ const [operator, setOperator] = React14.useState("");
3806
+ const [value, setValue] = React14.useState("");
3807
+ const [value2, setValue2] = React14.useState("");
3392
3808
  const current = fields.find((f) => f.field === field2) ?? fields[0];
3393
3809
  const op = operator && current?.operators.includes(operator) ? operator : current?.operators[0] ?? "IN";
3394
3810
  const valueless = op === "EMPTY" || op === "NOT_EMPTY";
@@ -3624,7 +4040,7 @@ function useKpiContents({
3624
4040
  defaultIconColor = "#7b8499",
3625
4041
  dateFormatter
3626
4042
  }) {
3627
- return React13.useMemo(() => {
4043
+ return React14.useMemo(() => {
3628
4044
  if (!contents?.length) return [];
3629
4045
  const row = rows?.[0];
3630
4046
  return contents.map((content, i) => {
@@ -3739,31 +4155,100 @@ function resolveDrillDown(config = {}) {
3739
4155
  }
3740
4156
  var WIDGET_TYPE = "KPI_CARD";
3741
4157
  var KpiWidget = (props) => {
3742
- const { title, description, emptyMessage, unconfiguredMessage, className, style, onItemClick, ...dataArgs } = props;
4158
+ const {
4159
+ title,
4160
+ description,
4161
+ emptyMessage,
4162
+ unconfiguredMessage,
4163
+ className,
4164
+ style,
4165
+ onItemClick,
4166
+ externalSeriesByIndex,
4167
+ ...dataArgs
4168
+ } = props;
3743
4169
  const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: WIDGET_TYPE });
3744
4170
  const { data, config, tokens: t } = ix;
3745
4171
  const chrome = resolveChromeConfig(config);
3746
4172
  const contents = config?.cardConfig?.contents ?? config?.contents ?? [];
3747
4173
  const cardTemplate = config?.cardConfig?.template ?? config?.template ?? "standard";
3748
- const vms = useKpiContents({ contents, rows: data.rows, cardTemplate });
4174
+ const [fetchedSeries, setFetchedSeries] = React14.useState({});
4175
+ const onSeries = React14.useCallback((index, values) => {
4176
+ setFetchedSeries((prev) => {
4177
+ if (JSON.stringify(prev[index]) === JSON.stringify(values)) return prev;
4178
+ const next = { ...prev };
4179
+ if (values) next[index] = values;
4180
+ else delete next[index];
4181
+ return next;
4182
+ });
4183
+ }, []);
4184
+ const seriesByIndex = React14.useMemo(
4185
+ () => externalSeriesByIndex ? { ...fetchedSeries, ...externalSeriesByIndex } : fetchedSeries,
4186
+ [fetchedSeries, externalSeriesByIndex]
4187
+ );
4188
+ const vms = useKpiContents({ contents, rows: data.rows, cardTemplate, externalSeriesByIndex: seriesByIndex });
3749
4189
  const state = data.state === "empty" && contents.length > 0 ? "ready" : data.state;
3750
- return /* @__PURE__ */ jsxRuntime.jsx(
3751
- WidgetShell,
3752
- {
3753
- widgetType: WIDGET_TYPE,
3754
- title: chrome.showHeader ? title ?? chrome.title : void 0,
3755
- description: description ?? chrome.description,
3756
- state,
3757
- error: data.error,
3758
- emptyMessage: emptyMessage ?? chrome.emptyMessage,
3759
- unconfiguredMessage,
3760
- className,
3761
- style,
3762
- actions: ix.toolbar,
3763
- ...ix.shell,
3764
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsxRuntime.jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
3765
- }
4190
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4191
+ contents.map(
4192
+ (content, i) => content?.seriesSource?.mode === "timeseries" && content.seriesSource.dataSource && !externalSeriesByIndex?.[i] ? /* @__PURE__ */ jsxRuntime.jsx(
4193
+ KpiSeriesFetcher,
4194
+ {
4195
+ index: i,
4196
+ source: content.seriesSource,
4197
+ onSeries,
4198
+ dashFilters: dataArgs.dashFilters,
4199
+ cdateFilters: dataArgs.cdateFilters,
4200
+ localFilters: dataArgs.localFilters,
4201
+ workflowTypeId: dataArgs.workflowTypeId
4202
+ },
4203
+ i
4204
+ ) : null
4205
+ ),
4206
+ /* @__PURE__ */ jsxRuntime.jsx(
4207
+ WidgetShell,
4208
+ {
4209
+ widgetType: WIDGET_TYPE,
4210
+ title: chrome.showHeader ? title ?? chrome.title : void 0,
4211
+ description: description ?? chrome.description,
4212
+ state,
4213
+ error: data.error,
4214
+ emptyMessage: emptyMessage ?? chrome.emptyMessage,
4215
+ unconfiguredMessage,
4216
+ className,
4217
+ style,
4218
+ actions: ix.toolbar,
4219
+ ...ix.shell,
4220
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsxRuntime.jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
4221
+ }
4222
+ )
4223
+ ] });
4224
+ };
4225
+ var KpiSeriesFetcher = ({ index, source, onSeries, dashFilters, cdateFilters, localFilters, workflowTypeId }) => {
4226
+ const { dataSource, xField, valueField, dateGap } = source;
4227
+ const configurations = React14.useMemo(
4228
+ () => ({ series: [{ argumentField: xField, valueField }] }),
4229
+ [xField, valueField]
4230
+ );
4231
+ const data = useWidgetData({
4232
+ widgetType: "KPI_SERIES",
4233
+ dataSource,
4234
+ configurations,
4235
+ dateGap: dateGap ?? null,
4236
+ dashFilters,
4237
+ cdateFilters,
4238
+ localFilters,
4239
+ workflowTypeId
4240
+ });
4241
+ const values = React14.useMemo(
4242
+ () => data.rows.map((r) => toNum(r?.[valueField])).filter((n) => n != null),
4243
+ [data.rows, valueField]
3766
4244
  );
4245
+ const ready = data.state === "ready" || data.state === "empty";
4246
+ const key = ready ? JSON.stringify(values) : "";
4247
+ React14.useEffect(() => {
4248
+ onSeries(index, ready ? values : void 0);
4249
+ }, [index, key, ready, onSeries]);
4250
+ React14.useEffect(() => () => onSeries(index, void 0), [index, onSeries]);
4251
+ return null;
3767
4252
  };
3768
4253
  var KpiItem = ({
3769
4254
  vm,
@@ -3796,7 +4281,7 @@ var ChartWidget = (props) => {
3796
4281
  } = props;
3797
4282
  const ix = useInteractiveWidgetData({ ...dataArgs, widgetType });
3798
4283
  const { data, config, tokens: t } = ix;
3799
- const colorFor = React13.useMemo(() => {
4284
+ const colorFor = React14.useMemo(() => {
3800
4285
  const palette = t.colors.charts.palette;
3801
4286
  return (index, s) => {
3802
4287
  if (s?.color) return s.color;
@@ -3820,7 +4305,7 @@ var ChartWidget = (props) => {
3820
4305
  const renderer = render ?? children;
3821
4306
  let body;
3822
4307
  if (renderer) body = renderer(args);
3823
- else if (override) body = React13__default.default.createElement(override, args);
4308
+ else if (override) body = React14__default.default.createElement(override, args);
3824
4309
  else body = /* @__PURE__ */ jsxRuntime.jsx(FallbackBars, { ...args });
3825
4310
  return /* @__PURE__ */ jsxRuntime.jsx(
3826
4311
  WidgetShell,
@@ -3884,7 +4369,7 @@ var RankingBarWidget = (props) => {
3884
4369
  const valueField = ranking.valueField ?? data.meta.series[0]?.valueField ?? "value";
3885
4370
  const order = ranking.order === "asc" ? "asc" : "desc";
3886
4371
  const limit = toNum(ranking.limit) ?? 10;
3887
- const { rows, max } = React13.useMemo(() => {
4372
+ const { rows, max } = React14.useMemo(() => {
3888
4373
  const mapped = data.rows.map((r) => ({
3889
4374
  label: r[labelField],
3890
4375
  value: toNum(r[valueField]) ?? 0,
@@ -3930,7 +4415,7 @@ var RankingBarWidget = (props) => {
3930
4415
  style,
3931
4416
  actions: ix.toolbar,
3932
4417
  ...ix.shell,
3933
- children: render ? render(args) : override ? React13__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultRanking, { ...args })
4418
+ children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultRanking, { ...args })
3934
4419
  }
3935
4420
  );
3936
4421
  };
@@ -4038,7 +4523,7 @@ var HeatMapWidget = (props) => {
4038
4523
  medium: toNum(hm.thresholds?.medium?.value) ?? 60,
4039
4524
  high: toNum(hm.thresholds?.high?.value) ?? 100
4040
4525
  };
4041
- const cells = React13.useMemo(() => data.rows.map((row) => {
4526
+ const cells = React14.useMemo(() => data.rows.map((row) => {
4042
4527
  const count = toNum(row[countField]) ?? 0;
4043
4528
  const band = count <= thresholds.low ? "low" : count <= thresholds.medium ? "medium" : "high";
4044
4529
  const configured = hm.thresholds?.[band]?.color;
@@ -4153,7 +4638,7 @@ function resolveCollectionMapping(config, series) {
4153
4638
  return { ...fromSeries, ...block ?? config ?? {} };
4154
4639
  }
4155
4640
  function useCollectionItems(rows, mapping) {
4156
- return React13.useMemo(() => {
4641
+ return React14.useMemo(() => {
4157
4642
  const m = mapping ?? {};
4158
4643
  const detailFields = m.fields ?? m.dataConfs ?? [];
4159
4644
  return rows.map((row, i) => {
@@ -4192,7 +4677,7 @@ var CollectionWidget = (props) => {
4192
4677
  const ix = useInteractiveWidgetData({ ...dataArgs, widgetType });
4193
4678
  const { data, config, tokens: t } = ix;
4194
4679
  const chrome = resolveChromeConfig(config);
4195
- const mapping = React13.useMemo(() => resolveCollectionMapping(config, data.meta.series), [config, data.meta.series]);
4680
+ const mapping = React14.useMemo(() => resolveCollectionMapping(config, data.meta.series), [config, data.meta.series]);
4196
4681
  const items = useCollectionItems(data.rows, mapping);
4197
4682
  const template = config?.template || "standard";
4198
4683
  const override = useTemplateOverride(widgetType, template);
@@ -4218,7 +4703,7 @@ var CollectionWidget = (props) => {
4218
4703
  style,
4219
4704
  actions: ix.toolbar,
4220
4705
  ...ix.shell,
4221
- children: render ? render(args) : override ? React13__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultList, { ...args })
4706
+ children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultList, { ...args })
4222
4707
  }
4223
4708
  );
4224
4709
  };
@@ -4287,7 +4772,7 @@ var KIND_BY_TYPE = {
4287
4772
  RICHTEXT: "html"
4288
4773
  };
4289
4774
  function useTableColumns(explicit, config, rows) {
4290
- return React13.useMemo(() => {
4775
+ return React14.useMemo(() => {
4291
4776
  if (explicit?.length) return explicit;
4292
4777
  const declared = [config?.columns, config?.additionalProperties?.columns, config?.fields, config?.table?.columns].find((d) => Array.isArray(d) && d.length);
4293
4778
  if (Array.isArray(declared) && declared.length) {
@@ -4388,8 +4873,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
4388
4873
  if (column.kind === "number") {
4389
4874
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
4390
4875
  }
4391
- const text = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4392
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(text) ?? text).replace(/<[^>]*>/g, "") });
4876
+ const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4877
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
4393
4878
  };
4394
4879
  var DefaultTable = ({
4395
4880
  rows,
@@ -4468,11 +4953,11 @@ var PivotGridWidget = (props) => {
4468
4953
  };
4469
4954
  function toDate(v) {
4470
4955
  if (v === null || v === void 0 || v === "") return null;
4471
- const d = dayjs8__default.default(v);
4956
+ const d = dayjs4__default.default(v);
4472
4957
  return d.isValid() ? d.toDate() : null;
4473
4958
  }
4474
4959
  function useTemporalEvents(rows, mapping) {
4475
- return React13.useMemo(() => {
4960
+ return React14.useMemo(() => {
4476
4961
  const m = mapping ?? {};
4477
4962
  const titleField = m.taskTitle ?? m.titleField ?? m.title ?? m.labelField;
4478
4963
  const groupField = m.projectTitle ?? m.groupField ?? m.group ?? m.resourceField;
@@ -4493,7 +4978,7 @@ function useTemporalEvents(rows, mapping) {
4493
4978
  start,
4494
4979
  end,
4495
4980
  // With no time component on either bound, treat it as an all-day span.
4496
- allDay: !!(start && !end) || !!start && dayjs8__default.default(start).startOf("day").isSame(start),
4981
+ allDay: !!(start && !end) || !!start && dayjs4__default.default(start).startOf("day").isSame(start),
4497
4982
  status: statusRaw !== void 0 ? { label: statusRaw, tone: statusTone(statusRaw) } : void 0,
4498
4983
  progress: Number.isFinite(rawProgress) ? Math.max(0, Math.min(1, rawProgress > 1 ? rawProgress / 100 : rawProgress)) : void 0,
4499
4984
  color: colorField ? row?.[colorField] : void 0,
@@ -4520,12 +5005,12 @@ var TemporalWidget = (props) => {
4520
5005
  const chrome = resolveChromeConfig(config);
4521
5006
  const mapping = config?.timeline ?? config?.gantt ?? config?.calendar ?? config?.scheduler ?? config?.mapping ?? config;
4522
5007
  const events = useTemporalEvents(data.rows, mapping);
4523
- const range = React13.useMemo(() => {
5008
+ const range = React14.useMemo(() => {
4524
5009
  const stamps = events.flatMap((e) => [e.start, e.end].filter(Boolean).map((d) => d.getTime()));
4525
5010
  if (!stamps.length) return null;
4526
5011
  return { start: new Date(Math.min(...stamps)), end: new Date(Math.max(...stamps)) };
4527
5012
  }, [events]);
4528
- const lanes = React13.useMemo(() => {
5013
+ const lanes = React14.useMemo(() => {
4529
5014
  const map = /* @__PURE__ */ new Map();
4530
5015
  events.forEach((e) => {
4531
5016
  const key = String(e.group ?? "");
@@ -4560,7 +5045,7 @@ var TemporalWidget = (props) => {
4560
5045
  style,
4561
5046
  actions: ix.toolbar,
4562
5047
  ...ix.shell,
4563
- children: render ? render(args) : override ? React13__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultTimeline, { ...args })
5048
+ children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultTimeline, { ...args })
4564
5049
  }
4565
5050
  );
4566
5051
  };
@@ -4587,8 +5072,8 @@ var DefaultTimeline = ({ events, tokens: t, onEventClick }) => /* @__PURE__ */ j
4587
5072
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
4588
5073
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: t.typography.body.fontSize, color: t.colors.textPrimary, fontWeight: 600 }, children: String(e.title ?? "") }),
4589
5074
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: t.typography.caption.fontSize, color: t.colors.textTertiary }, children: [
4590
- e.start ? dayjs8__default.default(e.start).format("DD MMM YYYY") : "",
4591
- e.end ? ` \u2192 ${dayjs8__default.default(e.end).format("DD MMM YYYY")}` : "",
5075
+ e.start ? dayjs4__default.default(e.start).format("DD MMM YYYY") : "",
5076
+ e.end ? ` \u2192 ${dayjs4__default.default(e.end).format("DD MMM YYYY")}` : "",
4592
5077
  e.group ? ` \xB7 ${String(e.group)}` : ""
4593
5078
  ] })
4594
5079
  ] }),
@@ -4619,7 +5104,7 @@ var StepperWidget = (props) => {
4619
5104
  const descField = stepper.descriptionField;
4620
5105
  const statusField = stepper.statusField ?? "status";
4621
5106
  const authored = Array.isArray(stepper.steps) && stepper.steps.length > 0;
4622
- const steps = React13.useMemo(() => {
5107
+ const steps = React14.useMemo(() => {
4623
5108
  if (authored) {
4624
5109
  const live = stepper.statusField ? String(data.rows[0]?.[stepper.statusField] ?? "").trim().toLowerCase() : "";
4625
5110
  const liveIdx = live ? stepper.steps.findIndex((s) => String(s?.title ?? s?.label ?? "").trim().toLowerCase() === live) : -1;
@@ -4683,7 +5168,7 @@ var StepperWidget = (props) => {
4683
5168
  style,
4684
5169
  actions: ix.toolbar,
4685
5170
  ...ix.shell,
4686
- children: render ? render(args) : override ? React13__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultStepper, { ...args })
5171
+ children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultStepper, { ...args })
4687
5172
  }
4688
5173
  );
4689
5174
  };
@@ -4755,19 +5240,19 @@ function renderHtmlVariables(template, rows, variables) {
4755
5240
  } else if (v.dataFormat === "AMOUNT" && typeof value === "number") {
4756
5241
  value = formatAmountValue(value, v.amountSuffix || "Rupees", v.precision ?? 2);
4757
5242
  } else if (v.dataFormat === "DATE" && value) {
4758
- const d = dayjs8__default.default(value);
5243
+ const d = dayjs4__default.default(value);
4759
5244
  if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
4760
5245
  }
4761
- let text;
5246
+ let text2;
4762
5247
  if (Array.isArray(value)) {
4763
- text = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
5248
+ text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
4764
5249
  } else if (value && typeof value === "object") {
4765
- text = String(value.view ?? value.title ?? value.name ?? "");
5250
+ text2 = String(value.view ?? value.title ?? value.name ?? "");
4766
5251
  } else {
4767
5252
  const n = typeof value === "number" ? value : Number(value);
4768
- text = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
5253
+ text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
4769
5254
  }
4770
- out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text);
5255
+ out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
4771
5256
  }
4772
5257
  return out;
4773
5258
  }
@@ -4788,7 +5273,7 @@ var HtmlWidget = (props) => {
4788
5273
  const chrome = resolveChromeConfig(config);
4789
5274
  const template = config?.media?.htmlText ?? config?.html ?? config?.content ?? config?.rawHtml ?? "";
4790
5275
  const variables = Array.isArray(config?.variables) ? config.variables : [];
4791
- const html = React13.useMemo(
5276
+ const html = React14.useMemo(
4792
5277
  () => variables.length ? renderHtmlVariables(template, data.rows, variables) : interpolate(template, data.rows[0]),
4793
5278
  [template, data.rows, variables]
4794
5279
  );
@@ -4825,7 +5310,7 @@ var IframeWidget = (props) => {
4825
5310
  const { data, config, tokens: t } = ix;
4826
5311
  const chrome = resolveChromeConfig(config);
4827
5312
  const raw2 = config?.media?.src ?? config?.url ?? config?.src ?? config?.iframe?.url ?? "";
4828
- const url = React13.useMemo(() => interpolate(raw2, data.rows[0]), [raw2, data.rows]);
5313
+ const url = React14.useMemo(() => interpolate(raw2, data.rows[0]), [raw2, data.rows]);
4829
5314
  const state = url ? "ready" : data.state === "loading" ? "loading" : "unconfigured";
4830
5315
  return /* @__PURE__ */ jsxRuntime.jsx(
4831
5316
  WidgetShell,
@@ -4865,14 +5350,14 @@ var GoogleMapWidget = (props) => {
4865
5350
  const labelField = m.labelField ?? m.titleField ?? m.pinDataTitle;
4866
5351
  const centerLat = gm?.center?.latitude ?? m.centerLat;
4867
5352
  const centerLng = gm?.center?.longitude ?? m.centerLng;
4868
- const markers = React13.useMemo(() => data.rows.flatMap((row, i) => {
5353
+ const markers = React14.useMemo(() => data.rows.flatMap((row, i) => {
4869
5354
  const lat = Number(row?.[latField]);
4870
5355
  const lng = Number(row?.[lngField]);
4871
5356
  if (!Number.isFinite(lat) || !Number.isFinite(lng)) return [];
4872
5357
  const label = titleParts.length ? titleParts.map((p, k) => `${k ? p.separator ?? " " : ""}${row?.[p.field] ?? ""}`).join("").trim() : labelField ? row?.[labelField] : void 0;
4873
5358
  return [{ id: row?.id ?? i, lat, lng, label, raw: row }];
4874
5359
  }), [data.rows, latField, lngField, labelField, titleParts]);
4875
- const center = React13.useMemo(() => {
5360
+ const center = React14.useMemo(() => {
4876
5361
  if (centerLat != null && centerLng != null && Number.isFinite(Number(centerLat))) {
4877
5362
  return { lat: Number(centerLat), lng: Number(centerLng) };
4878
5363
  }
@@ -4919,7 +5404,7 @@ var ActionCardWidget = (props) => {
4919
5404
  const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: "ACTION_CARD" });
4920
5405
  const { data, config, tokens: t } = ix;
4921
5406
  const chrome = resolveChromeConfig(config);
4922
- const actions = React13.useMemo(() => {
5407
+ const actions = React14.useMemo(() => {
4923
5408
  const declared = config?.actions ?? config?.buttons ?? (config?.action ? [{ ...config.action, actionType: config.actionType, label: config.action.label ?? config.action.FormTitle }] : []);
4924
5409
  return declared.map((a, i) => ({
4925
5410
  id: a?.id ?? a?.FormID ?? i,
@@ -5000,7 +5485,7 @@ var TreeWidget = (props) => {
5000
5485
  const { data, config, tokens: t } = ix;
5001
5486
  const chrome = resolveChromeConfig(config);
5002
5487
  const tree = config?.treeConfiguration ?? config?.tree ?? config ?? {};
5003
- const nodes = React13.useMemo(() => buildTree(
5488
+ const nodes = React14.useMemo(() => buildTree(
5004
5489
  data.rows,
5005
5490
  tree.idField ?? "id",
5006
5491
  tree.parentField ?? "parentId",
@@ -5240,6 +5725,7 @@ function resolveFieldType(el2) {
5240
5725
  if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
5241
5726
  if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
5242
5727
  if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
5728
+ if (sub && sub !== t && sub in KIND) return sub;
5243
5729
  if (t === "SCAN") return "INPUT_SCAN";
5244
5730
  if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
5245
5731
  if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
@@ -5302,7 +5788,11 @@ var KIND = {
5302
5788
  COMMENTS: "comments",
5303
5789
  ACTION_BUTTON: "action",
5304
5790
  INPUT_TRIGGER: "action",
5305
- CASCADED_ACTIONS: "action"
5791
+ CASCADED_ACTIONS: "action",
5792
+ // ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
5793
+ INPUT_TIME_SAMPLING: "trials",
5794
+ INPUT_PERCENT_CORRECT: "trials",
5795
+ INPUT_TASK_ANALYSIS: "trials"
5306
5796
  };
5307
5797
  function fieldKindOf(el2) {
5308
5798
  const t = resolveFieldType(el2);
@@ -5551,6 +6041,9 @@ function formulaScalar(v) {
5551
6041
  if (typeof v === "number" || typeof v === "boolean") return v;
5552
6042
  return String(v);
5553
6043
  }
6044
+ function toBraceFormula(formula, labels) {
6045
+ return braceLegacyLabels(formula, labels);
6046
+ }
5554
6047
  function braceLegacyLabels(formula, labels) {
5555
6048
  if (formula.includes("{")) return formula;
5556
6049
  const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
@@ -5586,6 +6079,31 @@ function formulaLabels(formula, labels = []) {
5586
6079
  const src = braceLegacyLabels(String(formula ?? ""), labels);
5587
6080
  return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
5588
6081
  }
6082
+ function isServerSafeFormula(formula, labels = []) {
6083
+ const f = String(formula ?? "");
6084
+ if (!f.trim()) return true;
6085
+ return braceLegacyLabels(f, labels) === f;
6086
+ }
6087
+ function computeRowFormulas(elements, row) {
6088
+ const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
6089
+ const cols = elements.filter((e) => formulaOf(e));
6090
+ if (!cols.length) return row;
6091
+ const labels = elements.map((e) => e.label).filter(Boolean);
6092
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
6093
+ const out = { ...row };
6094
+ for (let pass = 0; pass < 2; pass++) {
6095
+ cols.forEach((c) => {
6096
+ const refs = formulaLabels(formulaOf(c), labels);
6097
+ const empty = (v) => v === null || v === void 0 || v === "";
6098
+ if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
6099
+ out[c.elementId] = null;
6100
+ return;
6101
+ }
6102
+ out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
6103
+ });
6104
+ }
6105
+ return out;
6106
+ }
5589
6107
 
5590
6108
  // src/forms/validate.ts
5591
6109
  var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
@@ -5635,6 +6153,11 @@ function validateField(el2, value, required = el2.required) {
5635
6153
  const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
5636
6154
  if (required && blank) return `${name} is required`;
5637
6155
  if (blank) return void 0;
6156
+ if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
6157
+ const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
6158
+ const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
6159
+ if (min > 0 && n < min) return `Trials are less than ${min}`;
6160
+ }
5638
6161
  if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
5639
6162
  if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
5640
6163
  if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
@@ -5745,6 +6268,9 @@ function getEmptyValue(type) {
5745
6268
  case "SIGNATURE":
5746
6269
  case "TABLE_START":
5747
6270
  case "MATRIX":
6271
+ case "INPUT_TIME_SAMPLING":
6272
+ case "INPUT_PERCENT_CORRECT":
6273
+ case "INPUT_TASK_ANALYSIS":
5748
6274
  return null;
5749
6275
  default:
5750
6276
  return "";
@@ -5786,7 +6312,7 @@ var DATE_UNITS = {
5786
6312
  };
5787
6313
  function currentUserId(user) {
5788
6314
  const id = user?.userId ?? user?.UserID ?? user?.id;
5789
- return id != null && id !== "" ? String(id) : void 0;
6315
+ return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
5790
6316
  }
5791
6317
  function dynamicDefault(el2, user) {
5792
6318
  const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
@@ -5794,7 +6320,7 @@ function dynamicDefault(el2, user) {
5794
6320
  const t = resolveFieldType(el2);
5795
6321
  if ((dd.operator === "ADD" || dd.operator === "SUB") && (t === "DATE_PICKER" || t === "DATE_TIME_PICKER")) {
5796
6322
  const sign = dd.operator === "SUB" ? -1 : 1;
5797
- let d = dayjs8__default.default();
6323
+ let d = dayjs4__default.default();
5798
6324
  const u = dd.units;
5799
6325
  if (typeof u === "number" || typeof u === "string" && u.trim() !== "" && !isNaN(Number(u))) d = d.add(sign * Number(u), "day");
5800
6326
  else Object.entries(u ?? {}).forEach(([k, n]) => {
@@ -5834,7 +6360,7 @@ function buildInitialValues(elements, opts = {}) {
5834
6360
  }
5835
6361
  function formatDateish(t, value) {
5836
6362
  const isDayjs = (v) => v && typeof v === "object" && typeof v.isValid === "function" && typeof v.format === "function";
5837
- const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs8__default.default(v) : typeof v === "string" && v ? dayjs8__default.default(v) : null;
6363
+ const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs4__default.default(v) : typeof v === "string" && v ? dayjs4__default.default(v) : null;
5838
6364
  if (t === "DATE_PICKER") {
5839
6365
  const d = toD(value);
5840
6366
  return d?.isValid() ? d.format("YYYY-MM-DD") : value;
@@ -5949,9 +6475,9 @@ function formatFieldValue(el2, value) {
5949
6475
  const t = resolveFieldType(el2);
5950
6476
  if (t === "CHECK_BOX") return value ? "Yes" : "No";
5951
6477
  if (RELATIONAL_TYPES.has(t)) return (Array.isArray(value) ? value : [value]).map((v) => v?.view ?? v?.select ?? String(v)).join(", ");
5952
- if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs8__default.default(v).format("D MMM YYYY")).join(" \u2013 ");
5953
- if (t === "DATE_PICKER") return dayjs8__default.default(value).isValid() ? dayjs8__default.default(value).format("D MMM YYYY") : String(value);
5954
- if (t === "DATE_TIME_PICKER") return dayjs8__default.default(value).isValid() ? dayjs8__default.default(value).format("D MMM YYYY, HH:mm") : String(value);
6478
+ if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs4__default.default(v).format("D MMM YYYY")).join(" \u2013 ");
6479
+ if (t === "DATE_PICKER") return dayjs4__default.default(value).isValid() ? dayjs4__default.default(value).format("D MMM YYYY") : String(value);
6480
+ if (t === "DATE_TIME_PICKER") return dayjs4__default.default(value).isValid() ? dayjs4__default.default(value).format("D MMM YYYY, HH:mm") : String(value);
5955
6481
  if (t === "INPUT_PERCENT") return `${value}%`;
5956
6482
  if (Array.isArray(value)) return value.map((v) => v?.media_name ?? v?.view ?? String(v)).join(", ");
5957
6483
  return String(value);
@@ -6173,6 +6699,67 @@ async function invokeTrigger(client, triggerId, body) {
6173
6699
  }
6174
6700
  return res?.data ?? res;
6175
6701
  }
6702
+ async function invokeZeno(client, automationUuid, body) {
6703
+ const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
6704
+ const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
6705
+ if (typeof payload === "string") {
6706
+ try {
6707
+ return JSON.parse(payload);
6708
+ } catch {
6709
+ return payload;
6710
+ }
6711
+ }
6712
+ return payload;
6713
+ }
6714
+ async function invokeAgent(client, body, flowId, agentId) {
6715
+ const wf = wfOf(client);
6716
+ if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
6717
+ const headers = { ...await wf.getHeaders(true) };
6718
+ const post = async (url, data2) => {
6719
+ try {
6720
+ const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
6721
+ const d = res?.data;
6722
+ return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
6723
+ } catch (e) {
6724
+ const r = e?.response;
6725
+ 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);
6726
+ throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
6727
+ }
6728
+ };
6729
+ const agent = encodeURIComponent(String(agentId ?? ""));
6730
+ let data = null;
6731
+ if (flowId) {
6732
+ data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
6733
+ const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
6734
+ if (!unknownFlow && data?.error !== true) return data;
6735
+ if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6736
+ }
6737
+ const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
6738
+ data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
6739
+ if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6740
+ return data;
6741
+ }
6742
+ var parseMaybeJson = (v) => {
6743
+ if (typeof v !== "string") return v;
6744
+ const t = v.trim();
6745
+ if (!t.startsWith("{") && !t.startsWith("[")) return v;
6746
+ try {
6747
+ return JSON.parse(t);
6748
+ } catch {
6749
+ return v;
6750
+ }
6751
+ };
6752
+ function extractAgentFormData(reply) {
6753
+ let node = parseMaybeJson(reply);
6754
+ for (let d = 0; d < 5; d++) {
6755
+ if (!node || typeof node !== "object" || Array.isArray(node)) return null;
6756
+ const fd = parseMaybeJson(node.formData);
6757
+ if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
6758
+ if (!("response" in node)) return Object.keys(node).length ? node : null;
6759
+ node = parseMaybeJson(node.response);
6760
+ }
6761
+ return null;
6762
+ }
6176
6763
  function formDataOf(result) {
6177
6764
  let node = result;
6178
6765
  for (let d = 0; d < 4 && node; d++) {
@@ -6349,90 +6936,449 @@ async function searchPeople(client, el2, query) {
6349
6936
  const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
6350
6937
  return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
6351
6938
  }
6352
-
6353
- // src/forms/useZvolvForm.ts
6354
- var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
6355
- if (o == null) return null;
6356
- if (typeof o !== "object") return { label: String(o), value: String(o) };
6357
- const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
6358
- const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
6359
- return { label: String(label ?? ""), value, color: o.color };
6360
- }).filter(Boolean);
6361
- var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
6362
- function toDayjsFormat(fmt, withTime) {
6363
- const f = typeof fmt === "string" && fmt.trim() ? fmt.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
6364
- 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");
6365
- }
6366
- function acceptOf(el2) {
6367
- const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
6368
- if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6369
- const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
6370
- if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6371
- return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
6372
- }
6373
- var FormApiContext = React13__default.default.createContext(null);
6374
- function useFormApi() {
6375
- const f = React13__default.default.useContext(FormApiContext);
6376
- if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
6377
- return f;
6378
- }
6379
- function useZvolvForm(args) {
6380
- const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
6381
- const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
6382
- const rt = useZvolvRuntime();
6383
- const client = rt.client;
6384
- const argsRef = React13.useRef(args);
6385
- argsRef.current = args;
6386
- const [definition, setDefinition] = React13.useState(null);
6387
- const [status, setStatus] = React13.useState("loading");
6388
- const [error, setError] = React13.useState(null);
6389
- const [values, setValues] = React13.useState({});
6390
- const [serverErrors, setServerErrors] = React13.useState({});
6391
- const [touched, setTouched] = React13.useState(/* @__PURE__ */ new Set());
6392
- const [submitAttempted, setSubmitAttempted] = React13.useState(false);
6393
- const [flagged, setFlagged] = React13.useState(/* @__PURE__ */ new Set());
6394
- const [submitting, setSubmitting] = React13.useState(false);
6395
- const [submitError, setSubmitError] = React13.useState(null);
6396
- const [submittedId, setSubmittedId] = React13.useState(null);
6397
- const [dirty, setDirty] = React13.useState(false);
6398
- const [busy, setBusy] = React13.useState(/* @__PURE__ */ new Set());
6399
- const initialRef = React13.useRef({});
6400
- const valuesRef = React13.useRef({});
6401
- valuesRef.current = values;
6402
- const sheetDefs = React13.useRef(/* @__PURE__ */ new Map());
6403
- const user = React13.useMemo(() => {
6404
- try {
6405
- return client?.auth?.userInstance ?? null;
6406
- } catch {
6407
- return null;
6408
- }
6409
- }, [client]);
6410
- const onBehalfName = React13.useCallback((v) => {
6411
- if (v == null || v === "") return "";
6412
- if (String(v) === "3122") return "Super Admin";
6413
- if (user && String(v) === currentUserId(user)) return String(user.name ?? user.userName ?? user.fullName ?? user.email ?? v);
6414
- return String(v);
6415
- }, [user]);
6416
- const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef]);
6417
- React13.useEffect(() => {
6418
- let alive = true;
6419
- setStatus("loading");
6420
- setError(null);
6421
- setSubmittedId(null);
6422
- setSubmitAttempted(false);
6423
- (async () => {
6424
- const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
6425
- let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
6426
- let submission;
6427
- if (submissionId && mode !== "NEW") {
6428
- const s = await fetchSubmission(client, submissionId);
6429
- submission = s.values;
6430
- if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
6431
- }
6432
- if (!def) {
6433
- if (!formId) throw new Error("No form configured");
6434
- def = await fetchForm(client, formId);
6435
- }
6939
+ var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6940
+ var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6941
+ function useFieldOptions(form, field2, opts = {}) {
6942
+ const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6943
+ const el2 = field2.element;
6944
+ const cfg = React14.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6945
+ const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6946
+ const elements = form.definition?.elements ?? [];
6947
+ const rules = React14.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6948
+ const dep = React14.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6949
+ const depKey = JSON.stringify(dep.filters);
6950
+ const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6951
+ const single = maxCount === 1;
6952
+ const [search, setSearch] = React14.useState("");
6953
+ const [q, setQ] = React14.useState("");
6954
+ React14.useEffect(() => {
6955
+ const t = setTimeout(() => setQ(search), debounceMs);
6956
+ return () => clearTimeout(t);
6957
+ }, [search, debounceMs]);
6958
+ const [remote, setRemote] = React14.useState([]);
6959
+ const [loading, setLoading] = React14.useState(false);
6960
+ const [error, setError] = React14.useState(null);
6961
+ const [opened, setOpened] = React14.useState(loadOn === "mount");
6962
+ const [nonce, setNonce] = React14.useState(0);
6963
+ const lastFetch = React14.useRef(0);
6964
+ const maxAge = React14.useRef(1e4);
6965
+ const waitingFor = React14.useMemo(() => {
6966
+ if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6967
+ const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6968
+ const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6969
+ return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6970
+ }, [cfg, dep.satisfied, rules, form]);
6971
+ const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6972
+ const active = opened || depDriven || q !== "";
6973
+ React14.useEffect(() => {
6974
+ if (!cfg && !people) return;
6975
+ if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6976
+ setRemote([]);
6977
+ return;
6978
+ }
6979
+ if (!active) return;
6980
+ let alive = true;
6981
+ setLoading(true);
6982
+ setError(null);
6983
+ lastFetch.current = Date.now();
6984
+ const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6985
+ maxAge.current = 1e4;
6986
+ p.then((o) => {
6987
+ if (!alive) return;
6988
+ setRemote(o);
6989
+ if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6990
+ }).catch((e) => {
6991
+ if (alive) setError(e?.message ?? "Couldn't load options");
6992
+ }).finally(() => {
6993
+ if (alive) setLoading(false);
6994
+ });
6995
+ return () => {
6996
+ alive = false;
6997
+ };
6998
+ }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6999
+ const refresh = React14.useCallback(() => {
7000
+ maxAge.current = 0;
7001
+ setOpened(true);
7002
+ setNonce((n) => n + 1);
7003
+ }, []);
7004
+ const onFocus = React14.useCallback(() => {
7005
+ if (!cfg && !people) return;
7006
+ if (!opened) {
7007
+ setOpened(true);
7008
+ return;
7009
+ }
7010
+ if (cfg && Date.now() - lastFetch.current > 2e3) {
7011
+ maxAge.current = 2e3;
7012
+ setNonce((n) => n + 1);
7013
+ } else if (people && !remote.length) setNonce((n) => n + 1);
7014
+ }, [cfg, people, opened, remote.length]);
7015
+ const prevDepKey = React14.useRef(null);
7016
+ React14.useEffect(() => {
7017
+ if (!cfg || !rules.length) return;
7018
+ if (prevDepKey.current === null) {
7019
+ prevDepKey.current = depKey;
7020
+ return;
7021
+ }
7022
+ if (prevDepKey.current === depKey) return;
7023
+ prevDepKey.current = depKey;
7024
+ if (form.readOnly) return;
7025
+ if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
7026
+ }, [depKey]);
7027
+ const staticFiltered = React14.useMemo(() => {
7028
+ if (cfg || people) return remote;
7029
+ const needle = search.trim().toLowerCase();
7030
+ return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
7031
+ }, [cfg, people, remote, field2.options, search]);
7032
+ const pickedCache = React14.useRef(/* @__PURE__ */ new Map());
7033
+ const current = field2.value;
7034
+ const selected = React14.useMemo(() => {
7035
+ const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
7036
+ const pool = [...staticFiltered, ...field2.options];
7037
+ return arr.map((v) => {
7038
+ const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
7039
+ if (hit) return hit;
7040
+ if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
7041
+ return { label: String(v), value: v };
7042
+ });
7043
+ }, [current, staticFiltered, field2.options]);
7044
+ const store = React14.useCallback((picked) => {
7045
+ picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
7046
+ const t = field2.type;
7047
+ if (field2.kind === "lookup") {
7048
+ const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
7049
+ form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
7050
+ if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
7051
+ const fill2 = {};
7052
+ cfg.extraLabels.forEach((label, i) => {
7053
+ const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
7054
+ const v = picked[0].extra?.[i];
7055
+ if (target && v !== void 0) fill2[target.elementId] = v;
7056
+ });
7057
+ if (Object.keys(fill2).length) form.applyValues(fill2);
7058
+ }
7059
+ return;
7060
+ }
7061
+ if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
7062
+ else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
7063
+ }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
7064
+ const autoPickRef = React14.useRef(() => {
7065
+ });
7066
+ const lastAutoKey = React14.useRef(null);
7067
+ autoPickRef.current = (o, key) => {
7068
+ if (form.readOnly || lastAutoKey.current === key) return;
7069
+ lastAutoKey.current = key;
7070
+ if (isFilled(form.values[field2.id])) return;
7071
+ store([o]);
7072
+ };
7073
+ const select = React14.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
7074
+ const isSelected = React14.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
7075
+ const atLimit = field2.multiple && selected.length >= maxCount;
7076
+ const toggle = React14.useCallback((o) => {
7077
+ const on = isSelected(o);
7078
+ if (!on && field2.multiple && selected.length >= maxCount) return;
7079
+ store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
7080
+ }, [isSelected, selected, store, field2.multiple, maxCount]);
7081
+ const addFreeTag = React14.useCallback((text2) => {
7082
+ const t = text2.trim();
7083
+ if (!t || field2.type !== "INPUT_TAG") return;
7084
+ const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
7085
+ if (selected.some((s) => optionKey(s.value) === t)) return;
7086
+ if (field2.multiple) {
7087
+ if (selected.length < maxCount) store([...selected, o]);
7088
+ } else store([o]);
7089
+ setSearch("");
7090
+ }, [field2.type, field2.multiple, selected, maxCount, store]);
7091
+ return {
7092
+ options: staticFiltered,
7093
+ loading,
7094
+ error,
7095
+ search,
7096
+ setSearch,
7097
+ waitingFor,
7098
+ selected,
7099
+ select,
7100
+ toggle,
7101
+ isSelected,
7102
+ onFocus,
7103
+ refresh,
7104
+ maxCount,
7105
+ atLimit,
7106
+ addFreeTag
7107
+ };
7108
+ }
7109
+ 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(" "));
7110
+ function useFileField(form, field2, opts = {}) {
7111
+ const stored = Array.isArray(field2.value) ? field2.value : [];
7112
+ const [pending2, setPending] = React14.useState([]);
7113
+ const [reject, setReject] = React14.useState(null);
7114
+ const storedRef = React14.useRef(stored);
7115
+ storedRef.current = stored;
7116
+ const items = [
7117
+ ...stored.map((r, i) => ({
7118
+ key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
7119
+ name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
7120
+ size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
7121
+ type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
7122
+ url: r.media ?? r.url,
7123
+ uploading: false,
7124
+ record: r
7125
+ })),
7126
+ ...pending2
7127
+ ];
7128
+ const max = field2.maxCount;
7129
+ const add = React14.useCallback((list) => {
7130
+ setReject(null);
7131
+ const files = Array.from(list);
7132
+ const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
7133
+ if (!room) {
7134
+ setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
7135
+ return;
7136
+ }
7137
+ files.slice(0, room).forEach((file) => {
7138
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7139
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
7140
+ setReject(`Cannot upload files with the extension .${ext}`);
7141
+ return;
7142
+ }
7143
+ if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
7144
+ setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
7145
+ return;
7146
+ }
7147
+ const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
7148
+ 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)) {
7149
+ setReject(`${file.name} isn't an allowed file type`);
7150
+ return;
7151
+ }
7152
+ const key = `p${Date.now()}${Math.random()}`;
7153
+ setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
7154
+ uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
7155
+ setPending((p) => p.filter((x) => x.key !== key));
7156
+ const next = [...storedRef.current, rec];
7157
+ storedRef.current = next;
7158
+ form.setValue(field2.id, next);
7159
+ }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
7160
+ });
7161
+ if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
7162
+ }, [form, field2, max, pending2, opts.zappId]);
7163
+ const remove = React14.useCallback((key) => {
7164
+ if (key.startsWith("p")) {
7165
+ setPending((p) => p.filter((x) => x.key !== key));
7166
+ return;
7167
+ }
7168
+ const idx = items.findIndex((i) => i.key === key);
7169
+ if (idx < 0) return;
7170
+ const next = storedRef.current.filter((_2, i) => i !== idx);
7171
+ form.setValue(field2.id, next.length ? next : []);
7172
+ }, [items, form, field2.id]);
7173
+ return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
7174
+ }
7175
+ function sheetAllows(p, action) {
7176
+ const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
7177
+ const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
7178
+ if (!Array.isArray(list) || !list.length) return true;
7179
+ return list.map((x) => String(x).toUpperCase()).includes(action);
7180
+ }
7181
+ function useSheetField(form, field2) {
7182
+ const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
7183
+ const [child, setChild] = React14.useState(null);
7184
+ const [loading, setLoading] = React14.useState(!!childId);
7185
+ const [error, setError] = React14.useState(childId ? null : "No child form configured");
7186
+ React14.useEffect(() => {
7187
+ if (!childId) return;
7188
+ let alive = true;
7189
+ setLoading(true);
7190
+ fetchForm(form.client, childId).then(async (def) => {
7191
+ if (!alive) return;
7192
+ setChild(def);
7193
+ form.registerSheet(field2.id, def);
7194
+ const raw2 = Array.isArray(field2.value) ? field2.value : [];
7195
+ const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
7196
+ if (ids.length) {
7197
+ const rows2 = await fetchRows(form.client, childId, ids);
7198
+ if (alive) form.applyValues({ [field2.id]: rows2 });
7199
+ }
7200
+ }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
7201
+ return () => {
7202
+ alive = false;
7203
+ };
7204
+ }, [childId, form.client]);
7205
+ const columns = React14.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
7206
+ id: e.elementId,
7207
+ label: e.label,
7208
+ type: resolveFieldType(e),
7209
+ required: e.required,
7210
+ element: e,
7211
+ 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 })
7212
+ })), [child]);
7213
+ const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
7214
+ const p = field2.element.properties ?? {};
7215
+ const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
7216
+ const [limitMessage, setLimitMessage] = React14.useState(null);
7217
+ const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
7218
+ const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
7219
+ const addRow = React14.useCallback((vals) => {
7220
+ if (!addAllowed) return;
7221
+ if (maxRows && rows.length >= maxRows) {
7222
+ setLimitMessage(`Maximum number of rows reached (${maxRows})`);
7223
+ return;
7224
+ }
7225
+ setLimitMessage(null);
7226
+ const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
7227
+ form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
7228
+ }, [child, rows, form, field2.id, addAllowed, maxRows]);
7229
+ const updateRow = React14.useCallback((i, col, v) => {
7230
+ const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
7231
+ form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
7232
+ }, [rows, form, field2.id, child]);
7233
+ const removeRow = React14.useCallback((i) => {
7234
+ if (!canDelete) return;
7235
+ setLimitMessage(null);
7236
+ form.setValue(field2.id, rows.filter((_2, j) => j !== i));
7237
+ }, [rows, form, field2.id, canDelete]);
7238
+ const rowErrors = React14.useMemo(() => {
7239
+ const out = {};
7240
+ if (!form.submitAttempted) return out;
7241
+ rows.forEach((r, i) => {
7242
+ if (!sheetRowNeedsCheck(r)) return;
7243
+ columns.forEach((c) => {
7244
+ if (!sheetCellChecked(c.element)) return;
7245
+ const e = validateField(c.element, r[c.id]);
7246
+ if (e) out[`${i}:${c.id}`] = e;
7247
+ });
7248
+ });
7249
+ return out;
7250
+ }, [rows, columns, form.submitAttempted]);
7251
+ return {
7252
+ loading,
7253
+ error,
7254
+ columns,
7255
+ rows,
7256
+ addRow,
7257
+ updateRow,
7258
+ removeRow,
7259
+ rowErrors,
7260
+ childForm: child,
7261
+ canAdd: addAllowed && (!maxRows || rows.length < maxRows),
7262
+ canDelete,
7263
+ maxRows,
7264
+ limitMessage,
7265
+ display: (c, v) => formatFieldValue(c.element, v)
7266
+ };
7267
+ }
7268
+
7269
+ // src/forms/useZvolvForm.ts
7270
+ function submitElementsOf(elements) {
7271
+ const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
7272
+ return elements.filter((el2) => {
7273
+ const t = resolveFieldType(el2);
7274
+ return !NON_INPUT_TYPES.has(t) || always.has(t);
7275
+ });
7276
+ }
7277
+ var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
7278
+ if (o == null) return null;
7279
+ if (typeof o !== "object") return { label: String(o), value: String(o) };
7280
+ const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
7281
+ const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
7282
+ return { label: String(label ?? ""), value, color: o.color };
7283
+ }).filter(Boolean);
7284
+ var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
7285
+ var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
7286
+ var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
7287
+ function toDayjsFormat(fmt2, withTime) {
7288
+ const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
7289
+ 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");
7290
+ }
7291
+ function acceptOf(el2) {
7292
+ const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
7293
+ if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
7294
+ const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
7295
+ if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
7296
+ return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
7297
+ }
7298
+ var FormApiContext = React14__default.default.createContext(null);
7299
+ function useFormApi() {
7300
+ const f = React14__default.default.useContext(FormApiContext);
7301
+ if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
7302
+ return f;
7303
+ }
7304
+ function useZvolvForm(args) {
7305
+ const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
7306
+ const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
7307
+ const rt = useZvolvRuntime();
7308
+ const client = rt.client;
7309
+ const argsRef = React14.useRef(args);
7310
+ argsRef.current = args;
7311
+ const [definition, setDefinition] = React14.useState(null);
7312
+ const [status, setStatus] = React14.useState("loading");
7313
+ const [error, setError] = React14.useState(null);
7314
+ const [values, setValues] = React14.useState({});
7315
+ const [serverErrors, setServerErrors] = React14.useState({});
7316
+ const [touched, setTouched] = React14.useState(/* @__PURE__ */ new Set());
7317
+ const [submitAttempted, setSubmitAttempted] = React14.useState(false);
7318
+ const [flagged, setFlagged] = React14.useState(/* @__PURE__ */ new Set());
7319
+ const [submitting, setSubmitting] = React14.useState(false);
7320
+ const [submitError, setSubmitError] = React14.useState(null);
7321
+ const [submittedId, setSubmittedId] = React14.useState(null);
7322
+ const [dirty, setDirty] = React14.useState(false);
7323
+ const [busy, setBusy] = React14.useState(/* @__PURE__ */ new Set());
7324
+ const initialRef = React14.useRef({});
7325
+ const valuesRef = React14.useRef({});
7326
+ valuesRef.current = values;
7327
+ const sheetDefs = React14.useRef(/* @__PURE__ */ new Map());
7328
+ const [userTick, setUserTick] = React14.useState(0);
7329
+ React14.useEffect(() => {
7330
+ if (typeof window === "undefined") return;
7331
+ const bump = () => setUserTick((n) => n + 1);
7332
+ window.addEventListener(AUTH_CRYPT_READY, bump);
7333
+ return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
7334
+ }, []);
7335
+ const user = React14.useMemo(() => getStoredUser() ?? null, [client, userTick]);
7336
+ React14.useEffect(() => {
7337
+ if (status !== "ready" || mode !== "NEW") return;
7338
+ const uid = currentUserId(user);
7339
+ if (!uid) return;
7340
+ const fill2 = {};
7341
+ (definition?.elements ?? []).forEach((el2) => {
7342
+ const v = valuesRef.current[el2.elementId];
7343
+ if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
7344
+ });
7345
+ if (Object.keys(fill2).length) setValues((p) => {
7346
+ const m = { ...p, ...fill2 };
7347
+ valuesRef.current = m;
7348
+ return m;
7349
+ });
7350
+ }, [user, status]);
7351
+ const onBehalfName = React14.useCallback((v) => {
7352
+ if (v == null || v === "") return "";
7353
+ if (String(v) === "3122") return "Super Admin";
7354
+ if (isEncString(v)) return "";
7355
+ if (user && String(v) === currentUserId(user)) {
7356
+ const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
7357
+ return name ? String(name) : String(v);
7358
+ }
7359
+ return String(v);
7360
+ }, [user]);
7361
+ const [reloadTick, setReloadTick] = React14.useState(0);
7362
+ const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
7363
+ React14.useEffect(() => {
7364
+ let alive = true;
7365
+ setStatus("loading");
7366
+ setError(null);
7367
+ setSubmittedId(null);
7368
+ setSubmitAttempted(false);
7369
+ (async () => {
7370
+ const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
7371
+ let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
7372
+ let submission;
7373
+ if (submissionId && mode !== "NEW") {
7374
+ const s = await fetchSubmission(client, submissionId);
7375
+ submission = s.values;
7376
+ if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
7377
+ }
7378
+ if (!def) {
7379
+ if (!formId) throw new Error("No form configured");
7380
+ def = await fetchForm(client, formId);
7381
+ }
6436
7382
  const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
6437
7383
  if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
6438
7384
  const info = await clientInfo(client);
@@ -6463,13 +7409,13 @@ function useZvolvForm(args) {
6463
7409
  };
6464
7410
  }, [loadKey, client]);
6465
7411
  const elements = definition?.elements ?? [];
6466
- const byId = React13.useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
6467
- const labels = React13.useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
6468
- const overrides = React13.useMemo(() => applyRules(elements, values), [elements, values]);
7412
+ const byId = React14.useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
7413
+ const labels = React14.useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
7414
+ const overrides = React14.useMemo(() => applyRules(elements, values), [elements, values]);
6469
7415
  const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
6470
- const hiddenSet = React13.useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
6471
- const skipSet = React13.useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
6472
- const hiddenByParent = React13.useMemo(() => {
7416
+ const hiddenSet = React14.useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
7417
+ const skipSet = React14.useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
7418
+ const hiddenByParent = React14.useMemo(() => {
6473
7419
  const out = /* @__PURE__ */ new Set();
6474
7420
  const visit = (el2) => childIdsOf(el2).forEach((id) => {
6475
7421
  if (out.has(id)) return;
@@ -6484,7 +7430,7 @@ function useZvolvForm(args) {
6484
7430
  });
6485
7431
  return out;
6486
7432
  }, [elements, overrides, byId]);
6487
- const stateOf = React13.useCallback((el2) => {
7433
+ const stateOf = React14.useCallback((el2) => {
6488
7434
  const o = overrides.get(el2.elementId) ?? {};
6489
7435
  return {
6490
7436
  hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
@@ -6492,7 +7438,7 @@ function useZvolvForm(args) {
6492
7438
  required: o.isRequired ?? el2.required
6493
7439
  };
6494
7440
  }, [overrides, readOnly, hiddenByParent]);
6495
- React13.useEffect(() => {
7441
+ React14.useEffect(() => {
6496
7442
  if (status !== "ready") return;
6497
7443
  const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
6498
7444
  if (!formulas.length) return;
@@ -6514,7 +7460,7 @@ function useZvolvForm(args) {
6514
7460
  });
6515
7461
  if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
6516
7462
  }, [values, elements, labels, status]);
6517
- const applyValues = React13.useCallback((data) => {
7463
+ const applyValues = React14.useCallback((data) => {
6518
7464
  if (!data || typeof data !== "object") return;
6519
7465
  const loose = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, "");
6520
7466
  const index = /* @__PURE__ */ new Map();
@@ -6538,7 +7484,7 @@ function useZvolvForm(args) {
6538
7484
  return merged;
6539
7485
  });
6540
7486
  }, [elements]);
6541
- const withBusy = React13.useCallback(async (id, fn) => {
7487
+ const withBusy = React14.useCallback(async (id, fn) => {
6542
7488
  setBusy((b) => new Set(b).add(id));
6543
7489
  try {
6544
7490
  return await fn();
@@ -6550,11 +7496,11 @@ function useZvolvForm(args) {
6550
7496
  });
6551
7497
  }
6552
7498
  }, []);
6553
- const triggerBody = React13.useCallback(
7499
+ const triggerBody = React14.useCallback(
6554
7500
  (vals) => buildTriggerBody(elements, vals, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId }),
6555
7501
  [elements, definition, formId, submissionId, submittedId]
6556
7502
  );
6557
- const runFieldAutomations = React13.useCallback((el2, vals) => {
7503
+ const runFieldAutomations = React14.useCallback((el2, vals) => {
6558
7504
  if (argsRef.current.skipAutomation) return;
6559
7505
  const acts = (el2.properties?.actions ?? []).filter((a) => String(a?.type ?? "").toUpperCase() === "TRIGGER_AUTOMATION" && (a.triggerID ?? a.triggerId));
6560
7506
  acts.forEach((a) => {
@@ -6570,7 +7516,7 @@ function useZvolvForm(args) {
6570
7516
  });
6571
7517
  });
6572
7518
  }, [client, triggerBody, applyValues, withBusy]);
6573
- const setValue = React13.useCallback((id, value) => {
7519
+ const setValue = React14.useCallback((id, value) => {
6574
7520
  const el2 = byId.get(id);
6575
7521
  const next = { ...valuesRef.current, [id]: value };
6576
7522
  valuesRef.current = next;
@@ -6590,16 +7536,16 @@ function useZvolvForm(args) {
6590
7536
  if (other.elementId !== id && Array.isArray(watch) && watch.map(String).includes(id)) runFieldAutomations(other, next);
6591
7537
  });
6592
7538
  }, [byId, elements, runFieldAutomations]);
6593
- const touch = React13.useCallback((id) => setTouched((t) => t.has(id) ? t : new Set(t).add(id)), []);
6594
- const clientErrors = React13.useMemo(
7539
+ const touch = React14.useCallback((id) => setTouched((t) => t.has(id) ? t : new Set(t).add(id)), []);
7540
+ const clientErrors = React14.useMemo(
6595
7541
  () => validateForm(elements, values, (el2) => {
6596
7542
  const s = stateOf(el2);
6597
7543
  return { hidden: s.hidden || hiddenSet.has(el2.elementId) || hiddenSet.has(el2.label), required: s.required };
6598
7544
  }),
6599
7545
  [elements, values, stateOf, hiddenSet]
6600
7546
  );
6601
- const errors = React13.useMemo(() => ({ ...clientErrors, ...Object.fromEntries(Object.entries(serverErrors).filter(([, v]) => v)) }), [clientErrors, serverErrors]);
6602
- const validate = React13.useCallback((ids) => {
7547
+ const errors = React14.useMemo(() => ({ ...clientErrors, ...Object.fromEntries(Object.entries(serverErrors).filter(([, v]) => v)) }), [clientErrors, serverErrors]);
7548
+ const validate = React14.useCallback((ids) => {
6603
7549
  const keys = ids ?? Object.keys(errors);
6604
7550
  if (ids) setTouched((t) => {
6605
7551
  const n = new Set(t);
@@ -6615,7 +7561,7 @@ function useZvolvForm(args) {
6615
7561
  });
6616
7562
  return !bad.length;
6617
7563
  }, [errors]);
6618
- const fields = React13.useMemo(() => elements.map((el2) => {
7564
+ const fields = React14.useMemo(() => elements.map((el2) => {
6619
7565
  const t = resolveFieldType(el2);
6620
7566
  const kind = fieldKindOf(el2);
6621
7567
  const s = stateOf(el2);
@@ -6640,7 +7586,7 @@ function useZvolvForm(args) {
6640
7586
  value: values[el2.elementId],
6641
7587
  error: showErr ? errors[el2.elementId] : void 0,
6642
7588
  touched: touched.has(el2.elementId),
6643
- options: normalizeOptions(p.options),
7589
+ 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),
6644
7590
  multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
6645
7591
  allowAddOptions: !!p.allowAddOptions,
6646
7592
  min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
@@ -6655,7 +7601,7 @@ function useZvolvForm(args) {
6655
7601
  childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
6656
7602
  pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
6657
7603
  html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
6658
- variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
7604
+ variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
6659
7605
  displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
6660
7606
  dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
6661
7607
  maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
@@ -6665,18 +7611,18 @@ function useZvolvForm(args) {
6665
7611
  };
6666
7612
  return f;
6667
7613
  }), [elements, values, errors, stateOf, touched, flagged, serverErrors, hiddenSet, skipSet, setValue, touch, onBehalfName]);
6668
- const fieldMap2 = React13.useMemo(() => new Map(fields.map((f) => [f.id, f])), [fields]);
6669
- const claimed = React13.useMemo(() => {
7614
+ const fieldMap2 = React14.useMemo(() => new Map(fields.map((f) => [f.id, f])), [fields]);
7615
+ const claimed = React14.useMemo(() => {
6670
7616
  const s = /* @__PURE__ */ new Set();
6671
7617
  fields.forEach((f) => f.childIds.forEach((c) => s.add(c)));
6672
7618
  return s;
6673
7619
  }, [fields]);
6674
- const rootFields = React13.useMemo(() => fields.filter((f) => !claimed.has(f.id) && !f.hidden && f.kind !== "unknown"), [fields, claimed]);
6675
- const childrenOf = React13.useCallback((f, visibleOnly = true) => {
7620
+ const rootFields = React14.useMemo(() => fields.filter((f) => !claimed.has(f.id) && !f.hidden && f.kind !== "unknown"), [fields, claimed]);
7621
+ const childrenOf = React14.useCallback((f, visibleOnly = true) => {
6676
7622
  const ff = typeof f === "string" ? fieldMap2.get(f) : f;
6677
7623
  return (ff?.childIds ?? []).map((id) => fieldMap2.get(id)).filter((c) => !!c && (!visibleOnly || !c.hidden));
6678
7624
  }, [fieldMap2]);
6679
- const runRemoteValidation = React13.useCallback(async (vals) => {
7625
+ const runRemoteValidation = React14.useCallback(async (vals) => {
6680
7626
  if (argsRef.current.skipValidations) return {};
6681
7627
  const out = {};
6682
7628
  for (const el2 of elements) {
@@ -6698,7 +7644,7 @@ function useZvolvForm(args) {
6698
7644
  }
6699
7645
  return out;
6700
7646
  }, [elements, stateOf, client, triggerBody, definition]);
6701
- const postSheetRows = React13.useCallback(async (vals, parentId) => {
7647
+ const postSheetRows = React14.useCallback(async (vals, parentId) => {
6702
7648
  const out = { ...vals };
6703
7649
  for (const el2 of elements) {
6704
7650
  const t = resolveFieldType(el2);
@@ -6714,11 +7660,12 @@ function useZvolvForm(args) {
6714
7660
  saved.push(row);
6715
7661
  continue;
6716
7662
  }
6717
- const { _submissionId, _dirty, _rowKey, ...cells } = row;
7663
+ const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
6718
7664
  if (_submissionId && !_dirty) {
6719
7665
  saved.push(row);
6720
7666
  continue;
6721
7667
  }
7668
+ const cells = computeRowFormulas(child.elements, raw2);
6722
7669
  const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
6723
7670
  saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
6724
7671
  }
@@ -6726,7 +7673,7 @@ function useZvolvForm(args) {
6726
7673
  }
6727
7674
  return out;
6728
7675
  }, [elements, client]);
6729
- const submit = React13.useCallback(async () => {
7676
+ const submit = React14.useCallback(async () => {
6730
7677
  if (!definition || readOnly) return null;
6731
7678
  setSubmitAttempted(true);
6732
7679
  setSubmitError(null);
@@ -6749,7 +7696,7 @@ function useZvolvForm(args) {
6749
7696
  }
6750
7697
  const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
6751
7698
  const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
6752
- 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");
7699
+ const submitEls = submitElementsOf(elements);
6753
7700
  const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
6754
7701
  const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
6755
7702
  setValues(vals);
@@ -6765,446 +7712,411 @@ function useZvolvForm(args) {
6765
7712
  setSubmitting(false);
6766
7713
  }
6767
7714
  }, [definition, readOnly, clientErrors, runRemoteValidation, mode, postSheetRows, submissionId, elements, client]);
6768
- const reset = React13.useCallback(() => {
7715
+ const reset = React14.useCallback(() => {
6769
7716
  setValues(initialRef.current);
6770
7717
  setTouched(/* @__PURE__ */ new Set());
6771
7718
  setFlagged(/* @__PURE__ */ new Set());
6772
7719
  setServerErrors({});
6773
- setSubmitAttempted(false);
6774
- setSubmitError(null);
6775
- setDirty(false);
6776
- if (definition) setStatus("ready");
6777
- }, [definition]);
6778
- const apiRef = React13.useRef(null);
6779
- const runAction = React13.useCallback(async (field2) => {
6780
- const p = field2.element.properties ?? {};
6781
- const subType = String(field2.element.attributes?.subType ?? "");
6782
- const rawType = p.actionType ?? p.subType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "");
6783
- const actionType = String(rawType ?? "").toUpperCase();
6784
- if (field2.type === "CASCADED_ACTIONS") {
6785
- for (const id of p.actionEleIds ?? []) {
6786
- const f = fieldMap2.get(String(id));
6787
- if (f) {
6788
- const r2 = await runAction(f);
6789
- if (!r2.ok) return r2;
6790
- }
6791
- }
6792
- if (p.submit) await submit();
6793
- return { ok: true };
6794
- }
6795
- const handled = await argsRef.current.onAction?.(field2, apiRef.current);
6796
- if (handled) return handled;
6797
- const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
6798
- const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
6799
- if (isTrigger) {
6800
- if (!trig) return { ok: false, message: "No automation is linked to this button" };
6801
- return withBusy(field2.id, async () => {
6802
- try {
6803
- const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
6804
- const fd = formDataOf(res);
6805
- if (fd) applyValues(fd);
6806
- const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
6807
- if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
6808
- return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
6809
- } catch (e) {
6810
- return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
6811
- }
6812
- });
6813
- }
6814
- if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
6815
- const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
6816
- if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
6817
- return { ok: !!url, message: url ? void 0 : "No link configured" };
6818
- }
6819
- if (actionType === "SUBMIT" || p.SubmitButton) {
6820
- const r2 = await submit();
6821
- return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
6822
- }
6823
- const HOST = [
6824
- "SAVE_SUBMISSION",
6825
- "CREATE_SUBMISSION",
6826
- "EDIT_SUBMISSION",
6827
- "OPEN_TASK",
6828
- "PRINT_PREVIEW",
6829
- "DOWNLOAD_FILE",
6830
- "UPLOAD_FILE",
6831
- "OPEN_PROJECT",
6832
- "INVOKE_AGENT",
6833
- "AGENT",
6834
- "FLOW"
6835
- ];
6836
- if (HOST.includes(actionType)) {
6837
- return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
6838
- }
6839
- setValue(field2.id, true);
6840
- const r = await submit();
6841
- return { ok: !!r, message: r ? p.successMessage : void 0 };
6842
- }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
6843
- const registerSheet = React13.useCallback((id, def) => {
6844
- sheetDefs.current.set(id, def);
6845
- }, []);
6846
- const api = {
6847
- status,
6848
- error,
6849
- definition,
6850
- mode,
6851
- readOnly,
6852
- values,
6853
- errors,
6854
- fields,
6855
- rootFields,
6856
- field: (id) => fieldMap2.get(id),
6857
- childrenOf,
6858
- setValue,
6859
- applyValues,
6860
- dirty,
6861
- submitting,
6862
- submitAttempted,
6863
- submitError,
6864
- submittedId,
6865
- validate,
6866
- submit,
6867
- reset,
6868
- runAction,
6869
- busy,
6870
- client,
6871
- registerSheet
6872
- };
6873
- apiRef.current = api;
6874
- return api;
6875
- }
6876
- var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6877
- var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6878
- function useFieldOptions(form, field2, opts = {}) {
6879
- const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6880
- const el2 = field2.element;
6881
- const cfg = React13.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6882
- const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6883
- const elements = form.definition?.elements ?? [];
6884
- const rules = React13.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6885
- const dep = React13.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6886
- const depKey = JSON.stringify(dep.filters);
6887
- const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6888
- const single = maxCount === 1;
6889
- const [search, setSearch] = React13.useState("");
6890
- const [q, setQ] = React13.useState("");
6891
- React13.useEffect(() => {
6892
- const t = setTimeout(() => setQ(search), debounceMs);
6893
- return () => clearTimeout(t);
6894
- }, [search, debounceMs]);
6895
- const [remote, setRemote] = React13.useState([]);
6896
- const [loading, setLoading] = React13.useState(false);
6897
- const [error, setError] = React13.useState(null);
6898
- const [opened, setOpened] = React13.useState(loadOn === "mount");
6899
- const [nonce, setNonce] = React13.useState(0);
6900
- const lastFetch = React13.useRef(0);
6901
- const maxAge = React13.useRef(1e4);
6902
- const waitingFor = React13.useMemo(() => {
6903
- if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6904
- const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6905
- const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6906
- return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6907
- }, [cfg, dep.satisfied, rules, form]);
6908
- const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6909
- const active = opened || depDriven || q !== "";
6910
- React13.useEffect(() => {
6911
- if (!cfg && !people) return;
6912
- if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6913
- setRemote([]);
6914
- return;
6915
- }
6916
- if (!active) return;
6917
- let alive = true;
6918
- setLoading(true);
6919
- setError(null);
6920
- lastFetch.current = Date.now();
6921
- const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6922
- maxAge.current = 1e4;
6923
- p.then((o) => {
6924
- if (!alive) return;
6925
- setRemote(o);
6926
- if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6927
- }).catch((e) => {
6928
- if (alive) setError(e?.message ?? "Couldn't load options");
6929
- }).finally(() => {
6930
- if (alive) setLoading(false);
6931
- });
6932
- return () => {
6933
- alive = false;
6934
- };
6935
- }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6936
- const refresh = React13.useCallback(() => {
6937
- maxAge.current = 0;
6938
- setOpened(true);
6939
- setNonce((n) => n + 1);
6940
- }, []);
6941
- const onFocus = React13.useCallback(() => {
6942
- if (!cfg && !people) return;
6943
- if (!opened) {
6944
- setOpened(true);
6945
- return;
6946
- }
6947
- if (cfg && Date.now() - lastFetch.current > 2e3) {
6948
- maxAge.current = 2e3;
6949
- setNonce((n) => n + 1);
6950
- } else if (people && !remote.length) setNonce((n) => n + 1);
6951
- }, [cfg, people, opened, remote.length]);
6952
- const prevDepKey = React13.useRef(null);
6953
- React13.useEffect(() => {
6954
- if (!cfg || !rules.length) return;
6955
- if (prevDepKey.current === null) {
6956
- prevDepKey.current = depKey;
6957
- return;
6958
- }
6959
- if (prevDepKey.current === depKey) return;
6960
- prevDepKey.current = depKey;
6961
- if (form.readOnly) return;
6962
- if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
6963
- }, [depKey]);
6964
- const staticFiltered = React13.useMemo(() => {
6965
- if (cfg || people) return remote;
6966
- const needle = search.trim().toLowerCase();
6967
- return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
6968
- }, [cfg, people, remote, field2.options, search]);
6969
- const pickedCache = React13.useRef(/* @__PURE__ */ new Map());
6970
- const current = field2.value;
6971
- const selected = React13.useMemo(() => {
6972
- const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
6973
- const pool = [...staticFiltered, ...field2.options];
6974
- return arr.map((v) => {
6975
- const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
6976
- if (hit) return hit;
6977
- if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
6978
- return { label: String(v), value: v };
6979
- });
6980
- }, [current, staticFiltered, field2.options]);
6981
- const store = React13.useCallback((picked) => {
6982
- picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
6983
- const t = field2.type;
6984
- if (field2.kind === "lookup") {
6985
- const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
6986
- form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
6987
- if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
6988
- const fill2 = {};
6989
- cfg.extraLabels.forEach((label, i) => {
6990
- const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
6991
- const v = picked[0].extra?.[i];
6992
- if (target && v !== void 0) fill2[target.elementId] = v;
6993
- });
6994
- if (Object.keys(fill2).length) form.applyValues(fill2);
6995
- }
6996
- return;
6997
- }
6998
- if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
6999
- else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
7000
- }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
7001
- const autoPickRef = React13.useRef(() => {
7002
- });
7003
- const lastAutoKey = React13.useRef(null);
7004
- autoPickRef.current = (o, key) => {
7005
- if (form.readOnly || lastAutoKey.current === key) return;
7006
- lastAutoKey.current = key;
7007
- if (isFilled(form.values[field2.id])) return;
7008
- store([o]);
7009
- };
7010
- const select = React13.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
7011
- const isSelected = React13.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
7012
- const atLimit = field2.multiple && selected.length >= maxCount;
7013
- const toggle = React13.useCallback((o) => {
7014
- const on = isSelected(o);
7015
- if (!on && field2.multiple && selected.length >= maxCount) return;
7016
- store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
7017
- }, [isSelected, selected, store, field2.multiple, maxCount]);
7018
- const addFreeTag = React13.useCallback((text) => {
7019
- const t = text.trim();
7020
- if (!t || field2.type !== "INPUT_TAG") return;
7021
- const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
7022
- if (selected.some((s) => optionKey(s.value) === t)) return;
7023
- if (field2.multiple) {
7024
- if (selected.length < maxCount) store([...selected, o]);
7025
- } else store([o]);
7026
- setSearch("");
7027
- }, [field2.type, field2.multiple, selected, maxCount, store]);
7028
- return {
7029
- options: staticFiltered,
7030
- loading,
7031
- error,
7032
- search,
7033
- setSearch,
7034
- waitingFor,
7035
- selected,
7036
- select,
7037
- toggle,
7038
- isSelected,
7039
- onFocus,
7040
- refresh,
7041
- maxCount,
7042
- atLimit,
7043
- addFreeTag
7044
- };
7045
- }
7046
- 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(" "));
7047
- function useFileField(form, field2, opts = {}) {
7048
- const stored = Array.isArray(field2.value) ? field2.value : [];
7049
- const [pending2, setPending] = React13.useState([]);
7050
- const [reject, setReject] = React13.useState(null);
7051
- const storedRef = React13.useRef(stored);
7052
- storedRef.current = stored;
7053
- const items = [
7054
- ...stored.map((r, i) => ({
7055
- key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
7056
- name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
7057
- size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
7058
- type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
7059
- url: r.media ?? r.url,
7060
- uploading: false,
7061
- record: r
7062
- })),
7063
- ...pending2
7064
- ];
7065
- const max = field2.maxCount;
7066
- const add = React13.useCallback((list) => {
7067
- setReject(null);
7068
- const files = Array.from(list);
7069
- const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
7070
- if (!room) {
7071
- setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
7072
- return;
7720
+ setSubmitAttempted(false);
7721
+ setSubmitError(null);
7722
+ setDirty(false);
7723
+ if (definition) setStatus("ready");
7724
+ }, [definition]);
7725
+ const apiRef = React14.useRef(null);
7726
+ const runAction = React14.useCallback(async (field2, choice) => {
7727
+ const p = field2.element.properties ?? {};
7728
+ const subType = String(field2.element.attributes?.subType ?? "");
7729
+ const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
7730
+ const actionType = String(rawType || p.subType || "").toUpperCase();
7731
+ const value = valuesRef.current[field2.id] ?? field2.value;
7732
+ const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
7733
+ const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
7734
+ const browser = typeof window !== "undefined" && typeof document !== "undefined";
7735
+ if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
7736
+ const pick2 = choice ?? (typeof value === "string" ? value : "");
7737
+ const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
7738
+ if (!target) return { ok: false, message: "No actions found to perform!" };
7739
+ setValue(field2.id, pick2);
7740
+ return runAction(target);
7073
7741
  }
7074
- files.slice(0, room).forEach((file) => {
7075
- const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7076
- if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
7077
- setReject(`Cannot upload files with the extension .${ext}`);
7078
- return;
7742
+ if (field2.type === "CASCADED_ACTIONS") {
7743
+ for (const id of p.actionEleIds ?? []) {
7744
+ const f = fieldMap2.get(String(id));
7745
+ if (f) {
7746
+ const r = await runAction(f);
7747
+ if (!r.ok) return r;
7748
+ }
7079
7749
  }
7080
- if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
7081
- setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
7082
- return;
7750
+ if (p.submit) await submit();
7751
+ return { ok: true };
7752
+ }
7753
+ const handled = await argsRef.current.onAction?.(field2, apiRef.current);
7754
+ if (handled) return handled;
7755
+ const afterAutomation = (res) => {
7756
+ const fd = formDataOf(res);
7757
+ if (fd) applyValues(fd);
7758
+ if (p.refreshForm && (submissionId || submittedId)) {
7759
+ const delay = Number(p.refreshFormDelay) || 0;
7760
+ setTimeout(() => setReloadTick((n) => n + 1), delay);
7083
7761
  }
7084
- const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
7085
- 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)) {
7086
- setReject(`${file.name} isn't an allowed file type`);
7087
- return;
7762
+ };
7763
+ const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
7764
+ const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
7765
+ if (isTrigger) {
7766
+ const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
7767
+ if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
7768
+ if (!zeno && !trig) return fail("Automation ID missing from action");
7769
+ return withBusy(field2.id, async () => {
7770
+ try {
7771
+ const body = triggerBody(valuesRef.current);
7772
+ const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
7773
+ const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
7774
+ if (failed) {
7775
+ const fd = formDataOf(res);
7776
+ if (fd) applyValues(fd);
7777
+ return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
7778
+ }
7779
+ afterAutomation(res);
7780
+ return ok(res?.comment?.message ?? res?.message);
7781
+ } catch (e) {
7782
+ return fail(e?.message ?? `${field2.label} failed`);
7783
+ }
7784
+ });
7785
+ }
7786
+ switch (actionType) {
7787
+ case "":
7788
+ case "OPEN_URL":
7789
+ case "URL": {
7790
+ const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
7791
+ if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
7792
+ return url ? { ok: true } : fail("No link configured");
7793
+ }
7794
+ case "SUBMIT": {
7795
+ const r = await submit();
7796
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7797
+ }
7798
+ case "INVOKE_AGENT": {
7799
+ if (!p.agentId) return fail("Agent ID missing from action");
7800
+ return withBusy(field2.id, async () => {
7801
+ try {
7802
+ const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
7803
+ const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
7804
+ if (!res) return fail(`${field2.label} failed`);
7805
+ const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
7806
+ if (fd) applyValues(fd);
7807
+ return p.successMessage ? ok() : { ok: true };
7808
+ } catch (e) {
7809
+ return fail(e?.message ?? `${field2.label} failed`);
7810
+ }
7811
+ });
7812
+ }
7813
+ case "DOWNLOAD_FILE": {
7814
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7815
+ if (!file) return fail("File URL missing from action");
7816
+ if (browser) {
7817
+ const a = document.createElement("a");
7818
+ a.href = String(file);
7819
+ a.download = p.fileName ?? "";
7820
+ a.target = "_blank";
7821
+ a.rel = "noopener";
7822
+ a.click();
7823
+ }
7824
+ return { ok: true };
7825
+ }
7826
+ case "PRINT_PREVIEW": {
7827
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7828
+ if (!file) return fail("File URL missing from action");
7829
+ if (!browser) return fail("Printing needs a browser");
7830
+ return withBusy(field2.id, async () => {
7831
+ try {
7832
+ const blob = await (await fetch(String(file))).blob();
7833
+ const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
7834
+ const frame = document.createElement("iframe");
7835
+ frame.style.display = "none";
7836
+ frame.src = url;
7837
+ document.body.appendChild(frame);
7838
+ frame.onload = () => frame.contentWindow?.print();
7839
+ return { ok: true };
7840
+ } catch {
7841
+ return fail("Failed to load file for printing");
7842
+ }
7843
+ });
7844
+ }
7845
+ case "UPLOAD_FILE": {
7846
+ if (!browser) return fail("Uploading needs a browser");
7847
+ const file = await new Promise((resolve) => {
7848
+ const input = document.createElement("input");
7849
+ input.type = "file";
7850
+ input.onchange = () => resolve(input.files?.[0] ?? null);
7851
+ input.click();
7852
+ });
7853
+ if (!file) return fail("File upload failed");
7854
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7855
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
7856
+ const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
7857
+ if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
7858
+ return withBusy(field2.id, async () => {
7859
+ try {
7860
+ const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
7861
+ setValue(field2.id, rec.media);
7862
+ return ok("File successfully uploaded");
7863
+ } catch (e) {
7864
+ return fail(e?.message ?? "File upload failed");
7865
+ }
7866
+ });
7867
+ }
7868
+ case "CREATE_SUBMISSION":
7869
+ case "EDIT_SUBMISSION":
7870
+ case "SAVE_SUBMISSION":
7871
+ case "OPEN_TASK":
7872
+ case "OPEN_PROJECT":
7873
+ case "AGENT":
7874
+ case "FLOW":
7875
+ return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
7876
+ default: {
7877
+ setValue(field2.id, true);
7878
+ const r = await submit();
7879
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7088
7880
  }
7089
- const key = `p${Date.now()}${Math.random()}`;
7090
- setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
7091
- uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
7092
- setPending((p) => p.filter((x) => x.key !== key));
7093
- const next = [...storedRef.current, rec];
7094
- storedRef.current = next;
7095
- form.setValue(field2.id, next);
7096
- }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
7097
- });
7098
- if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
7099
- }, [form, field2, max, pending2, opts.zappId]);
7100
- const remove = React13.useCallback((key) => {
7101
- if (key.startsWith("p")) {
7102
- setPending((p) => p.filter((x) => x.key !== key));
7103
- return;
7104
7881
  }
7105
- const idx = items.findIndex((i) => i.key === key);
7106
- if (idx < 0) return;
7107
- const next = storedRef.current.filter((_2, i) => i !== idx);
7108
- form.setValue(field2.id, next.length ? next : []);
7109
- }, [items, form, field2.id]);
7110
- return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
7111
- }
7112
- function sheetAllows(p, action) {
7113
- const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
7114
- const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
7115
- if (!Array.isArray(list) || !list.length) return true;
7116
- return list.map((x) => String(x).toUpperCase()).includes(action);
7882
+ }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
7883
+ const reload = React14.useCallback(() => setReloadTick((n) => n + 1), []);
7884
+ const [sheetVersion, setSheetVersion] = React14.useState(0);
7885
+ const registerSheet = React14.useCallback((id, def) => {
7886
+ if (sheetDefs.current.get(id) === def) return;
7887
+ sheetDefs.current.set(id, def);
7888
+ setSheetVersion((n) => n + 1);
7889
+ }, []);
7890
+ const sheetDefinition = React14.useCallback((id) => sheetDefs.current.get(id), []);
7891
+ const api = {
7892
+ status,
7893
+ error,
7894
+ definition,
7895
+ mode,
7896
+ readOnly,
7897
+ values,
7898
+ errors,
7899
+ fields,
7900
+ rootFields,
7901
+ field: (id) => fieldMap2.get(id),
7902
+ childrenOf,
7903
+ setValue,
7904
+ applyValues,
7905
+ dirty,
7906
+ submitting,
7907
+ submitAttempted,
7908
+ submitError,
7909
+ submittedId,
7910
+ validate,
7911
+ submit,
7912
+ reset,
7913
+ runAction,
7914
+ reload,
7915
+ busy,
7916
+ client,
7917
+ registerSheet,
7918
+ sheetDefinition,
7919
+ sheetVersion
7920
+ };
7921
+ apiRef.current = api;
7922
+ return api;
7117
7923
  }
7118
- function useSheetField(form, field2) {
7119
- const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
7120
- const [child, setChild] = React13.useState(null);
7121
- const [loading, setLoading] = React13.useState(!!childId);
7122
- const [error, setError] = React13.useState(childId ? null : "No child form configured");
7123
- React13.useEffect(() => {
7124
- if (!childId) return;
7125
- let alive = true;
7126
- setLoading(true);
7127
- fetchForm(form.client, childId).then(async (def) => {
7128
- if (!alive) return;
7129
- setChild(def);
7130
- form.registerSheet(field2.id, def);
7131
- const raw2 = Array.isArray(field2.value) ? field2.value : [];
7132
- const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
7133
- if (ids.length) {
7134
- const rows2 = await fetchRows(form.client, childId, ids);
7135
- if (alive) form.applyValues({ [field2.id]: rows2 });
7136
- }
7137
- }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
7138
- return () => {
7139
- alive = false;
7140
- };
7141
- }, [childId, form.client]);
7142
- const columns = React13.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
7143
- id: e.elementId,
7144
- label: e.label,
7145
- type: resolveFieldType(e),
7146
- required: e.required,
7147
- element: e,
7148
- 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 })
7149
- })), [child]);
7150
- const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
7924
+ function openFormRequest(field2, form) {
7151
7925
  const p = field2.element.properties ?? {};
7152
- const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
7153
- const [limitMessage, setLimitMessage] = React13.useState(null);
7154
- const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
7155
- const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
7156
- const addRow = React13.useCallback((vals) => {
7157
- if (!addAllowed) return;
7158
- if (maxRows && rows.length >= maxRows) {
7159
- setLimitMessage(`Maximum number of rows reached (${maxRows})`);
7160
- return;
7161
- }
7162
- setLimitMessage(null);
7163
- const blank = child ? buildInitialValues(child.elements, { defaults: vals }) : { ...vals ?? {} };
7164
- form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
7165
- }, [child, rows, form, field2.id, addAllowed, maxRows]);
7166
- const updateRow = React13.useCallback((i, col, v) => {
7167
- form.setValue(field2.id, rows.map((r, j) => j === i ? { ...r, [col]: v, _dirty: true } : r));
7168
- }, [rows, form, field2.id]);
7169
- const removeRow = React13.useCallback((i) => {
7170
- if (!canDelete) return;
7171
- setLimitMessage(null);
7172
- form.setValue(field2.id, rows.filter((_2, j) => j !== i));
7173
- }, [rows, form, field2.id, canDelete]);
7174
- const rowErrors = React13.useMemo(() => {
7175
- const out = {};
7176
- if (!form.submitAttempted) return out;
7177
- rows.forEach((r, i) => {
7178
- if (!sheetRowNeedsCheck(r)) return;
7179
- columns.forEach((c) => {
7180
- if (!sheetCellChecked(c.element)) return;
7181
- const e = validateField(c.element, r[c.id]);
7182
- if (e) out[`${i}:${c.id}`] = e;
7926
+ const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
7927
+ const value = form.values[field2.id] ?? field2.value;
7928
+ const has2 = value != null && value !== "" && value !== true;
7929
+ const formId = p.formId ?? p.formID;
7930
+ switch (t) {
7931
+ case "CREATE_SUBMISSION":
7932
+ return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
7933
+ case "EDIT_SUBMISSION":
7934
+ case "OPEN_TASK":
7935
+ return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
7936
+ case "OPEN_PROJECT":
7937
+ return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
7938
+ case "SAVE_SUBMISSION": {
7939
+ if (!formId) return null;
7940
+ const defaults = {};
7941
+ (Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
7942
+ if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
7183
7943
  });
7184
- });
7185
- return out;
7186
- }, [rows, columns, form.submitAttempted]);
7944
+ return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
7945
+ }
7946
+ default:
7947
+ return null;
7948
+ }
7949
+ }
7950
+ var CORRECT = ["IND", "NR", "CHECK"];
7951
+ var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
7952
+ var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
7953
+ var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
7954
+ label,
7955
+ icon: c?.icon,
7956
+ color: c?.color,
7957
+ hideLabel: !!c?.hideLabel,
7958
+ disabled: !!c?.isDisabled,
7959
+ prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
7960
+ }));
7961
+ function useTrialField(form, field2) {
7962
+ const t = field2.type;
7963
+ const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
7964
+ const props = field2.element.properties ?? {};
7965
+ const value = field2.value && typeof field2.value === "object" ? field2.value : null;
7966
+ const conf = React14.useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
7967
+ const disabled = field2.disabled || form.readOnly;
7968
+ const set = (v) => form.setValue(field2.id, v);
7969
+ const [active, setActive] = React14.useState();
7970
+ const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
7971
+ const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7972
+ const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
7973
+ const linkedTimer = () => {
7974
+ const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
7975
+ return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
7976
+ };
7977
+ const timeLimit = Number(conf.timeLimit ?? 300);
7978
+ const interval = Number(conf.timeInterval ?? 30) || 30;
7979
+ const [timer, setTimer] = React14.useState(Number(value?.lastStatus?.timer ?? 0));
7980
+ const [running, setRunning] = React14.useState(false);
7981
+ const timerRef = React14.useRef(timer);
7982
+ timerRef.current = timer;
7983
+ React14.useEffect(() => {
7984
+ if (!running) return;
7985
+ const id = setInterval(() => setTimer((p) => {
7986
+ const n = p + 1;
7987
+ timerRef.current = n;
7988
+ if (n >= timeLimit) setRunning(false);
7989
+ return n;
7990
+ }), 1e3);
7991
+ return () => clearInterval(id);
7992
+ }, [running, timeLimit]);
7993
+ const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7994
+ const bound = (s) => {
7995
+ const i = Math.floor(s / interval);
7996
+ return `${i * interval}-${(i + 1) * interval}`;
7997
+ };
7998
+ const tsEmit = (resp, at) => set({
7999
+ conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
8000
+ buttonResp: resp,
8001
+ lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
8002
+ });
8003
+ const steps = React14.useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
8004
+ const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
8005
+ const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
8006
+ const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
8007
+ const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
8008
+ const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
8009
+ const stepName = steps[taStep] ?? "";
8010
+ const taEmit = (resp, trial, step) => set({
8011
+ conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
8012
+ buttonResp: resp,
8013
+ currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
8014
+ });
8015
+ const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
8016
+ const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
8017
+ const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
8018
+ const buttons = React14.useMemo(() => toButtons(rb), [rb]);
8019
+ const record = React14.useCallback((response, promptLabel) => {
8020
+ if (disabled) return;
8021
+ if (type === "percent") {
8022
+ if (pcTrial >= maxTrials) return;
8023
+ const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
8024
+ const next = pcTrial + 1;
8025
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
8026
+ } else if (type === "time") {
8027
+ const at = timerRef.current;
8028
+ tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
8029
+ } else {
8030
+ if (taTrial >= maxTrials || !steps.length) return;
8031
+ const key = `Trial ${taTrial}`;
8032
+ const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
8033
+ let step = taStep, trial = taTrial;
8034
+ if (chain === "backward") {
8035
+ step -= 1;
8036
+ if (step < 0) {
8037
+ step = steps.length - 1;
8038
+ trial += 1;
8039
+ }
8040
+ } else {
8041
+ step += 1;
8042
+ if (step >= steps.length) {
8043
+ step = 0;
8044
+ trial += 1;
8045
+ }
8046
+ }
8047
+ taEmit(resp, trial, step);
8048
+ }
8049
+ setActive(void 0);
8050
+ }, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
8051
+ const respond = React14.useCallback((label) => {
8052
+ const b = buttons.find((x) => x.label === label);
8053
+ if (!b || b.disabled) return;
8054
+ if (b.prompts.length) setActive(label);
8055
+ else record(label);
8056
+ }, [buttons, record]);
8057
+ const prompt = React14.useCallback((label) => {
8058
+ if (active) record(active, label);
8059
+ }, [active, record]);
8060
+ const prev = React14.useCallback(() => {
8061
+ if (disabled) return;
8062
+ if (type === "percent" && pcTrial > 0) {
8063
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
8064
+ } else if (type === "task" && taTrial > 0) {
8065
+ const resp = { ...taResp };
8066
+ delete resp[`Trial ${taTrial}`];
8067
+ taEmit(resp, taTrial - 1, firstStep);
8068
+ }
8069
+ }, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
8070
+ const reset = React14.useCallback(() => {
8071
+ if (type !== "time" || disabled) return;
8072
+ setTimer(0);
8073
+ setRunning(false);
8074
+ setActive(void 0);
8075
+ tsEmit([], 0);
8076
+ }, [type, disabled, value, conf]);
8077
+ 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 }));
8078
+ const log = allResp.map(({ r, trial }, i) => ({
8079
+ label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
8080
+ correct: isCorrect(r.responseButtonPressed),
8081
+ detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
8082
+ }));
8083
+ const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
7187
8084
  return {
7188
- loading,
7189
- error,
7190
- columns,
7191
- rows,
7192
- addRow,
7193
- updateRow,
7194
- removeRow,
7195
- rowErrors,
7196
- childForm: child,
7197
- canAdd: addAllowed && (!maxRows || rows.length < maxRows),
7198
- canDelete,
7199
- maxRows,
7200
- limitMessage,
7201
- display: (c, v) => formatFieldValue(c.element, v)
8085
+ type,
8086
+ buttons,
8087
+ activeResponse: active,
8088
+ prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
8089
+ respond,
8090
+ prompt,
8091
+ cancelPrompt: () => setActive(void 0),
8092
+ prev,
8093
+ currentTrial,
8094
+ maxTrials,
8095
+ complete,
8096
+ percentCorrect,
8097
+ log,
8098
+ disabled,
8099
+ timer,
8100
+ formattedTimer: fmt(timer),
8101
+ running,
8102
+ start: () => {
8103
+ if (!disabled && timer < timeLimit) setRunning(true);
8104
+ },
8105
+ pause: () => setRunning(false),
8106
+ reset,
8107
+ interval,
8108
+ intervalCount: Math.floor(timer / interval),
8109
+ timeLimit,
8110
+ steps,
8111
+ currentStep: taStep,
8112
+ stepName,
8113
+ chain
7202
8114
  };
7203
8115
  }
7204
- var FormKitSlot = React13__default.default.createContext(null);
8116
+ var FormKitSlot = React14__default.default.createContext(null);
7205
8117
  function useKit(local) {
7206
- const ctx = React13__default.default.useContext(FormKitSlot);
7207
- return React13__default.default.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
8118
+ const ctx = React14__default.default.useContext(FormKitSlot);
8119
+ return React14__default.default.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
7208
8120
  }
7209
8121
  var FormFields = ({ form, fields, kit: local }) => {
7210
8122
  const kit = useKit(local);
@@ -7342,6 +8254,33 @@ var DefaultSheet = ({ field: field2, form }) => {
7342
8254
  s.limitMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: s.limitMessage })
7343
8255
  ] });
7344
8256
  };
8257
+ var DefaultTrials = ({ field: field2, form }) => {
8258
+ const t = useTrialField(form, field2);
8259
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
8260
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
8261
+ t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
8262
+ t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
8263
+ " \xB7 ",
8264
+ t.percentCorrect,
8265
+ "% correct",
8266
+ t.complete ? " \xB7 complete" : ""
8267
+ ] }),
8268
+ t.type === "time" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
8269
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
8270
+ " ",
8271
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
8272
+ ] }),
8273
+ t.type !== "time" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
8274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
8275
+ (t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsxRuntime.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)),
8276
+ t.activeResponse && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
8277
+ ] }),
8278
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
8279
+ l.label,
8280
+ l.detail ? ` ${l.detail}` : ""
8281
+ ] }, i)) })
8282
+ ] });
8283
+ };
7345
8284
  var DefaultField = ({ field: field2, form }) => {
7346
8285
  const f = field2;
7347
8286
  const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
@@ -7370,7 +8309,20 @@ var DefaultField = ({ field: field2, form }) => {
7370
8309
  case "display":
7371
8310
  return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
7372
8311
  case "action":
8312
+ if (f.variant === "dropdown") {
8313
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
8314
+ f.label,
8315
+ " ",
8316
+ /* @__PURE__ */ jsxRuntime.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: [
8317
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
8318
+ f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
8319
+ ] })
8320
+ ] });
8321
+ }
7373
8322
  return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
8323
+ case "trials":
8324
+ control = /* @__PURE__ */ jsxRuntime.jsx(DefaultTrials, { field: f, form });
8325
+ break;
7374
8326
  case "checkbox":
7375
8327
  control = /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
7376
8328
  /* @__PURE__ */ jsxRuntime.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 }),
@@ -7452,12 +8404,12 @@ var TabWidget = ({
7452
8404
  render
7453
8405
  }) => {
7454
8406
  const t = useTokens();
7455
- const config = React13.useMemo(
8407
+ const config = React14.useMemo(
7456
8408
  () => resolveWidgetDefaults(t, "TAB", parseConfig(configurations)),
7457
8409
  [t, configurations]
7458
8410
  );
7459
8411
  const chrome = resolveChromeConfig(config);
7460
- const tabs = React13.useMemo(() => {
8412
+ const tabs = React14.useMemo(() => {
7461
8413
  const declared = config?.tabs ?? config?.childWidget ?? [];
7462
8414
  return declared.map((tab, i) => ({
7463
8415
  key: String(tab?.key ?? tab?.id ?? i),
@@ -7465,7 +8417,7 @@ var TabWidget = ({
7465
8417
  widget: tab?.widget ?? tab
7466
8418
  }));
7467
8419
  }, [config]);
7468
- const [activeKey, setActiveKey] = React13.useState("");
8420
+ const [activeKey, setActiveKey] = React14.useState("");
7469
8421
  const current = activeKey || tabs[0]?.key || "";
7470
8422
  const args = { tabs, activeKey: current, setActiveKey, tokens: t, config };
7471
8423
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -7525,7 +8477,7 @@ var FormWidget = ({
7525
8477
  onSubmitted
7526
8478
  }) => {
7527
8479
  const t = useTokens();
7528
- const config = React13.useMemo(
8480
+ const config = React14.useMemo(
7529
8481
  () => resolveWidgetDefaults(t, "FORM", parseConfig(configurations)),
7530
8482
  [t, configurations]
7531
8483
  );
@@ -7566,12 +8518,12 @@ var FilterPanelWidget = ({
7566
8518
  render
7567
8519
  }) => {
7568
8520
  const t = useTokens();
7569
- const config = React13.useMemo(
8521
+ const config = React14.useMemo(
7570
8522
  () => resolveWidgetDefaults(t, "FILTER_PANEL", parseConfig(configurations)),
7571
8523
  [t, configurations]
7572
8524
  );
7573
8525
  const chrome = resolveChromeConfig(config);
7574
- const controls = React13.useMemo(() => {
8526
+ const controls = React14.useMemo(() => {
7575
8527
  const declared = config?.filters ?? config?.controls ?? [];
7576
8528
  return declared.map((f, i) => ({
7577
8529
  key: String(f?.key ?? f?.field ?? f?.masterfield ?? i),
@@ -7580,7 +8532,7 @@ var FilterPanelWidget = ({
7580
8532
  options: f?.options
7581
8533
  }));
7582
8534
  }, [config]);
7583
- const [draft, setDraft] = React13.useState({});
8535
+ const [draft, setDraft] = React14.useState({});
7584
8536
  const values = { ...draft, ...value || {} };
7585
8537
  const setValue = (key, v) => {
7586
8538
  const next = { ...values, [key]: v };
@@ -7645,7 +8597,7 @@ var ZaiChatWidget = ({
7645
8597
  render
7646
8598
  }) => {
7647
8599
  const t = useTokens();
7648
- const config = React13.useMemo(
8600
+ const config = React14.useMemo(
7649
8601
  () => resolveWidgetDefaults(t, "ZAI_CHAT", parseConfig(configurations)),
7650
8602
  [t, configurations]
7651
8603
  );
@@ -7876,7 +8828,7 @@ function buildDashFilters(values = [], dateRange, operator = "AND") {
7876
8828
  }
7877
8829
  function dateRangeFor(token) {
7878
8830
  const f = (d) => d.format("YYYY-MM-DD");
7879
- const now = dayjs8__default.default();
8831
+ const now = dayjs4__default.default();
7880
8832
  switch (token) {
7881
8833
  case "TODAY":
7882
8834
  return { start: f(now), end: f(now) };
@@ -7948,6 +8900,232 @@ function buildDashboardTree(elements, isAdmin = false) {
7948
8900
  });
7949
8901
  return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
7950
8902
  }
8903
+ var text = (v) => {
8904
+ if (v === null || v === void 0 || v === "") return "";
8905
+ if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
8906
+ if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
8907
+ return String(v);
8908
+ };
8909
+ 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;
8910
+ function expectedOf(dep, labelOf) {
8911
+ const conds = dep.conditions ?? dep.Conditions ?? [];
8912
+ if (conds.length) {
8913
+ const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
8914
+ return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
8915
+ }
8916
+ if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
8917
+ return `= ${text(dep.value) || "(empty)"}`;
8918
+ }
8919
+ function checksOf(el2, required) {
8920
+ const t = resolveFieldType(el2);
8921
+ const p = el2.properties ?? {};
8922
+ const out = [];
8923
+ if (required) out.push("required");
8924
+ if (t === "INPUT_EMAIL") out.push("email");
8925
+ if (t === "INPUT_URL") out.push("url");
8926
+ if (t === "INPUT_PHONE") out.push("phone");
8927
+ if (t === "INPUT_PERCENT") out.push("0\u2013100");
8928
+ const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
8929
+ if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
8930
+ if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
8931
+ const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
8932
+ if (len) out.push(`\u2264 ${len} chars`);
8933
+ if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
8934
+ (el2.validations ?? []).forEach((v) => {
8935
+ const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
8936
+ if (k === "regex") out.push("pattern");
8937
+ else if (k === "expression") out.push("expression");
8938
+ else if (k) out.push(`${k} (server)`);
8939
+ });
8940
+ return out;
8941
+ }
8942
+ var sameNumber = (a, b) => {
8943
+ if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
8944
+ const na = Number(a), nb = Number(b);
8945
+ return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
8946
+ };
8947
+ function formulaRows(elements, values, scope, extra = {}) {
8948
+ const labels = elements.map((e) => e.label).filter(Boolean);
8949
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
8950
+ return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
8951
+ const formula = String(e.properties.formula);
8952
+ const refs = formulaLabels(formula, labels);
8953
+ const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
8954
+ 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);
8955
+ const current = values[e.elementId] ?? null;
8956
+ return {
8957
+ fieldId: e.elementId,
8958
+ label: e.label,
8959
+ scope,
8960
+ sheetLabel: extra.sheetLabel,
8961
+ row: extra.row,
8962
+ formula,
8963
+ serverFormula: toBraceFormula(formula, labels),
8964
+ serverSafe: isServerSafeFormula(formula, labels),
8965
+ inputs,
8966
+ expected,
8967
+ current,
8968
+ inSync: sameNumber(expected, current)
8969
+ };
8970
+ });
8971
+ }
8972
+ function inspectForm(form) {
8973
+ const def = form.definition;
8974
+ if (!def) {
8975
+ return {
8976
+ rules: [],
8977
+ filters: [],
8978
+ validation: [],
8979
+ formulas: [],
8980
+ defaults: [],
8981
+ payload: null,
8982
+ summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
8983
+ };
8984
+ }
8985
+ const elements = def.elements;
8986
+ const values = form.values;
8987
+ const byId = new Map(elements.map((e) => [e.elementId, e]));
8988
+ const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
8989
+ const rules = [];
8990
+ elements.forEach((el2) => {
8991
+ const origin = el2.properties?.triage ? "triage" : "dependencies";
8992
+ rulesOf(el2).forEach((dep) => {
8993
+ const action = String(dep.dependencyType ?? dep.type ?? "");
8994
+ const effect = effectOf(action);
8995
+ const target = String(dep.elementId ?? dep.targetElementId ?? "");
8996
+ if (!effect || !target) return;
8997
+ rules.push({
8998
+ sourceId: el2.elementId,
8999
+ sourceLabel: el2.label,
9000
+ sourceValue: text(values[el2.elementId]) || "(empty)",
9001
+ action,
9002
+ effect,
9003
+ targetId: target,
9004
+ targetLabel: labelOf(target),
9005
+ expected: expectedOf(dep, labelOf),
9006
+ matched: ruleMatches(el2, dep, values),
9007
+ origin
9008
+ });
9009
+ });
9010
+ });
9011
+ const filters2 = [];
9012
+ elements.forEach((el2) => {
9013
+ const cfg = lookupConfigOf(el2);
9014
+ if (!cfg) return;
9015
+ const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
9016
+ optionFilterRules(elements, el2.elementId).forEach((r) => {
9017
+ const v = text(values[r.elementId]);
9018
+ filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
9019
+ });
9020
+ cfg.dynamicFilters.forEach((df) => {
9021
+ const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
9022
+ const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
9023
+ if (!dep) return;
9024
+ const src = elements.find((e) => e.label === dep);
9025
+ const v = src ? text(values[src.elementId]) : "";
9026
+ filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
9027
+ });
9028
+ cfg.staticFilters.forEach((sf) => {
9029
+ const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
9030
+ filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
9031
+ });
9032
+ });
9033
+ const validation = [];
9034
+ form.fields.forEach((f) => {
9035
+ const t = resolveFieldType(f.element);
9036
+ if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
9037
+ validation.push({
9038
+ id: f.id,
9039
+ label: f.label,
9040
+ type: t,
9041
+ required: f.required,
9042
+ hidden: f.hidden,
9043
+ checks: checksOf(f.element, f.required),
9044
+ value: formatFieldValue(f.element, f.value),
9045
+ error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
9046
+ shownError: f.error
9047
+ });
9048
+ });
9049
+ const formulas = formulaRows(elements, values, "form");
9050
+ const sheets = [];
9051
+ elements.forEach((el2) => {
9052
+ const t = resolveFieldType(el2);
9053
+ if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
9054
+ const child = form.sheetDefinition(el2.elementId);
9055
+ if (!child) return;
9056
+ const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
9057
+ const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
9058
+ rows.forEach((row, i) => {
9059
+ const { _submissionId, _dirty, _rowKey, ...cells } = row;
9060
+ const scope = `${el2.label} \xB7 row ${i + 1}`;
9061
+ if (sheetRowNeedsCheck(row)) {
9062
+ child.elements.forEach((c) => {
9063
+ if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
9064
+ validation.push({
9065
+ id: `${el2.elementId}:${i}:${c.elementId}`,
9066
+ label: c.label,
9067
+ type: resolveFieldType(c),
9068
+ scope,
9069
+ required: !!c.required,
9070
+ hidden: false,
9071
+ checks: checksOf(c, !!c.required),
9072
+ value: formatFieldValue(c, cells[c.elementId]),
9073
+ error: validateField(c, cells[c.elementId])
9074
+ });
9075
+ });
9076
+ }
9077
+ const fresh = computeRowFormulas(child.elements, cells);
9078
+ formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
9079
+ sheet.rows.push(buildSubmissionElements(child.elements, fresh));
9080
+ });
9081
+ sheets.push(sheet);
9082
+ });
9083
+ const defaults = [];
9084
+ elements.forEach((el2) => {
9085
+ const dd = el2.attributes?.dynamicDefault;
9086
+ const ds = el2.properties?.dataSource ?? {};
9087
+ let source = "";
9088
+ if (dd && typeof dd === "object") {
9089
+ const u = dd.units ?? {};
9090
+ const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
9091
+ source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
9092
+ } else if (typeof dd === "string" && dd) source = dd;
9093
+ else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
9094
+ else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
9095
+ else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
9096
+ if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
9097
+ });
9098
+ const submitEls = submitElementsOf(elements);
9099
+ const sheetIds = new Set(sheets.map((s) => s.fieldId));
9100
+ const shown = { ...values };
9101
+ sheets.forEach((s) => {
9102
+ shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
9103
+ });
9104
+ const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
9105
+ const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
9106
+ const errors = validation.filter((v) => !v.hidden && v.error).length;
9107
+ const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
9108
+ return {
9109
+ rules,
9110
+ filters: filters2,
9111
+ validation,
9112
+ formulas,
9113
+ defaults,
9114
+ payload,
9115
+ summary: {
9116
+ rules: rules.length,
9117
+ matched: rules.filter((r) => r.matched).length,
9118
+ filters: filters2.length,
9119
+ activeFilters: filters2.filter((f) => f.active).length,
9120
+ errors,
9121
+ formulas: formulas.length,
9122
+ formulaIssues
9123
+ }
9124
+ };
9125
+ }
9126
+ function useFormInspector(form) {
9127
+ return React14.useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
9128
+ }
7951
9129
 
7952
9130
  // src/forms/authoring.ts
7953
9131
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
@@ -8062,6 +9240,7 @@ function formWidget(o) {
8062
9240
  exports.ActionCardWidget = ActionCardWidget;
8063
9241
  exports.ActivityFeedWidget = ActivityFeedWidget;
8064
9242
  exports.AreaChartWidget = AreaChartWidget;
9243
+ exports.BLOCKED_FILE_EXTENSIONS = BLOCKED_FILE_EXTENSIONS;
8065
9244
  exports.BannerWidget = BannerWidget;
8066
9245
  exports.BarChartWidget = BarChartWidget;
8067
9246
  exports.COLUMN_FILTER_BLANK = COLUMN_FILTER_BLANK;
@@ -8126,8 +9305,10 @@ exports.ZvolvForm = ZvolvForm;
8126
9305
  exports.ZvolvProvider = ZvolvProvider;
8127
9306
  exports.ZvolvThemeProvider = ZvolvThemeProvider;
8128
9307
  exports.ZvolvWidget = ZvolvWidget;
9308
+ exports.addPeriod = addPeriod;
8129
9309
  exports.aggregate = aggregate;
8130
9310
  exports.alignSummaryToSeries = alignSummaryToSeries;
9311
+ exports.applyCumulative = applyCumulative;
8131
9312
  exports.applyLocalColumnFilters = applyLocalColumnFilters;
8132
9313
  exports.applyLocalSearch = applyLocalSearch;
8133
9314
  exports.applyLocalSort = applyLocalSort;
@@ -8151,11 +9332,13 @@ exports.childIdsOf = childIdsOf;
8151
9332
  exports.cleanTitle = cleanTitle;
8152
9333
  exports.clearLookupCache = clearLookupCache;
8153
9334
  exports.coerceSeriesNumbers = coerceSeriesNumbers;
9335
+ exports.computeRowFormulas = computeRowFormulas;
8154
9336
  exports.createMockRuntime = createMockRuntime;
8155
9337
  exports.createSubmission = createSubmission;
8156
9338
  exports.createZvolvRuntime = createZvolvRuntime;
8157
9339
  exports.dashboardDefaultDateToken = dashboardDefaultDateToken;
8158
9340
  exports.dashboardFilterDefs = dashboardFilterDefs;
9341
+ exports.dateDiff = dateDiff;
8159
9342
  exports.dateRangeFor = dateRangeFor;
8160
9343
  exports.defaultTokens = defaultTokens;
8161
9344
  exports.defineConfig = defineConfig;
@@ -8166,6 +9349,7 @@ exports.distinctColumnValues = distinctColumnValues;
8166
9349
  exports.eid = eid;
8167
9350
  exports.el = el;
8168
9351
  exports.evaluateFormula = evaluateFormula;
9352
+ exports.extractAgentFormData = extractAgentFormData;
8169
9353
  exports.fetchForm = fetchForm;
8170
9354
  exports.fetchLookupOptions = fetchLookupOptions;
8171
9355
  exports.fetchSubmission = fetchSubmission;
@@ -8174,6 +9358,7 @@ exports.fieldKind = fieldKind;
8174
9358
  exports.fieldKindOf = fieldKindOf;
8175
9359
  exports.fillDateGaps = fillDateGaps;
8176
9360
  exports.filterableFields = filterableFields;
9361
+ exports.flattenPercentiles = flattenPercentiles;
8177
9362
  exports.formSource = formSource;
8178
9363
  exports.formWidget = formWidget;
8179
9364
  exports.formatAmountValue = formatAmountValue;
@@ -8188,20 +9373,27 @@ exports.gridHeight = gridHeight;
8188
9373
  exports.gridWidgets = gridWidgets;
8189
9374
  exports.groupBy = groupBy;
8190
9375
  exports.hasActiveInteractions = hasActiveInteractions;
9376
+ exports.inspectForm = inspectForm;
8191
9377
  exports.interpolate = interpolate;
9378
+ exports.invokeAgent = invokeAgent;
8192
9379
  exports.invokeTrigger = invokeTrigger;
9380
+ exports.invokeZeno = invokeZeno;
8193
9381
  exports.isDataSourceConfigured = isDataSourceConfigured;
8194
9382
  exports.isLegacyId = isLegacyId;
8195
9383
  exports.isLikelyTimestamp = isLikelyTimestamp;
9384
+ exports.isServerSafeFormula = isServerSafeFormula;
8196
9385
  exports.lookupConfigOf = lookupConfigOf;
8197
9386
  exports.mergeTokens = mergeTokens;
8198
9387
  exports.normaliseElement = normaliseElement;
8199
9388
  exports.normaliseForm = normaliseForm;
8200
9389
  exports.normalizeWidget = normalizeWidget;
9390
+ exports.openFormRequest = openFormRequest;
8201
9391
  exports.operatorsForKind = operatorsForKind;
8202
9392
  exports.optionFilterRules = optionFilterRules;
8203
9393
  exports.parseConfig = parseConfig;
9394
+ exports.parseDateValue = parseDateValue;
8204
9395
  exports.parseDeep = parseDeep;
9396
+ exports.percentileKey = percentileKey;
8205
9397
  exports.pivot = pivot;
8206
9398
  exports.renderHtmlVariables = renderHtmlVariables;
8207
9399
  exports.resetQueryUser = resetQueryUser;
@@ -8221,13 +9413,17 @@ exports.resolveWidgetDefaults = resolveWidgetDefaults;
8221
9413
  exports.resolveXKey = resolveXKey;
8222
9414
  exports.rowSubmissionIds = rowSubmissionIds;
8223
9415
  exports.ruleMatches = ruleMatches;
9416
+ exports.rulesOf = rulesOf;
8224
9417
  exports.searchPeople = searchPeople;
8225
9418
  exports.serializeElementValue = serializeElementValue;
9419
+ exports.startOfPeriod = startOfPeriod;
8226
9420
  exports.statusTone = statusTone;
8227
9421
  exports.submissionIdOf = submissionIdOf;
8228
9422
  exports.submissionToValues = submissionToValues;
9423
+ exports.submitElementsOf = submitElementsOf;
8229
9424
  exports.taskField = taskField;
8230
9425
  exports.taskSource = taskSource;
9426
+ exports.toBraceFormula = toBraceFormula;
8231
9427
  exports.toNum = toNum;
8232
9428
  exports.toRelational = toRelational;
8233
9429
  exports.toneColor = toneColor;
@@ -8238,6 +9434,7 @@ exports.useCollectionItems = useCollectionItems;
8238
9434
  exports.useFieldOptions = useFieldOptions;
8239
9435
  exports.useFileField = useFileField;
8240
9436
  exports.useFormApi = useFormApi;
9437
+ exports.useFormInspector = useFormInspector;
8241
9438
  exports.useInteractiveWidgetData = useInteractiveWidgetData;
8242
9439
  exports.useKpiContents = useKpiContents;
8243
9440
  exports.useSheetField = useSheetField;
@@ -8245,6 +9442,7 @@ exports.useTableColumns = useTableColumns;
8245
9442
  exports.useTemplateOverride = useTemplateOverride;
8246
9443
  exports.useTemporalEvents = useTemporalEvents;
8247
9444
  exports.useTokens = useTokens;
9445
+ exports.useTrialField = useTrialField;
8248
9446
  exports.useWidgetData = useWidgetData;
8249
9447
  exports.useWidgetInteractions = useWidgetInteractions;
8250
9448
  exports.useZvolvClient = useZvolvClient;