@zestl/zvolv-widgets 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,9 +46,47 @@ function getRuntimeClient() {
46
46
  if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
47
47
  return c;
48
48
  }
49
+ var sdk = {};
50
+ try {
51
+ import('@zestl/zvolv-js-sdk').then((m) => {
52
+ sdk = m ?? {};
53
+ }).catch(() => {
54
+ });
55
+ } catch {
56
+ }
57
+ var AUTH_CRYPT_READY = "zvAuthCryptReady";
58
+ var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
59
+ var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
60
+ function stripUndecrypted(node) {
61
+ if (Array.isArray(node)) return node.map(stripUndecrypted);
62
+ if (node && typeof node === "object") {
63
+ const out = {};
64
+ for (const [k, v] of Object.entries(node)) {
65
+ if (v && typeof v === "object") out[k] = stripUndecrypted(v);
66
+ else if (!isEnc(v)) out[k] = v;
67
+ }
68
+ return out;
69
+ }
70
+ return isEnc(node) ? void 0 : node;
71
+ }
49
72
  function getStoredUser() {
50
73
  try {
51
- return userReader() ?? clientGetter()?.auth?.userInstance ?? null;
74
+ let u = userReader() ?? null;
75
+ if (!u) {
76
+ try {
77
+ u = sdk.readUserFromStorage?.() ?? null;
78
+ } catch {
79
+ u = null;
80
+ }
81
+ }
82
+ if (!u) u = clientGetter()?.auth?.userInstance ?? null;
83
+ if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
84
+ try {
85
+ u = sdk.decryptAuthObject(u);
86
+ } catch {
87
+ }
88
+ }
89
+ return u ? stripUndecrypted(u) : null;
52
90
  } catch {
53
91
  return null;
54
92
  }
@@ -134,60 +172,60 @@ function getDataType(elementType) {
134
172
  }
135
173
  function getDateRange(ops) {
136
174
  const values = [];
137
- const fmt = "YYYY-MM-DD";
175
+ const fmt2 = "YYYY-MM-DD";
138
176
  const today = dayjs8__default.default();
139
177
  switch (ops) {
140
178
  case "TODAY":
141
- values[0] = today.format(fmt);
142
- values[1] = today.format(fmt);
179
+ values[0] = today.format(fmt2);
180
+ values[1] = today.format(fmt2);
143
181
  break;
144
182
  case "LAST_SEVEN_DAYS":
145
- values[0] = today.subtract(7, "day").format(fmt);
146
- values[1] = today.add(1, "day").format(fmt);
183
+ values[0] = today.subtract(7, "day").format(fmt2);
184
+ values[1] = today.add(1, "day").format(fmt2);
147
185
  break;
148
186
  case "LAST_THIRTY_DAYS":
149
- values[0] = today.subtract(30, "day").format(fmt);
150
- values[1] = today.add(1, "day").format(fmt);
187
+ values[0] = today.subtract(30, "day").format(fmt2);
188
+ values[1] = today.add(1, "day").format(fmt2);
151
189
  break;
152
190
  case "THIS_WEEK":
153
- values[0] = today.startOf("week").format(fmt);
154
- values[1] = today.endOf("week").format(fmt);
191
+ values[0] = today.startOf("week").format(fmt2);
192
+ values[1] = today.endOf("week").format(fmt2);
155
193
  break;
156
194
  case "THIS_MONTH":
157
- values[0] = today.startOf("month").format(fmt);
158
- values[1] = today.endOf("month").format(fmt);
195
+ values[0] = today.startOf("month").format(fmt2);
196
+ values[1] = today.endOf("month").format(fmt2);
159
197
  break;
160
198
  case "THIS_YEAR":
161
- values[0] = today.startOf("year").format(fmt);
162
- values[1] = today.endOf("year").format(fmt);
199
+ values[0] = today.startOf("year").format(fmt2);
200
+ values[1] = today.endOf("year").format(fmt2);
163
201
  break;
164
202
  case "THIS_FINANCIAL_YEAR": {
165
203
  const m = today.month();
166
204
  const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
167
205
  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);
206
+ values[0] = fiscalStart.format(fmt2);
207
+ values[1] = fiscalEnd.format(fmt2);
170
208
  break;
171
209
  }
172
210
  case "NEXT_THREE_DAYS":
173
- values[0] = today.format(fmt);
174
- values[1] = today.add(4, "day").format(fmt);
211
+ values[0] = today.format(fmt2);
212
+ values[1] = today.add(4, "day").format(fmt2);
175
213
  break;
176
214
  case "NEXT_SEVEN_DAYS":
177
- values[0] = today.format(fmt);
178
- values[1] = today.add(8, "day").format(fmt);
215
+ values[0] = today.format(fmt2);
216
+ values[1] = today.add(8, "day").format(fmt2);
179
217
  break;
180
218
  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);
219
+ values[0] = today.add(1, "week").startOf("week").format(fmt2);
220
+ values[1] = today.add(1, "week").endOf("week").format(fmt2);
183
221
  break;
184
222
  case "NEXT_FIFTEEN_DAYS":
185
- values[0] = today.format(fmt);
186
- values[1] = today.add(16, "day").format(fmt);
223
+ values[0] = today.format(fmt2);
224
+ values[1] = today.add(16, "day").format(fmt2);
187
225
  break;
188
226
  case "NEXT_THIRTY_DAYS":
189
- values[0] = today.format(fmt);
190
- values[1] = today.add(30, "day").format(fmt);
227
+ values[0] = today.format(fmt2);
228
+ values[1] = today.add(30, "day").format(fmt2);
191
229
  break;
192
230
  case "THIS_QUARTER": {
193
231
  const m = today.month();
@@ -205,8 +243,8 @@ function getDateRange(ops) {
205
243
  qStart = today.startOf("year").month(9).date(1);
206
244
  qEnd = today.startOf("year").month(11).date(31);
207
245
  }
208
- values[0] = qStart.format(fmt);
209
- values[1] = qEnd.format(fmt);
246
+ values[0] = qStart.format(fmt2);
247
+ values[1] = qEnd.format(fmt2);
210
248
  break;
211
249
  }
212
250
  }
@@ -453,56 +491,56 @@ function isDateMath(value) {
453
491
  return typeof value === "string" && dateMathPattern.test(value.trim());
454
492
  }
455
493
  function getDate(dateFormula) {
456
- const fmt = "YYYY-MM-DDTHH:mm:ssZ";
494
+ const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
457
495
  const d = dayjs8__default.default();
458
496
  switch (dateFormula) {
459
497
  case "{@[TODAY]}":
460
- return d.startOf("day").format(fmt);
498
+ return d.startOf("day").format(fmt2);
461
499
  case "{@[3DAYSLATER]}":
462
- return d.add(3, "day").startOf("day").format(fmt);
500
+ return d.add(3, "day").startOf("day").format(fmt2);
463
501
  case "{@[7DAYSLATER]}":
464
- return d.add(7, "day").startOf("day").format(fmt);
502
+ return d.add(7, "day").startOf("day").format(fmt2);
465
503
  case "{@[14DAYSLATER]}":
466
- return d.add(14, "day").startOf("day").format(fmt);
504
+ return d.add(14, "day").startOf("day").format(fmt2);
467
505
  case "{@[15DAYSLATER]}":
468
- return d.add(15, "day").startOf("day").format(fmt);
506
+ return d.add(15, "day").startOf("day").format(fmt2);
469
507
  case "{@[21DAYSLATER]}":
470
- return d.add(21, "day").startOf("day").format(fmt);
508
+ return d.add(21, "day").startOf("day").format(fmt2);
471
509
  case "{@[28DAYSLATER]}":
472
- return d.add(28, "day").startOf("day").format(fmt);
510
+ return d.add(28, "day").startOf("day").format(fmt2);
473
511
  case "{@[30DAYSLATER]}":
474
- return d.add(30, "day").startOf("day").format(fmt);
512
+ return d.add(30, "day").startOf("day").format(fmt2);
475
513
  case "{@[60DAYSLATER]}":
476
- return d.add(60, "day").startOf("day").format(fmt);
514
+ return d.add(60, "day").startOf("day").format(fmt2);
477
515
  case "{@[90DAYSLATER]}":
478
- return d.add(90, "day").startOf("day").format(fmt);
516
+ return d.add(90, "day").startOf("day").format(fmt2);
479
517
  case "{@[180DAYSLATER]}":
480
- return d.add(180, "day").startOf("day").format(fmt);
518
+ return d.add(180, "day").startOf("day").format(fmt2);
481
519
  case "{@[365DAYSLATER]}":
482
- return d.add(365, "day").startOf("day").format(fmt);
520
+ return d.add(365, "day").startOf("day").format(fmt2);
483
521
  case "{@[3DAYSAGO]}":
484
- return d.subtract(3, "day").startOf("day").format(fmt);
522
+ return d.subtract(3, "day").startOf("day").format(fmt2);
485
523
  case "{@[7DAYSAGO]}":
486
- return d.subtract(7, "day").startOf("day").format(fmt);
524
+ return d.subtract(7, "day").startOf("day").format(fmt2);
487
525
  case "{@[14DAYSAGO]}":
488
- return d.subtract(14, "day").startOf("day").format(fmt);
526
+ return d.subtract(14, "day").startOf("day").format(fmt2);
489
527
  case "{@[15DAYSAGO]}":
490
- return d.subtract(15, "day").startOf("day").format(fmt);
528
+ return d.subtract(15, "day").startOf("day").format(fmt2);
491
529
  case "{@[21DAYSAGO]}":
492
- return d.subtract(21, "day").startOf("day").format(fmt);
530
+ return d.subtract(21, "day").startOf("day").format(fmt2);
493
531
  case "{@[28DAYSAGO]}":
494
- return d.subtract(28, "day").startOf("day").format(fmt);
532
+ return d.subtract(28, "day").startOf("day").format(fmt2);
495
533
  case "{@[30DAYSAGO]}":
496
- return d.subtract(20, "day").startOf("day").format(fmt);
534
+ return d.subtract(20, "day").startOf("day").format(fmt2);
497
535
  // note: original uses 20
498
536
  case "{@[60DAYSAGO]}":
499
- return d.subtract(60, "day").startOf("day").format(fmt);
537
+ return d.subtract(60, "day").startOf("day").format(fmt2);
500
538
  case "{@[90DAYSAGO]}":
501
- return d.subtract(90, "day").startOf("day").format(fmt);
539
+ return d.subtract(90, "day").startOf("day").format(fmt2);
502
540
  case "{@[180DAYSAGO]}":
503
- return d.subtract(180, "day").startOf("day").format(fmt);
541
+ return d.subtract(180, "day").startOf("day").format(fmt2);
504
542
  case "{@[365DAYSAGO]}":
505
- return d.subtract(365, "day").startOf("day").format(fmt);
543
+ return d.subtract(365, "day").startOf("day").format(fmt2);
506
544
  default:
507
545
  return dateFormula;
508
546
  }
@@ -582,8 +620,8 @@ function getFormula(formula, fMap, isFilter = false) {
582
620
  }
583
621
  }
584
622
  });
585
- formula.format.split("%s").forEach((text, i) => {
586
- textFormat += i === 0 ? `'${text}'` : ` + '${text}'`;
623
+ formula.format.split("%s").forEach((text2, i) => {
624
+ textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
587
625
  if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
588
626
  });
589
627
  script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
@@ -1665,7 +1703,7 @@ var resolveUserLabels = async (values, kind) => {
1665
1703
  // src/runtime/index.ts
1666
1704
  function createZvolvRuntime({ client, readUser }) {
1667
1705
  const getClient = typeof client === "function" ? client : () => client;
1668
- setRuntimeClient(getClient, readUser ?? (() => getClient()?.auth?.userInstance));
1706
+ setRuntimeClient(getClient, readUser ?? (() => null));
1669
1707
  resetQueryUser();
1670
1708
  return {
1671
1709
  get client() {
@@ -1908,19 +1946,19 @@ function resolveDateToken(token, now = dayjs8__default.default()) {
1908
1946
  }
1909
1947
  }
1910
1948
  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();
1949
+ function searchAsDate(text2) {
1950
+ const t = text2.trim();
1913
1951
  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);
1952
+ for (const fmt2 of SEARCH_DATE_FORMATS) {
1953
+ const [y, m, d] = parseStrict(t, fmt2);
1916
1954
  if (y) return `${y}-${m}-${d}`;
1917
1955
  }
1918
1956
  return null;
1919
1957
  }
1920
- function parseStrict(text, fmt) {
1921
- const sep = fmt.includes("/") ? "/" : "-";
1922
- const parts = text.split(sep);
1923
- const order = fmt.split(sep);
1958
+ function parseStrict(text2, fmt2) {
1959
+ const sep = fmt2.includes("/") ? "/" : "-";
1960
+ const parts = text2.split(sep);
1961
+ const order = fmt2.split(sep);
1924
1962
  if (parts.length !== 3 || order.length !== 3) return [null, null, null];
1925
1963
  const map = {};
1926
1964
  order.forEach((k, i) => {
@@ -1934,10 +1972,10 @@ function parseStrict(text, fmt) {
1934
1972
  return [y, mm, dd];
1935
1973
  }
1936
1974
  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 } };
1975
+ const text2 = (search ?? "").trim();
1976
+ if (!text2) return null;
1977
+ const iso = searchAsDate(text2);
1978
+ return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
1941
1979
  }
1942
1980
  function buildWidgetFilterClauses(dataSource, filters2 = []) {
1943
1981
  const isTask = resolveIsTask2(dataSource);
@@ -2348,11 +2386,11 @@ var Delta = ({ vm }) => {
2348
2386
  if (!vm.delta) return null;
2349
2387
  const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
2350
2388
  const sign = vm.delta.value >= 0 ? "+" : "";
2351
- const text = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2389
+ const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2352
2390
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
2353
2391
  vm.delta.isPositive ? "\u25B2" : "\u25BC",
2354
2392
  " ",
2355
- text
2393
+ text2
2356
2394
  ] });
2357
2395
  };
2358
2396
  var Sparkline = ({
@@ -4388,8 +4426,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
4388
4426
  if (column.kind === "number") {
4389
4427
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
4390
4428
  }
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, "") });
4429
+ const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4430
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
4393
4431
  };
4394
4432
  var DefaultTable = ({
4395
4433
  rows,
@@ -4758,16 +4796,16 @@ function renderHtmlVariables(template, rows, variables) {
4758
4796
  const d = dayjs8__default.default(value);
4759
4797
  if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
4760
4798
  }
4761
- let text;
4799
+ let text2;
4762
4800
  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(", ");
4801
+ text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
4764
4802
  } else if (value && typeof value === "object") {
4765
- text = String(value.view ?? value.title ?? value.name ?? "");
4803
+ text2 = String(value.view ?? value.title ?? value.name ?? "");
4766
4804
  } else {
4767
4805
  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 ?? "");
4806
+ text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
4769
4807
  }
4770
- out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text);
4808
+ out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
4771
4809
  }
4772
4810
  return out;
4773
4811
  }
@@ -5240,6 +5278,7 @@ function resolveFieldType(el2) {
5240
5278
  if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
5241
5279
  if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
5242
5280
  if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
5281
+ if (sub && sub !== t && sub in KIND) return sub;
5243
5282
  if (t === "SCAN") return "INPUT_SCAN";
5244
5283
  if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
5245
5284
  if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
@@ -5302,7 +5341,11 @@ var KIND = {
5302
5341
  COMMENTS: "comments",
5303
5342
  ACTION_BUTTON: "action",
5304
5343
  INPUT_TRIGGER: "action",
5305
- CASCADED_ACTIONS: "action"
5344
+ CASCADED_ACTIONS: "action",
5345
+ // ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
5346
+ INPUT_TIME_SAMPLING: "trials",
5347
+ INPUT_PERCENT_CORRECT: "trials",
5348
+ INPUT_TASK_ANALYSIS: "trials"
5306
5349
  };
5307
5350
  function fieldKindOf(el2) {
5308
5351
  const t = resolveFieldType(el2);
@@ -5551,6 +5594,9 @@ function formulaScalar(v) {
5551
5594
  if (typeof v === "number" || typeof v === "boolean") return v;
5552
5595
  return String(v);
5553
5596
  }
5597
+ function toBraceFormula(formula, labels) {
5598
+ return braceLegacyLabels(formula, labels);
5599
+ }
5554
5600
  function braceLegacyLabels(formula, labels) {
5555
5601
  if (formula.includes("{")) return formula;
5556
5602
  const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
@@ -5586,6 +5632,31 @@ function formulaLabels(formula, labels = []) {
5586
5632
  const src = braceLegacyLabels(String(formula ?? ""), labels);
5587
5633
  return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
5588
5634
  }
5635
+ function isServerSafeFormula(formula, labels = []) {
5636
+ const f = String(formula ?? "");
5637
+ if (!f.trim()) return true;
5638
+ return braceLegacyLabels(f, labels) === f;
5639
+ }
5640
+ function computeRowFormulas(elements, row) {
5641
+ const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
5642
+ const cols = elements.filter((e) => formulaOf(e));
5643
+ if (!cols.length) return row;
5644
+ const labels = elements.map((e) => e.label).filter(Boolean);
5645
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
5646
+ const out = { ...row };
5647
+ for (let pass = 0; pass < 2; pass++) {
5648
+ cols.forEach((c) => {
5649
+ const refs = formulaLabels(formulaOf(c), labels);
5650
+ const empty = (v) => v === null || v === void 0 || v === "";
5651
+ if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
5652
+ out[c.elementId] = null;
5653
+ return;
5654
+ }
5655
+ out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
5656
+ });
5657
+ }
5658
+ return out;
5659
+ }
5589
5660
 
5590
5661
  // src/forms/validate.ts
5591
5662
  var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
@@ -5635,6 +5706,11 @@ function validateField(el2, value, required = el2.required) {
5635
5706
  const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
5636
5707
  if (required && blank) return `${name} is required`;
5637
5708
  if (blank) return void 0;
5709
+ if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
5710
+ const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
5711
+ const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
5712
+ if (min > 0 && n < min) return `Trials are less than ${min}`;
5713
+ }
5638
5714
  if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
5639
5715
  if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
5640
5716
  if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
@@ -5745,6 +5821,9 @@ function getEmptyValue(type) {
5745
5821
  case "SIGNATURE":
5746
5822
  case "TABLE_START":
5747
5823
  case "MATRIX":
5824
+ case "INPUT_TIME_SAMPLING":
5825
+ case "INPUT_PERCENT_CORRECT":
5826
+ case "INPUT_TASK_ANALYSIS":
5748
5827
  return null;
5749
5828
  default:
5750
5829
  return "";
@@ -5786,7 +5865,7 @@ var DATE_UNITS = {
5786
5865
  };
5787
5866
  function currentUserId(user) {
5788
5867
  const id = user?.userId ?? user?.UserID ?? user?.id;
5789
- return id != null && id !== "" ? String(id) : void 0;
5868
+ return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
5790
5869
  }
5791
5870
  function dynamicDefault(el2, user) {
5792
5871
  const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
@@ -6173,6 +6252,67 @@ async function invokeTrigger(client, triggerId, body) {
6173
6252
  }
6174
6253
  return res?.data ?? res;
6175
6254
  }
6255
+ async function invokeZeno(client, automationUuid, body) {
6256
+ const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
6257
+ const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
6258
+ if (typeof payload === "string") {
6259
+ try {
6260
+ return JSON.parse(payload);
6261
+ } catch {
6262
+ return payload;
6263
+ }
6264
+ }
6265
+ return payload;
6266
+ }
6267
+ async function invokeAgent(client, body, flowId, agentId) {
6268
+ const wf = wfOf(client);
6269
+ if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
6270
+ const headers = { ...await wf.getHeaders(true) };
6271
+ const post = async (url, data2) => {
6272
+ try {
6273
+ const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
6274
+ const d = res?.data;
6275
+ return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
6276
+ } catch (e) {
6277
+ const r = e?.response;
6278
+ 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);
6279
+ throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
6280
+ }
6281
+ };
6282
+ const agent = encodeURIComponent(String(agentId ?? ""));
6283
+ let data = null;
6284
+ if (flowId) {
6285
+ data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
6286
+ const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
6287
+ if (!unknownFlow && data?.error !== true) return data;
6288
+ if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6289
+ }
6290
+ const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
6291
+ data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
6292
+ if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6293
+ return data;
6294
+ }
6295
+ var parseMaybeJson = (v) => {
6296
+ if (typeof v !== "string") return v;
6297
+ const t = v.trim();
6298
+ if (!t.startsWith("{") && !t.startsWith("[")) return v;
6299
+ try {
6300
+ return JSON.parse(t);
6301
+ } catch {
6302
+ return v;
6303
+ }
6304
+ };
6305
+ function extractAgentFormData(reply) {
6306
+ let node = parseMaybeJson(reply);
6307
+ for (let d = 0; d < 5; d++) {
6308
+ if (!node || typeof node !== "object" || Array.isArray(node)) return null;
6309
+ const fd = parseMaybeJson(node.formData);
6310
+ if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
6311
+ if (!("response" in node)) return Object.keys(node).length ? node : null;
6312
+ node = parseMaybeJson(node.response);
6313
+ }
6314
+ return null;
6315
+ }
6176
6316
  function formDataOf(result) {
6177
6317
  let node = result;
6178
6318
  for (let d = 0; d < 4 && node; d++) {
@@ -6349,168 +6489,527 @@ async function searchPeople(client, el2, query) {
6349
6489
  const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
6350
6490
  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
6491
  }
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");
6492
+ var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6493
+ var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6494
+ function useFieldOptions(form, field2, opts = {}) {
6495
+ const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6496
+ const el2 = field2.element;
6497
+ const cfg = React13.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6498
+ const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6499
+ const elements = form.definition?.elements ?? [];
6500
+ const rules = React13.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6501
+ const dep = React13.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6502
+ const depKey = JSON.stringify(dep.filters);
6503
+ const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6504
+ const single = maxCount === 1;
6505
+ const [search, setSearch] = React13.useState("");
6506
+ const [q, setQ] = React13.useState("");
6507
+ React13.useEffect(() => {
6508
+ const t = setTimeout(() => setQ(search), debounceMs);
6509
+ return () => clearTimeout(t);
6510
+ }, [search, debounceMs]);
6511
+ const [remote, setRemote] = React13.useState([]);
6512
+ const [loading, setLoading] = React13.useState(false);
6388
6513
  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]);
6514
+ const [opened, setOpened] = React13.useState(loadOn === "mount");
6515
+ const [nonce, setNonce] = React13.useState(0);
6516
+ const lastFetch = React13.useRef(0);
6517
+ const maxAge = React13.useRef(1e4);
6518
+ const waitingFor = React13.useMemo(() => {
6519
+ if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6520
+ const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6521
+ const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6522
+ return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6523
+ }, [cfg, dep.satisfied, rules, form]);
6524
+ const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6525
+ const active = opened || depDriven || q !== "";
6417
6526
  React13.useEffect(() => {
6527
+ if (!cfg && !people) return;
6528
+ if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6529
+ setRemote([]);
6530
+ return;
6531
+ }
6532
+ if (!active) return;
6418
6533
  let alive = true;
6419
- setStatus("loading");
6534
+ setLoading(true);
6420
6535
  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
- }
6436
- const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
6437
- if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
6438
- const info = await clientInfo(client);
6439
- def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
6440
- if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
6441
- });
6442
- }
6443
- def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
6444
- if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
6445
- });
6536
+ lastFetch.current = Date.now();
6537
+ const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6538
+ maxAge.current = 1e4;
6539
+ p.then((o) => {
6446
6540
  if (!alive) return;
6447
- initialRef.current = init;
6448
- setDefinition(def);
6449
- setValues(init);
6450
- setTouched(/* @__PURE__ */ new Set());
6451
- setFlagged(/* @__PURE__ */ new Set());
6452
- setServerErrors({});
6453
- setDirty(false);
6454
- setStatus("ready");
6455
- })().catch((e) => {
6456
- if (alive) {
6457
- setError(e instanceof Error ? e : new Error(String(e)));
6458
- setStatus("error");
6459
- }
6541
+ setRemote(o);
6542
+ if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6543
+ }).catch((e) => {
6544
+ if (alive) setError(e?.message ?? "Couldn't load options");
6545
+ }).finally(() => {
6546
+ if (alive) setLoading(false);
6460
6547
  });
6461
6548
  return () => {
6462
6549
  alive = false;
6463
6550
  };
6464
- }, [loadKey, client]);
6465
- 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]);
6469
- 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(() => {
6473
- const out = /* @__PURE__ */ new Set();
6474
- const visit = (el2) => childIdsOf(el2).forEach((id) => {
6475
- if (out.has(id)) return;
6476
- out.add(id);
6477
- const c = byId.get(id);
6478
- if (c) visit(c);
6479
- });
6480
- elements.forEach((el2) => {
6481
- if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
6482
- const o = overrides.get(el2.elementId) ?? {};
6483
- if (o.isHidden ?? el2.hidden) visit(el2);
6484
- });
6485
- return out;
6486
- }, [elements, overrides, byId]);
6487
- const stateOf = React13.useCallback((el2) => {
6488
- const o = overrides.get(el2.elementId) ?? {};
6489
- return {
6490
- hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
6491
- disabled: readOnly || (o.isDisabled ?? el2.disabled),
6492
- required: o.isRequired ?? el2.required
6493
- };
6494
- }, [overrides, readOnly, hiddenByParent]);
6551
+ }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6552
+ const refresh = React13.useCallback(() => {
6553
+ maxAge.current = 0;
6554
+ setOpened(true);
6555
+ setNonce((n) => n + 1);
6556
+ }, []);
6557
+ const onFocus = React13.useCallback(() => {
6558
+ if (!cfg && !people) return;
6559
+ if (!opened) {
6560
+ setOpened(true);
6561
+ return;
6562
+ }
6563
+ if (cfg && Date.now() - lastFetch.current > 2e3) {
6564
+ maxAge.current = 2e3;
6565
+ setNonce((n) => n + 1);
6566
+ } else if (people && !remote.length) setNonce((n) => n + 1);
6567
+ }, [cfg, people, opened, remote.length]);
6568
+ const prevDepKey = React13.useRef(null);
6495
6569
  React13.useEffect(() => {
6496
- if (status !== "ready") return;
6497
- const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
6498
- if (!formulas.length) return;
6499
- const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
6500
- const next = {};
6501
- const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
6502
- formulas.forEach((el2) => {
6503
- const refs = formulaLabels(el2.properties.formula, labels);
6504
- if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
6505
- if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
6506
- return;
6507
- }
6508
- const r = evaluateFormula(el2.properties.formula, (label) => {
6509
- const id = byLabel.get(label) ?? label;
6510
- return id in next ? next[id] : values[id];
6511
- }, labels);
6512
- const v = r === null ? null : r;
6513
- if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
6570
+ if (!cfg || !rules.length) return;
6571
+ if (prevDepKey.current === null) {
6572
+ prevDepKey.current = depKey;
6573
+ return;
6574
+ }
6575
+ if (prevDepKey.current === depKey) return;
6576
+ prevDepKey.current = depKey;
6577
+ if (form.readOnly) return;
6578
+ if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
6579
+ }, [depKey]);
6580
+ const staticFiltered = React13.useMemo(() => {
6581
+ if (cfg || people) return remote;
6582
+ const needle = search.trim().toLowerCase();
6583
+ return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
6584
+ }, [cfg, people, remote, field2.options, search]);
6585
+ const pickedCache = React13.useRef(/* @__PURE__ */ new Map());
6586
+ const current = field2.value;
6587
+ const selected = React13.useMemo(() => {
6588
+ const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
6589
+ const pool = [...staticFiltered, ...field2.options];
6590
+ return arr.map((v) => {
6591
+ const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
6592
+ if (hit) return hit;
6593
+ if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
6594
+ return { label: String(v), value: v };
6595
+ });
6596
+ }, [current, staticFiltered, field2.options]);
6597
+ const store = React13.useCallback((picked) => {
6598
+ picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
6599
+ const t = field2.type;
6600
+ if (field2.kind === "lookup") {
6601
+ const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
6602
+ form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
6603
+ if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
6604
+ const fill2 = {};
6605
+ cfg.extraLabels.forEach((label, i) => {
6606
+ const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
6607
+ const v = picked[0].extra?.[i];
6608
+ if (target && v !== void 0) fill2[target.elementId] = v;
6609
+ });
6610
+ if (Object.keys(fill2).length) form.applyValues(fill2);
6611
+ }
6612
+ return;
6613
+ }
6614
+ if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
6615
+ else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
6616
+ }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
6617
+ const autoPickRef = React13.useRef(() => {
6618
+ });
6619
+ const lastAutoKey = React13.useRef(null);
6620
+ autoPickRef.current = (o, key) => {
6621
+ if (form.readOnly || lastAutoKey.current === key) return;
6622
+ lastAutoKey.current = key;
6623
+ if (isFilled(form.values[field2.id])) return;
6624
+ store([o]);
6625
+ };
6626
+ const select = React13.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
6627
+ const isSelected = React13.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
6628
+ const atLimit = field2.multiple && selected.length >= maxCount;
6629
+ const toggle = React13.useCallback((o) => {
6630
+ const on = isSelected(o);
6631
+ if (!on && field2.multiple && selected.length >= maxCount) return;
6632
+ store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
6633
+ }, [isSelected, selected, store, field2.multiple, maxCount]);
6634
+ const addFreeTag = React13.useCallback((text2) => {
6635
+ const t = text2.trim();
6636
+ if (!t || field2.type !== "INPUT_TAG") return;
6637
+ const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
6638
+ if (selected.some((s) => optionKey(s.value) === t)) return;
6639
+ if (field2.multiple) {
6640
+ if (selected.length < maxCount) store([...selected, o]);
6641
+ } else store([o]);
6642
+ setSearch("");
6643
+ }, [field2.type, field2.multiple, selected, maxCount, store]);
6644
+ return {
6645
+ options: staticFiltered,
6646
+ loading,
6647
+ error,
6648
+ search,
6649
+ setSearch,
6650
+ waitingFor,
6651
+ selected,
6652
+ select,
6653
+ toggle,
6654
+ isSelected,
6655
+ onFocus,
6656
+ refresh,
6657
+ maxCount,
6658
+ atLimit,
6659
+ addFreeTag
6660
+ };
6661
+ }
6662
+ 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(" "));
6663
+ function useFileField(form, field2, opts = {}) {
6664
+ const stored = Array.isArray(field2.value) ? field2.value : [];
6665
+ const [pending2, setPending] = React13.useState([]);
6666
+ const [reject, setReject] = React13.useState(null);
6667
+ const storedRef = React13.useRef(stored);
6668
+ storedRef.current = stored;
6669
+ const items = [
6670
+ ...stored.map((r, i) => ({
6671
+ key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
6672
+ name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
6673
+ size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
6674
+ type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
6675
+ url: r.media ?? r.url,
6676
+ uploading: false,
6677
+ record: r
6678
+ })),
6679
+ ...pending2
6680
+ ];
6681
+ const max = field2.maxCount;
6682
+ const add = React13.useCallback((list) => {
6683
+ setReject(null);
6684
+ const files = Array.from(list);
6685
+ const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
6686
+ if (!room) {
6687
+ setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
6688
+ return;
6689
+ }
6690
+ files.slice(0, room).forEach((file) => {
6691
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
6692
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
6693
+ setReject(`Cannot upload files with the extension .${ext}`);
6694
+ return;
6695
+ }
6696
+ if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
6697
+ setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
6698
+ return;
6699
+ }
6700
+ const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
6701
+ 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)) {
6702
+ setReject(`${file.name} isn't an allowed file type`);
6703
+ return;
6704
+ }
6705
+ const key = `p${Date.now()}${Math.random()}`;
6706
+ setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
6707
+ uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
6708
+ setPending((p) => p.filter((x) => x.key !== key));
6709
+ const next = [...storedRef.current, rec];
6710
+ storedRef.current = next;
6711
+ form.setValue(field2.id, next);
6712
+ }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
6713
+ });
6714
+ if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
6715
+ }, [form, field2, max, pending2, opts.zappId]);
6716
+ const remove = React13.useCallback((key) => {
6717
+ if (key.startsWith("p")) {
6718
+ setPending((p) => p.filter((x) => x.key !== key));
6719
+ return;
6720
+ }
6721
+ const idx = items.findIndex((i) => i.key === key);
6722
+ if (idx < 0) return;
6723
+ const next = storedRef.current.filter((_2, i) => i !== idx);
6724
+ form.setValue(field2.id, next.length ? next : []);
6725
+ }, [items, form, field2.id]);
6726
+ return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
6727
+ }
6728
+ function sheetAllows(p, action) {
6729
+ const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
6730
+ const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
6731
+ if (!Array.isArray(list) || !list.length) return true;
6732
+ return list.map((x) => String(x).toUpperCase()).includes(action);
6733
+ }
6734
+ function useSheetField(form, field2) {
6735
+ const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
6736
+ const [child, setChild] = React13.useState(null);
6737
+ const [loading, setLoading] = React13.useState(!!childId);
6738
+ const [error, setError] = React13.useState(childId ? null : "No child form configured");
6739
+ React13.useEffect(() => {
6740
+ if (!childId) return;
6741
+ let alive = true;
6742
+ setLoading(true);
6743
+ fetchForm(form.client, childId).then(async (def) => {
6744
+ if (!alive) return;
6745
+ setChild(def);
6746
+ form.registerSheet(field2.id, def);
6747
+ const raw2 = Array.isArray(field2.value) ? field2.value : [];
6748
+ const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
6749
+ if (ids.length) {
6750
+ const rows2 = await fetchRows(form.client, childId, ids);
6751
+ if (alive) form.applyValues({ [field2.id]: rows2 });
6752
+ }
6753
+ }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
6754
+ return () => {
6755
+ alive = false;
6756
+ };
6757
+ }, [childId, form.client]);
6758
+ const columns = React13.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
6759
+ id: e.elementId,
6760
+ label: e.label,
6761
+ type: resolveFieldType(e),
6762
+ required: e.required,
6763
+ element: e,
6764
+ 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 })
6765
+ })), [child]);
6766
+ const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
6767
+ const p = field2.element.properties ?? {};
6768
+ const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
6769
+ const [limitMessage, setLimitMessage] = React13.useState(null);
6770
+ const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
6771
+ const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
6772
+ const addRow = React13.useCallback((vals) => {
6773
+ if (!addAllowed) return;
6774
+ if (maxRows && rows.length >= maxRows) {
6775
+ setLimitMessage(`Maximum number of rows reached (${maxRows})`);
6776
+ return;
6777
+ }
6778
+ setLimitMessage(null);
6779
+ const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
6780
+ form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
6781
+ }, [child, rows, form, field2.id, addAllowed, maxRows]);
6782
+ const updateRow = React13.useCallback((i, col, v) => {
6783
+ const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
6784
+ form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
6785
+ }, [rows, form, field2.id, child]);
6786
+ const removeRow = React13.useCallback((i) => {
6787
+ if (!canDelete) return;
6788
+ setLimitMessage(null);
6789
+ form.setValue(field2.id, rows.filter((_2, j) => j !== i));
6790
+ }, [rows, form, field2.id, canDelete]);
6791
+ const rowErrors = React13.useMemo(() => {
6792
+ const out = {};
6793
+ if (!form.submitAttempted) return out;
6794
+ rows.forEach((r, i) => {
6795
+ if (!sheetRowNeedsCheck(r)) return;
6796
+ columns.forEach((c) => {
6797
+ if (!sheetCellChecked(c.element)) return;
6798
+ const e = validateField(c.element, r[c.id]);
6799
+ if (e) out[`${i}:${c.id}`] = e;
6800
+ });
6801
+ });
6802
+ return out;
6803
+ }, [rows, columns, form.submitAttempted]);
6804
+ return {
6805
+ loading,
6806
+ error,
6807
+ columns,
6808
+ rows,
6809
+ addRow,
6810
+ updateRow,
6811
+ removeRow,
6812
+ rowErrors,
6813
+ childForm: child,
6814
+ canAdd: addAllowed && (!maxRows || rows.length < maxRows),
6815
+ canDelete,
6816
+ maxRows,
6817
+ limitMessage,
6818
+ display: (c, v) => formatFieldValue(c.element, v)
6819
+ };
6820
+ }
6821
+
6822
+ // src/forms/useZvolvForm.ts
6823
+ function submitElementsOf(elements) {
6824
+ const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
6825
+ return elements.filter((el2) => {
6826
+ const t = resolveFieldType(el2);
6827
+ return !NON_INPUT_TYPES.has(t) || always.has(t);
6828
+ });
6829
+ }
6830
+ var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
6831
+ if (o == null) return null;
6832
+ if (typeof o !== "object") return { label: String(o), value: String(o) };
6833
+ const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
6834
+ const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
6835
+ return { label: String(label ?? ""), value, color: o.color };
6836
+ }).filter(Boolean);
6837
+ var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
6838
+ var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
6839
+ var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
6840
+ function toDayjsFormat(fmt2, withTime) {
6841
+ const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
6842
+ 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");
6843
+ }
6844
+ function acceptOf(el2) {
6845
+ const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
6846
+ if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6847
+ const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
6848
+ if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6849
+ return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
6850
+ }
6851
+ var FormApiContext = React13__default.default.createContext(null);
6852
+ function useFormApi() {
6853
+ const f = React13__default.default.useContext(FormApiContext);
6854
+ if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
6855
+ return f;
6856
+ }
6857
+ function useZvolvForm(args) {
6858
+ const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
6859
+ const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
6860
+ const rt = useZvolvRuntime();
6861
+ const client = rt.client;
6862
+ const argsRef = React13.useRef(args);
6863
+ argsRef.current = args;
6864
+ const [definition, setDefinition] = React13.useState(null);
6865
+ const [status, setStatus] = React13.useState("loading");
6866
+ const [error, setError] = React13.useState(null);
6867
+ const [values, setValues] = React13.useState({});
6868
+ const [serverErrors, setServerErrors] = React13.useState({});
6869
+ const [touched, setTouched] = React13.useState(/* @__PURE__ */ new Set());
6870
+ const [submitAttempted, setSubmitAttempted] = React13.useState(false);
6871
+ const [flagged, setFlagged] = React13.useState(/* @__PURE__ */ new Set());
6872
+ const [submitting, setSubmitting] = React13.useState(false);
6873
+ const [submitError, setSubmitError] = React13.useState(null);
6874
+ const [submittedId, setSubmittedId] = React13.useState(null);
6875
+ const [dirty, setDirty] = React13.useState(false);
6876
+ const [busy, setBusy] = React13.useState(/* @__PURE__ */ new Set());
6877
+ const initialRef = React13.useRef({});
6878
+ const valuesRef = React13.useRef({});
6879
+ valuesRef.current = values;
6880
+ const sheetDefs = React13.useRef(/* @__PURE__ */ new Map());
6881
+ const [userTick, setUserTick] = React13.useState(0);
6882
+ React13.useEffect(() => {
6883
+ if (typeof window === "undefined") return;
6884
+ const bump = () => setUserTick((n) => n + 1);
6885
+ window.addEventListener(AUTH_CRYPT_READY, bump);
6886
+ return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
6887
+ }, []);
6888
+ const user = React13.useMemo(() => getStoredUser() ?? null, [client, userTick]);
6889
+ React13.useEffect(() => {
6890
+ if (status !== "ready" || mode !== "NEW") return;
6891
+ const uid = currentUserId(user);
6892
+ if (!uid) return;
6893
+ const fill2 = {};
6894
+ (definition?.elements ?? []).forEach((el2) => {
6895
+ const v = valuesRef.current[el2.elementId];
6896
+ if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
6897
+ });
6898
+ if (Object.keys(fill2).length) setValues((p) => {
6899
+ const m = { ...p, ...fill2 };
6900
+ valuesRef.current = m;
6901
+ return m;
6902
+ });
6903
+ }, [user, status]);
6904
+ const onBehalfName = React13.useCallback((v) => {
6905
+ if (v == null || v === "") return "";
6906
+ if (String(v) === "3122") return "Super Admin";
6907
+ if (isEncString(v)) return "";
6908
+ if (user && String(v) === currentUserId(user)) {
6909
+ const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
6910
+ return name ? String(name) : String(v);
6911
+ }
6912
+ return String(v);
6913
+ }, [user]);
6914
+ const [reloadTick, setReloadTick] = React13.useState(0);
6915
+ const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
6916
+ React13.useEffect(() => {
6917
+ let alive = true;
6918
+ setStatus("loading");
6919
+ setError(null);
6920
+ setSubmittedId(null);
6921
+ setSubmitAttempted(false);
6922
+ (async () => {
6923
+ const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
6924
+ let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
6925
+ let submission;
6926
+ if (submissionId && mode !== "NEW") {
6927
+ const s = await fetchSubmission(client, submissionId);
6928
+ submission = s.values;
6929
+ if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
6930
+ }
6931
+ if (!def) {
6932
+ if (!formId) throw new Error("No form configured");
6933
+ def = await fetchForm(client, formId);
6934
+ }
6935
+ const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
6936
+ if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
6937
+ const info = await clientInfo(client);
6938
+ def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
6939
+ if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
6940
+ });
6941
+ }
6942
+ def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
6943
+ if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
6944
+ });
6945
+ if (!alive) return;
6946
+ initialRef.current = init;
6947
+ setDefinition(def);
6948
+ setValues(init);
6949
+ setTouched(/* @__PURE__ */ new Set());
6950
+ setFlagged(/* @__PURE__ */ new Set());
6951
+ setServerErrors({});
6952
+ setDirty(false);
6953
+ setStatus("ready");
6954
+ })().catch((e) => {
6955
+ if (alive) {
6956
+ setError(e instanceof Error ? e : new Error(String(e)));
6957
+ setStatus("error");
6958
+ }
6959
+ });
6960
+ return () => {
6961
+ alive = false;
6962
+ };
6963
+ }, [loadKey, client]);
6964
+ const elements = definition?.elements ?? [];
6965
+ const byId = React13.useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
6966
+ const labels = React13.useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
6967
+ const overrides = React13.useMemo(() => applyRules(elements, values), [elements, values]);
6968
+ const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
6969
+ const hiddenSet = React13.useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
6970
+ const skipSet = React13.useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
6971
+ const hiddenByParent = React13.useMemo(() => {
6972
+ const out = /* @__PURE__ */ new Set();
6973
+ const visit = (el2) => childIdsOf(el2).forEach((id) => {
6974
+ if (out.has(id)) return;
6975
+ out.add(id);
6976
+ const c = byId.get(id);
6977
+ if (c) visit(c);
6978
+ });
6979
+ elements.forEach((el2) => {
6980
+ if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
6981
+ const o = overrides.get(el2.elementId) ?? {};
6982
+ if (o.isHidden ?? el2.hidden) visit(el2);
6983
+ });
6984
+ return out;
6985
+ }, [elements, overrides, byId]);
6986
+ const stateOf = React13.useCallback((el2) => {
6987
+ const o = overrides.get(el2.elementId) ?? {};
6988
+ return {
6989
+ hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
6990
+ disabled: readOnly || (o.isDisabled ?? el2.disabled),
6991
+ required: o.isRequired ?? el2.required
6992
+ };
6993
+ }, [overrides, readOnly, hiddenByParent]);
6994
+ React13.useEffect(() => {
6995
+ if (status !== "ready") return;
6996
+ const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
6997
+ if (!formulas.length) return;
6998
+ const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
6999
+ const next = {};
7000
+ const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
7001
+ formulas.forEach((el2) => {
7002
+ const refs = formulaLabels(el2.properties.formula, labels);
7003
+ if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
7004
+ if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
7005
+ return;
7006
+ }
7007
+ const r = evaluateFormula(el2.properties.formula, (label) => {
7008
+ const id = byLabel.get(label) ?? label;
7009
+ return id in next ? next[id] : values[id];
7010
+ }, labels);
7011
+ const v = r === null ? null : r;
7012
+ if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
6514
7013
  });
6515
7014
  if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
6516
7015
  }, [values, elements, labels, status]);
@@ -6640,7 +7139,7 @@ function useZvolvForm(args) {
6640
7139
  value: values[el2.elementId],
6641
7140
  error: showErr ? errors[el2.elementId] : void 0,
6642
7141
  touched: touched.has(el2.elementId),
6643
- options: normalizeOptions(p.options),
7142
+ 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
7143
  multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
6645
7144
  allowAddOptions: !!p.allowAddOptions,
6646
7145
  min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
@@ -6655,7 +7154,7 @@ function useZvolvForm(args) {
6655
7154
  childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
6656
7155
  pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
6657
7156
  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,
7157
+ variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
6659
7158
  displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
6660
7159
  dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
6661
7160
  maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
@@ -6714,11 +7213,12 @@ function useZvolvForm(args) {
6714
7213
  saved.push(row);
6715
7214
  continue;
6716
7215
  }
6717
- const { _submissionId, _dirty, _rowKey, ...cells } = row;
7216
+ const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
6718
7217
  if (_submissionId && !_dirty) {
6719
7218
  saved.push(row);
6720
7219
  continue;
6721
7220
  }
7221
+ const cells = computeRowFormulas(child.elements, raw2);
6722
7222
  const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
6723
7223
  saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
6724
7224
  }
@@ -6749,7 +7249,7 @@ function useZvolvForm(args) {
6749
7249
  }
6750
7250
  const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
6751
7251
  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");
7252
+ const submitEls = submitElementsOf(elements);
6753
7253
  const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
6754
7254
  const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
6755
7255
  setValues(vals);
@@ -6776,429 +7276,394 @@ function useZvolvForm(args) {
6776
7276
  if (definition) setStatus("ready");
6777
7277
  }, [definition]);
6778
7278
  const apiRef = React13.useRef(null);
6779
- const runAction = React13.useCallback(async (field2) => {
7279
+ const runAction = React13.useCallback(async (field2, choice) => {
6780
7280
  const p = field2.element.properties ?? {};
6781
7281
  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();
7282
+ const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
7283
+ const actionType = String(rawType || p.subType || "").toUpperCase();
7284
+ const value = valuesRef.current[field2.id] ?? field2.value;
7285
+ const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
7286
+ const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
7287
+ const browser = typeof window !== "undefined" && typeof document !== "undefined";
7288
+ if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
7289
+ const pick2 = choice ?? (typeof value === "string" ? value : "");
7290
+ const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
7291
+ if (!target) return { ok: false, message: "No actions found to perform!" };
7292
+ setValue(field2.id, pick2);
7293
+ return runAction(target);
7294
+ }
6784
7295
  if (field2.type === "CASCADED_ACTIONS") {
6785
7296
  for (const id of p.actionEleIds ?? []) {
6786
7297
  const f = fieldMap2.get(String(id));
6787
7298
  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;
7299
+ const r = await runAction(f);
7300
+ if (!r.ok) return r;
7301
+ }
7302
+ }
7303
+ if (p.submit) await submit();
7304
+ return { ok: true };
7073
7305
  }
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;
7306
+ const handled = await argsRef.current.onAction?.(field2, apiRef.current);
7307
+ if (handled) return handled;
7308
+ const afterAutomation = (res) => {
7309
+ const fd = formDataOf(res);
7310
+ if (fd) applyValues(fd);
7311
+ if (p.refreshForm && (submissionId || submittedId)) {
7312
+ const delay = Number(p.refreshFormDelay) || 0;
7313
+ setTimeout(() => setReloadTick((n) => n + 1), delay);
7079
7314
  }
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;
7315
+ };
7316
+ const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
7317
+ const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
7318
+ if (isTrigger) {
7319
+ const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
7320
+ if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
7321
+ if (!zeno && !trig) return fail("Automation ID missing from action");
7322
+ return withBusy(field2.id, async () => {
7323
+ try {
7324
+ const body = triggerBody(valuesRef.current);
7325
+ const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
7326
+ const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
7327
+ if (failed) {
7328
+ const fd = formDataOf(res);
7329
+ if (fd) applyValues(fd);
7330
+ return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
7331
+ }
7332
+ afterAutomation(res);
7333
+ return ok(res?.comment?.message ?? res?.message);
7334
+ } catch (e) {
7335
+ return fail(e?.message ?? `${field2.label} failed`);
7336
+ }
7337
+ });
7338
+ }
7339
+ switch (actionType) {
7340
+ case "":
7341
+ case "OPEN_URL":
7342
+ case "URL": {
7343
+ const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
7344
+ if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
7345
+ return url ? { ok: true } : fail("No link configured");
7083
7346
  }
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;
7347
+ case "SUBMIT": {
7348
+ const r = await submit();
7349
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7350
+ }
7351
+ case "INVOKE_AGENT": {
7352
+ if (!p.agentId) return fail("Agent ID missing from action");
7353
+ return withBusy(field2.id, async () => {
7354
+ try {
7355
+ const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
7356
+ const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
7357
+ if (!res) return fail(`${field2.label} failed`);
7358
+ const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
7359
+ if (fd) applyValues(fd);
7360
+ return p.successMessage ? ok() : { ok: true };
7361
+ } catch (e) {
7362
+ return fail(e?.message ?? `${field2.label} failed`);
7363
+ }
7364
+ });
7365
+ }
7366
+ case "DOWNLOAD_FILE": {
7367
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7368
+ if (!file) return fail("File URL missing from action");
7369
+ if (browser) {
7370
+ const a = document.createElement("a");
7371
+ a.href = String(file);
7372
+ a.download = p.fileName ?? "";
7373
+ a.target = "_blank";
7374
+ a.rel = "noopener";
7375
+ a.click();
7376
+ }
7377
+ return { ok: true };
7378
+ }
7379
+ case "PRINT_PREVIEW": {
7380
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7381
+ if (!file) return fail("File URL missing from action");
7382
+ if (!browser) return fail("Printing needs a browser");
7383
+ return withBusy(field2.id, async () => {
7384
+ try {
7385
+ const blob = await (await fetch(String(file))).blob();
7386
+ const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
7387
+ const frame = document.createElement("iframe");
7388
+ frame.style.display = "none";
7389
+ frame.src = url;
7390
+ document.body.appendChild(frame);
7391
+ frame.onload = () => frame.contentWindow?.print();
7392
+ return { ok: true };
7393
+ } catch {
7394
+ return fail("Failed to load file for printing");
7395
+ }
7396
+ });
7397
+ }
7398
+ case "UPLOAD_FILE": {
7399
+ if (!browser) return fail("Uploading needs a browser");
7400
+ const file = await new Promise((resolve) => {
7401
+ const input = document.createElement("input");
7402
+ input.type = "file";
7403
+ input.onchange = () => resolve(input.files?.[0] ?? null);
7404
+ input.click();
7405
+ });
7406
+ if (!file) return fail("File upload failed");
7407
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7408
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
7409
+ const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
7410
+ if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
7411
+ return withBusy(field2.id, async () => {
7412
+ try {
7413
+ const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
7414
+ setValue(field2.id, rec.media);
7415
+ return ok("File successfully uploaded");
7416
+ } catch (e) {
7417
+ return fail(e?.message ?? "File upload failed");
7418
+ }
7419
+ });
7420
+ }
7421
+ case "CREATE_SUBMISSION":
7422
+ case "EDIT_SUBMISSION":
7423
+ case "SAVE_SUBMISSION":
7424
+ case "OPEN_TASK":
7425
+ case "OPEN_PROJECT":
7426
+ case "AGENT":
7427
+ case "FLOW":
7428
+ return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
7429
+ default: {
7430
+ setValue(field2.id, true);
7431
+ const r = await submit();
7432
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7088
7433
  }
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
7434
  }
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 };
7435
+ }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
7436
+ const reload = React13.useCallback(() => setReloadTick((n) => n + 1), []);
7437
+ const [sheetVersion, setSheetVersion] = React13.useState(0);
7438
+ const registerSheet = React13.useCallback((id, def) => {
7439
+ if (sheetDefs.current.get(id) === def) return;
7440
+ sheetDefs.current.set(id, def);
7441
+ setSheetVersion((n) => n + 1);
7442
+ }, []);
7443
+ const sheetDefinition = React13.useCallback((id) => sheetDefs.current.get(id), []);
7444
+ const api = {
7445
+ status,
7446
+ error,
7447
+ definition,
7448
+ mode,
7449
+ readOnly,
7450
+ values,
7451
+ errors,
7452
+ fields,
7453
+ rootFields,
7454
+ field: (id) => fieldMap2.get(id),
7455
+ childrenOf,
7456
+ setValue,
7457
+ applyValues,
7458
+ dirty,
7459
+ submitting,
7460
+ submitAttempted,
7461
+ submitError,
7462
+ submittedId,
7463
+ validate,
7464
+ submit,
7465
+ reset,
7466
+ runAction,
7467
+ reload,
7468
+ busy,
7469
+ client,
7470
+ registerSheet,
7471
+ sheetDefinition,
7472
+ sheetVersion
7473
+ };
7474
+ apiRef.current = api;
7475
+ return api;
7111
7476
  }
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);
7477
+ function openFormRequest(field2, form) {
7478
+ const p = field2.element.properties ?? {};
7479
+ const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
7480
+ const value = form.values[field2.id] ?? field2.value;
7481
+ const has2 = value != null && value !== "" && value !== true;
7482
+ const formId = p.formId ?? p.formID;
7483
+ switch (t) {
7484
+ case "CREATE_SUBMISSION":
7485
+ return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
7486
+ case "EDIT_SUBMISSION":
7487
+ case "OPEN_TASK":
7488
+ return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
7489
+ case "OPEN_PROJECT":
7490
+ return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
7491
+ case "SAVE_SUBMISSION": {
7492
+ if (!formId) return null;
7493
+ const defaults = {};
7494
+ (Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
7495
+ if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
7496
+ });
7497
+ return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
7498
+ }
7499
+ default:
7500
+ return null;
7501
+ }
7117
7502
  }
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");
7503
+ var CORRECT = ["IND", "NR", "CHECK"];
7504
+ var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
7505
+ var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
7506
+ var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
7507
+ label,
7508
+ icon: c?.icon,
7509
+ color: c?.color,
7510
+ hideLabel: !!c?.hideLabel,
7511
+ disabled: !!c?.isDisabled,
7512
+ prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
7513
+ }));
7514
+ function useTrialField(form, field2) {
7515
+ const t = field2.type;
7516
+ const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
7517
+ const props = field2.element.properties ?? {};
7518
+ const value = field2.value && typeof field2.value === "object" ? field2.value : null;
7519
+ const conf = React13.useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
7520
+ const disabled = field2.disabled || form.readOnly;
7521
+ const set = (v) => form.setValue(field2.id, v);
7522
+ const [active, setActive] = React13.useState();
7523
+ const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
7524
+ const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7525
+ const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
7526
+ const linkedTimer = () => {
7527
+ const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
7528
+ return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
7529
+ };
7530
+ const timeLimit = Number(conf.timeLimit ?? 300);
7531
+ const interval = Number(conf.timeInterval ?? 30) || 30;
7532
+ const [timer, setTimer] = React13.useState(Number(value?.lastStatus?.timer ?? 0));
7533
+ const [running, setRunning] = React13.useState(false);
7534
+ const timerRef = React13.useRef(timer);
7535
+ timerRef.current = timer;
7123
7536
  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 });
7537
+ if (!running) return;
7538
+ const id = setInterval(() => setTimer((p) => {
7539
+ const n = p + 1;
7540
+ timerRef.current = n;
7541
+ if (n >= timeLimit) setRunning(false);
7542
+ return n;
7543
+ }), 1e3);
7544
+ return () => clearInterval(id);
7545
+ }, [running, timeLimit]);
7546
+ const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7547
+ const bound = (s) => {
7548
+ const i = Math.floor(s / interval);
7549
+ return `${i * interval}-${(i + 1) * interval}`;
7550
+ };
7551
+ const tsEmit = (resp, at) => set({
7552
+ conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
7553
+ buttonResp: resp,
7554
+ lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
7555
+ });
7556
+ const steps = React13.useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
7557
+ const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
7558
+ const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
7559
+ const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
7560
+ const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
7561
+ const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
7562
+ const stepName = steps[taStep] ?? "";
7563
+ const taEmit = (resp, trial, step) => set({
7564
+ conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
7565
+ buttonResp: resp,
7566
+ currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
7567
+ });
7568
+ const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
7569
+ const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
7570
+ const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
7571
+ const buttons = React13.useMemo(() => toButtons(rb), [rb]);
7572
+ const record = React13.useCallback((response, promptLabel) => {
7573
+ if (disabled) return;
7574
+ if (type === "percent") {
7575
+ if (pcTrial >= maxTrials) return;
7576
+ const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
7577
+ const next = pcTrial + 1;
7578
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
7579
+ } else if (type === "time") {
7580
+ const at = timerRef.current;
7581
+ tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
7582
+ } else {
7583
+ if (taTrial >= maxTrials || !steps.length) return;
7584
+ const key = `Trial ${taTrial}`;
7585
+ const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
7586
+ let step = taStep, trial = taTrial;
7587
+ if (chain === "backward") {
7588
+ step -= 1;
7589
+ if (step < 0) {
7590
+ step = steps.length - 1;
7591
+ trial += 1;
7592
+ }
7593
+ } else {
7594
+ step += 1;
7595
+ if (step >= steps.length) {
7596
+ step = 0;
7597
+ trial += 1;
7598
+ }
7136
7599
  }
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");
7151
- 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;
7183
- });
7184
- });
7185
- return out;
7186
- }, [rows, columns, form.submitAttempted]);
7600
+ taEmit(resp, trial, step);
7601
+ }
7602
+ setActive(void 0);
7603
+ }, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
7604
+ const respond = React13.useCallback((label) => {
7605
+ const b = buttons.find((x) => x.label === label);
7606
+ if (!b || b.disabled) return;
7607
+ if (b.prompts.length) setActive(label);
7608
+ else record(label);
7609
+ }, [buttons, record]);
7610
+ const prompt = React13.useCallback((label) => {
7611
+ if (active) record(active, label);
7612
+ }, [active, record]);
7613
+ const prev = React13.useCallback(() => {
7614
+ if (disabled) return;
7615
+ if (type === "percent" && pcTrial > 0) {
7616
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
7617
+ } else if (type === "task" && taTrial > 0) {
7618
+ const resp = { ...taResp };
7619
+ delete resp[`Trial ${taTrial}`];
7620
+ taEmit(resp, taTrial - 1, firstStep);
7621
+ }
7622
+ }, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
7623
+ const reset = React13.useCallback(() => {
7624
+ if (type !== "time" || disabled) return;
7625
+ setTimer(0);
7626
+ setRunning(false);
7627
+ setActive(void 0);
7628
+ tsEmit([], 0);
7629
+ }, [type, disabled, value, conf]);
7630
+ 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 }));
7631
+ const log = allResp.map(({ r, trial }, i) => ({
7632
+ label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
7633
+ correct: isCorrect(r.responseButtonPressed),
7634
+ detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
7635
+ }));
7636
+ const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
7187
7637
  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)
7638
+ type,
7639
+ buttons,
7640
+ activeResponse: active,
7641
+ prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
7642
+ respond,
7643
+ prompt,
7644
+ cancelPrompt: () => setActive(void 0),
7645
+ prev,
7646
+ currentTrial,
7647
+ maxTrials,
7648
+ complete,
7649
+ percentCorrect,
7650
+ log,
7651
+ disabled,
7652
+ timer,
7653
+ formattedTimer: fmt(timer),
7654
+ running,
7655
+ start: () => {
7656
+ if (!disabled && timer < timeLimit) setRunning(true);
7657
+ },
7658
+ pause: () => setRunning(false),
7659
+ reset,
7660
+ interval,
7661
+ intervalCount: Math.floor(timer / interval),
7662
+ timeLimit,
7663
+ steps,
7664
+ currentStep: taStep,
7665
+ stepName,
7666
+ chain
7202
7667
  };
7203
7668
  }
7204
7669
  var FormKitSlot = React13__default.default.createContext(null);
@@ -7342,6 +7807,33 @@ var DefaultSheet = ({ field: field2, form }) => {
7342
7807
  s.limitMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: s.limitMessage })
7343
7808
  ] });
7344
7809
  };
7810
+ var DefaultTrials = ({ field: field2, form }) => {
7811
+ const t = useTrialField(form, field2);
7812
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7813
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7814
+ t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
7815
+ t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
7816
+ " \xB7 ",
7817
+ t.percentCorrect,
7818
+ "% correct",
7819
+ t.complete ? " \xB7 complete" : ""
7820
+ ] }),
7821
+ t.type === "time" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
7822
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
7823
+ " ",
7824
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
7825
+ ] }),
7826
+ t.type !== "time" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
7827
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7828
+ (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)),
7829
+ t.activeResponse && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
7830
+ ] }),
7831
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
7832
+ l.label,
7833
+ l.detail ? ` ${l.detail}` : ""
7834
+ ] }, i)) })
7835
+ ] });
7836
+ };
7345
7837
  var DefaultField = ({ field: field2, form }) => {
7346
7838
  const f = field2;
7347
7839
  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 +7862,20 @@ var DefaultField = ({ field: field2, form }) => {
7370
7862
  case "display":
7371
7863
  return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
7372
7864
  case "action":
7865
+ if (f.variant === "dropdown") {
7866
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
7867
+ f.label,
7868
+ " ",
7869
+ /* @__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: [
7870
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
7871
+ f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
7872
+ ] })
7873
+ ] });
7874
+ }
7373
7875
  return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
7876
+ case "trials":
7877
+ control = /* @__PURE__ */ jsxRuntime.jsx(DefaultTrials, { field: f, form });
7878
+ break;
7374
7879
  case "checkbox":
7375
7880
  control = /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
7376
7881
  /* @__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 }),
@@ -7948,6 +8453,232 @@ function buildDashboardTree(elements, isAdmin = false) {
7948
8453
  });
7949
8454
  return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
7950
8455
  }
8456
+ var text = (v) => {
8457
+ if (v === null || v === void 0 || v === "") return "";
8458
+ if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
8459
+ if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
8460
+ return String(v);
8461
+ };
8462
+ 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;
8463
+ function expectedOf(dep, labelOf) {
8464
+ const conds = dep.conditions ?? dep.Conditions ?? [];
8465
+ if (conds.length) {
8466
+ const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
8467
+ return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
8468
+ }
8469
+ if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
8470
+ return `= ${text(dep.value) || "(empty)"}`;
8471
+ }
8472
+ function checksOf(el2, required) {
8473
+ const t = resolveFieldType(el2);
8474
+ const p = el2.properties ?? {};
8475
+ const out = [];
8476
+ if (required) out.push("required");
8477
+ if (t === "INPUT_EMAIL") out.push("email");
8478
+ if (t === "INPUT_URL") out.push("url");
8479
+ if (t === "INPUT_PHONE") out.push("phone");
8480
+ if (t === "INPUT_PERCENT") out.push("0\u2013100");
8481
+ const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
8482
+ if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
8483
+ if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
8484
+ const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
8485
+ if (len) out.push(`\u2264 ${len} chars`);
8486
+ if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
8487
+ (el2.validations ?? []).forEach((v) => {
8488
+ const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
8489
+ if (k === "regex") out.push("pattern");
8490
+ else if (k === "expression") out.push("expression");
8491
+ else if (k) out.push(`${k} (server)`);
8492
+ });
8493
+ return out;
8494
+ }
8495
+ var sameNumber = (a, b) => {
8496
+ if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
8497
+ const na = Number(a), nb = Number(b);
8498
+ return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
8499
+ };
8500
+ function formulaRows(elements, values, scope, extra = {}) {
8501
+ const labels = elements.map((e) => e.label).filter(Boolean);
8502
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
8503
+ return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
8504
+ const formula = String(e.properties.formula);
8505
+ const refs = formulaLabels(formula, labels);
8506
+ const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
8507
+ 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);
8508
+ const current = values[e.elementId] ?? null;
8509
+ return {
8510
+ fieldId: e.elementId,
8511
+ label: e.label,
8512
+ scope,
8513
+ sheetLabel: extra.sheetLabel,
8514
+ row: extra.row,
8515
+ formula,
8516
+ serverFormula: toBraceFormula(formula, labels),
8517
+ serverSafe: isServerSafeFormula(formula, labels),
8518
+ inputs,
8519
+ expected,
8520
+ current,
8521
+ inSync: sameNumber(expected, current)
8522
+ };
8523
+ });
8524
+ }
8525
+ function inspectForm(form) {
8526
+ const def = form.definition;
8527
+ if (!def) {
8528
+ return {
8529
+ rules: [],
8530
+ filters: [],
8531
+ validation: [],
8532
+ formulas: [],
8533
+ defaults: [],
8534
+ payload: null,
8535
+ summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
8536
+ };
8537
+ }
8538
+ const elements = def.elements;
8539
+ const values = form.values;
8540
+ const byId = new Map(elements.map((e) => [e.elementId, e]));
8541
+ const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
8542
+ const rules = [];
8543
+ elements.forEach((el2) => {
8544
+ const origin = el2.properties?.triage ? "triage" : "dependencies";
8545
+ rulesOf(el2).forEach((dep) => {
8546
+ const action = String(dep.dependencyType ?? dep.type ?? "");
8547
+ const effect = effectOf(action);
8548
+ const target = String(dep.elementId ?? dep.targetElementId ?? "");
8549
+ if (!effect || !target) return;
8550
+ rules.push({
8551
+ sourceId: el2.elementId,
8552
+ sourceLabel: el2.label,
8553
+ sourceValue: text(values[el2.elementId]) || "(empty)",
8554
+ action,
8555
+ effect,
8556
+ targetId: target,
8557
+ targetLabel: labelOf(target),
8558
+ expected: expectedOf(dep, labelOf),
8559
+ matched: ruleMatches(el2, dep, values),
8560
+ origin
8561
+ });
8562
+ });
8563
+ });
8564
+ const filters2 = [];
8565
+ elements.forEach((el2) => {
8566
+ const cfg = lookupConfigOf(el2);
8567
+ if (!cfg) return;
8568
+ const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
8569
+ optionFilterRules(elements, el2.elementId).forEach((r) => {
8570
+ const v = text(values[r.elementId]);
8571
+ filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
8572
+ });
8573
+ cfg.dynamicFilters.forEach((df) => {
8574
+ const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
8575
+ const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
8576
+ if (!dep) return;
8577
+ const src = elements.find((e) => e.label === dep);
8578
+ const v = src ? text(values[src.elementId]) : "";
8579
+ filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
8580
+ });
8581
+ cfg.staticFilters.forEach((sf) => {
8582
+ const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
8583
+ filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
8584
+ });
8585
+ });
8586
+ const validation = [];
8587
+ form.fields.forEach((f) => {
8588
+ const t = resolveFieldType(f.element);
8589
+ if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
8590
+ validation.push({
8591
+ id: f.id,
8592
+ label: f.label,
8593
+ type: t,
8594
+ required: f.required,
8595
+ hidden: f.hidden,
8596
+ checks: checksOf(f.element, f.required),
8597
+ value: formatFieldValue(f.element, f.value),
8598
+ error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
8599
+ shownError: f.error
8600
+ });
8601
+ });
8602
+ const formulas = formulaRows(elements, values, "form");
8603
+ const sheets = [];
8604
+ elements.forEach((el2) => {
8605
+ const t = resolveFieldType(el2);
8606
+ if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
8607
+ const child = form.sheetDefinition(el2.elementId);
8608
+ if (!child) return;
8609
+ const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
8610
+ const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
8611
+ rows.forEach((row, i) => {
8612
+ const { _submissionId, _dirty, _rowKey, ...cells } = row;
8613
+ const scope = `${el2.label} \xB7 row ${i + 1}`;
8614
+ if (sheetRowNeedsCheck(row)) {
8615
+ child.elements.forEach((c) => {
8616
+ if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
8617
+ validation.push({
8618
+ id: `${el2.elementId}:${i}:${c.elementId}`,
8619
+ label: c.label,
8620
+ type: resolveFieldType(c),
8621
+ scope,
8622
+ required: !!c.required,
8623
+ hidden: false,
8624
+ checks: checksOf(c, !!c.required),
8625
+ value: formatFieldValue(c, cells[c.elementId]),
8626
+ error: validateField(c, cells[c.elementId])
8627
+ });
8628
+ });
8629
+ }
8630
+ const fresh = computeRowFormulas(child.elements, cells);
8631
+ formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
8632
+ sheet.rows.push(buildSubmissionElements(child.elements, fresh));
8633
+ });
8634
+ sheets.push(sheet);
8635
+ });
8636
+ const defaults = [];
8637
+ elements.forEach((el2) => {
8638
+ const dd = el2.attributes?.dynamicDefault;
8639
+ const ds = el2.properties?.dataSource ?? {};
8640
+ let source = "";
8641
+ if (dd && typeof dd === "object") {
8642
+ const u = dd.units ?? {};
8643
+ const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
8644
+ source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
8645
+ } else if (typeof dd === "string" && dd) source = dd;
8646
+ else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
8647
+ else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
8648
+ else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
8649
+ if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
8650
+ });
8651
+ const submitEls = submitElementsOf(elements);
8652
+ const sheetIds = new Set(sheets.map((s) => s.fieldId));
8653
+ const shown = { ...values };
8654
+ sheets.forEach((s) => {
8655
+ shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
8656
+ });
8657
+ const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
8658
+ const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
8659
+ const errors = validation.filter((v) => !v.hidden && v.error).length;
8660
+ const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
8661
+ return {
8662
+ rules,
8663
+ filters: filters2,
8664
+ validation,
8665
+ formulas,
8666
+ defaults,
8667
+ payload,
8668
+ summary: {
8669
+ rules: rules.length,
8670
+ matched: rules.filter((r) => r.matched).length,
8671
+ filters: filters2.length,
8672
+ activeFilters: filters2.filter((f) => f.active).length,
8673
+ errors,
8674
+ formulas: formulas.length,
8675
+ formulaIssues
8676
+ }
8677
+ };
8678
+ }
8679
+ function useFormInspector(form) {
8680
+ return React13.useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
8681
+ }
7951
8682
 
7952
8683
  // src/forms/authoring.ts
7953
8684
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
@@ -8062,6 +8793,7 @@ function formWidget(o) {
8062
8793
  exports.ActionCardWidget = ActionCardWidget;
8063
8794
  exports.ActivityFeedWidget = ActivityFeedWidget;
8064
8795
  exports.AreaChartWidget = AreaChartWidget;
8796
+ exports.BLOCKED_FILE_EXTENSIONS = BLOCKED_FILE_EXTENSIONS;
8065
8797
  exports.BannerWidget = BannerWidget;
8066
8798
  exports.BarChartWidget = BarChartWidget;
8067
8799
  exports.COLUMN_FILTER_BLANK = COLUMN_FILTER_BLANK;
@@ -8151,6 +8883,7 @@ exports.childIdsOf = childIdsOf;
8151
8883
  exports.cleanTitle = cleanTitle;
8152
8884
  exports.clearLookupCache = clearLookupCache;
8153
8885
  exports.coerceSeriesNumbers = coerceSeriesNumbers;
8886
+ exports.computeRowFormulas = computeRowFormulas;
8154
8887
  exports.createMockRuntime = createMockRuntime;
8155
8888
  exports.createSubmission = createSubmission;
8156
8889
  exports.createZvolvRuntime = createZvolvRuntime;
@@ -8166,6 +8899,7 @@ exports.distinctColumnValues = distinctColumnValues;
8166
8899
  exports.eid = eid;
8167
8900
  exports.el = el;
8168
8901
  exports.evaluateFormula = evaluateFormula;
8902
+ exports.extractAgentFormData = extractAgentFormData;
8169
8903
  exports.fetchForm = fetchForm;
8170
8904
  exports.fetchLookupOptions = fetchLookupOptions;
8171
8905
  exports.fetchSubmission = fetchSubmission;
@@ -8188,16 +8922,21 @@ exports.gridHeight = gridHeight;
8188
8922
  exports.gridWidgets = gridWidgets;
8189
8923
  exports.groupBy = groupBy;
8190
8924
  exports.hasActiveInteractions = hasActiveInteractions;
8925
+ exports.inspectForm = inspectForm;
8191
8926
  exports.interpolate = interpolate;
8927
+ exports.invokeAgent = invokeAgent;
8192
8928
  exports.invokeTrigger = invokeTrigger;
8929
+ exports.invokeZeno = invokeZeno;
8193
8930
  exports.isDataSourceConfigured = isDataSourceConfigured;
8194
8931
  exports.isLegacyId = isLegacyId;
8195
8932
  exports.isLikelyTimestamp = isLikelyTimestamp;
8933
+ exports.isServerSafeFormula = isServerSafeFormula;
8196
8934
  exports.lookupConfigOf = lookupConfigOf;
8197
8935
  exports.mergeTokens = mergeTokens;
8198
8936
  exports.normaliseElement = normaliseElement;
8199
8937
  exports.normaliseForm = normaliseForm;
8200
8938
  exports.normalizeWidget = normalizeWidget;
8939
+ exports.openFormRequest = openFormRequest;
8201
8940
  exports.operatorsForKind = operatorsForKind;
8202
8941
  exports.optionFilterRules = optionFilterRules;
8203
8942
  exports.parseConfig = parseConfig;
@@ -8221,13 +8960,16 @@ exports.resolveWidgetDefaults = resolveWidgetDefaults;
8221
8960
  exports.resolveXKey = resolveXKey;
8222
8961
  exports.rowSubmissionIds = rowSubmissionIds;
8223
8962
  exports.ruleMatches = ruleMatches;
8963
+ exports.rulesOf = rulesOf;
8224
8964
  exports.searchPeople = searchPeople;
8225
8965
  exports.serializeElementValue = serializeElementValue;
8226
8966
  exports.statusTone = statusTone;
8227
8967
  exports.submissionIdOf = submissionIdOf;
8228
8968
  exports.submissionToValues = submissionToValues;
8969
+ exports.submitElementsOf = submitElementsOf;
8229
8970
  exports.taskField = taskField;
8230
8971
  exports.taskSource = taskSource;
8972
+ exports.toBraceFormula = toBraceFormula;
8231
8973
  exports.toNum = toNum;
8232
8974
  exports.toRelational = toRelational;
8233
8975
  exports.toneColor = toneColor;
@@ -8238,6 +8980,7 @@ exports.useCollectionItems = useCollectionItems;
8238
8980
  exports.useFieldOptions = useFieldOptions;
8239
8981
  exports.useFileField = useFileField;
8240
8982
  exports.useFormApi = useFormApi;
8983
+ exports.useFormInspector = useFormInspector;
8241
8984
  exports.useInteractiveWidgetData = useInteractiveWidgetData;
8242
8985
  exports.useKpiContents = useKpiContents;
8243
8986
  exports.useSheetField = useSheetField;
@@ -8245,6 +8988,7 @@ exports.useTableColumns = useTableColumns;
8245
8988
  exports.useTemplateOverride = useTemplateOverride;
8246
8989
  exports.useTemporalEvents = useTemporalEvents;
8247
8990
  exports.useTokens = useTokens;
8991
+ exports.useTrialField = useTrialField;
8248
8992
  exports.useWidgetData = useWidgetData;
8249
8993
  exports.useWidgetInteractions = useWidgetInteractions;
8250
8994
  exports.useZvolvClient = useZvolvClient;