@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.js CHANGED
@@ -36,9 +36,47 @@ function getRuntimeClient() {
36
36
  if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
37
37
  return c;
38
38
  }
39
+ var sdk = {};
40
+ try {
41
+ import('@zestl/zvolv-js-sdk').then((m) => {
42
+ sdk = m ?? {};
43
+ }).catch(() => {
44
+ });
45
+ } catch {
46
+ }
47
+ var AUTH_CRYPT_READY = "zvAuthCryptReady";
48
+ var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
49
+ var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
50
+ function stripUndecrypted(node) {
51
+ if (Array.isArray(node)) return node.map(stripUndecrypted);
52
+ if (node && typeof node === "object") {
53
+ const out = {};
54
+ for (const [k, v] of Object.entries(node)) {
55
+ if (v && typeof v === "object") out[k] = stripUndecrypted(v);
56
+ else if (!isEnc(v)) out[k] = v;
57
+ }
58
+ return out;
59
+ }
60
+ return isEnc(node) ? void 0 : node;
61
+ }
39
62
  function getStoredUser() {
40
63
  try {
41
- return userReader() ?? clientGetter()?.auth?.userInstance ?? null;
64
+ let u = userReader() ?? null;
65
+ if (!u) {
66
+ try {
67
+ u = sdk.readUserFromStorage?.() ?? null;
68
+ } catch {
69
+ u = null;
70
+ }
71
+ }
72
+ if (!u) u = clientGetter()?.auth?.userInstance ?? null;
73
+ if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
74
+ try {
75
+ u = sdk.decryptAuthObject(u);
76
+ } catch {
77
+ }
78
+ }
79
+ return u ? stripUndecrypted(u) : null;
42
80
  } catch {
43
81
  return null;
44
82
  }
@@ -124,60 +162,60 @@ function getDataType(elementType) {
124
162
  }
125
163
  function getDateRange(ops) {
126
164
  const values = [];
127
- const fmt = "YYYY-MM-DD";
165
+ const fmt2 = "YYYY-MM-DD";
128
166
  const today = dayjs8();
129
167
  switch (ops) {
130
168
  case "TODAY":
131
- values[0] = today.format(fmt);
132
- values[1] = today.format(fmt);
169
+ values[0] = today.format(fmt2);
170
+ values[1] = today.format(fmt2);
133
171
  break;
134
172
  case "LAST_SEVEN_DAYS":
135
- values[0] = today.subtract(7, "day").format(fmt);
136
- values[1] = today.add(1, "day").format(fmt);
173
+ values[0] = today.subtract(7, "day").format(fmt2);
174
+ values[1] = today.add(1, "day").format(fmt2);
137
175
  break;
138
176
  case "LAST_THIRTY_DAYS":
139
- values[0] = today.subtract(30, "day").format(fmt);
140
- values[1] = today.add(1, "day").format(fmt);
177
+ values[0] = today.subtract(30, "day").format(fmt2);
178
+ values[1] = today.add(1, "day").format(fmt2);
141
179
  break;
142
180
  case "THIS_WEEK":
143
- values[0] = today.startOf("week").format(fmt);
144
- values[1] = today.endOf("week").format(fmt);
181
+ values[0] = today.startOf("week").format(fmt2);
182
+ values[1] = today.endOf("week").format(fmt2);
145
183
  break;
146
184
  case "THIS_MONTH":
147
- values[0] = today.startOf("month").format(fmt);
148
- values[1] = today.endOf("month").format(fmt);
185
+ values[0] = today.startOf("month").format(fmt2);
186
+ values[1] = today.endOf("month").format(fmt2);
149
187
  break;
150
188
  case "THIS_YEAR":
151
- values[0] = today.startOf("year").format(fmt);
152
- values[1] = today.endOf("year").format(fmt);
189
+ values[0] = today.startOf("year").format(fmt2);
190
+ values[1] = today.endOf("year").format(fmt2);
153
191
  break;
154
192
  case "THIS_FINANCIAL_YEAR": {
155
193
  const m = today.month();
156
194
  const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
157
195
  const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
158
- values[0] = fiscalStart.format(fmt);
159
- values[1] = fiscalEnd.format(fmt);
196
+ values[0] = fiscalStart.format(fmt2);
197
+ values[1] = fiscalEnd.format(fmt2);
160
198
  break;
161
199
  }
162
200
  case "NEXT_THREE_DAYS":
163
- values[0] = today.format(fmt);
164
- values[1] = today.add(4, "day").format(fmt);
201
+ values[0] = today.format(fmt2);
202
+ values[1] = today.add(4, "day").format(fmt2);
165
203
  break;
166
204
  case "NEXT_SEVEN_DAYS":
167
- values[0] = today.format(fmt);
168
- values[1] = today.add(8, "day").format(fmt);
205
+ values[0] = today.format(fmt2);
206
+ values[1] = today.add(8, "day").format(fmt2);
169
207
  break;
170
208
  case "NEXT_WEEK":
171
- values[0] = today.add(1, "week").startOf("week").format(fmt);
172
- values[1] = today.add(1, "week").endOf("week").format(fmt);
209
+ values[0] = today.add(1, "week").startOf("week").format(fmt2);
210
+ values[1] = today.add(1, "week").endOf("week").format(fmt2);
173
211
  break;
174
212
  case "NEXT_FIFTEEN_DAYS":
175
- values[0] = today.format(fmt);
176
- values[1] = today.add(16, "day").format(fmt);
213
+ values[0] = today.format(fmt2);
214
+ values[1] = today.add(16, "day").format(fmt2);
177
215
  break;
178
216
  case "NEXT_THIRTY_DAYS":
179
- values[0] = today.format(fmt);
180
- values[1] = today.add(30, "day").format(fmt);
217
+ values[0] = today.format(fmt2);
218
+ values[1] = today.add(30, "day").format(fmt2);
181
219
  break;
182
220
  case "THIS_QUARTER": {
183
221
  const m = today.month();
@@ -195,8 +233,8 @@ function getDateRange(ops) {
195
233
  qStart = today.startOf("year").month(9).date(1);
196
234
  qEnd = today.startOf("year").month(11).date(31);
197
235
  }
198
- values[0] = qStart.format(fmt);
199
- values[1] = qEnd.format(fmt);
236
+ values[0] = qStart.format(fmt2);
237
+ values[1] = qEnd.format(fmt2);
200
238
  break;
201
239
  }
202
240
  }
@@ -443,56 +481,56 @@ function isDateMath(value) {
443
481
  return typeof value === "string" && dateMathPattern.test(value.trim());
444
482
  }
445
483
  function getDate(dateFormula) {
446
- const fmt = "YYYY-MM-DDTHH:mm:ssZ";
484
+ const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
447
485
  const d = dayjs8();
448
486
  switch (dateFormula) {
449
487
  case "{@[TODAY]}":
450
- return d.startOf("day").format(fmt);
488
+ return d.startOf("day").format(fmt2);
451
489
  case "{@[3DAYSLATER]}":
452
- return d.add(3, "day").startOf("day").format(fmt);
490
+ return d.add(3, "day").startOf("day").format(fmt2);
453
491
  case "{@[7DAYSLATER]}":
454
- return d.add(7, "day").startOf("day").format(fmt);
492
+ return d.add(7, "day").startOf("day").format(fmt2);
455
493
  case "{@[14DAYSLATER]}":
456
- return d.add(14, "day").startOf("day").format(fmt);
494
+ return d.add(14, "day").startOf("day").format(fmt2);
457
495
  case "{@[15DAYSLATER]}":
458
- return d.add(15, "day").startOf("day").format(fmt);
496
+ return d.add(15, "day").startOf("day").format(fmt2);
459
497
  case "{@[21DAYSLATER]}":
460
- return d.add(21, "day").startOf("day").format(fmt);
498
+ return d.add(21, "day").startOf("day").format(fmt2);
461
499
  case "{@[28DAYSLATER]}":
462
- return d.add(28, "day").startOf("day").format(fmt);
500
+ return d.add(28, "day").startOf("day").format(fmt2);
463
501
  case "{@[30DAYSLATER]}":
464
- return d.add(30, "day").startOf("day").format(fmt);
502
+ return d.add(30, "day").startOf("day").format(fmt2);
465
503
  case "{@[60DAYSLATER]}":
466
- return d.add(60, "day").startOf("day").format(fmt);
504
+ return d.add(60, "day").startOf("day").format(fmt2);
467
505
  case "{@[90DAYSLATER]}":
468
- return d.add(90, "day").startOf("day").format(fmt);
506
+ return d.add(90, "day").startOf("day").format(fmt2);
469
507
  case "{@[180DAYSLATER]}":
470
- return d.add(180, "day").startOf("day").format(fmt);
508
+ return d.add(180, "day").startOf("day").format(fmt2);
471
509
  case "{@[365DAYSLATER]}":
472
- return d.add(365, "day").startOf("day").format(fmt);
510
+ return d.add(365, "day").startOf("day").format(fmt2);
473
511
  case "{@[3DAYSAGO]}":
474
- return d.subtract(3, "day").startOf("day").format(fmt);
512
+ return d.subtract(3, "day").startOf("day").format(fmt2);
475
513
  case "{@[7DAYSAGO]}":
476
- return d.subtract(7, "day").startOf("day").format(fmt);
514
+ return d.subtract(7, "day").startOf("day").format(fmt2);
477
515
  case "{@[14DAYSAGO]}":
478
- return d.subtract(14, "day").startOf("day").format(fmt);
516
+ return d.subtract(14, "day").startOf("day").format(fmt2);
479
517
  case "{@[15DAYSAGO]}":
480
- return d.subtract(15, "day").startOf("day").format(fmt);
518
+ return d.subtract(15, "day").startOf("day").format(fmt2);
481
519
  case "{@[21DAYSAGO]}":
482
- return d.subtract(21, "day").startOf("day").format(fmt);
520
+ return d.subtract(21, "day").startOf("day").format(fmt2);
483
521
  case "{@[28DAYSAGO]}":
484
- return d.subtract(28, "day").startOf("day").format(fmt);
522
+ return d.subtract(28, "day").startOf("day").format(fmt2);
485
523
  case "{@[30DAYSAGO]}":
486
- return d.subtract(20, "day").startOf("day").format(fmt);
524
+ return d.subtract(20, "day").startOf("day").format(fmt2);
487
525
  // note: original uses 20
488
526
  case "{@[60DAYSAGO]}":
489
- return d.subtract(60, "day").startOf("day").format(fmt);
527
+ return d.subtract(60, "day").startOf("day").format(fmt2);
490
528
  case "{@[90DAYSAGO]}":
491
- return d.subtract(90, "day").startOf("day").format(fmt);
529
+ return d.subtract(90, "day").startOf("day").format(fmt2);
492
530
  case "{@[180DAYSAGO]}":
493
- return d.subtract(180, "day").startOf("day").format(fmt);
531
+ return d.subtract(180, "day").startOf("day").format(fmt2);
494
532
  case "{@[365DAYSAGO]}":
495
- return d.subtract(365, "day").startOf("day").format(fmt);
533
+ return d.subtract(365, "day").startOf("day").format(fmt2);
496
534
  default:
497
535
  return dateFormula;
498
536
  }
@@ -572,8 +610,8 @@ function getFormula(formula, fMap, isFilter = false) {
572
610
  }
573
611
  }
574
612
  });
575
- formula.format.split("%s").forEach((text, i) => {
576
- textFormat += i === 0 ? `'${text}'` : ` + '${text}'`;
613
+ formula.format.split("%s").forEach((text2, i) => {
614
+ textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
577
615
  if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
578
616
  });
579
617
  script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
@@ -1655,7 +1693,7 @@ var resolveUserLabels = async (values, kind) => {
1655
1693
  // src/runtime/index.ts
1656
1694
  function createZvolvRuntime({ client, readUser }) {
1657
1695
  const getClient = typeof client === "function" ? client : () => client;
1658
- setRuntimeClient(getClient, readUser ?? (() => getClient()?.auth?.userInstance));
1696
+ setRuntimeClient(getClient, readUser ?? (() => null));
1659
1697
  resetQueryUser();
1660
1698
  return {
1661
1699
  get client() {
@@ -1898,19 +1936,19 @@ function resolveDateToken(token, now = dayjs8()) {
1898
1936
  }
1899
1937
  }
1900
1938
  var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
1901
- function searchAsDate(text) {
1902
- const t = text.trim();
1939
+ function searchAsDate(text2) {
1940
+ const t = text2.trim();
1903
1941
  if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
1904
- for (const fmt of SEARCH_DATE_FORMATS) {
1905
- const [y, m, d] = parseStrict(t, fmt);
1942
+ for (const fmt2 of SEARCH_DATE_FORMATS) {
1943
+ const [y, m, d] = parseStrict(t, fmt2);
1906
1944
  if (y) return `${y}-${m}-${d}`;
1907
1945
  }
1908
1946
  return null;
1909
1947
  }
1910
- function parseStrict(text, fmt) {
1911
- const sep = fmt.includes("/") ? "/" : "-";
1912
- const parts = text.split(sep);
1913
- const order = fmt.split(sep);
1948
+ function parseStrict(text2, fmt2) {
1949
+ const sep = fmt2.includes("/") ? "/" : "-";
1950
+ const parts = text2.split(sep);
1951
+ const order = fmt2.split(sep);
1914
1952
  if (parts.length !== 3 || order.length !== 3) return [null, null, null];
1915
1953
  const map = {};
1916
1954
  order.forEach((k, i) => {
@@ -1924,10 +1962,10 @@ function parseStrict(text, fmt) {
1924
1962
  return [y, mm, dd];
1925
1963
  }
1926
1964
  function buildSearchClause(search) {
1927
- const text = (search ?? "").trim();
1928
- if (!text) return null;
1929
- const iso = searchAsDate(text);
1930
- return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text } };
1965
+ const text2 = (search ?? "").trim();
1966
+ if (!text2) return null;
1967
+ const iso = searchAsDate(text2);
1968
+ return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
1931
1969
  }
1932
1970
  function buildWidgetFilterClauses(dataSource, filters2 = []) {
1933
1971
  const isTask = resolveIsTask2(dataSource);
@@ -2338,11 +2376,11 @@ var Delta = ({ vm }) => {
2338
2376
  if (!vm.delta) return null;
2339
2377
  const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
2340
2378
  const sign = vm.delta.value >= 0 ? "+" : "";
2341
- const text = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2379
+ const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
2342
2380
  return /* @__PURE__ */ jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
2343
2381
  vm.delta.isPositive ? "\u25B2" : "\u25BC",
2344
2382
  " ",
2345
- text
2383
+ text2
2346
2384
  ] });
2347
2385
  };
2348
2386
  var Sparkline = ({
@@ -4378,8 +4416,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
4378
4416
  if (column.kind === "number") {
4379
4417
  return /* @__PURE__ */ jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
4380
4418
  }
4381
- const text = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4382
- return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text) ?? text).replace(/<[^>]*>/g, "") });
4419
+ const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
4420
+ return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
4383
4421
  };
4384
4422
  var DefaultTable = ({
4385
4423
  rows,
@@ -4748,16 +4786,16 @@ function renderHtmlVariables(template, rows, variables) {
4748
4786
  const d = dayjs8(value);
4749
4787
  if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
4750
4788
  }
4751
- let text;
4789
+ let text2;
4752
4790
  if (Array.isArray(value)) {
4753
- text = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
4791
+ text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
4754
4792
  } else if (value && typeof value === "object") {
4755
- text = String(value.view ?? value.title ?? value.name ?? "");
4793
+ text2 = String(value.view ?? value.title ?? value.name ?? "");
4756
4794
  } else {
4757
4795
  const n = typeof value === "number" ? value : Number(value);
4758
- text = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
4796
+ text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
4759
4797
  }
4760
- out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text);
4798
+ out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
4761
4799
  }
4762
4800
  return out;
4763
4801
  }
@@ -5230,6 +5268,7 @@ function resolveFieldType(el2) {
5230
5268
  if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
5231
5269
  if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
5232
5270
  if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
5271
+ if (sub && sub !== t && sub in KIND) return sub;
5233
5272
  if (t === "SCAN") return "INPUT_SCAN";
5234
5273
  if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
5235
5274
  if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
@@ -5292,7 +5331,11 @@ var KIND = {
5292
5331
  COMMENTS: "comments",
5293
5332
  ACTION_BUTTON: "action",
5294
5333
  INPUT_TRIGGER: "action",
5295
- CASCADED_ACTIONS: "action"
5334
+ CASCADED_ACTIONS: "action",
5335
+ // ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
5336
+ INPUT_TIME_SAMPLING: "trials",
5337
+ INPUT_PERCENT_CORRECT: "trials",
5338
+ INPUT_TASK_ANALYSIS: "trials"
5296
5339
  };
5297
5340
  function fieldKindOf(el2) {
5298
5341
  const t = resolveFieldType(el2);
@@ -5541,6 +5584,9 @@ function formulaScalar(v) {
5541
5584
  if (typeof v === "number" || typeof v === "boolean") return v;
5542
5585
  return String(v);
5543
5586
  }
5587
+ function toBraceFormula(formula, labels) {
5588
+ return braceLegacyLabels(formula, labels);
5589
+ }
5544
5590
  function braceLegacyLabels(formula, labels) {
5545
5591
  if (formula.includes("{")) return formula;
5546
5592
  const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
@@ -5576,6 +5622,31 @@ function formulaLabels(formula, labels = []) {
5576
5622
  const src = braceLegacyLabels(String(formula ?? ""), labels);
5577
5623
  return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
5578
5624
  }
5625
+ function isServerSafeFormula(formula, labels = []) {
5626
+ const f = String(formula ?? "");
5627
+ if (!f.trim()) return true;
5628
+ return braceLegacyLabels(f, labels) === f;
5629
+ }
5630
+ function computeRowFormulas(elements, row) {
5631
+ const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
5632
+ const cols = elements.filter((e) => formulaOf(e));
5633
+ if (!cols.length) return row;
5634
+ const labels = elements.map((e) => e.label).filter(Boolean);
5635
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
5636
+ const out = { ...row };
5637
+ for (let pass = 0; pass < 2; pass++) {
5638
+ cols.forEach((c) => {
5639
+ const refs = formulaLabels(formulaOf(c), labels);
5640
+ const empty = (v) => v === null || v === void 0 || v === "";
5641
+ if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
5642
+ out[c.elementId] = null;
5643
+ return;
5644
+ }
5645
+ out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
5646
+ });
5647
+ }
5648
+ return out;
5649
+ }
5579
5650
 
5580
5651
  // src/forms/validate.ts
5581
5652
  var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
@@ -5625,6 +5696,11 @@ function validateField(el2, value, required = el2.required) {
5625
5696
  const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
5626
5697
  if (required && blank) return `${name} is required`;
5627
5698
  if (blank) return void 0;
5699
+ if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
5700
+ const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
5701
+ const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
5702
+ if (min > 0 && n < min) return `Trials are less than ${min}`;
5703
+ }
5628
5704
  if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
5629
5705
  if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
5630
5706
  if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
@@ -5735,6 +5811,9 @@ function getEmptyValue(type) {
5735
5811
  case "SIGNATURE":
5736
5812
  case "TABLE_START":
5737
5813
  case "MATRIX":
5814
+ case "INPUT_TIME_SAMPLING":
5815
+ case "INPUT_PERCENT_CORRECT":
5816
+ case "INPUT_TASK_ANALYSIS":
5738
5817
  return null;
5739
5818
  default:
5740
5819
  return "";
@@ -5776,7 +5855,7 @@ var DATE_UNITS = {
5776
5855
  };
5777
5856
  function currentUserId(user) {
5778
5857
  const id = user?.userId ?? user?.UserID ?? user?.id;
5779
- return id != null && id !== "" ? String(id) : void 0;
5858
+ return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
5780
5859
  }
5781
5860
  function dynamicDefault(el2, user) {
5782
5861
  const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
@@ -6163,6 +6242,67 @@ async function invokeTrigger(client, triggerId, body) {
6163
6242
  }
6164
6243
  return res?.data ?? res;
6165
6244
  }
6245
+ async function invokeZeno(client, automationUuid, body) {
6246
+ const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
6247
+ const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
6248
+ if (typeof payload === "string") {
6249
+ try {
6250
+ return JSON.parse(payload);
6251
+ } catch {
6252
+ return payload;
6253
+ }
6254
+ }
6255
+ return payload;
6256
+ }
6257
+ async function invokeAgent(client, body, flowId, agentId) {
6258
+ const wf = wfOf(client);
6259
+ if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
6260
+ const headers = { ...await wf.getHeaders(true) };
6261
+ const post = async (url, data2) => {
6262
+ try {
6263
+ const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
6264
+ const d = res?.data;
6265
+ return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
6266
+ } catch (e) {
6267
+ const r = e?.response;
6268
+ 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);
6269
+ throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
6270
+ }
6271
+ };
6272
+ const agent = encodeURIComponent(String(agentId ?? ""));
6273
+ let data = null;
6274
+ if (flowId) {
6275
+ data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
6276
+ const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
6277
+ if (!unknownFlow && data?.error !== true) return data;
6278
+ if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6279
+ }
6280
+ const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
6281
+ data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
6282
+ if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
6283
+ return data;
6284
+ }
6285
+ var parseMaybeJson = (v) => {
6286
+ if (typeof v !== "string") return v;
6287
+ const t = v.trim();
6288
+ if (!t.startsWith("{") && !t.startsWith("[")) return v;
6289
+ try {
6290
+ return JSON.parse(t);
6291
+ } catch {
6292
+ return v;
6293
+ }
6294
+ };
6295
+ function extractAgentFormData(reply) {
6296
+ let node = parseMaybeJson(reply);
6297
+ for (let d = 0; d < 5; d++) {
6298
+ if (!node || typeof node !== "object" || Array.isArray(node)) return null;
6299
+ const fd = parseMaybeJson(node.formData);
6300
+ if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
6301
+ if (!("response" in node)) return Object.keys(node).length ? node : null;
6302
+ node = parseMaybeJson(node.response);
6303
+ }
6304
+ return null;
6305
+ }
6166
6306
  function formDataOf(result) {
6167
6307
  let node = result;
6168
6308
  for (let d = 0; d < 4 && node; d++) {
@@ -6339,168 +6479,527 @@ async function searchPeople(client, el2, query) {
6339
6479
  const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
6340
6480
  return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
6341
6481
  }
6342
-
6343
- // src/forms/useZvolvForm.ts
6344
- var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
6345
- if (o == null) return null;
6346
- if (typeof o !== "object") return { label: String(o), value: String(o) };
6347
- const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
6348
- const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
6349
- return { label: String(label ?? ""), value, color: o.color };
6350
- }).filter(Boolean);
6351
- var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
6352
- function toDayjsFormat(fmt, withTime) {
6353
- const f = typeof fmt === "string" && fmt.trim() ? fmt.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
6354
- return f.replace(/yyyy/g, "YYYY").replace(/yy/g, "YY").replace(/dd/g, "DD").replace(/(^|[^D])d(?!d)/g, "$1D").replace(/EEE+/g, "ddd").replace(/a/g, "A");
6355
- }
6356
- function acceptOf(el2) {
6357
- const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
6358
- if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6359
- const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
6360
- if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6361
- return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
6362
- }
6363
- var FormApiContext = React13.createContext(null);
6364
- function useFormApi() {
6365
- const f = React13.useContext(FormApiContext);
6366
- if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
6367
- return f;
6368
- }
6369
- function useZvolvForm(args) {
6370
- const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
6371
- const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
6372
- const rt = useZvolvRuntime();
6373
- const client = rt.client;
6374
- const argsRef = useRef(args);
6375
- argsRef.current = args;
6376
- const [definition, setDefinition] = useState(null);
6377
- const [status, setStatus] = useState("loading");
6482
+ var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6483
+ var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6484
+ function useFieldOptions(form, field2, opts = {}) {
6485
+ const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6486
+ const el2 = field2.element;
6487
+ const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6488
+ const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6489
+ const elements = form.definition?.elements ?? [];
6490
+ const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6491
+ const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6492
+ const depKey = JSON.stringify(dep.filters);
6493
+ const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6494
+ const single = maxCount === 1;
6495
+ const [search, setSearch] = useState("");
6496
+ const [q, setQ] = useState("");
6497
+ useEffect(() => {
6498
+ const t = setTimeout(() => setQ(search), debounceMs);
6499
+ return () => clearTimeout(t);
6500
+ }, [search, debounceMs]);
6501
+ const [remote, setRemote] = useState([]);
6502
+ const [loading, setLoading] = useState(false);
6378
6503
  const [error, setError] = useState(null);
6379
- const [values, setValues] = useState({});
6380
- const [serverErrors, setServerErrors] = useState({});
6381
- const [touched, setTouched] = useState(/* @__PURE__ */ new Set());
6382
- const [submitAttempted, setSubmitAttempted] = useState(false);
6383
- const [flagged, setFlagged] = useState(/* @__PURE__ */ new Set());
6384
- const [submitting, setSubmitting] = useState(false);
6385
- const [submitError, setSubmitError] = useState(null);
6386
- const [submittedId, setSubmittedId] = useState(null);
6387
- const [dirty, setDirty] = useState(false);
6388
- const [busy, setBusy] = useState(/* @__PURE__ */ new Set());
6389
- const initialRef = useRef({});
6390
- const valuesRef = useRef({});
6391
- valuesRef.current = values;
6392
- const sheetDefs = useRef(/* @__PURE__ */ new Map());
6393
- const user = useMemo(() => {
6394
- try {
6395
- return client?.auth?.userInstance ?? null;
6396
- } catch {
6397
- return null;
6398
- }
6399
- }, [client]);
6400
- const onBehalfName = useCallback((v) => {
6401
- if (v == null || v === "") return "";
6402
- if (String(v) === "3122") return "Super Admin";
6403
- if (user && String(v) === currentUserId(user)) return String(user.name ?? user.userName ?? user.fullName ?? user.email ?? v);
6404
- return String(v);
6405
- }, [user]);
6406
- const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef]);
6504
+ const [opened, setOpened] = useState(loadOn === "mount");
6505
+ const [nonce, setNonce] = useState(0);
6506
+ const lastFetch = useRef(0);
6507
+ const maxAge = useRef(1e4);
6508
+ const waitingFor = useMemo(() => {
6509
+ if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6510
+ const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6511
+ const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6512
+ return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6513
+ }, [cfg, dep.satisfied, rules, form]);
6514
+ const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6515
+ const active = opened || depDriven || q !== "";
6407
6516
  useEffect(() => {
6517
+ if (!cfg && !people) return;
6518
+ if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6519
+ setRemote([]);
6520
+ return;
6521
+ }
6522
+ if (!active) return;
6408
6523
  let alive = true;
6409
- setStatus("loading");
6524
+ setLoading(true);
6410
6525
  setError(null);
6411
- setSubmittedId(null);
6412
- setSubmitAttempted(false);
6413
- (async () => {
6414
- const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
6415
- let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
6416
- let submission;
6417
- if (submissionId && mode !== "NEW") {
6418
- const s = await fetchSubmission(client, submissionId);
6419
- submission = s.values;
6420
- if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
6421
- }
6422
- if (!def) {
6423
- if (!formId) throw new Error("No form configured");
6424
- def = await fetchForm(client, formId);
6425
- }
6426
- const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
6427
- if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
6428
- const info = await clientInfo(client);
6429
- def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
6430
- if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
6431
- });
6432
- }
6433
- def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
6434
- if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
6435
- });
6526
+ lastFetch.current = Date.now();
6527
+ const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6528
+ maxAge.current = 1e4;
6529
+ p.then((o) => {
6436
6530
  if (!alive) return;
6437
- initialRef.current = init;
6438
- setDefinition(def);
6439
- setValues(init);
6440
- setTouched(/* @__PURE__ */ new Set());
6441
- setFlagged(/* @__PURE__ */ new Set());
6442
- setServerErrors({});
6443
- setDirty(false);
6444
- setStatus("ready");
6445
- })().catch((e) => {
6446
- if (alive) {
6447
- setError(e instanceof Error ? e : new Error(String(e)));
6448
- setStatus("error");
6449
- }
6531
+ setRemote(o);
6532
+ if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6533
+ }).catch((e) => {
6534
+ if (alive) setError(e?.message ?? "Couldn't load options");
6535
+ }).finally(() => {
6536
+ if (alive) setLoading(false);
6450
6537
  });
6451
6538
  return () => {
6452
6539
  alive = false;
6453
6540
  };
6454
- }, [loadKey, client]);
6455
- const elements = definition?.elements ?? [];
6456
- const byId = useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
6457
- const labels = useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
6458
- const overrides = useMemo(() => applyRules(elements, values), [elements, values]);
6459
- const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
6460
- const hiddenSet = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
6461
- const skipSet = useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
6462
- const hiddenByParent = useMemo(() => {
6463
- const out = /* @__PURE__ */ new Set();
6464
- const visit = (el2) => childIdsOf(el2).forEach((id) => {
6465
- if (out.has(id)) return;
6466
- out.add(id);
6467
- const c = byId.get(id);
6468
- if (c) visit(c);
6469
- });
6470
- elements.forEach((el2) => {
6471
- if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
6472
- const o = overrides.get(el2.elementId) ?? {};
6473
- if (o.isHidden ?? el2.hidden) visit(el2);
6474
- });
6475
- return out;
6476
- }, [elements, overrides, byId]);
6477
- const stateOf = useCallback((el2) => {
6478
- const o = overrides.get(el2.elementId) ?? {};
6479
- return {
6480
- hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
6481
- disabled: readOnly || (o.isDisabled ?? el2.disabled),
6482
- required: o.isRequired ?? el2.required
6483
- };
6484
- }, [overrides, readOnly, hiddenByParent]);
6541
+ }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6542
+ const refresh = useCallback(() => {
6543
+ maxAge.current = 0;
6544
+ setOpened(true);
6545
+ setNonce((n) => n + 1);
6546
+ }, []);
6547
+ const onFocus = useCallback(() => {
6548
+ if (!cfg && !people) return;
6549
+ if (!opened) {
6550
+ setOpened(true);
6551
+ return;
6552
+ }
6553
+ if (cfg && Date.now() - lastFetch.current > 2e3) {
6554
+ maxAge.current = 2e3;
6555
+ setNonce((n) => n + 1);
6556
+ } else if (people && !remote.length) setNonce((n) => n + 1);
6557
+ }, [cfg, people, opened, remote.length]);
6558
+ const prevDepKey = useRef(null);
6485
6559
  useEffect(() => {
6486
- if (status !== "ready") return;
6487
- const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
6488
- if (!formulas.length) return;
6489
- const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
6490
- const next = {};
6491
- const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
6492
- formulas.forEach((el2) => {
6493
- const refs = formulaLabels(el2.properties.formula, labels);
6494
- if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
6495
- if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
6496
- return;
6497
- }
6498
- const r = evaluateFormula(el2.properties.formula, (label) => {
6499
- const id = byLabel.get(label) ?? label;
6500
- return id in next ? next[id] : values[id];
6501
- }, labels);
6502
- const v = r === null ? null : r;
6503
- if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
6560
+ if (!cfg || !rules.length) return;
6561
+ if (prevDepKey.current === null) {
6562
+ prevDepKey.current = depKey;
6563
+ return;
6564
+ }
6565
+ if (prevDepKey.current === depKey) return;
6566
+ prevDepKey.current = depKey;
6567
+ if (form.readOnly) return;
6568
+ if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
6569
+ }, [depKey]);
6570
+ const staticFiltered = useMemo(() => {
6571
+ if (cfg || people) return remote;
6572
+ const needle = search.trim().toLowerCase();
6573
+ return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
6574
+ }, [cfg, people, remote, field2.options, search]);
6575
+ const pickedCache = useRef(/* @__PURE__ */ new Map());
6576
+ const current = field2.value;
6577
+ const selected = useMemo(() => {
6578
+ const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
6579
+ const pool = [...staticFiltered, ...field2.options];
6580
+ return arr.map((v) => {
6581
+ const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
6582
+ if (hit) return hit;
6583
+ if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
6584
+ return { label: String(v), value: v };
6585
+ });
6586
+ }, [current, staticFiltered, field2.options]);
6587
+ const store = useCallback((picked) => {
6588
+ picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
6589
+ const t = field2.type;
6590
+ if (field2.kind === "lookup") {
6591
+ const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
6592
+ form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
6593
+ if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
6594
+ const fill2 = {};
6595
+ cfg.extraLabels.forEach((label, i) => {
6596
+ const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
6597
+ const v = picked[0].extra?.[i];
6598
+ if (target && v !== void 0) fill2[target.elementId] = v;
6599
+ });
6600
+ if (Object.keys(fill2).length) form.applyValues(fill2);
6601
+ }
6602
+ return;
6603
+ }
6604
+ if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
6605
+ else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
6606
+ }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
6607
+ const autoPickRef = useRef(() => {
6608
+ });
6609
+ const lastAutoKey = useRef(null);
6610
+ autoPickRef.current = (o, key) => {
6611
+ if (form.readOnly || lastAutoKey.current === key) return;
6612
+ lastAutoKey.current = key;
6613
+ if (isFilled(form.values[field2.id])) return;
6614
+ store([o]);
6615
+ };
6616
+ const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
6617
+ const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
6618
+ const atLimit = field2.multiple && selected.length >= maxCount;
6619
+ const toggle = useCallback((o) => {
6620
+ const on = isSelected(o);
6621
+ if (!on && field2.multiple && selected.length >= maxCount) return;
6622
+ store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
6623
+ }, [isSelected, selected, store, field2.multiple, maxCount]);
6624
+ const addFreeTag = useCallback((text2) => {
6625
+ const t = text2.trim();
6626
+ if (!t || field2.type !== "INPUT_TAG") return;
6627
+ const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
6628
+ if (selected.some((s) => optionKey(s.value) === t)) return;
6629
+ if (field2.multiple) {
6630
+ if (selected.length < maxCount) store([...selected, o]);
6631
+ } else store([o]);
6632
+ setSearch("");
6633
+ }, [field2.type, field2.multiple, selected, maxCount, store]);
6634
+ return {
6635
+ options: staticFiltered,
6636
+ loading,
6637
+ error,
6638
+ search,
6639
+ setSearch,
6640
+ waitingFor,
6641
+ selected,
6642
+ select,
6643
+ toggle,
6644
+ isSelected,
6645
+ onFocus,
6646
+ refresh,
6647
+ maxCount,
6648
+ atLimit,
6649
+ addFreeTag
6650
+ };
6651
+ }
6652
+ 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(" "));
6653
+ function useFileField(form, field2, opts = {}) {
6654
+ const stored = Array.isArray(field2.value) ? field2.value : [];
6655
+ const [pending2, setPending] = useState([]);
6656
+ const [reject, setReject] = useState(null);
6657
+ const storedRef = useRef(stored);
6658
+ storedRef.current = stored;
6659
+ const items = [
6660
+ ...stored.map((r, i) => ({
6661
+ key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
6662
+ name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
6663
+ size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
6664
+ type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
6665
+ url: r.media ?? r.url,
6666
+ uploading: false,
6667
+ record: r
6668
+ })),
6669
+ ...pending2
6670
+ ];
6671
+ const max = field2.maxCount;
6672
+ const add = useCallback((list) => {
6673
+ setReject(null);
6674
+ const files = Array.from(list);
6675
+ const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
6676
+ if (!room) {
6677
+ setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
6678
+ return;
6679
+ }
6680
+ files.slice(0, room).forEach((file) => {
6681
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
6682
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
6683
+ setReject(`Cannot upload files with the extension .${ext}`);
6684
+ return;
6685
+ }
6686
+ if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
6687
+ setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
6688
+ return;
6689
+ }
6690
+ const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
6691
+ 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)) {
6692
+ setReject(`${file.name} isn't an allowed file type`);
6693
+ return;
6694
+ }
6695
+ const key = `p${Date.now()}${Math.random()}`;
6696
+ setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
6697
+ uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
6698
+ setPending((p) => p.filter((x) => x.key !== key));
6699
+ const next = [...storedRef.current, rec];
6700
+ storedRef.current = next;
6701
+ form.setValue(field2.id, next);
6702
+ }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
6703
+ });
6704
+ if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
6705
+ }, [form, field2, max, pending2, opts.zappId]);
6706
+ const remove = useCallback((key) => {
6707
+ if (key.startsWith("p")) {
6708
+ setPending((p) => p.filter((x) => x.key !== key));
6709
+ return;
6710
+ }
6711
+ const idx = items.findIndex((i) => i.key === key);
6712
+ if (idx < 0) return;
6713
+ const next = storedRef.current.filter((_2, i) => i !== idx);
6714
+ form.setValue(field2.id, next.length ? next : []);
6715
+ }, [items, form, field2.id]);
6716
+ return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
6717
+ }
6718
+ function sheetAllows(p, action) {
6719
+ const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
6720
+ const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
6721
+ if (!Array.isArray(list) || !list.length) return true;
6722
+ return list.map((x) => String(x).toUpperCase()).includes(action);
6723
+ }
6724
+ function useSheetField(form, field2) {
6725
+ const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
6726
+ const [child, setChild] = useState(null);
6727
+ const [loading, setLoading] = useState(!!childId);
6728
+ const [error, setError] = useState(childId ? null : "No child form configured");
6729
+ useEffect(() => {
6730
+ if (!childId) return;
6731
+ let alive = true;
6732
+ setLoading(true);
6733
+ fetchForm(form.client, childId).then(async (def) => {
6734
+ if (!alive) return;
6735
+ setChild(def);
6736
+ form.registerSheet(field2.id, def);
6737
+ const raw2 = Array.isArray(field2.value) ? field2.value : [];
6738
+ const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
6739
+ if (ids.length) {
6740
+ const rows2 = await fetchRows(form.client, childId, ids);
6741
+ if (alive) form.applyValues({ [field2.id]: rows2 });
6742
+ }
6743
+ }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
6744
+ return () => {
6745
+ alive = false;
6746
+ };
6747
+ }, [childId, form.client]);
6748
+ const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
6749
+ id: e.elementId,
6750
+ label: e.label,
6751
+ type: resolveFieldType(e),
6752
+ required: e.required,
6753
+ element: e,
6754
+ 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 })
6755
+ })), [child]);
6756
+ const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
6757
+ const p = field2.element.properties ?? {};
6758
+ const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
6759
+ const [limitMessage, setLimitMessage] = useState(null);
6760
+ const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
6761
+ const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
6762
+ const addRow = useCallback((vals) => {
6763
+ if (!addAllowed) return;
6764
+ if (maxRows && rows.length >= maxRows) {
6765
+ setLimitMessage(`Maximum number of rows reached (${maxRows})`);
6766
+ return;
6767
+ }
6768
+ setLimitMessage(null);
6769
+ const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
6770
+ form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
6771
+ }, [child, rows, form, field2.id, addAllowed, maxRows]);
6772
+ const updateRow = useCallback((i, col, v) => {
6773
+ const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
6774
+ form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
6775
+ }, [rows, form, field2.id, child]);
6776
+ const removeRow = useCallback((i) => {
6777
+ if (!canDelete) return;
6778
+ setLimitMessage(null);
6779
+ form.setValue(field2.id, rows.filter((_2, j) => j !== i));
6780
+ }, [rows, form, field2.id, canDelete]);
6781
+ const rowErrors = useMemo(() => {
6782
+ const out = {};
6783
+ if (!form.submitAttempted) return out;
6784
+ rows.forEach((r, i) => {
6785
+ if (!sheetRowNeedsCheck(r)) return;
6786
+ columns.forEach((c) => {
6787
+ if (!sheetCellChecked(c.element)) return;
6788
+ const e = validateField(c.element, r[c.id]);
6789
+ if (e) out[`${i}:${c.id}`] = e;
6790
+ });
6791
+ });
6792
+ return out;
6793
+ }, [rows, columns, form.submitAttempted]);
6794
+ return {
6795
+ loading,
6796
+ error,
6797
+ columns,
6798
+ rows,
6799
+ addRow,
6800
+ updateRow,
6801
+ removeRow,
6802
+ rowErrors,
6803
+ childForm: child,
6804
+ canAdd: addAllowed && (!maxRows || rows.length < maxRows),
6805
+ canDelete,
6806
+ maxRows,
6807
+ limitMessage,
6808
+ display: (c, v) => formatFieldValue(c.element, v)
6809
+ };
6810
+ }
6811
+
6812
+ // src/forms/useZvolvForm.ts
6813
+ function submitElementsOf(elements) {
6814
+ const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
6815
+ return elements.filter((el2) => {
6816
+ const t = resolveFieldType(el2);
6817
+ return !NON_INPUT_TYPES.has(t) || always.has(t);
6818
+ });
6819
+ }
6820
+ var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
6821
+ if (o == null) return null;
6822
+ if (typeof o !== "object") return { label: String(o), value: String(o) };
6823
+ const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
6824
+ const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
6825
+ return { label: String(label ?? ""), value, color: o.color };
6826
+ }).filter(Boolean);
6827
+ var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
6828
+ var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
6829
+ var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
6830
+ function toDayjsFormat(fmt2, withTime) {
6831
+ const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
6832
+ 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");
6833
+ }
6834
+ function acceptOf(el2) {
6835
+ const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
6836
+ if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6837
+ const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
6838
+ if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
6839
+ return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
6840
+ }
6841
+ var FormApiContext = React13.createContext(null);
6842
+ function useFormApi() {
6843
+ const f = React13.useContext(FormApiContext);
6844
+ if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
6845
+ return f;
6846
+ }
6847
+ function useZvolvForm(args) {
6848
+ const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
6849
+ const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
6850
+ const rt = useZvolvRuntime();
6851
+ const client = rt.client;
6852
+ const argsRef = useRef(args);
6853
+ argsRef.current = args;
6854
+ const [definition, setDefinition] = useState(null);
6855
+ const [status, setStatus] = useState("loading");
6856
+ const [error, setError] = useState(null);
6857
+ const [values, setValues] = useState({});
6858
+ const [serverErrors, setServerErrors] = useState({});
6859
+ const [touched, setTouched] = useState(/* @__PURE__ */ new Set());
6860
+ const [submitAttempted, setSubmitAttempted] = useState(false);
6861
+ const [flagged, setFlagged] = useState(/* @__PURE__ */ new Set());
6862
+ const [submitting, setSubmitting] = useState(false);
6863
+ const [submitError, setSubmitError] = useState(null);
6864
+ const [submittedId, setSubmittedId] = useState(null);
6865
+ const [dirty, setDirty] = useState(false);
6866
+ const [busy, setBusy] = useState(/* @__PURE__ */ new Set());
6867
+ const initialRef = useRef({});
6868
+ const valuesRef = useRef({});
6869
+ valuesRef.current = values;
6870
+ const sheetDefs = useRef(/* @__PURE__ */ new Map());
6871
+ const [userTick, setUserTick] = useState(0);
6872
+ useEffect(() => {
6873
+ if (typeof window === "undefined") return;
6874
+ const bump = () => setUserTick((n) => n + 1);
6875
+ window.addEventListener(AUTH_CRYPT_READY, bump);
6876
+ return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
6877
+ }, []);
6878
+ const user = useMemo(() => getStoredUser() ?? null, [client, userTick]);
6879
+ useEffect(() => {
6880
+ if (status !== "ready" || mode !== "NEW") return;
6881
+ const uid = currentUserId(user);
6882
+ if (!uid) return;
6883
+ const fill2 = {};
6884
+ (definition?.elements ?? []).forEach((el2) => {
6885
+ const v = valuesRef.current[el2.elementId];
6886
+ if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
6887
+ });
6888
+ if (Object.keys(fill2).length) setValues((p) => {
6889
+ const m = { ...p, ...fill2 };
6890
+ valuesRef.current = m;
6891
+ return m;
6892
+ });
6893
+ }, [user, status]);
6894
+ const onBehalfName = useCallback((v) => {
6895
+ if (v == null || v === "") return "";
6896
+ if (String(v) === "3122") return "Super Admin";
6897
+ if (isEncString(v)) return "";
6898
+ if (user && String(v) === currentUserId(user)) {
6899
+ const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
6900
+ return name ? String(name) : String(v);
6901
+ }
6902
+ return String(v);
6903
+ }, [user]);
6904
+ const [reloadTick, setReloadTick] = useState(0);
6905
+ const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
6906
+ useEffect(() => {
6907
+ let alive = true;
6908
+ setStatus("loading");
6909
+ setError(null);
6910
+ setSubmittedId(null);
6911
+ setSubmitAttempted(false);
6912
+ (async () => {
6913
+ const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
6914
+ let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
6915
+ let submission;
6916
+ if (submissionId && mode !== "NEW") {
6917
+ const s = await fetchSubmission(client, submissionId);
6918
+ submission = s.values;
6919
+ if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
6920
+ }
6921
+ if (!def) {
6922
+ if (!formId) throw new Error("No form configured");
6923
+ def = await fetchForm(client, formId);
6924
+ }
6925
+ const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
6926
+ if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
6927
+ const info = await clientInfo(client);
6928
+ def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
6929
+ if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
6930
+ });
6931
+ }
6932
+ def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
6933
+ if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
6934
+ });
6935
+ if (!alive) return;
6936
+ initialRef.current = init;
6937
+ setDefinition(def);
6938
+ setValues(init);
6939
+ setTouched(/* @__PURE__ */ new Set());
6940
+ setFlagged(/* @__PURE__ */ new Set());
6941
+ setServerErrors({});
6942
+ setDirty(false);
6943
+ setStatus("ready");
6944
+ })().catch((e) => {
6945
+ if (alive) {
6946
+ setError(e instanceof Error ? e : new Error(String(e)));
6947
+ setStatus("error");
6948
+ }
6949
+ });
6950
+ return () => {
6951
+ alive = false;
6952
+ };
6953
+ }, [loadKey, client]);
6954
+ const elements = definition?.elements ?? [];
6955
+ const byId = useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
6956
+ const labels = useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
6957
+ const overrides = useMemo(() => applyRules(elements, values), [elements, values]);
6958
+ const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
6959
+ const hiddenSet = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
6960
+ const skipSet = useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
6961
+ const hiddenByParent = useMemo(() => {
6962
+ const out = /* @__PURE__ */ new Set();
6963
+ const visit = (el2) => childIdsOf(el2).forEach((id) => {
6964
+ if (out.has(id)) return;
6965
+ out.add(id);
6966
+ const c = byId.get(id);
6967
+ if (c) visit(c);
6968
+ });
6969
+ elements.forEach((el2) => {
6970
+ if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
6971
+ const o = overrides.get(el2.elementId) ?? {};
6972
+ if (o.isHidden ?? el2.hidden) visit(el2);
6973
+ });
6974
+ return out;
6975
+ }, [elements, overrides, byId]);
6976
+ const stateOf = useCallback((el2) => {
6977
+ const o = overrides.get(el2.elementId) ?? {};
6978
+ return {
6979
+ hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
6980
+ disabled: readOnly || (o.isDisabled ?? el2.disabled),
6981
+ required: o.isRequired ?? el2.required
6982
+ };
6983
+ }, [overrides, readOnly, hiddenByParent]);
6984
+ useEffect(() => {
6985
+ if (status !== "ready") return;
6986
+ const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
6987
+ if (!formulas.length) return;
6988
+ const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
6989
+ const next = {};
6990
+ const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
6991
+ formulas.forEach((el2) => {
6992
+ const refs = formulaLabels(el2.properties.formula, labels);
6993
+ if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
6994
+ if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
6995
+ return;
6996
+ }
6997
+ const r = evaluateFormula(el2.properties.formula, (label) => {
6998
+ const id = byLabel.get(label) ?? label;
6999
+ return id in next ? next[id] : values[id];
7000
+ }, labels);
7001
+ const v = r === null ? null : r;
7002
+ if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
6504
7003
  });
6505
7004
  if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
6506
7005
  }, [values, elements, labels, status]);
@@ -6630,7 +7129,7 @@ function useZvolvForm(args) {
6630
7129
  value: values[el2.elementId],
6631
7130
  error: showErr ? errors[el2.elementId] : void 0,
6632
7131
  touched: touched.has(el2.elementId),
6633
- options: normalizeOptions(p.options),
7132
+ options: kind === "action" && isActionDropdown(el2) ? (p.actionEleIds ?? []).map((id) => byId.get(String(id))).filter(Boolean).map((o) => ({ label: o.label, value: o.label })) : normalizeOptions(p.options),
6634
7133
  multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
6635
7134
  allowAddOptions: !!p.allowAddOptions,
6636
7135
  min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
@@ -6645,7 +7144,7 @@ function useZvolvForm(args) {
6645
7144
  childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
6646
7145
  pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
6647
7146
  html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
6648
- variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
7147
+ variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
6649
7148
  displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
6650
7149
  dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
6651
7150
  maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
@@ -6704,11 +7203,12 @@ function useZvolvForm(args) {
6704
7203
  saved.push(row);
6705
7204
  continue;
6706
7205
  }
6707
- const { _submissionId, _dirty, _rowKey, ...cells } = row;
7206
+ const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
6708
7207
  if (_submissionId && !_dirty) {
6709
7208
  saved.push(row);
6710
7209
  continue;
6711
7210
  }
7211
+ const cells = computeRowFormulas(child.elements, raw2);
6712
7212
  const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
6713
7213
  saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
6714
7214
  }
@@ -6739,7 +7239,7 @@ function useZvolvForm(args) {
6739
7239
  }
6740
7240
  const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
6741
7241
  const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
6742
- const submitEls = elements.filter((el2) => !NON_INPUT_TYPES.has(resolveFieldType(el2)) || resolveFieldType(el2) === "INPUT_FORMULA" || resolveFieldType(el2) === "ON_BEHALF_OF" || resolveFieldType(el2) === "INPUT_IP" || resolveFieldType(el2) === "INPUT_DEVICE_INFO");
7242
+ const submitEls = submitElementsOf(elements);
6743
7243
  const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
6744
7244
  const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
6745
7245
  setValues(vals);
@@ -6766,429 +7266,394 @@ function useZvolvForm(args) {
6766
7266
  if (definition) setStatus("ready");
6767
7267
  }, [definition]);
6768
7268
  const apiRef = useRef(null);
6769
- const runAction = useCallback(async (field2) => {
7269
+ const runAction = useCallback(async (field2, choice) => {
6770
7270
  const p = field2.element.properties ?? {};
6771
7271
  const subType = String(field2.element.attributes?.subType ?? "");
6772
- const rawType = p.actionType ?? p.subType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "");
6773
- const actionType = String(rawType ?? "").toUpperCase();
7272
+ const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
7273
+ const actionType = String(rawType || p.subType || "").toUpperCase();
7274
+ const value = valuesRef.current[field2.id] ?? field2.value;
7275
+ const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
7276
+ const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
7277
+ const browser = typeof window !== "undefined" && typeof document !== "undefined";
7278
+ if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
7279
+ const pick2 = choice ?? (typeof value === "string" ? value : "");
7280
+ const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
7281
+ if (!target) return { ok: false, message: "No actions found to perform!" };
7282
+ setValue(field2.id, pick2);
7283
+ return runAction(target);
7284
+ }
6774
7285
  if (field2.type === "CASCADED_ACTIONS") {
6775
7286
  for (const id of p.actionEleIds ?? []) {
6776
7287
  const f = fieldMap2.get(String(id));
6777
7288
  if (f) {
6778
- const r2 = await runAction(f);
6779
- if (!r2.ok) return r2;
6780
- }
6781
- }
6782
- if (p.submit) await submit();
6783
- return { ok: true };
6784
- }
6785
- const handled = await argsRef.current.onAction?.(field2, apiRef.current);
6786
- if (handled) return handled;
6787
- const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
6788
- const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
6789
- if (isTrigger) {
6790
- if (!trig) return { ok: false, message: "No automation is linked to this button" };
6791
- return withBusy(field2.id, async () => {
6792
- try {
6793
- const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
6794
- const fd = formDataOf(res);
6795
- if (fd) applyValues(fd);
6796
- const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
6797
- if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
6798
- return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
6799
- } catch (e) {
6800
- return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
6801
- }
6802
- });
6803
- }
6804
- if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
6805
- const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
6806
- if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
6807
- return { ok: !!url, message: url ? void 0 : "No link configured" };
6808
- }
6809
- if (actionType === "SUBMIT" || p.SubmitButton) {
6810
- const r2 = await submit();
6811
- return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
6812
- }
6813
- const HOST = [
6814
- "SAVE_SUBMISSION",
6815
- "CREATE_SUBMISSION",
6816
- "EDIT_SUBMISSION",
6817
- "OPEN_TASK",
6818
- "PRINT_PREVIEW",
6819
- "DOWNLOAD_FILE",
6820
- "UPLOAD_FILE",
6821
- "OPEN_PROJECT",
6822
- "INVOKE_AGENT",
6823
- "AGENT",
6824
- "FLOW"
6825
- ];
6826
- if (HOST.includes(actionType)) {
6827
- return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
6828
- }
6829
- setValue(field2.id, true);
6830
- const r = await submit();
6831
- return { ok: !!r, message: r ? p.successMessage : void 0 };
6832
- }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
6833
- const registerSheet = useCallback((id, def) => {
6834
- sheetDefs.current.set(id, def);
6835
- }, []);
6836
- const api = {
6837
- status,
6838
- error,
6839
- definition,
6840
- mode,
6841
- readOnly,
6842
- values,
6843
- errors,
6844
- fields,
6845
- rootFields,
6846
- field: (id) => fieldMap2.get(id),
6847
- childrenOf,
6848
- setValue,
6849
- applyValues,
6850
- dirty,
6851
- submitting,
6852
- submitAttempted,
6853
- submitError,
6854
- submittedId,
6855
- validate,
6856
- submit,
6857
- reset,
6858
- runAction,
6859
- busy,
6860
- client,
6861
- registerSheet
6862
- };
6863
- apiRef.current = api;
6864
- return api;
6865
- }
6866
- var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
6867
- var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
6868
- function useFieldOptions(form, field2, opts = {}) {
6869
- const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
6870
- const el2 = field2.element;
6871
- const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
6872
- const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
6873
- const elements = form.definition?.elements ?? [];
6874
- const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
6875
- const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
6876
- const depKey = JSON.stringify(dep.filters);
6877
- const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
6878
- const single = maxCount === 1;
6879
- const [search, setSearch] = useState("");
6880
- const [q, setQ] = useState("");
6881
- useEffect(() => {
6882
- const t = setTimeout(() => setQ(search), debounceMs);
6883
- return () => clearTimeout(t);
6884
- }, [search, debounceMs]);
6885
- const [remote, setRemote] = useState([]);
6886
- const [loading, setLoading] = useState(false);
6887
- const [error, setError] = useState(null);
6888
- const [opened, setOpened] = useState(loadOn === "mount");
6889
- const [nonce, setNonce] = useState(0);
6890
- const lastFetch = useRef(0);
6891
- const maxAge = useRef(1e4);
6892
- const waitingFor = useMemo(() => {
6893
- if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
6894
- const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
6895
- const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
6896
- return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
6897
- }, [cfg, dep.satisfied, rules, form]);
6898
- const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
6899
- const active = opened || depDriven || q !== "";
6900
- useEffect(() => {
6901
- if (!cfg && !people) return;
6902
- if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
6903
- setRemote([]);
6904
- return;
6905
- }
6906
- if (!active) return;
6907
- let alive = true;
6908
- setLoading(true);
6909
- setError(null);
6910
- lastFetch.current = Date.now();
6911
- const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
6912
- maxAge.current = 1e4;
6913
- p.then((o) => {
6914
- if (!alive) return;
6915
- setRemote(o);
6916
- if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
6917
- }).catch((e) => {
6918
- if (alive) setError(e?.message ?? "Couldn't load options");
6919
- }).finally(() => {
6920
- if (alive) setLoading(false);
6921
- });
6922
- return () => {
6923
- alive = false;
6924
- };
6925
- }, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
6926
- const refresh = useCallback(() => {
6927
- maxAge.current = 0;
6928
- setOpened(true);
6929
- setNonce((n) => n + 1);
6930
- }, []);
6931
- const onFocus = useCallback(() => {
6932
- if (!cfg && !people) return;
6933
- if (!opened) {
6934
- setOpened(true);
6935
- return;
6936
- }
6937
- if (cfg && Date.now() - lastFetch.current > 2e3) {
6938
- maxAge.current = 2e3;
6939
- setNonce((n) => n + 1);
6940
- } else if (people && !remote.length) setNonce((n) => n + 1);
6941
- }, [cfg, people, opened, remote.length]);
6942
- const prevDepKey = useRef(null);
6943
- useEffect(() => {
6944
- if (!cfg || !rules.length) return;
6945
- if (prevDepKey.current === null) {
6946
- prevDepKey.current = depKey;
6947
- return;
6948
- }
6949
- if (prevDepKey.current === depKey) return;
6950
- prevDepKey.current = depKey;
6951
- if (form.readOnly) return;
6952
- if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
6953
- }, [depKey]);
6954
- const staticFiltered = useMemo(() => {
6955
- if (cfg || people) return remote;
6956
- const needle = search.trim().toLowerCase();
6957
- return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
6958
- }, [cfg, people, remote, field2.options, search]);
6959
- const pickedCache = useRef(/* @__PURE__ */ new Map());
6960
- const current = field2.value;
6961
- const selected = useMemo(() => {
6962
- const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
6963
- const pool = [...staticFiltered, ...field2.options];
6964
- return arr.map((v) => {
6965
- const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
6966
- if (hit) return hit;
6967
- if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
6968
- return { label: String(v), value: v };
6969
- });
6970
- }, [current, staticFiltered, field2.options]);
6971
- const store = useCallback((picked) => {
6972
- picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
6973
- const t = field2.type;
6974
- if (field2.kind === "lookup") {
6975
- const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
6976
- form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
6977
- if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
6978
- const fill2 = {};
6979
- cfg.extraLabels.forEach((label, i) => {
6980
- const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
6981
- const v = picked[0].extra?.[i];
6982
- if (target && v !== void 0) fill2[target.elementId] = v;
6983
- });
6984
- if (Object.keys(fill2).length) form.applyValues(fill2);
6985
- }
6986
- return;
6987
- }
6988
- if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
6989
- else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
6990
- }, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
6991
- const autoPickRef = useRef(() => {
6992
- });
6993
- const lastAutoKey = useRef(null);
6994
- autoPickRef.current = (o, key) => {
6995
- if (form.readOnly || lastAutoKey.current === key) return;
6996
- lastAutoKey.current = key;
6997
- if (isFilled(form.values[field2.id])) return;
6998
- store([o]);
6999
- };
7000
- const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
7001
- const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
7002
- const atLimit = field2.multiple && selected.length >= maxCount;
7003
- const toggle = useCallback((o) => {
7004
- const on = isSelected(o);
7005
- if (!on && field2.multiple && selected.length >= maxCount) return;
7006
- store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
7007
- }, [isSelected, selected, store, field2.multiple, maxCount]);
7008
- const addFreeTag = useCallback((text) => {
7009
- const t = text.trim();
7010
- if (!t || field2.type !== "INPUT_TAG") return;
7011
- const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
7012
- if (selected.some((s) => optionKey(s.value) === t)) return;
7013
- if (field2.multiple) {
7014
- if (selected.length < maxCount) store([...selected, o]);
7015
- } else store([o]);
7016
- setSearch("");
7017
- }, [field2.type, field2.multiple, selected, maxCount, store]);
7018
- return {
7019
- options: staticFiltered,
7020
- loading,
7021
- error,
7022
- search,
7023
- setSearch,
7024
- waitingFor,
7025
- selected,
7026
- select,
7027
- toggle,
7028
- isSelected,
7029
- onFocus,
7030
- refresh,
7031
- maxCount,
7032
- atLimit,
7033
- addFreeTag
7034
- };
7035
- }
7036
- var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
7037
- function useFileField(form, field2, opts = {}) {
7038
- const stored = Array.isArray(field2.value) ? field2.value : [];
7039
- const [pending2, setPending] = useState([]);
7040
- const [reject, setReject] = useState(null);
7041
- const storedRef = useRef(stored);
7042
- storedRef.current = stored;
7043
- const items = [
7044
- ...stored.map((r, i) => ({
7045
- key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
7046
- name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
7047
- size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
7048
- type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
7049
- url: r.media ?? r.url,
7050
- uploading: false,
7051
- record: r
7052
- })),
7053
- ...pending2
7054
- ];
7055
- const max = field2.maxCount;
7056
- const add = useCallback((list) => {
7057
- setReject(null);
7058
- const files = Array.from(list);
7059
- const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
7060
- if (!room) {
7061
- setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
7062
- return;
7289
+ const r = await runAction(f);
7290
+ if (!r.ok) return r;
7291
+ }
7292
+ }
7293
+ if (p.submit) await submit();
7294
+ return { ok: true };
7063
7295
  }
7064
- files.slice(0, room).forEach((file) => {
7065
- const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7066
- if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
7067
- setReject(`Cannot upload files with the extension .${ext}`);
7068
- return;
7296
+ const handled = await argsRef.current.onAction?.(field2, apiRef.current);
7297
+ if (handled) return handled;
7298
+ const afterAutomation = (res) => {
7299
+ const fd = formDataOf(res);
7300
+ if (fd) applyValues(fd);
7301
+ if (p.refreshForm && (submissionId || submittedId)) {
7302
+ const delay = Number(p.refreshFormDelay) || 0;
7303
+ setTimeout(() => setReloadTick((n) => n + 1), delay);
7069
7304
  }
7070
- if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
7071
- setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
7072
- return;
7305
+ };
7306
+ const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
7307
+ const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
7308
+ if (isTrigger) {
7309
+ const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
7310
+ if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
7311
+ if (!zeno && !trig) return fail("Automation ID missing from action");
7312
+ return withBusy(field2.id, async () => {
7313
+ try {
7314
+ const body = triggerBody(valuesRef.current);
7315
+ const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
7316
+ const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
7317
+ if (failed) {
7318
+ const fd = formDataOf(res);
7319
+ if (fd) applyValues(fd);
7320
+ return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
7321
+ }
7322
+ afterAutomation(res);
7323
+ return ok(res?.comment?.message ?? res?.message);
7324
+ } catch (e) {
7325
+ return fail(e?.message ?? `${field2.label} failed`);
7326
+ }
7327
+ });
7328
+ }
7329
+ switch (actionType) {
7330
+ case "":
7331
+ case "OPEN_URL":
7332
+ case "URL": {
7333
+ const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
7334
+ if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
7335
+ return url ? { ok: true } : fail("No link configured");
7073
7336
  }
7074
- const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
7075
- if (accept?.length && !accept.some((a) => a.endsWith("/*") ? file.type.startsWith(a.slice(0, -1)) : a.startsWith(".") ? file.name.toLowerCase().endsWith(a) : file.type === a)) {
7076
- setReject(`${file.name} isn't an allowed file type`);
7077
- return;
7337
+ case "SUBMIT": {
7338
+ const r = await submit();
7339
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7340
+ }
7341
+ case "INVOKE_AGENT": {
7342
+ if (!p.agentId) return fail("Agent ID missing from action");
7343
+ return withBusy(field2.id, async () => {
7344
+ try {
7345
+ const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
7346
+ const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
7347
+ if (!res) return fail(`${field2.label} failed`);
7348
+ const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
7349
+ if (fd) applyValues(fd);
7350
+ return p.successMessage ? ok() : { ok: true };
7351
+ } catch (e) {
7352
+ return fail(e?.message ?? `${field2.label} failed`);
7353
+ }
7354
+ });
7355
+ }
7356
+ case "DOWNLOAD_FILE": {
7357
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7358
+ if (!file) return fail("File URL missing from action");
7359
+ if (browser) {
7360
+ const a = document.createElement("a");
7361
+ a.href = String(file);
7362
+ a.download = p.fileName ?? "";
7363
+ a.target = "_blank";
7364
+ a.rel = "noopener";
7365
+ a.click();
7366
+ }
7367
+ return { ok: true };
7368
+ }
7369
+ case "PRINT_PREVIEW": {
7370
+ const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
7371
+ if (!file) return fail("File URL missing from action");
7372
+ if (!browser) return fail("Printing needs a browser");
7373
+ return withBusy(field2.id, async () => {
7374
+ try {
7375
+ const blob = await (await fetch(String(file))).blob();
7376
+ const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
7377
+ const frame = document.createElement("iframe");
7378
+ frame.style.display = "none";
7379
+ frame.src = url;
7380
+ document.body.appendChild(frame);
7381
+ frame.onload = () => frame.contentWindow?.print();
7382
+ return { ok: true };
7383
+ } catch {
7384
+ return fail("Failed to load file for printing");
7385
+ }
7386
+ });
7387
+ }
7388
+ case "UPLOAD_FILE": {
7389
+ if (!browser) return fail("Uploading needs a browser");
7390
+ const file = await new Promise((resolve) => {
7391
+ const input = document.createElement("input");
7392
+ input.type = "file";
7393
+ input.onchange = () => resolve(input.files?.[0] ?? null);
7394
+ input.click();
7395
+ });
7396
+ if (!file) return fail("File upload failed");
7397
+ const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
7398
+ if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
7399
+ const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
7400
+ if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
7401
+ return withBusy(field2.id, async () => {
7402
+ try {
7403
+ const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
7404
+ setValue(field2.id, rec.media);
7405
+ return ok("File successfully uploaded");
7406
+ } catch (e) {
7407
+ return fail(e?.message ?? "File upload failed");
7408
+ }
7409
+ });
7410
+ }
7411
+ case "CREATE_SUBMISSION":
7412
+ case "EDIT_SUBMISSION":
7413
+ case "SAVE_SUBMISSION":
7414
+ case "OPEN_TASK":
7415
+ case "OPEN_PROJECT":
7416
+ case "AGENT":
7417
+ case "FLOW":
7418
+ return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
7419
+ default: {
7420
+ setValue(field2.id, true);
7421
+ const r = await submit();
7422
+ return { ok: !!r, message: r ? p.successMessage : void 0 };
7078
7423
  }
7079
- const key = `p${Date.now()}${Math.random()}`;
7080
- setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
7081
- uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
7082
- setPending((p) => p.filter((x) => x.key !== key));
7083
- const next = [...storedRef.current, rec];
7084
- storedRef.current = next;
7085
- form.setValue(field2.id, next);
7086
- }).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
7087
- });
7088
- if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
7089
- }, [form, field2, max, pending2, opts.zappId]);
7090
- const remove = useCallback((key) => {
7091
- if (key.startsWith("p")) {
7092
- setPending((p) => p.filter((x) => x.key !== key));
7093
- return;
7094
7424
  }
7095
- const idx = items.findIndex((i) => i.key === key);
7096
- if (idx < 0) return;
7097
- const next = storedRef.current.filter((_2, i) => i !== idx);
7098
- form.setValue(field2.id, next.length ? next : []);
7099
- }, [items, form, field2.id]);
7100
- return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
7425
+ }, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
7426
+ const reload = useCallback(() => setReloadTick((n) => n + 1), []);
7427
+ const [sheetVersion, setSheetVersion] = useState(0);
7428
+ const registerSheet = useCallback((id, def) => {
7429
+ if (sheetDefs.current.get(id) === def) return;
7430
+ sheetDefs.current.set(id, def);
7431
+ setSheetVersion((n) => n + 1);
7432
+ }, []);
7433
+ const sheetDefinition = useCallback((id) => sheetDefs.current.get(id), []);
7434
+ const api = {
7435
+ status,
7436
+ error,
7437
+ definition,
7438
+ mode,
7439
+ readOnly,
7440
+ values,
7441
+ errors,
7442
+ fields,
7443
+ rootFields,
7444
+ field: (id) => fieldMap2.get(id),
7445
+ childrenOf,
7446
+ setValue,
7447
+ applyValues,
7448
+ dirty,
7449
+ submitting,
7450
+ submitAttempted,
7451
+ submitError,
7452
+ submittedId,
7453
+ validate,
7454
+ submit,
7455
+ reset,
7456
+ runAction,
7457
+ reload,
7458
+ busy,
7459
+ client,
7460
+ registerSheet,
7461
+ sheetDefinition,
7462
+ sheetVersion
7463
+ };
7464
+ apiRef.current = api;
7465
+ return api;
7101
7466
  }
7102
- function sheetAllows(p, action) {
7103
- const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
7104
- const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
7105
- if (!Array.isArray(list) || !list.length) return true;
7106
- return list.map((x) => String(x).toUpperCase()).includes(action);
7467
+ function openFormRequest(field2, form) {
7468
+ const p = field2.element.properties ?? {};
7469
+ const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
7470
+ const value = form.values[field2.id] ?? field2.value;
7471
+ const has2 = value != null && value !== "" && value !== true;
7472
+ const formId = p.formId ?? p.formID;
7473
+ switch (t) {
7474
+ case "CREATE_SUBMISSION":
7475
+ return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
7476
+ case "EDIT_SUBMISSION":
7477
+ case "OPEN_TASK":
7478
+ return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
7479
+ case "OPEN_PROJECT":
7480
+ return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
7481
+ case "SAVE_SUBMISSION": {
7482
+ if (!formId) return null;
7483
+ const defaults = {};
7484
+ (Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
7485
+ if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
7486
+ });
7487
+ return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
7488
+ }
7489
+ default:
7490
+ return null;
7491
+ }
7107
7492
  }
7108
- function useSheetField(form, field2) {
7109
- const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
7110
- const [child, setChild] = useState(null);
7111
- const [loading, setLoading] = useState(!!childId);
7112
- const [error, setError] = useState(childId ? null : "No child form configured");
7493
+ var CORRECT = ["IND", "NR", "CHECK"];
7494
+ var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
7495
+ var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
7496
+ var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
7497
+ label,
7498
+ icon: c?.icon,
7499
+ color: c?.color,
7500
+ hideLabel: !!c?.hideLabel,
7501
+ disabled: !!c?.isDisabled,
7502
+ prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
7503
+ }));
7504
+ function useTrialField(form, field2) {
7505
+ const t = field2.type;
7506
+ const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
7507
+ const props = field2.element.properties ?? {};
7508
+ const value = field2.value && typeof field2.value === "object" ? field2.value : null;
7509
+ const conf = useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
7510
+ const disabled = field2.disabled || form.readOnly;
7511
+ const set = (v) => form.setValue(field2.id, v);
7512
+ const [active, setActive] = useState();
7513
+ const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
7514
+ const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7515
+ const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
7516
+ const linkedTimer = () => {
7517
+ const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
7518
+ return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
7519
+ };
7520
+ const timeLimit = Number(conf.timeLimit ?? 300);
7521
+ const interval = Number(conf.timeInterval ?? 30) || 30;
7522
+ const [timer, setTimer] = useState(Number(value?.lastStatus?.timer ?? 0));
7523
+ const [running, setRunning] = useState(false);
7524
+ const timerRef = useRef(timer);
7525
+ timerRef.current = timer;
7113
7526
  useEffect(() => {
7114
- if (!childId) return;
7115
- let alive = true;
7116
- setLoading(true);
7117
- fetchForm(form.client, childId).then(async (def) => {
7118
- if (!alive) return;
7119
- setChild(def);
7120
- form.registerSheet(field2.id, def);
7121
- const raw2 = Array.isArray(field2.value) ? field2.value : [];
7122
- const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
7123
- if (ids.length) {
7124
- const rows2 = await fetchRows(form.client, childId, ids);
7125
- if (alive) form.applyValues({ [field2.id]: rows2 });
7527
+ if (!running) return;
7528
+ const id = setInterval(() => setTimer((p) => {
7529
+ const n = p + 1;
7530
+ timerRef.current = n;
7531
+ if (n >= timeLimit) setRunning(false);
7532
+ return n;
7533
+ }), 1e3);
7534
+ return () => clearInterval(id);
7535
+ }, [running, timeLimit]);
7536
+ const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
7537
+ const bound = (s) => {
7538
+ const i = Math.floor(s / interval);
7539
+ return `${i * interval}-${(i + 1) * interval}`;
7540
+ };
7541
+ const tsEmit = (resp, at) => set({
7542
+ conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
7543
+ buttonResp: resp,
7544
+ lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
7545
+ });
7546
+ const steps = useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
7547
+ const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
7548
+ const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
7549
+ const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
7550
+ const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
7551
+ const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
7552
+ const stepName = steps[taStep] ?? "";
7553
+ const taEmit = (resp, trial, step) => set({
7554
+ conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
7555
+ buttonResp: resp,
7556
+ currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
7557
+ });
7558
+ const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
7559
+ const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
7560
+ const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
7561
+ const buttons = useMemo(() => toButtons(rb), [rb]);
7562
+ const record = useCallback((response, promptLabel) => {
7563
+ if (disabled) return;
7564
+ if (type === "percent") {
7565
+ if (pcTrial >= maxTrials) return;
7566
+ const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
7567
+ const next = pcTrial + 1;
7568
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
7569
+ } else if (type === "time") {
7570
+ const at = timerRef.current;
7571
+ tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
7572
+ } else {
7573
+ if (taTrial >= maxTrials || !steps.length) return;
7574
+ const key = `Trial ${taTrial}`;
7575
+ const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
7576
+ let step = taStep, trial = taTrial;
7577
+ if (chain === "backward") {
7578
+ step -= 1;
7579
+ if (step < 0) {
7580
+ step = steps.length - 1;
7581
+ trial += 1;
7582
+ }
7583
+ } else {
7584
+ step += 1;
7585
+ if (step >= steps.length) {
7586
+ step = 0;
7587
+ trial += 1;
7588
+ }
7126
7589
  }
7127
- }).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
7128
- return () => {
7129
- alive = false;
7130
- };
7131
- }, [childId, form.client]);
7132
- const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
7133
- id: e.elementId,
7134
- label: e.label,
7135
- type: resolveFieldType(e),
7136
- required: e.required,
7137
- element: e,
7138
- options: (Array.isArray(e.properties?.options) ? e.properties.options : []).map((o) => typeof o === "object" ? { label: String(o.view ?? o.label ?? o.select), value: o.select ?? o.value } : { label: String(o), value: o })
7139
- })), [child]);
7140
- const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
7141
- const p = field2.element.properties ?? {};
7142
- const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
7143
- const [limitMessage, setLimitMessage] = useState(null);
7144
- const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
7145
- const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
7146
- const addRow = useCallback((vals) => {
7147
- if (!addAllowed) return;
7148
- if (maxRows && rows.length >= maxRows) {
7149
- setLimitMessage(`Maximum number of rows reached (${maxRows})`);
7150
- return;
7151
- }
7152
- setLimitMessage(null);
7153
- const blank = child ? buildInitialValues(child.elements, { defaults: vals }) : { ...vals ?? {} };
7154
- form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
7155
- }, [child, rows, form, field2.id, addAllowed, maxRows]);
7156
- const updateRow = useCallback((i, col, v) => {
7157
- form.setValue(field2.id, rows.map((r, j) => j === i ? { ...r, [col]: v, _dirty: true } : r));
7158
- }, [rows, form, field2.id]);
7159
- const removeRow = useCallback((i) => {
7160
- if (!canDelete) return;
7161
- setLimitMessage(null);
7162
- form.setValue(field2.id, rows.filter((_2, j) => j !== i));
7163
- }, [rows, form, field2.id, canDelete]);
7164
- const rowErrors = useMemo(() => {
7165
- const out = {};
7166
- if (!form.submitAttempted) return out;
7167
- rows.forEach((r, i) => {
7168
- if (!sheetRowNeedsCheck(r)) return;
7169
- columns.forEach((c) => {
7170
- if (!sheetCellChecked(c.element)) return;
7171
- const e = validateField(c.element, r[c.id]);
7172
- if (e) out[`${i}:${c.id}`] = e;
7173
- });
7174
- });
7175
- return out;
7176
- }, [rows, columns, form.submitAttempted]);
7590
+ taEmit(resp, trial, step);
7591
+ }
7592
+ setActive(void 0);
7593
+ }, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
7594
+ const respond = useCallback((label) => {
7595
+ const b = buttons.find((x) => x.label === label);
7596
+ if (!b || b.disabled) return;
7597
+ if (b.prompts.length) setActive(label);
7598
+ else record(label);
7599
+ }, [buttons, record]);
7600
+ const prompt = useCallback((label) => {
7601
+ if (active) record(active, label);
7602
+ }, [active, record]);
7603
+ const prev = useCallback(() => {
7604
+ if (disabled) return;
7605
+ if (type === "percent" && pcTrial > 0) {
7606
+ set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
7607
+ } else if (type === "task" && taTrial > 0) {
7608
+ const resp = { ...taResp };
7609
+ delete resp[`Trial ${taTrial}`];
7610
+ taEmit(resp, taTrial - 1, firstStep);
7611
+ }
7612
+ }, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
7613
+ const reset = useCallback(() => {
7614
+ if (type !== "time" || disabled) return;
7615
+ setTimer(0);
7616
+ setRunning(false);
7617
+ setActive(void 0);
7618
+ tsEmit([], 0);
7619
+ }, [type, disabled, value, conf]);
7620
+ 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 }));
7621
+ const log = allResp.map(({ r, trial }, i) => ({
7622
+ label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
7623
+ correct: isCorrect(r.responseButtonPressed),
7624
+ detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
7625
+ }));
7626
+ const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
7177
7627
  return {
7178
- loading,
7179
- error,
7180
- columns,
7181
- rows,
7182
- addRow,
7183
- updateRow,
7184
- removeRow,
7185
- rowErrors,
7186
- childForm: child,
7187
- canAdd: addAllowed && (!maxRows || rows.length < maxRows),
7188
- canDelete,
7189
- maxRows,
7190
- limitMessage,
7191
- display: (c, v) => formatFieldValue(c.element, v)
7628
+ type,
7629
+ buttons,
7630
+ activeResponse: active,
7631
+ prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
7632
+ respond,
7633
+ prompt,
7634
+ cancelPrompt: () => setActive(void 0),
7635
+ prev,
7636
+ currentTrial,
7637
+ maxTrials,
7638
+ complete,
7639
+ percentCorrect,
7640
+ log,
7641
+ disabled,
7642
+ timer,
7643
+ formattedTimer: fmt(timer),
7644
+ running,
7645
+ start: () => {
7646
+ if (!disabled && timer < timeLimit) setRunning(true);
7647
+ },
7648
+ pause: () => setRunning(false),
7649
+ reset,
7650
+ interval,
7651
+ intervalCount: Math.floor(timer / interval),
7652
+ timeLimit,
7653
+ steps,
7654
+ currentStep: taStep,
7655
+ stepName,
7656
+ chain
7192
7657
  };
7193
7658
  }
7194
7659
  var FormKitSlot = React13.createContext(null);
@@ -7332,6 +7797,33 @@ var DefaultSheet = ({ field: field2, form }) => {
7332
7797
  s.limitMessage && /* @__PURE__ */ jsx("div", { role: "alert", children: s.limitMessage })
7333
7798
  ] });
7334
7799
  };
7800
+ var DefaultTrials = ({ field: field2, form }) => {
7801
+ const t = useTrialField(form, field2);
7802
+ return /* @__PURE__ */ jsxs("div", { children: [
7803
+ /* @__PURE__ */ jsxs("div", { children: [
7804
+ t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
7805
+ t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
7806
+ " \xB7 ",
7807
+ t.percentCorrect,
7808
+ "% correct",
7809
+ t.complete ? " \xB7 complete" : ""
7810
+ ] }),
7811
+ t.type === "time" && /* @__PURE__ */ jsxs("span", { children: [
7812
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
7813
+ " ",
7814
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
7815
+ ] }),
7816
+ t.type !== "time" && /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
7817
+ /* @__PURE__ */ jsxs("div", { children: [
7818
+ (t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: () => t.activeResponse ? t.prompt(b.label) : t.respond(b.label), children: b.label }, b.label)),
7819
+ t.activeResponse && /* @__PURE__ */ jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
7820
+ ] }),
7821
+ /* @__PURE__ */ jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
7822
+ l.label,
7823
+ l.detail ? ` ${l.detail}` : ""
7824
+ ] }, i)) })
7825
+ ] });
7826
+ };
7335
7827
  var DefaultField = ({ field: field2, form }) => {
7336
7828
  const f = field2;
7337
7829
  const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
@@ -7360,7 +7852,20 @@ var DefaultField = ({ field: field2, form }) => {
7360
7852
  case "display":
7361
7853
  return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
7362
7854
  case "action":
7855
+ if (f.variant === "dropdown") {
7856
+ return /* @__PURE__ */ jsxs("label", { children: [
7857
+ f.label,
7858
+ " ",
7859
+ /* @__PURE__ */ jsxs("select", { disabled: f.disabled, value: typeof f.value === "string" ? f.value : "", onChange: (e) => e.target.value && void form.runAction(f, e.target.value), children: [
7860
+ /* @__PURE__ */ jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
7861
+ f.options.map((o) => /* @__PURE__ */ jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
7862
+ ] })
7863
+ ] });
7864
+ }
7363
7865
  return /* @__PURE__ */ jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
7866
+ case "trials":
7867
+ control = /* @__PURE__ */ jsx(DefaultTrials, { field: f, form });
7868
+ break;
7364
7869
  case "checkbox":
7365
7870
  control = /* @__PURE__ */ jsxs("label", { children: [
7366
7871
  /* @__PURE__ */ jsx("input", { id: f.id, type: "checkbox", role: f.variant === "switch" ? "switch" : void 0, disabled: f.disabled, checked: f.value === true, onChange: (e) => f.setValue(e.target.checked), onBlur: f.onBlur }),
@@ -7938,6 +8443,232 @@ function buildDashboardTree(elements, isAdmin = false) {
7938
8443
  });
7939
8444
  return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
7940
8445
  }
8446
+ var text = (v) => {
8447
+ if (v === null || v === void 0 || v === "") return "";
8448
+ if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
8449
+ if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
8450
+ return String(v);
8451
+ };
8452
+ 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;
8453
+ function expectedOf(dep, labelOf) {
8454
+ const conds = dep.conditions ?? dep.Conditions ?? [];
8455
+ if (conds.length) {
8456
+ const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
8457
+ return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
8458
+ }
8459
+ if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
8460
+ return `= ${text(dep.value) || "(empty)"}`;
8461
+ }
8462
+ function checksOf(el2, required) {
8463
+ const t = resolveFieldType(el2);
8464
+ const p = el2.properties ?? {};
8465
+ const out = [];
8466
+ if (required) out.push("required");
8467
+ if (t === "INPUT_EMAIL") out.push("email");
8468
+ if (t === "INPUT_URL") out.push("url");
8469
+ if (t === "INPUT_PHONE") out.push("phone");
8470
+ if (t === "INPUT_PERCENT") out.push("0\u2013100");
8471
+ const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
8472
+ if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
8473
+ if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
8474
+ const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
8475
+ if (len) out.push(`\u2264 ${len} chars`);
8476
+ if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
8477
+ (el2.validations ?? []).forEach((v) => {
8478
+ const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
8479
+ if (k === "regex") out.push("pattern");
8480
+ else if (k === "expression") out.push("expression");
8481
+ else if (k) out.push(`${k} (server)`);
8482
+ });
8483
+ return out;
8484
+ }
8485
+ var sameNumber = (a, b) => {
8486
+ if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
8487
+ const na = Number(a), nb = Number(b);
8488
+ return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
8489
+ };
8490
+ function formulaRows(elements, values, scope, extra = {}) {
8491
+ const labels = elements.map((e) => e.label).filter(Boolean);
8492
+ const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
8493
+ return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
8494
+ const formula = String(e.properties.formula);
8495
+ const refs = formulaLabels(formula, labels);
8496
+ const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
8497
+ 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);
8498
+ const current = values[e.elementId] ?? null;
8499
+ return {
8500
+ fieldId: e.elementId,
8501
+ label: e.label,
8502
+ scope,
8503
+ sheetLabel: extra.sheetLabel,
8504
+ row: extra.row,
8505
+ formula,
8506
+ serverFormula: toBraceFormula(formula, labels),
8507
+ serverSafe: isServerSafeFormula(formula, labels),
8508
+ inputs,
8509
+ expected,
8510
+ current,
8511
+ inSync: sameNumber(expected, current)
8512
+ };
8513
+ });
8514
+ }
8515
+ function inspectForm(form) {
8516
+ const def = form.definition;
8517
+ if (!def) {
8518
+ return {
8519
+ rules: [],
8520
+ filters: [],
8521
+ validation: [],
8522
+ formulas: [],
8523
+ defaults: [],
8524
+ payload: null,
8525
+ summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
8526
+ };
8527
+ }
8528
+ const elements = def.elements;
8529
+ const values = form.values;
8530
+ const byId = new Map(elements.map((e) => [e.elementId, e]));
8531
+ const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
8532
+ const rules = [];
8533
+ elements.forEach((el2) => {
8534
+ const origin = el2.properties?.triage ? "triage" : "dependencies";
8535
+ rulesOf(el2).forEach((dep) => {
8536
+ const action = String(dep.dependencyType ?? dep.type ?? "");
8537
+ const effect = effectOf(action);
8538
+ const target = String(dep.elementId ?? dep.targetElementId ?? "");
8539
+ if (!effect || !target) return;
8540
+ rules.push({
8541
+ sourceId: el2.elementId,
8542
+ sourceLabel: el2.label,
8543
+ sourceValue: text(values[el2.elementId]) || "(empty)",
8544
+ action,
8545
+ effect,
8546
+ targetId: target,
8547
+ targetLabel: labelOf(target),
8548
+ expected: expectedOf(dep, labelOf),
8549
+ matched: ruleMatches(el2, dep, values),
8550
+ origin
8551
+ });
8552
+ });
8553
+ });
8554
+ const filters2 = [];
8555
+ elements.forEach((el2) => {
8556
+ const cfg = lookupConfigOf(el2);
8557
+ if (!cfg) return;
8558
+ const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
8559
+ optionFilterRules(elements, el2.elementId).forEach((r) => {
8560
+ const v = text(values[r.elementId]);
8561
+ filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
8562
+ });
8563
+ cfg.dynamicFilters.forEach((df) => {
8564
+ const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
8565
+ const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
8566
+ if (!dep) return;
8567
+ const src = elements.find((e) => e.label === dep);
8568
+ const v = src ? text(values[src.elementId]) : "";
8569
+ filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
8570
+ });
8571
+ cfg.staticFilters.forEach((sf) => {
8572
+ const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
8573
+ filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
8574
+ });
8575
+ });
8576
+ const validation = [];
8577
+ form.fields.forEach((f) => {
8578
+ const t = resolveFieldType(f.element);
8579
+ if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
8580
+ validation.push({
8581
+ id: f.id,
8582
+ label: f.label,
8583
+ type: t,
8584
+ required: f.required,
8585
+ hidden: f.hidden,
8586
+ checks: checksOf(f.element, f.required),
8587
+ value: formatFieldValue(f.element, f.value),
8588
+ error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
8589
+ shownError: f.error
8590
+ });
8591
+ });
8592
+ const formulas = formulaRows(elements, values, "form");
8593
+ const sheets = [];
8594
+ elements.forEach((el2) => {
8595
+ const t = resolveFieldType(el2);
8596
+ if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
8597
+ const child = form.sheetDefinition(el2.elementId);
8598
+ if (!child) return;
8599
+ const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
8600
+ const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
8601
+ rows.forEach((row, i) => {
8602
+ const { _submissionId, _dirty, _rowKey, ...cells } = row;
8603
+ const scope = `${el2.label} \xB7 row ${i + 1}`;
8604
+ if (sheetRowNeedsCheck(row)) {
8605
+ child.elements.forEach((c) => {
8606
+ if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
8607
+ validation.push({
8608
+ id: `${el2.elementId}:${i}:${c.elementId}`,
8609
+ label: c.label,
8610
+ type: resolveFieldType(c),
8611
+ scope,
8612
+ required: !!c.required,
8613
+ hidden: false,
8614
+ checks: checksOf(c, !!c.required),
8615
+ value: formatFieldValue(c, cells[c.elementId]),
8616
+ error: validateField(c, cells[c.elementId])
8617
+ });
8618
+ });
8619
+ }
8620
+ const fresh = computeRowFormulas(child.elements, cells);
8621
+ formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
8622
+ sheet.rows.push(buildSubmissionElements(child.elements, fresh));
8623
+ });
8624
+ sheets.push(sheet);
8625
+ });
8626
+ const defaults = [];
8627
+ elements.forEach((el2) => {
8628
+ const dd = el2.attributes?.dynamicDefault;
8629
+ const ds = el2.properties?.dataSource ?? {};
8630
+ let source = "";
8631
+ if (dd && typeof dd === "object") {
8632
+ const u = dd.units ?? {};
8633
+ const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
8634
+ source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
8635
+ } else if (typeof dd === "string" && dd) source = dd;
8636
+ else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
8637
+ else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
8638
+ else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
8639
+ if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
8640
+ });
8641
+ const submitEls = submitElementsOf(elements);
8642
+ const sheetIds = new Set(sheets.map((s) => s.fieldId));
8643
+ const shown = { ...values };
8644
+ sheets.forEach((s) => {
8645
+ shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
8646
+ });
8647
+ const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
8648
+ const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
8649
+ const errors = validation.filter((v) => !v.hidden && v.error).length;
8650
+ const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
8651
+ return {
8652
+ rules,
8653
+ filters: filters2,
8654
+ validation,
8655
+ formulas,
8656
+ defaults,
8657
+ payload,
8658
+ summary: {
8659
+ rules: rules.length,
8660
+ matched: rules.filter((r) => r.matched).length,
8661
+ filters: filters2.length,
8662
+ activeFilters: filters2.filter((f) => f.active).length,
8663
+ errors,
8664
+ formulas: formulas.length,
8665
+ formulaIssues
8666
+ }
8667
+ };
8668
+ }
8669
+ function useFormInspector(form) {
8670
+ return useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
8671
+ }
7941
8672
 
7942
8673
  // src/forms/authoring.ts
7943
8674
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
@@ -8049,6 +8780,6 @@ function formWidget(o) {
8049
8780
  };
8050
8781
  }
8051
8782
 
8052
- export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, interpolate, invokeTrigger, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, taskField, taskSource, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
8783
+ export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BLOCKED_FILE_EXTENSIONS, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, computeRowFormulas, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, extractAgentFormData, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, inspectForm, interpolate, invokeAgent, invokeTrigger, invokeZeno, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, isServerSafeFormula, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, openFormRequest, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, rulesOf, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, submitElementsOf, taskField, taskSource, toBraceFormula, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useFormInspector, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useTrialField, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
8053
8784
  //# sourceMappingURL=index.js.map
8054
8785
  //# sourceMappingURL=index.js.map