@unvired/turboforms-embed-sdk 1.0.10 → 1.0.12

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.
@@ -141,7 +141,7 @@ function sendEventCallback({
141
141
  if (type === "ERROR") {
142
142
  eventStructure = {
143
143
  type: "ERROR",
144
- errorMessage,
144
+ errorMessage: errorMessage + ", contact your admin or tech team",
145
145
  data
146
146
  };
147
147
  } else {
@@ -223,11 +223,11 @@ async function validateAttachments(template, submissionData, sendEventCallback2)
223
223
  if (typeof fileId === "string" && fileId.trim()) {
224
224
  try {
225
225
  const fileRecord = await getFileFromIndexedDB(fileId);
226
- if (!fileRecord || !fileRecord.id || !fileRecord.data || !fileRecord.url) {
226
+ if (!fileRecord || !fileRecord.id || !fileRecord.data && !fileRecord.url) {
227
227
  missingFiles.push({
228
228
  fileId,
229
229
  field: key,
230
- reason: fileRecord ? "Incomplete file record (missing id/data/url)" : "File not found in IndexedDB"
230
+ reason: fileRecord ? "Incomplete file record (missing id and data/url)" : "File not found in IndexedDB"
231
231
  });
232
232
  }
233
233
  } catch (err) {
@@ -281,6 +281,7 @@ function getFileFromIndexedDB(fileId) {
281
281
  // src/lib/FileProcessor.js
282
282
  async function processFileIds(data, sendEventCallback2) {
283
283
  if (!data || typeof data !== "object") return data;
284
+ const clonedData = JSON.parse(JSON.stringify(data));
284
285
  async function traverse(obj) {
285
286
  for (const key in obj) {
286
287
  const value = obj[key];
@@ -298,21 +299,12 @@ async function processFileIds(data, sendEventCallback2) {
298
299
  type: fileObject.type,
299
300
  url: fileObject.url,
300
301
  key,
301
- uid: value[i]
302
+ uid: value[i],
303
+ mode: fileObject.mode || "G"
304
+ // Fix: Preserve the mode or default to G
302
305
  });
303
306
  } else {
304
307
  console.warn(`File not found in IndexedDB: ${value[i]} - removing from submission data`);
305
- if (sendEventCallback2) {
306
- sendEventCallback2({
307
- type: "ERROR",
308
- errorMessage: `ErrorCode : 015, contact your admin or tech team`,
309
- data: {
310
- reason: `File not found in IndexedDB: ${value[i]}`,
311
- fileId: value[i],
312
- field: key
313
- }
314
- });
315
- }
316
308
  }
317
309
  } else if (typeof value[i] === "object" && value[i] !== null) {
318
310
  await traverse(value[i]);
@@ -327,14 +319,25 @@ async function processFileIds(data, sendEventCallback2) {
327
319
  }
328
320
  }
329
321
  }
330
- await traverse(data);
331
- return data;
322
+ await traverse(clonedData);
323
+ return clonedData;
332
324
  }
333
325
  async function getFileFromIndexedDB2(fileId) {
334
326
  return new Promise((resolve) => {
335
327
  const request = indexedDB.open("Forms-Attachments", 3);
328
+ request.onupgradeneeded = (e) => {
329
+ const db = e.target.result;
330
+ if (!db.objectStoreNames.contains("Files")) {
331
+ db.createObjectStore("Files");
332
+ }
333
+ };
336
334
  request.onsuccess = (event) => {
337
335
  const db = event.target.result;
336
+ if (!db.objectStoreNames.contains("Files")) {
337
+ console.warn("Files store not found");
338
+ resolve(null);
339
+ return;
340
+ }
338
341
  const store = db.transaction(["Files"], "readonly").objectStore("Files");
339
342
  const getRequest = store.get(fileId);
340
343
  getRequest.onsuccess = () => resolve(getRequest.result);
@@ -344,6 +347,473 @@ async function getFileFromIndexedDB2(fileId) {
344
347
  });
345
348
  }
346
349
 
350
+ // src/lib/dynamicValueProcessor.js
351
+ function processDynamicValues(formJson, appData) {
352
+ if (!formJson || !formJson.components) return null;
353
+ let dataMap = {};
354
+ const hasAppData = appData && (Array.isArray(appData) && appData.length > 0 || !Array.isArray(appData) && Object.keys(appData).length > 0);
355
+ if (hasAppData) {
356
+ if (Array.isArray(appData)) {
357
+ appData.forEach((item) => {
358
+ if (item.key) dataMap[item.key] = item.value;
359
+ });
360
+ } else {
361
+ dataMap = appData || {};
362
+ }
363
+ }
364
+ function processComponents(comps) {
365
+ if (!comps || !Array.isArray(comps)) return null;
366
+ for (const comp of comps) {
367
+ if (typeof comp.defaultValue === "string" && comp.defaultValue.trim().startsWith("{{") && comp.defaultValue.trim().endsWith("}}")) {
368
+ const key = comp.defaultValue.trim();
369
+ if (hasAppData && dataMap[key] !== void 0) {
370
+ comp.defaultValue = dataMap[key];
371
+ }
372
+ }
373
+ let err;
374
+ if (comp.components) {
375
+ err = processComponents(comp.components);
376
+ if (err) return err;
377
+ }
378
+ if (comp.columns && Array.isArray(comp.columns)) {
379
+ for (const col of comp.columns) {
380
+ err = processComponents(col.components);
381
+ if (err) return err;
382
+ }
383
+ }
384
+ if (comp.rows && Array.isArray(comp.rows)) {
385
+ for (const row of comp.rows) {
386
+ if (Array.isArray(row)) {
387
+ for (const cell of row) {
388
+ err = processComponents(cell.components);
389
+ if (err) return err;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ }
395
+ return null;
396
+ }
397
+ return processComponents(formJson.components);
398
+ }
399
+
400
+ // src/lib/appDocuments.js
401
+ var DB_NAME = "AppDocuments";
402
+ var STORE_NAME = "documents";
403
+ var STORE_NAME_TYPO = "documnets";
404
+ function openDB(version) {
405
+ if (version) {
406
+ return new Promise((resolve, reject) => {
407
+ const request = indexedDB.open(DB_NAME, version);
408
+ request.onupgradeneeded = (event) => {
409
+ const db = event.target.result;
410
+ if (!db.objectStoreNames.contains(STORE_NAME) && !db.objectStoreNames.contains(STORE_NAME_TYPO)) {
411
+ db.createObjectStore(STORE_NAME, { keyPath: "id", autoIncrement: true });
412
+ }
413
+ };
414
+ request.onsuccess = (event) => resolve(event.target.result);
415
+ request.onerror = (event) => {
416
+ console.error("AppDocuments DB error:", event.target.error);
417
+ reject(event.target.error);
418
+ };
419
+ });
420
+ }
421
+ if (typeof window !== "undefined" && window.indexedDBUtils && window.indexedDBUtils.openDynamicDB) {
422
+ return window.indexedDBUtils.openDynamicDB(DB_NAME, STORE_NAME, STORE_NAME_TYPO);
423
+ }
424
+ return new Promise((resolve, reject) => {
425
+ const checkRequest = indexedDB.open(DB_NAME);
426
+ checkRequest.onupgradeneeded = (e) => {
427
+ const db = e.target.result;
428
+ if (!db.objectStoreNames.contains(STORE_NAME) && !db.objectStoreNames.contains(STORE_NAME_TYPO)) {
429
+ db.createObjectStore(STORE_NAME, { keyPath: "id", autoIncrement: true });
430
+ }
431
+ };
432
+ checkRequest.onsuccess = (e) => {
433
+ const db = e.target.result;
434
+ if (db.objectStoreNames.contains(STORE_NAME) || db.objectStoreNames.contains(STORE_NAME_TYPO)) {
435
+ resolve(db);
436
+ return;
437
+ }
438
+ const currentVersion = db.version;
439
+ const newVersion = currentVersion + 1;
440
+ db.close();
441
+ console.log(`[AppDocuments] Store missing. Upgrading from v${currentVersion} to v${newVersion}.`);
442
+ const upgradeRequest = indexedDB.open(DB_NAME, newVersion);
443
+ upgradeRequest.onupgradeneeded = (evt) => {
444
+ const upgradeDb = evt.target.result;
445
+ if (!upgradeDb.objectStoreNames.contains(STORE_NAME)) {
446
+ upgradeDb.createObjectStore(STORE_NAME, { keyPath: "id", autoIncrement: true });
447
+ }
448
+ };
449
+ upgradeRequest.onsuccess = (evt) => resolve(evt.target.result);
450
+ upgradeRequest.onerror = (evt) => {
451
+ console.error("[AppDocuments] DB Upgrade failed:", evt);
452
+ reject(evt);
453
+ };
454
+ };
455
+ checkRequest.onerror = (e) => {
456
+ console.error("[AppDocuments] DB Open failed:", e);
457
+ reject(e.target.error);
458
+ };
459
+ });
460
+ }
461
+ function getStoreName(db) {
462
+ if (db.objectStoreNames.contains(STORE_NAME)) return STORE_NAME;
463
+ if (db.objectStoreNames.contains(STORE_NAME_TYPO)) return STORE_NAME_TYPO;
464
+ return null;
465
+ }
466
+ async function hasDocuments() {
467
+ try {
468
+ const db = await openDB();
469
+ const actualStoreName = getStoreName(db);
470
+ if (!actualStoreName) {
471
+ console.warn(`AppDocuments DB found but no '${STORE_NAME}' or '${STORE_NAME_TYPO}' store exists.`);
472
+ db.close();
473
+ return false;
474
+ }
475
+ return new Promise((resolve) => {
476
+ const transaction = db.transaction([actualStoreName], "readonly");
477
+ const store = transaction.objectStore(actualStoreName);
478
+ const countRequest = store.count();
479
+ countRequest.onsuccess = () => {
480
+ console.log(`[AppDocuments] Count in ${actualStoreName}:`, countRequest.result);
481
+ db.close();
482
+ resolve(countRequest.result > 0);
483
+ };
484
+ countRequest.onerror = (e) => {
485
+ console.error("[AppDocuments] Error counting:", e);
486
+ db.close();
487
+ resolve(false);
488
+ };
489
+ });
490
+ } catch (e) {
491
+ console.error("[AppDocuments] Error checking documents:", e);
492
+ return false;
493
+ }
494
+ }
495
+ async function getAllDocuments() {
496
+ try {
497
+ const db = await openDB();
498
+ const actualStoreName = getStoreName(db);
499
+ if (!actualStoreName) {
500
+ db.close();
501
+ return [];
502
+ }
503
+ return new Promise((resolve) => {
504
+ const transaction = db.transaction([actualStoreName], "readonly");
505
+ const store = transaction.objectStore(actualStoreName);
506
+ const request = store.getAll();
507
+ request.onsuccess = () => {
508
+ db.close();
509
+ resolve(request.result);
510
+ };
511
+ request.onerror = () => {
512
+ db.close();
513
+ resolve([]);
514
+ };
515
+ });
516
+ } catch (e) {
517
+ console.error("Error getting documents:", e);
518
+ return [];
519
+ }
520
+ }
521
+ async function insertDocument(doc) {
522
+ try {
523
+ let db = await openDB();
524
+ let actualStoreName = getStoreName(db);
525
+ if (!actualStoreName) {
526
+ db.close();
527
+ throw new Error("Failed to open database with valid store.");
528
+ }
529
+ return new Promise((resolve, reject) => {
530
+ const transaction = db.transaction([actualStoreName], "readwrite");
531
+ const store = transaction.objectStore(actualStoreName);
532
+ const request = store.add(doc);
533
+ request.onsuccess = () => {
534
+ db.close();
535
+ resolve(request.result);
536
+ };
537
+ request.onerror = (e) => {
538
+ db.close();
539
+ reject(e.target.error);
540
+ };
541
+ });
542
+ } catch (e) {
543
+ console.error("Error inserting document:", e);
544
+ throw e;
545
+ }
546
+ }
547
+ async function deleteDocument(id) {
548
+ try {
549
+ const db = await openDB();
550
+ const actualStoreName = getStoreName(db);
551
+ if (!actualStoreName) {
552
+ db.close();
553
+ console.error("No valid document store found for deletion");
554
+ return;
555
+ }
556
+ return new Promise((resolve, reject) => {
557
+ const transaction = db.transaction([actualStoreName], "readwrite");
558
+ const store = transaction.objectStore(actualStoreName);
559
+ const request = store.delete(id);
560
+ request.onsuccess = () => {
561
+ db.close();
562
+ resolve();
563
+ };
564
+ request.onerror = (e) => {
565
+ db.close();
566
+ reject(e.target.error);
567
+ };
568
+ });
569
+ } catch (e) {
570
+ console.error("Error deleting document:", e);
571
+ throw e;
572
+ }
573
+ }
574
+
575
+ // src/html-template/utils/openDocumentsModal.js
576
+ var DB_NAME2 = "AppDocuments";
577
+ var STORE_NAME2 = "documents";
578
+ var STORE_NAME_TYPO2 = "documnets";
579
+ function openDB2() {
580
+ return new Promise((resolve, reject) => {
581
+ const request = indexedDB.open(DB_NAME2);
582
+ request.onsuccess = (event) => resolve(event.target.result);
583
+ request.onerror = (event) => reject(event.target.error);
584
+ });
585
+ }
586
+ function getStoreName2(db) {
587
+ if (db.objectStoreNames.contains(STORE_NAME2)) return STORE_NAME2;
588
+ if (db.objectStoreNames.contains(STORE_NAME_TYPO2)) return STORE_NAME_TYPO2;
589
+ return null;
590
+ }
591
+ async function getAllDocuments2() {
592
+ try {
593
+ const db = await openDB2();
594
+ const actualStoreName = getStoreName2(db);
595
+ if (!actualStoreName) {
596
+ db.close();
597
+ return [];
598
+ }
599
+ return new Promise((resolve) => {
600
+ const transaction = db.transaction([actualStoreName], "readonly");
601
+ const store = transaction.objectStore(actualStoreName);
602
+ const request = store.getAll();
603
+ request.onsuccess = () => {
604
+ db.close();
605
+ resolve(request.result);
606
+ };
607
+ request.onerror = () => {
608
+ db.close();
609
+ resolve([]);
610
+ };
611
+ });
612
+ } catch (e) {
613
+ console.warn("Error reading AppDocuments DB:", e);
614
+ return [];
615
+ }
616
+ }
617
+ async function openDocumentsModal() {
618
+ if (typeof window.showDynamicModal !== "function") {
619
+ console.error("showDynamicModal is not defined.");
620
+ return;
621
+ }
622
+ window.showDynamicModal("App Documents", [], `
623
+ <div style="text-align: center; padding: 40px;">
624
+ <i class="fa fa-spinner fa-spin" style="font-size: 30px; color: #2185d0;"></i>
625
+ <div style="margin-top: 15px; color: #666; font-size: 16px;">Loading documents...</div>
626
+ </div>
627
+ `);
628
+ try {
629
+ const docs = await getAllDocuments2();
630
+ let bodyContent = "";
631
+ if (docs && docs.length > 0) {
632
+ bodyContent = `<div class="ui middle aligned divided list" style="width:100%;margin:0;">`;
633
+ docs.forEach((doc) => {
634
+ const name = doc.name || doc.title || "Untitled Document";
635
+ const desc = doc.description || "";
636
+ let metaInfo = "";
637
+ let iconClass = "file outline";
638
+ if (doc.type) {
639
+ if (doc.type.includes("pdf")) iconClass = "file pdf outline red";
640
+ else if (doc.type.includes("image")) iconClass = "file image outline blue";
641
+ else if (doc.type.includes("text")) iconClass = "file text outline grey";
642
+ }
643
+ if (Number.isInteger(doc.id)) {
644
+ metaInfo = new Date(doc.id).toLocaleDateString();
645
+ }
646
+ bodyContent += `
647
+ <div class="item" style="padding:12px 10px;display:flex;align-items:center;">
648
+ <i class="${iconClass} icon large" style="margin-right:15px;"></i>
649
+
650
+ <div class="content" style="flex:1;min-width:0;">
651
+ <div class="header" style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
652
+ ${name}
653
+ </div>
654
+ ${desc ? `<div class="description" style="font-size:12px;color:#666;">${desc}</div>` : ""}
655
+ ${metaInfo ? `<div class="meta" style="font-size:11px;color:#999;">${metaInfo}</div>` : ""}
656
+ </div>
657
+
658
+ <button class="ui button mini basic primary"
659
+ onclick="window.downloadDocument(${doc.id})">
660
+ <i class="download icon"></i> Download
661
+ </button>
662
+ </div>`;
663
+ });
664
+ bodyContent += `</div>`;
665
+ } else {
666
+ bodyContent = `
667
+ <div style="text-align:center;padding:50px;color:#999;">
668
+ <i class="folder open outline icon" style="font-size:40px;"></i>
669
+ <div>No documents available</div>
670
+ </div>`;
671
+ }
672
+ window.showDynamicModal(
673
+ "App Documents",
674
+ [{ label: "Close", callback: () => {
675
+ }, className: "ui button" }],
676
+ bodyContent
677
+ );
678
+ if (!window.downloadDocument) {
679
+ window.downloadDocument = async function(id) {
680
+ const all = await getAllDocuments2();
681
+ const found = all.find((d) => d.id == id);
682
+ if (!found) {
683
+ console.log("\u274C Document not found.");
684
+ if (window.sendEventCallback) {
685
+ window.sendEventCallback({
686
+ type: "ERROR",
687
+ errorMessage: "ErrorCode : 017, Document not found",
688
+ data: { reason: "Document not found", technicalError: null }
689
+ });
690
+ }
691
+ return;
692
+ }
693
+ console.log("Downloading:", found);
694
+ let incomingData = found.url || found.data || found.file && found.file.data;
695
+ let downloadUrl = null;
696
+ if (incomingData && incomingData.startsWith("data:")) {
697
+ const comma = incomingData.indexOf(",");
698
+ if (comma === -1) {
699
+ console.log("\u274C Invalid file format.");
700
+ if (window.sendEventCallback) {
701
+ window.sendEventCallback({
702
+ type: "ERROR",
703
+ errorMessage: "ErrorCode : 018, Invalid file format",
704
+ data: { reason: "Invalid file format", technicalError: null }
705
+ });
706
+ }
707
+ return;
708
+ }
709
+ const header = incomingData.substring(0, comma);
710
+ let base64 = incomingData.substring(comma + 1);
711
+ const mimeMatch = header.match(/:(.*?);/);
712
+ const mime = mimeMatch ? mimeMatch[1] : "application/octet-stream";
713
+ base64 = base64.replace(/\s/g, "");
714
+ if (!/^[A-Za-z0-9+/=]+$/.test(base64)) {
715
+ console.error("Invalid base64:", base64.slice(0, 50));
716
+ console.log("\u274C File is corrupted (invalid base64).");
717
+ if (window.sendEventCallback) {
718
+ window.sendEventCallback({
719
+ type: "ERROR",
720
+ errorMessage: "ErrorCode : 019, File is corrupted (invalid base64)",
721
+ data: { reason: "File is corrupted (invalid base64)", technicalError: null }
722
+ });
723
+ }
724
+ return;
725
+ }
726
+ try {
727
+ const binary = atob(base64);
728
+ const bytes = new Uint8Array(binary.length);
729
+ for (let i = 0; i < binary.length; i++) {
730
+ bytes[i] = binary.charCodeAt(i);
731
+ }
732
+ const blob = new Blob([bytes], { type: mime });
733
+ if (!blob.size) {
734
+ console.log("\u274C File is empty.");
735
+ if (window.sendEventCallback) {
736
+ window.sendEventCallback({
737
+ type: "ERROR",
738
+ errorMessage: "ErrorCode : 020, File is empty",
739
+ data: { reason: "File is empty", technicalError: null }
740
+ });
741
+ }
742
+ return;
743
+ }
744
+ downloadUrl = URL.createObjectURL(blob);
745
+ } catch (err) {
746
+ console.error("Base64 decode failed:", err);
747
+ if (err.name === "InvalidCharacterError") {
748
+ console.log("\u274C File is corrupted (invalid encoding).");
749
+ if (window.sendEventCallback) {
750
+ window.sendEventCallback({
751
+ type: "ERROR",
752
+ errorMessage: "ErrorCode : 021, File is corrupted (invalid encoding)",
753
+ data: { reason: "File is corrupted (invalid encoding)", technicalError: err }
754
+ });
755
+ }
756
+ } else {
757
+ console.log("\u274C Failed to process file.");
758
+ if (window.sendEventCallback) {
759
+ window.sendEventCallback({
760
+ type: "ERROR",
761
+ errorMessage: "ErrorCode : 022, Failed to process file",
762
+ data: { reason: "Failed to process file", technicalError: err }
763
+ });
764
+ }
765
+ }
766
+ return;
767
+ }
768
+ } else if (found.file instanceof Blob) {
769
+ if (!found.file.size) {
770
+ console.log("\u274C File is empty.");
771
+ if (window.sendEventCallback) {
772
+ window.sendEventCallback({
773
+ type: "ERROR",
774
+ errorMessage: "ErrorCode : 023, File is empty",
775
+ data: { reason: "File is empty", technicalError: null }
776
+ });
777
+ }
778
+ return;
779
+ }
780
+ downloadUrl = URL.createObjectURL(found.file);
781
+ } else {
782
+ console.log("\u274C No downloadable content found.");
783
+ if (window.sendEventCallback) {
784
+ window.sendEventCallback({
785
+ type: "ERROR",
786
+ errorMessage: "ErrorCode : 024, No downloadable content found",
787
+ data: { reason: "No downloadable content found", technicalError: null }
788
+ });
789
+ }
790
+ return;
791
+ }
792
+ const a = document.createElement("a");
793
+ a.href = downloadUrl;
794
+ a.download = found.name || "document";
795
+ document.body.appendChild(a);
796
+ a.click();
797
+ setTimeout(() => {
798
+ URL.revokeObjectURL(downloadUrl);
799
+ a.remove();
800
+ }, 1e4);
801
+ };
802
+ }
803
+ } catch (err) {
804
+ console.error("Load failed", err);
805
+ console.log("\u274C Failed to load documents.");
806
+ if (window.sendEventCallback) {
807
+ window.sendEventCallback({
808
+ type: "ERROR",
809
+ errorMessage: "ErrorCode : 025, Failed to load documents",
810
+ data: { reason: "Failed to load documents", technicalError: err }
811
+ });
812
+ }
813
+ }
814
+ }
815
+ window.openDocumentsModal = openDocumentsModal;
816
+
347
817
  // src/__temp_build.js
348
818
  async function loadUnviredForms({
349
819
  formsData = "formIOComponent",
@@ -353,33 +823,37 @@ async function loadUnviredForms({
353
823
  options = {
354
824
  nestedFormData: [{}],
355
825
  masterData: [{}],
356
- title: "the form title entered",
357
- descrciption: "the form description entered",
358
826
  mode: "",
359
- privateExternal: "",
360
- permission: "",
361
827
  platform: "",
828
+ permission: "",
362
829
  language: "",
830
+ privateExternal: "",
831
+ title: "the form title entered",
832
+ descrciption: "the form description entered",
363
833
  translations: {},
364
- environmentVariable: [],
365
834
  themeData: {},
366
835
  userData: {},
367
- appData: {},
368
836
  controlData: {},
369
- showBackButton: false,
370
- showMoreButton: true,
371
837
  commentsData: [],
372
838
  vobArr: [],
839
+ appData: [],
840
+ environmentVariable: [],
373
841
  formioLibPath: {
374
842
  formioPath: "assets/formio.full.min.js"
375
843
  },
376
- showLoader: true
844
+ showComments: false,
845
+ showHelp: false,
846
+ showBackButton: false,
847
+ showLoader: true,
848
+ showMoreButton: true,
849
+ showCompleteAlways: false,
850
+ requireCompleteForm: true
377
851
  }
378
852
  }) {
379
853
  var _a, _b;
380
854
  let fileKeys = [];
381
855
  let formJson;
382
- console.log("0000000000", formJson);
856
+ console.log("data recive step 1", formsData, submissionData, options);
383
857
  function sendEventCallback2(params) {
384
858
  const eventStructure = sendEventCallback(params);
385
859
  if (typeof eventCallback === "function") {
@@ -396,6 +870,7 @@ async function loadUnviredForms({
396
870
  });
397
871
  return;
398
872
  }
873
+ processDynamicValues(formJson, options.appData);
399
874
  const template = formJson;
400
875
  const nestedFormArrTemp = options.nestedFormData || [];
401
876
  const masterDataArrTemp = options.masterData || [];
@@ -433,13 +908,17 @@ async function loadUnviredForms({
433
908
  "masterdata"
434
909
  );
435
910
  if (submissionData && template) {
436
- const r = await validateAttachments(
437
- mergedWithMasterData,
438
- submissionData,
439
- sendEventCallback2
440
- );
441
- fileKeys = r.fileKeys;
442
- if (r.missingFiles.length > 0) {
911
+ try {
912
+ const r = await validateAttachments(
913
+ mergedWithMasterData,
914
+ submissionData,
915
+ sendEventCallback2
916
+ );
917
+ fileKeys = r.fileKeys;
918
+ if (r.missingFiles.length > 0) {
919
+ }
920
+ } catch (error) {
921
+ console.warn("Attachment validation skipped:", error);
443
922
  }
444
923
  }
445
924
  if ((options == null ? void 0 : options.appData) && ((_a = Object.keys(options == null ? void 0 : options.appData)) == null ? void 0 : _a.length) > 0) {
@@ -1158,6 +1637,14 @@ body {
1158
1637
  /* subtle shadow for elevation */
1159
1638
  }
1160
1639
 
1640
+
1641
+ #submitBtn,
1642
+ #saveBtn {
1643
+ width: 110px;
1644
+ display: flex;
1645
+ justify-content: space-evenly;
1646
+ }
1647
+
1161
1648
  .form-cmt-btn {
1162
1649
  width: 40px;
1163
1650
  height: 40px;
@@ -1193,8 +1680,9 @@ body {
1193
1680
  width: 100%;
1194
1681
  background-color: #ffffff;
1195
1682
  padding: 6px;
1196
- justify-content: space-between;
1683
+ justify-content: flex-end;
1197
1684
  align-items: center;
1685
+ gap: 8px;
1198
1686
  border-top: 1px solid #ddd;
1199
1687
  z-index: 1000;
1200
1688
  box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.05);
@@ -1206,18 +1694,20 @@ body {
1206
1694
  display: none;
1207
1695
  position: absolute;
1208
1696
  bottom: 100%;
1209
- left: 0;
1210
- margin-bottom: 10px;
1697
+ left: -170px;
1698
+ margin-bottom: 16px;
1211
1699
  background-color: #fff;
1212
1700
  border: 1px solid #ddd;
1213
1701
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1214
1702
  padding: 6px;
1215
1703
  border-radius: 6px;
1216
- z-index: 9999;
1704
+ z-index: 1000;
1217
1705
  flex-direction: column;
1218
1706
  gap: 0px;
1219
1707
  }
1220
1708
 
1709
+ #unvired-more-btn {}
1710
+
1221
1711
  /* Print and PDF Modes */
1222
1712
  @media print {
1223
1713
 
@@ -1429,7 +1919,6 @@ body {
1429
1919
 
1430
1920
  @keyframes sdk-pulse {
1431
1921
 
1432
- 0%,
1433
1922
  100% {
1434
1923
  opacity: 1;
1435
1924
  }
@@ -1439,6 +1928,48 @@ body {
1439
1928
  }
1440
1929
  }
1441
1930
 
1931
+ /* Responsive Footer for Mobile */
1932
+ @media (max-width: 600px) {
1933
+ #sticky-footer {
1934
+ justify-content: flex-end;
1935
+ padding: 4px;
1936
+ gap: 4px;
1937
+ }
1938
+
1939
+ #submitBtn {
1940
+
1941
+ width: 105px;
1942
+ min-width: 108px;
1943
+ flex-grow: 0;
1944
+ }
1945
+
1946
+
1947
+ #saveBtn {
1948
+ width: 80px;
1949
+ min-width: 90px;
1950
+ flex-grow: 0;
1951
+ }
1952
+
1953
+ #unvired-more-btn {
1954
+ margin-left: 10px;
1955
+ margin-right: 10px;
1956
+ }
1957
+
1958
+ /* Fix tooltip on mobile to prevent cutoff */
1959
+ #moreTooltip {
1960
+ position: fixed;
1961
+ bottom: 70px;
1962
+ left: 20px;
1963
+ right: 20px;
1964
+ width: auto;
1965
+ margin-bottom: 0;
1966
+ max-width: 300px;
1967
+ margin-left: auto;
1968
+ margin-right: auto;
1969
+
1970
+ }
1971
+ }
1972
+
1442
1973
  /* === STYLE_SEPARATOR === */
1443
1974
 
1444
1975
  .r6o-drawing{cursor:none}.r6o-relations-layer.readonly .handle rect{pointer-events:none}.r6o-relations-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.r6o-relations-layer circle{stroke:#515151;stroke-width:.4;fill:#3f3f3f}.r6o-relations-layer path{stroke:#595959;stroke-linecap:round;stroke-linejoin:round;fill:transparent}.r6o-relations-layer path.connection{stroke-width:1.6;stroke-dasharray:2,3}.r6o-relations-layer path.r6o-arrow{stroke-width:1.8;fill:#7f7f7f}.r6o-relations-layer .handle rect{stroke-width:1;stroke:#595959;fill:#fff;pointer-events:auto;cursor:pointer}.r6o-relations-layer .handle text{font-size:10px}.r6o-relations-layer .hover{stroke:rgba(63,63,63,.9);stroke-width:1.4;fill:transparent}
@@ -27129,7 +27660,9 @@ select.ui.dropdown {
27129
27660
  /* -----------------
27130
27661
  Floating label end
27131
27662
  -------------------- */
27132
- `.split("\n\n/* === STYLE_SEPARATOR === */\n\n");
27663
+ `.split(
27664
+ "\n\n/* === STYLE_SEPARATOR === */\n\n"
27665
+ );
27133
27666
  cssContents.forEach((cssContent, index) => {
27134
27667
  if (cssContent.trim()) {
27135
27668
  const styleElement = document.createElement("style");
@@ -27201,6 +27734,34 @@ select.ui.dropdown {
27201
27734
  0%, 100% { opacity: 1; }
27202
27735
  50% { opacity: 0.5; }
27203
27736
  }
27737
+ .sdk-loader-back-btn {
27738
+ position: absolute;
27739
+ top: 20px;
27740
+ left: 20px;
27741
+ background: transparent;
27742
+ border: none;
27743
+ color: #333;
27744
+ font-size: 1.2rem;
27745
+ cursor: pointer;
27746
+ padding: 10px;
27747
+ border-radius: 50%;
27748
+ transition: background 0.2s ease;
27749
+ pointer-events: auto;
27750
+ display: flex;
27751
+ align-items: center;
27752
+ justify-content: center;
27753
+ margin: 0;
27754
+ width: 40px;
27755
+ height: 40px;
27756
+ }
27757
+ .sdk-loader-back-btn:hover {
27758
+ background: rgba(0, 0, 0, 0.05);
27759
+ color: #000;
27760
+ border: none;
27761
+ }
27762
+ .sdk-loader-back-btn i {
27763
+ margin: 0;
27764
+ }
27204
27765
  `;
27205
27766
  document.head.appendChild(additionalStyle);
27206
27767
  }
@@ -27229,24 +27790,52 @@ select.ui.dropdown {
27229
27790
  <div id="formio-cmt" style="margin-bottom: 20px;"></div>
27230
27791
  </div>
27231
27792
  <div id="sticky-footer">
27793
+ <button class="ui button primary dataGrid-addRow" id="saveBtn" disabled="true" onclick="FormOnSave()">
27794
+ <i class="icon save large"></i>Save
27795
+ </button>
27796
+ <button class="ui button primary dataGrid-addRow" id="submitBtn" style="display:none" onclick="FormOnSubmit()">
27797
+ <i class="icon save large"></i>Submit
27798
+ </button>
27799
+
27800
+ <!-- Quick Action Buttons -->
27801
+ <div id="quick-actions" style="display: flex; align-items: center;">
27802
+ <i id="quickCompleteBtn" class="icon clipboard check large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="FormOnSubmit()" title="Complete"></i>
27803
+ <i id="quickSaveBtn" class="icon save large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="FormOnSave()" title="Save"></i>
27804
+ <i id="quickDocumentsBtn" class="icon file alternate large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="openDocumentsModal()" title="Documents"></i>
27805
+ <i id="quickCommentsBtn" class="icon comment large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="loadComments()" title="Comments"></i>
27806
+ <i id="quickHelpBtn" class="icon question circle large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="openHelpLink()" title="Help"></i>
27807
+ </div>
27808
+
27232
27809
  <div class="relative-position">
27233
- <button id="moreBtn" class="ui button primary dataGrid-addRow" onclick="toggleTooltip()">
27234
- <i class="icon options"></i>More
27810
+ <button class="ui button primary " id="unvired-more-btn" onclick="toggleTooltip(event)">
27811
+ <i class="icon bars large" style="margin: 0;"></i>
27235
27812
  </button>
27236
27813
  <div id="moreTooltip" style="display: none;" class="ui vertical menu">
27237
- <div class="item">Documents</div>
27238
- <div class="divider" id="comments-divider" style="display: none;"></div>
27814
+ <div class="item" id="completeOption" onclick="FormOnSubmit()" style="display: none;">Complete</div>
27815
+ <div class="divider"></div>
27816
+ <div class="item" id="saveOption" onclick="FormOnSave()" style="display: none;">Save</div>
27817
+ <div class="divider"></div>
27818
+ <div class="item" id="documentsOption" onclick="openDocumentsModal()" style="display: none;">Documents</div>
27819
+ <div class="divider"></div>
27239
27820
  <div class="item" id="comments-item" style="display: none;" onclick="loadComments()">Comments</div>
27240
27821
  <div class="divider"></div>
27241
- <div class="item" onclick="openHelpLink()">Help</div>
27822
+ <div class="item" id="help-item" style="display: none;" onclick="openHelpLink()">Help</div>
27242
27823
  </div>
27243
27824
  </div>
27244
- <button class="ui button primary dataGrid-addRow" id="saveBtn" disabled="true" onclick="FormOnSave()">
27245
- <i class="icon save"></i>Save
27246
- </button>
27247
- <button class="ui button primary dataGrid-addRow" id="submitBtn" style="display:none" onclick="FormOnSubmit()">
27248
- <i class="icon save"></i>Submit
27249
- </button>
27825
+ </div>
27826
+ <div class="ui modal" id="documentsModal">
27827
+ <i class="close icon"></i>
27828
+ <div class="header">
27829
+ App Documents
27830
+ </div>
27831
+ <div class="content">
27832
+ <div class="ui relaxed divided list" id="documentsList">
27833
+ <div class="item">Loading...</div>
27834
+ </div>
27835
+ </div>
27836
+ <div class="actions">
27837
+ <div class="ui button" onclick="closeDocumentsModal()">Close</div>
27838
+ </div>
27250
27839
  </div>
27251
27840
  `;
27252
27841
  let loaderElement = null;
@@ -27257,23 +27846,48 @@ select.ui.dropdown {
27257
27846
  <div class="sdk-loader-spinner"></div>
27258
27847
  <div class="sdk-loader-text">Loading Form...</div>
27259
27848
  <div class="sdk-loader-subtext">Please wait while we prepare your form</div>
27849
+ <button class="form-back-btn sdk-loader-back-btn" onclick="FormOnBackLoading()">
27850
+ <i class="icon arrow left"></i>
27851
+ </button>
27260
27852
  `;
27261
27853
  container.appendChild(loaderElement);
27262
27854
  }
27263
- window.toggleTooltip = function() {
27855
+ window.toggleTooltip = function(event) {
27856
+ if (event) {
27857
+ event.stopPropagation();
27858
+ }
27264
27859
  const tooltip = document.getElementById("moreTooltip");
27265
- const commentsDivider = document.getElementById("comments-divider");
27266
27860
  const commentsItem = document.getElementById("comments-item");
27861
+ const helpItem = document.getElementById("help-item");
27862
+ if (options.showHelp) {
27863
+ helpItem.style.display = "block";
27864
+ } else {
27865
+ helpItem.style.display = "none";
27866
+ }
27867
+ if (options.showComments) {
27868
+ commentsItem.style.display = "block";
27869
+ } else {
27870
+ commentsItem.style.display = "none";
27871
+ }
27267
27872
  if (tooltip.style.display === "none") {
27268
27873
  tooltip.style.display = "flex";
27269
- setTimeout(() => {
27270
- commentsDivider.style.display = "";
27271
- commentsItem.style.display = "";
27272
- }, 500);
27874
+ tooltip.style.flexDirection = "column";
27273
27875
  } else {
27274
27876
  tooltip.style.display = "none";
27275
- commentsDivider.style.display = "none";
27276
- commentsItem.style.display = "none";
27877
+ return;
27878
+ }
27879
+ const items = Array.from(tooltip.querySelectorAll(".item"));
27880
+ const dividers = Array.from(tooltip.querySelectorAll(".divider"));
27881
+ dividers.forEach((d) => d.style.display = "none");
27882
+ const visibleItems = items.filter((item) => {
27883
+ return item.style.display !== "none" && item.offsetParent !== null;
27884
+ }).filter((item) => item.style.display !== "none");
27885
+ for (let i = 0; i < visibleItems.length - 1; i++) {
27886
+ const item = visibleItems[i];
27887
+ let next = item.nextElementSibling;
27888
+ if (next && next.classList.contains("divider")) {
27889
+ next.style.display = "block";
27890
+ }
27277
27891
  }
27278
27892
  };
27279
27893
  window.openHelpLink = function() {
@@ -27288,36 +27902,46 @@ select.ui.dropdown {
27288
27902
  }
27289
27903
  };
27290
27904
  document.addEventListener("click", function(event) {
27291
- const moreBtn2 = document.getElementById("moreBtn");
27905
+ const moreBtn2 = document.getElementById("unvired-more-btn");
27292
27906
  const tooltip = document.getElementById("moreTooltip");
27293
27907
  if (moreBtn2 && tooltip && !moreBtn2.contains(event.target) && !tooltip.contains(event.target)) {
27294
27908
  tooltip.style.display = "none";
27295
27909
  }
27296
27910
  });
27297
- window.loadComments = function() {
27911
+ window.loadComments = window.loadComments || function() {
27298
27912
  if (typeof window.loadCommentsFunction === "function") {
27299
27913
  window.loadCommentsFunction();
27300
27914
  }
27301
27915
  };
27302
- window.FormOnBack = function() {
27916
+ window.FormOnBack = window.FormOnBack || function() {
27303
27917
  sendEventCallback2({ type: "FORM_BACK_NAVIGATION" });
27304
27918
  };
27305
- window.CommentOnBack = function() {
27919
+ window.FormOnBackLoading = window.FormOnBackLoading || function() {
27920
+ console.log("Back button clicked during loading, canceling form load");
27921
+ if (loaderElement) {
27922
+ loaderElement.classList.add("hidden");
27923
+ if (loaderElement.parentNode) {
27924
+ loaderElement.parentNode.removeChild(loaderElement);
27925
+ }
27926
+ }
27927
+ sendEventCallback2({ type: "FORM_BACK_NAVIGATION" });
27928
+ };
27929
+ window.CommentOnBack = window.CommentOnBack || function() {
27306
27930
  if (typeof window.commentOnBackFunction === "function") {
27307
27931
  window.commentOnBackFunction();
27308
27932
  }
27309
27933
  };
27310
- window.submitComments = function() {
27934
+ window.submitComments = window.submitComments || function() {
27311
27935
  if (typeof window.submitCommentsFunction === "function") {
27312
27936
  window.submitCommentsFunction();
27313
27937
  }
27314
27938
  };
27315
- window.FormOnSave = function() {
27939
+ window.FormOnSave = window.FormOnSave || function() {
27316
27940
  if (typeof window.formOnSaveFunction === "function") {
27317
27941
  window.formOnSaveFunction();
27318
27942
  }
27319
27943
  };
27320
- window.FormOnSubmit = function() {
27944
+ window.FormOnSubmit = window.FormOnSubmit || function() {
27321
27945
  if (typeof window.formOnSubmitFunction === "function") {
27322
27946
  window.formOnSubmitFunction();
27323
27947
  }
@@ -48239,6 +48863,14 @@ async function startCameraScanner() {
48239
48863
 
48240
48864
  showBarcodeError(errorMessage);
48241
48865
  setTimeout(() => closeScannerModal(), 3000);
48866
+
48867
+ if (window.sendEventCallback) {
48868
+ window.sendEventCallback({
48869
+ type: "ERROR",
48870
+ errorMessage: "ErrorCode : 012, Camera access error",
48871
+ data: { technicalError: err, reason: errorMessage }
48872
+ });
48873
+ }
48242
48874
  }
48243
48875
  }
48244
48876
 
@@ -48622,7 +49254,7 @@ function injectBarcodeModalStyles() {
48622
49254
 
48623
49255
  (function(){
48624
49256
  // Listen for "getLocation" custom event
48625
- document.addEventListener("getLocation", function (event) {
49257
+ document.addEventListener("getLocation", function (event) {
48626
49258
  const { compObj, controlId } = event.detail;
48627
49259
  window.compId = controlId;
48628
49260
  getLocationFromBrowser(controlId); // Pass controlId to getLocationFromBrowser
@@ -48632,7 +49264,7 @@ function injectBarcodeModalStyles() {
48632
49264
  function getLocationFromBrowser(componentID) {
48633
49265
  // Check if running in Cordova environment
48634
49266
  const isCordova = !!(window.cordova || window.PhoneGap || window.phonegap);
48635
-
49267
+
48636
49268
  if (!navigator.geolocation) {
48637
49269
  showLocationError("Geolocation is not supported by this device.");
48638
49270
  return;
@@ -48653,7 +49285,7 @@ function getLocationFromBrowser(componentID) {
48653
49285
  showLocationError("Location permission was denied. Please enable it in browser settings and refresh the page.");
48654
49286
  return;
48655
49287
  }
48656
-
49288
+
48657
49289
  if (permissionStatus.state === "granted" || permissionStatus.state === "prompt") {
48658
49290
  requestLocation(componentID);
48659
49291
  }
@@ -48671,7 +49303,7 @@ function getLocationFromBrowser(componentID) {
48671
49303
 
48672
49304
  function requestLocation(componentID) {
48673
49305
  const isCordova = !!(window.cordova || window.PhoneGap || window.phonegap);
48674
-
49306
+
48675
49307
  const options = {
48676
49308
  enableHighAccuracy: true,
48677
49309
  timeout: isCordova ? 30000 : 10000, // Longer timeout for Cordova
@@ -48688,7 +49320,7 @@ function requestLocation(componentID) {
48688
49320
  },
48689
49321
  });
48690
49322
  document.dispatchEvent(customEvent);
48691
-
49323
+
48692
49324
  // Send success event
48693
49325
  if (window.sendEventCallback) {
48694
49326
  window.sendEventCallback({
@@ -48706,36 +49338,36 @@ function requestLocation(componentID) {
48706
49338
  }
48707
49339
 
48708
49340
  function handleLocationError(error) {
48709
- let errorMessage = "Unable to retrieve location";
48710
- let errorCode = "008";
48711
-
49341
+ let errorMessage = "Location unknown error";
49342
+ let errorCode = "011";
49343
+
48712
49344
  switch (error.code) {
48713
49345
  case error.PERMISSION_DENIED:
48714
- errorMessage = "Location permission denied. Please allow location access and try again.";
49346
+ errorMessage = "Location permission denied";
48715
49347
  errorCode = "008";
48716
49348
  break;
48717
49349
  case error.POSITION_UNAVAILABLE:
48718
- errorMessage = "Location information is unavailable. Please check your GPS settings.";
49350
+ errorMessage = "Location unavailable";
48719
49351
  errorCode = "009";
48720
49352
  break;
48721
49353
  case error.TIMEOUT:
48722
- errorMessage = "Location request timed out. Please try again.";
49354
+ errorMessage = "Location request timeout";
48723
49355
  errorCode = "010";
48724
49356
  break;
48725
49357
  default:
48726
- errorMessage = "An unknown error occurred while retrieving location.";
49358
+ errorMessage = "Location unknown error";
48727
49359
  errorCode = "011";
48728
49360
  break;
48729
49361
  }
48730
-
49362
+
48731
49363
  showLocationError(errorMessage);
48732
49364
  console.error("Geolocation error:", error);
48733
-
49365
+
48734
49366
  // Send error event
48735
49367
  if (window.sendEventCallback) {
48736
49368
  window.sendEventCallback({
48737
49369
  type: "ERROR",
48738
- errorMessage: \`ErrorCode : \${errorCode}, Location error. Contact your admin or tech team.\`,
49370
+ errorMessage: \`ErrorCode : \${errorCode}, \${errorMessage}\`,
48739
49371
  data: { technicalError: error, reason: errorMessage }
48740
49372
  });
48741
49373
  }
@@ -48760,9 +49392,9 @@ function showLocationError(message) {
48760
49392
  text-align: center;
48761
49393
  \`;
48762
49394
  errorDiv.textContent = message;
48763
-
49395
+
48764
49396
  document.body.appendChild(errorDiv);
48765
-
49397
+
48766
49398
  // Remove after 5 seconds
48767
49399
  setTimeout(() => {
48768
49400
  if (errorDiv.parentNode) {
@@ -48805,15 +49437,18 @@ let capturedImageData = "";
48805
49437
  function openCameraModal() {
48806
49438
  injectCameraModalStyles();
48807
49439
 
49440
+ // Ensure clean slate: Remove any existing modal if present
48808
49441
  const existingModal = document.getElementById("cameraModal");
49442
+ if (existingModal) {
49443
+ existingModal.remove();
49444
+ }
48809
49445
 
48810
49446
  capturedImageData = "";
48811
49447
 
48812
- if (!existingModal) {
48813
- const modalHTML = \`
49448
+ const modalHTML = \`
48814
49449
  <div id="cameraModal">
48815
49450
  <div id="cameraContainer">
48816
- <video id="cameraVideo" autoplay playsinline style="opacity: 0;"></video>
49451
+ <video id="cameraVideo" playsinline webkit-playsinline muted style="opacity: 0; pointer-events: none;"></video>
48817
49452
  <div id="cameraLoader">
48818
49453
  <div class="loader-spinner"></div>
48819
49454
  <p class="loader-text">Starting camera...</p>
@@ -48824,6 +49459,11 @@ function openCameraModal() {
48824
49459
  <path d="M18 6L6 18M6 6L18 18" stroke="white" stroke-width="2" stroke-linecap="round"/>
48825
49460
  </svg>
48826
49461
  </button>
49462
+ <div class="camera-select-container">
49463
+ <select id="cameraSelect" class="camera-select">
49464
+ <option value="" disabled selected>Select Camera</option>
49465
+ </select>
49466
+ </div>
48827
49467
  <div id="captureButton" style="opacity: 0; pointer-events: none;">
48828
49468
  <div class="capture-ring">
48829
49469
  <div class="capture-inner"></div>
@@ -48833,35 +49473,18 @@ function openCameraModal() {
48833
49473
  </div>
48834
49474
  </div>
48835
49475
  \`;
48836
- document.body.insertAdjacentHTML("beforeend", modalHTML);
48837
- document.getElementById("closeCameraBtn").addEventListener("click", closeCameraModal);
48838
- document.getElementById("captureButton").addEventListener("click", captureImage);
48839
- } else {
48840
- existingModal.style.display = "block";
48841
-
48842
- // Reset UI state when reopening
48843
- const video = document.getElementById("cameraVideo");
48844
- const loader = document.getElementById("cameraLoader");
48845
- const captureBtn = document.getElementById("captureButton");
48846
-
48847
- if (video) {
48848
- video.style.opacity = "0";
48849
- video.srcObject = null;
48850
- }
48851
- if (loader) {
48852
- loader.style.display = "flex";
48853
- loader.style.opacity = "1";
48854
- }
48855
- if (captureBtn) {
48856
- captureBtn.style.opacity = "0";
48857
- captureBtn.style.pointerEvents = "none";
48858
- }
48859
- }
49476
+ document.body.insertAdjacentHTML("beforeend", modalHTML);
49477
+ document.getElementById("closeCameraBtn").addEventListener("click", closeCameraModal);
49478
+ document.getElementById("captureButton").addEventListener("click", captureImage);
49479
+ document.getElementById("cameraSelect").addEventListener("change", (e) => {
49480
+ startCameraStream(e.target.value);
49481
+ });
48860
49482
 
49483
+ // Small delay to ensure DOM is ready
48861
49484
  setTimeout(startCameraStream, 100);
48862
49485
  }
48863
49486
 
48864
- async function startCameraStream() {
49487
+ async function startCameraStream(deviceId = null) {
48865
49488
  if (videoStream) {
48866
49489
  try {
48867
49490
  videoStream.getTracks().forEach(track => track.stop());
@@ -48872,42 +49495,84 @@ async function startCameraStream() {
48872
49495
  }
48873
49496
 
48874
49497
  try {
49498
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (window.platform && window.platform.iosPlatform);
49499
+
49500
+ // Check if API is supported
49501
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
49502
+ const errorMsg = "Camera API is not supported. Please ensure you are using HTTPS or localhost.";
49503
+ alert(errorMsg);
49504
+ showCameraError(errorMsg);
49505
+ return;
49506
+ }
49507
+
48875
49508
  const video = document.getElementById("cameraVideo");
48876
49509
  if (!video) {
48877
49510
  alert("Camera video element not found.");
48878
49511
  return;
48879
49512
  }
48880
49513
 
48881
- // Get available cameras
48882
- const devices = await navigator.mediaDevices.enumerateDevices();
48883
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
49514
+ // Simplified approach: Ask for camera directly.
49515
+ // This allows the browser to handle permissions correctly on all platforms (Windows/Android/iOS)
49516
+ // and defaults to the system default camera (webcam) on laptops.
49517
+ // Simplified approach: Ask for camera directly.
49518
+ // We race against a timeout because WebViews often hang indefinitely if permissions aren't handled.
49519
+ // OPTIMIZATION: Request 16:9 aspect ratio (1280x720) instead of 4:3 (640x480).
49520
+ // This reduces the "zoom" effect on modern mobile screens which are typically 16:9 or taller.
49521
+ const constraints = {
49522
+ video: {
49523
+ width: { ideal: 1280 },
49524
+ height: { ideal: 720 }
49525
+ }
49526
+ };
48884
49527
 
48885
- if (!videoDevices.length) {
48886
- alert("No camera devices found.");
48887
- return;
49528
+ if (deviceId) {
49529
+ constraints.video.deviceId = { exact: deviceId };
49530
+ } else {
49531
+ constraints.video.facingMode = "environment";
48888
49532
  }
48889
49533
 
48890
- // Prefer back camera
48891
- const backCamera = videoDevices.find(device =>
48892
- device.label.toLowerCase().includes('back') ||
48893
- device.label.toLowerCase().includes('rear')
48894
- ) || videoDevices[0];
49534
+ const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
48895
49535
 
48896
- videoStream = await navigator.mediaDevices.getUserMedia({
48897
- video: {
48898
- deviceId: backCamera.deviceId,
48899
- facingMode: "environment",
48900
- width: { ideal: 640 },
48901
- height: { ideal: 480 }
49536
+ const timeoutPromise = new Promise((_, reject) =>
49537
+ setTimeout(() => reject(new Error("PermissionTimeout")), 6000)
49538
+ );
49539
+
49540
+ try {
49541
+ videoStream = await Promise.race([streamPromise, timeoutPromise]);
49542
+ } catch (err) {
49543
+ if (err.message === "PermissionTimeout") {
49544
+ throw new Error("Permission request timed out. Please check app permissions.");
48902
49545
  }
48903
- });
49546
+ throw err;
49547
+ }
48904
49548
 
49549
+ // iOS Specific: Ensure inline playback setup BEFORE stream assignment
49550
+ if (isIOS) {
49551
+ video.setAttribute("playsinline", "");
49552
+ video.setAttribute("webkit-playsinline", "");
49553
+ video.playsInline = true; // Set property as well
49554
+ }
49555
+
49556
+ // Mute video to prevent audio feedback and allow autoplay in restrictive policies
49557
+ video.muted = true;
49558
+
49559
+ // Assign stream
48905
49560
  video.srcObject = videoStream;
48906
49561
 
49562
+ // Ensure video doesn't stay paused if it somehow exits fullscreen or gets interrupted
49563
+ video.onpause = () => {
49564
+ if (cameraStarted && video.srcObject) {
49565
+ // Only try to resume if we expect it to be playing
49566
+ video.play().catch(e => console.log("Resume error", e));
49567
+ }
49568
+ };
49569
+
48907
49570
  // Wait for video to start playing, then hide loader
48908
49571
  video.onloadedmetadata = () => {
48909
- video.play().then(() => {
48910
- // Hide loader and show camera UI
49572
+ // Once permission is granted and stream starts, listing devices will return labels
49573
+ populateCameraDevices();
49574
+
49575
+ const showUI = () => {
48911
49576
  const loader = document.getElementById("cameraLoader");
48912
49577
  const captureBtn = document.getElementById("captureButton");
48913
49578
 
@@ -48915,23 +49580,34 @@ async function startCameraStream() {
48915
49580
  loader.style.display = "none";
48916
49581
  loader.style.opacity = "0";
48917
49582
  }
49583
+
48918
49584
  video.style.opacity = "1";
49585
+
48919
49586
  if (captureBtn) {
48920
49587
  captureBtn.style.opacity = "1";
48921
49588
  captureBtn.style.pointerEvents = "all";
48922
49589
  }
49590
+ };
48923
49591
 
49592
+ video.play().then(() => {
49593
+ showUI();
48924
49594
  cameraStarted = true;
48925
49595
  }).catch(err => {
48926
49596
  console.error("Video play error:", err);
48927
- alert("Failed to start video: " + err.message);
48928
- closeCameraModal();
49597
+ if (!video.paused) {
49598
+ console.log("Video is playing despite error. Continuing.");
49599
+ showUI();
49600
+ cameraStarted = true;
49601
+ } else {
49602
+ alert("Failed to start video preview: " + err.message);
49603
+ closeCameraModal();
49604
+ }
48929
49605
  });
48930
49606
  };
48931
49607
 
48932
49608
  } catch (err) {
48933
49609
  console.error("Camera start error:", err);
48934
- let errorMessage = "Failed to start camera";
49610
+ let errorMessage = err.message || "Failed to start camera";
48935
49611
 
48936
49612
  if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
48937
49613
  errorMessage = "Camera permission denied. Please allow camera access and try again.";
@@ -48947,18 +49623,50 @@ async function startCameraStream() {
48947
49623
  errorMessage = "Camera access was interrupted.";
48948
49624
  }
48949
49625
 
48950
- showCameraError(errorMessage);
49626
+ alert(errorMessage);
49627
+ closeCameraModal();
49628
+ }
49629
+ }
49630
+
49631
+ async function populateCameraDevices() {
49632
+ const select = document.getElementById("cameraSelect");
49633
+ if (!select) return;
48951
49634
 
48952
- // Send error event
48953
- if (window.sendEventCallback) {
48954
- window.sendEventCallback({
48955
- type: "ERROR",
48956
- errorMessage: "ErrorCode : 012, Camera access error. Contact your admin or tech team.",
48957
- data: { technicalError: err, reason: errorMessage }
49635
+ try {
49636
+ const devices = await navigator.mediaDevices.enumerateDevices();
49637
+ const videoDevices = devices.filter(device => device.kind === 'videoinput');
49638
+
49639
+ // Only show if we have more than 1 camera, or at least we want to show the current one
49640
+ if (videoDevices.length > 0) {
49641
+ select.innerHTML = '<option value="" disabled>Select Camera</option>';
49642
+
49643
+ let currentDeviceId = "";
49644
+ if (videoStream) {
49645
+ const track = videoStream.getVideoTracks()[0];
49646
+ if (track) {
49647
+ const settings = track.getSettings();
49648
+ currentDeviceId = settings.deviceId;
49649
+ }
49650
+ }
49651
+
49652
+ videoDevices.forEach((device, index) => {
49653
+ const option = document.createElement("option");
49654
+ option.value = device.deviceId;
49655
+ option.text = device.label || \`Camera \${index + 1}\`;
49656
+ if (device.deviceId === currentDeviceId) {
49657
+ option.selected = true;
49658
+ }
49659
+ select.appendChild(option);
48958
49660
  });
48959
- }
48960
49661
 
48961
- setTimeout(() => closeCameraModal(), 3000);
49662
+ // If there are multiple devices, ensure the dropdown container is visible
49663
+ const container = document.querySelector('.camera-select-container');
49664
+ if (container) {
49665
+ container.style.display = videoDevices.length > 1 ? 'block' : 'none';
49666
+ }
49667
+ }
49668
+ } catch (e) {
49669
+ console.warn("Error enumerating devices:", e);
48962
49670
  }
48963
49671
  }
48964
49672
 
@@ -49028,19 +49736,19 @@ function closeCameraModal() {
49028
49736
  const modal = document.getElementById("cameraModal");
49029
49737
 
49030
49738
  if (videoStream) {
49031
- videoStream.getTracks().forEach(track => track.stop());
49739
+ try {
49740
+ videoStream.getTracks().forEach(track => track.stop());
49741
+ } catch (e) {
49742
+ console.warn("Error stopping tracks", e);
49743
+ }
49032
49744
  videoStream = null;
49033
49745
  cameraStarted = false;
49034
49746
  }
49035
49747
 
49036
- // Reset video element
49037
- const video = document.getElementById("cameraVideo");
49038
- if (video) {
49039
- video.srcObject = null;
49040
- video.style.opacity = "0";
49748
+ // Completely remove from DOM to reset video element state for iOS
49749
+ if (modal) {
49750
+ modal.remove();
49041
49751
  }
49042
-
49043
- if (modal) modal.style.display = "none";
49044
49752
  }
49045
49753
 
49046
49754
  function injectCameraModalStyles() {
@@ -49071,6 +49779,7 @@ function injectCameraModalStyles() {
49071
49779
  object-fit: cover;
49072
49780
  background: #000;
49073
49781
  transition: opacity 0.3s ease;
49782
+ pointer-events: none;
49074
49783
  }
49075
49784
 
49076
49785
  #cameraLoader {
@@ -49151,6 +49860,39 @@ function injectCameraModalStyles() {
49151
49860
  background: rgba(0, 0, 0, 0.7);
49152
49861
  }
49153
49862
 
49863
+ .camera-select-container {
49864
+ position: absolute;
49865
+ top: 20px;
49866
+ right: 20px;
49867
+ z-index: 10001;
49868
+ pointer-events: all;
49869
+ display: none; /* Hidden by default until devices found */
49870
+ }
49871
+
49872
+ .camera-select {
49873
+ background: rgba(0, 0, 0, 0.5);
49874
+ color: white;
49875
+ border: 1px solid rgba(255, 255, 255, 0.3);
49876
+ padding: 8px 12px;
49877
+ border-radius: 20px;
49878
+ font-size: 14px;
49879
+ backdrop-filter: blur(10px);
49880
+ outline: none;
49881
+ cursor: pointer;
49882
+ appearance: none;
49883
+ -webkit-appearance: none;
49884
+ padding-right: 30px;
49885
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
49886
+ background-repeat: no-repeat;
49887
+ background-position: right 10px center;
49888
+ background-size: 10px;
49889
+ }
49890
+
49891
+ .camera-select option {
49892
+ background: #333;
49893
+ color: white;
49894
+ }
49895
+
49154
49896
  #captureButton {
49155
49897
  position: absolute;
49156
49898
  bottom: 40px;
@@ -49217,6 +49959,11 @@ function injectCameraModalStyles() {
49217
49959
  bottom: env(safe-area-inset-bottom, 40px);
49218
49960
  bottom: max(40px, env(safe-area-inset-bottom));
49219
49961
  }
49962
+
49963
+ .camera-select-container {
49964
+ top: env(safe-area-inset-top, 20px);
49965
+ top: max(20px, env(safe-area-inset-top));
49966
+ }
49220
49967
  }
49221
49968
 
49222
49969
  /* Landscape mode adjustments */
@@ -49851,7 +50598,7 @@ see README and LICENSE for details
49851
50598
 
49852
50599
  let flag = false;
49853
50600
  class SmartStorage {
49854
- constructor() {}
50601
+ constructor() { }
49855
50602
 
49856
50603
  static get title() {
49857
50604
  return "SmartStorage";
@@ -49877,15 +50624,38 @@ class SmartStorage {
49877
50624
 
49878
50625
  return new Promise((resolve, reject) => {
49879
50626
  const request = indexedDB.open("Forms-Attachments", 3);
50627
+
49880
50628
  request.onsuccess = function (event) {
49881
50629
  const db = event.target.result;
50630
+ if (!db.objectStoreNames.contains("Files")) {
50631
+ console.warn("[SmartStorage] Files store missing. Recreating database.");
50632
+ db.close();
50633
+ const deleteReq = indexedDB.deleteDatabase("Forms-Attachments");
50634
+ deleteReq.onsuccess = () => {
50635
+ const retryReq = indexedDB.open("Forms-Attachments", 3);
50636
+ retryReq.onupgradeneeded = (e) => {
50637
+ const newDb = e.target.result;
50638
+ newDb.createObjectStore("Files");
50639
+ };
50640
+ retryReq.onsuccess = (e) => resolve(e.target.result);
50641
+ retryReq.onerror = (e) => reject(e);
50642
+ };
50643
+ deleteReq.onerror = (e) => reject(e);
50644
+ return;
50645
+ }
49882
50646
  resolve(db);
49883
50647
  };
49884
50648
 
49885
50649
  request.onupgradeneeded = function (e) {
49886
50650
  const db = e.target.result;
49887
- db.createObjectStore("Files");
49888
- resolve(db);
50651
+ if (!db.objectStoreNames.contains("Files")) {
50652
+ db.createObjectStore("Files");
50653
+ }
50654
+ resolve(db); // Note: resolve here calls with incomplete db during upgrade transaction, but it's typically fine for callers waiting on db handle.
50655
+ // Ideally we resolve in onsuccess, but existing code had it this way too?
50656
+ // Actually, in the original code, it resolved in BOTH. Resolving in upgrade gives the transaction-bound db.
50657
+ // But Standard practice is waiting for success.
50658
+ // The original code had \`resolve(db)\` in \`onupgradeneeded\`.
49889
50659
  };
49890
50660
 
49891
50661
  request.onerror = function (e) {
@@ -49912,6 +50682,7 @@ class SmartStorage {
49912
50682
  size: file.size,
49913
50683
  type: file.type,
49914
50684
  url,
50685
+ mode: "A",
49915
50686
  };
49916
50687
  if (
49917
50688
  window.platform.isBrowser ||
@@ -49933,6 +50704,7 @@ class SmartStorage {
49933
50704
  type: file.type,
49934
50705
  url: url,
49935
50706
  id,
50707
+ mode: "A",
49936
50708
  });
49937
50709
  };
49938
50710
  } else {
@@ -49943,6 +50715,7 @@ class SmartStorage {
49943
50715
  type: file.type,
49944
50716
  url: url,
49945
50717
  id,
50718
+ mode: "A",
49946
50719
  });
49947
50720
  }
49948
50721
  };
@@ -50029,6 +50802,7 @@ class SmartStorage {
50029
50802
  size: blobObject.size,
50030
50803
  type: file.type,
50031
50804
  url: file.url,
50805
+ mode: file.mode || "G",
50032
50806
  };
50033
50807
  const trans = db.transaction(["Files"], "readwrite");
50034
50808
  const addReq = trans.objectStore("Files").put(data, id);
@@ -50688,7 +51462,21 @@ class SmartFileComponent extends FieldComponent {
50688
51462
  }
50689
51463
 
50690
51464
  getValue() {
50691
- return this.dataValue;
51465
+ const value = Array.isArray(this.dataValue) ? this.dataValue : (this.dataValue ? [this.dataValue] : []);
51466
+ return value.concat(this.deletedFiles || []);
51467
+ }
51468
+
51469
+ setValue(value, flags) {
51470
+ if (Array.isArray(value)) {
51471
+ this.deletedFiles = value.filter(f => f.mode === 'D');
51472
+ value = value.filter(f => f.mode !== 'D');
51473
+ } else if (value && value.mode === 'D') {
51474
+ this.deletedFiles = [value];
51475
+ value = [];
51476
+ } else {
51477
+ this.deletedFiles = [];
51478
+ }
51479
+ return super.setValue(value, flags);
50692
51480
  }
50693
51481
 
50694
51482
  get defaultValue() {
@@ -51011,6 +51799,13 @@ class SmartFileComponent extends FieldComponent {
51011
51799
  new SmartStorage().markFileAsDeleted(fileInfo.id);
51012
51800
  this.deleteFile(fileInfo);
51013
51801
  event.preventDefault();
51802
+
51803
+ fileInfo.mode = "D";
51804
+ if (!this.deletedFiles) {
51805
+ this.deletedFiles = [];
51806
+ }
51807
+ this.deletedFiles.push(fileInfo);
51808
+
51014
51809
  this.splice(index);
51015
51810
  this.redraw();
51016
51811
  });
@@ -51265,10 +52060,8 @@ class SmartFileComponent extends FieldComponent {
51265
52060
  let id = this.component.id;
51266
52061
  isfileBrowse = false;
51267
52062
 
51268
- // let isbrowser = (!document.URL.startsWith('http:') || document.URL.startsWith('http://localhost:8000'))
51269
- // if (window.platform.isAndroid && !isbrowser) {
51270
-
51271
- if (window.platform.isAndroid) {
52063
+ // Dispatch custom camera event for both Android and iOS
52064
+ if (window.platform.isAndroid || window.platform.iosPlatform) {
51272
52065
  window.isListenerSet = false;
51273
52066
  let eventCustom = new CustomEvent("openCamera", {
51274
52067
  detail: {
@@ -51652,6 +52445,24 @@ class SmartFileComponent extends FieldComponent {
51652
52445
  if (this.component.storage && files && files.length) {
51653
52446
  // if (this.component.storage && files) {
51654
52447
  Array.prototype.forEach.call(files, async (file) => {
52448
+ // Modify iOS filenames with timestamp BEFORE duplicate check
52449
+ let originalFileName = file.name;
52450
+ if (window.platform.iosPlatform) {
52451
+ const fileNameOriginal = file.name;
52452
+ const lastDotIndex = fileNameOriginal.lastIndexOf(".");
52453
+ let newName = "";
52454
+ if (lastDotIndex !== -1) {
52455
+ newName = \`\${fileNameOriginal.substring(
52456
+ 0,
52457
+ lastDotIndex
52458
+ )}-\${Date.now()}\${fileNameOriginal.substring(lastDotIndex)}\`;
52459
+ } else {
52460
+ newName = \`\${fileNameOriginal}-\${Date.now()}\`;
52461
+ }
52462
+ file = new File([file], newName, {
52463
+ type: file.type,
52464
+ });
52465
+ }
51655
52466
  const fileName = uniqueName(
51656
52467
  file.name,
51657
52468
  this.component.fileNameTemplate,
@@ -51694,8 +52505,9 @@ class SmartFileComponent extends FieldComponent {
51694
52505
  // Check if file with the same name is being uploaded
51695
52506
  var fileWithSameNameUploaded = false;
51696
52507
  if (window.platform.iosPlatform) {
52508
+ // For iOS, compare using the escaped filename (after timestamp modification)
51697
52509
  fileWithSameNameUploaded = this.dataValue.some(
51698
- (fileStatus) => fileStatus.url === imagUrl
52510
+ (fileStatus) => fileStatus.originalName === escapedFileName
51699
52511
  );
51700
52512
  } else {
51701
52513
  fileWithSameNameUploaded = isfileBrowse
@@ -51905,19 +52717,27 @@ class SmartFileComponent extends FieldComponent {
51905
52717
  fileInfo.originalName = escapedFileName;
51906
52718
  fileInfo.hash = fileUpload.hash;
51907
52719
  fileInfo.compnentId = this.component.id;
52720
+ fileInfo.mode = fileUpload.mode || "A";
51908
52721
  if (!this.hasValue()) {
51909
52722
  this.dataValue = [];
51910
52723
  }
51911
52724
  if (replace) {
51912
- let ind;
52725
+ let ind = -1;
51913
52726
  for (let i = 0; i < this.dataValue.length; i++) {
51914
52727
  if (this.dataValue[i].originalName == fileInfo.originalName) {
51915
52728
  console.log("original name matches");
51916
52729
  ind = i;
52730
+ break;
51917
52731
  }
51918
52732
  }
51919
52733
  if (ind !== -1) {
51920
52734
  let originalFileId = this.dataValue[ind].id;
52735
+ let originalFileMode = this.dataValue[ind].mode;
52736
+
52737
+ if (originalFileMode === "G" || originalFileMode === "M") {
52738
+ fileInfo.mode = "M";
52739
+ }
52740
+
51921
52741
  console.log("originalFileId = " + originalFileId);
51922
52742
  this.dataValue[ind] = fileInfo;
51923
52743
  let eventCustom = new CustomEvent(
@@ -59464,7 +60284,7 @@ class SmartSelectComponent extends SmartSelectField {
59464
60284
  return {
59465
60285
  ...super.conditionOperatorsSettings,
59466
60286
  valueComponent(classComp) {
59467
- const valueComp = { ... classComp, type: 'select' };
60287
+ const valueComp = { ...classComp, type: 'select' };
59468
60288
 
59469
60289
  if (isSelectResourceWithObjectValue(classComp)) {
59470
60290
  valueComp.reference = false;
@@ -59616,7 +60436,7 @@ class SmartSelectComponent extends SmartSelectField {
59616
60436
  return this.component.valueProperty;
59617
60437
  }
59618
60438
  // Force values datasource to use values without actually setting it on the component settings.
59619
- if(this.component.dataSrc === 'values') {
60439
+ if (this.component.dataSrc === 'values') {
59620
60440
  return 'value';
59621
60441
  }
59622
60442
  return '';
@@ -59654,10 +60474,10 @@ class SmartSelectComponent extends SmartSelectField {
59654
60474
 
59655
60475
  get shouldInitialLoad() {
59656
60476
  if (this.component.widget === 'html5' &&
59657
- this.isEntireObjectDisplay() &&
59658
- this.component.searchField &&
59659
- this.dataValue) {
59660
- return false;
60477
+ this.isEntireObjectDisplay() &&
60478
+ this.component.searchField &&
60479
+ this.dataValue) {
60480
+ return false;
59661
60481
  }
59662
60482
 
59663
60483
  return super.shouldLoad;
@@ -59692,7 +60512,7 @@ class SmartSelectComponent extends SmartSelectField {
59692
60512
  if (this.component.multiple && _.isArray(this.dataValue) ? this.dataValue.find((val) => value === val) : (this.dataValue === value)) {
59693
60513
  const selectData = this.selectData;
59694
60514
  if (selectData) {
59695
- const templateValue = this.component.reference && value && (value._id ? value._id.toString() : value);
60515
+ const templateValue = this.component.reference && value && (value._id ? value._id.toString() : value);
59696
60516
  if (!this.templateData || !this.templateData[templateValue]) {
59697
60517
  this.getOptionTemplate(data, value);
59698
60518
  }
@@ -59719,7 +60539,7 @@ class SmartSelectComponent extends SmartSelectField {
59719
60539
  });
59720
60540
  }
59721
60541
 
59722
- if (data.data) {
60542
+ if (data.data) {
59723
60543
  // checking additional fields in the template for the selected Entire Object option
59724
60544
  const hasNestedFields = /item\\.data\\.\\w*/g.test(this.component.template);
59725
60545
  data.data = this.isEntireObjectDisplay() && _.isObject(data.data) && !hasNestedFields ?
@@ -59738,8 +60558,8 @@ class SmartSelectComponent extends SmartSelectField {
59738
60558
  hasTranslator = this.i18next.translator;
59739
60559
  }
59740
60560
  if (!label || (hasTranslator && !this.t(label, {
59741
- _userInput: true
59742
- }))) return;
60561
+ _userInput: true
60562
+ }))) return;
59743
60563
  return hasTranslator ? template.replace(label, this.t(label, {
59744
60564
  _userInput: true
59745
60565
  })) : label;
@@ -59766,7 +60586,7 @@ class SmartSelectComponent extends SmartSelectField {
59766
60586
  // ...idPath
59767
60587
  // };
59768
60588
 
59769
- const option = Object.assign({
60589
+ const option = Object.assign({
59770
60590
  value: this.getOptionValue(value),
59771
60591
  label,
59772
60592
  }, idPath);
@@ -59922,7 +60742,7 @@ class SmartSelectComponent extends SmartSelectField {
59922
60742
  if (this.root && this.root.options.editForm && this.root.options.editForm._id && this.root.options.editForm._id === item._id) return;
59923
60743
  const itemValueAndLabel = this.selectValueAndLabel(item);
59924
60744
  this.addOption(itemValueAndLabel.value, itemValueAndLabel.label, {}, _.get(item, this.component.idPath, String(index)));
59925
-
60745
+
59926
60746
  });
59927
60747
  if (this.choices) {
59928
60748
  if ((this.component.dataSrc === 'masterdata') && this.component.masterdata) {
@@ -59964,10 +60784,10 @@ class SmartSelectComponent extends SmartSelectField {
59964
60784
  // If a value is provided, then select it.
59965
60785
  if (!this.isEmpty()) {
59966
60786
  if (localStorage.getItem('renderMode') === 'html') {
59967
- var tmplt = this.component.template;
59968
- var str1 = tmplt.substring(tmplt.lastIndexOf('.') + 1, tmplt.length);
59969
- var label = str1.substring(0, str1.indexOf('}'), str1.length);
59970
- label = label.trim();
60787
+ var tmplt = this.component.template;
60788
+ var str1 = tmplt.substring(tmplt.lastIndexOf('.') + 1, tmplt.length);
60789
+ var label = str1.substring(0, str1.indexOf('}'), str1.length);
60790
+ label = label.trim();
59971
60791
 
59972
60792
  if (items.length > 0) {
59973
60793
  if (this.component.multiple) {
@@ -59980,13 +60800,13 @@ class SmartSelectComponent extends SmartSelectField {
59980
60800
  });
59981
60801
  } else {
59982
60802
  }
60803
+ }
60804
+ } else {
60805
+ this.setValue(this.dataValue, {
60806
+ noUpdateEvent: true
60807
+ });
59983
60808
  }
59984
- }else{
59985
- this.setValue(this.dataValue, {
59986
- noUpdateEvent: true
59987
- });
59988
- }
59989
- }else if (this.shouldAddDefaultValue && !this.options.readOnly) {
60809
+ } else if (this.shouldAddDefaultValue && !this.options.readOnly) {
59990
60810
  // If a default value is provided then select it.
59991
60811
  const defaultValue = this.defaultValue;
59992
60812
  if (!this.isEmpty(defaultValue)) {
@@ -60293,32 +61113,32 @@ class SmartSelectComponent extends SmartSelectField {
60293
61113
  } else {
60294
61114
  if (!this.component.dropdownOpened && this.data[this.key] && String(this.data[this.key]).length > 0) {
60295
61115
  let result = [];
60296
- if(this.component.multiple && this.component.masterdata){
60297
- this.data[this.key].forEach(item =>
61116
+ if (this.component.multiple && this.component.masterdata) {
61117
+ this.data[this.key].forEach(item =>
60298
61118
  this.component.masterdata.filter((value) => {
60299
61119
  const propValue = value[this.component.valueProperty];
60300
- // Convert to string only if necessary
60301
- const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
60302
- if(valPropData.includes(item)){
61120
+ // Convert to string only if necessary
61121
+ const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
61122
+ if (valPropData.includes(item)) {
60303
61123
  result.push(value)
60304
61124
  }
60305
61125
  })
60306
61126
  );
60307
61127
  // result = this.component.masterdata;
60308
- }else{
60309
- if(this.component.masterdata){
60310
- result = this.component.masterdata.filter(value => {
60311
- const propValue = value[this.component.valueProperty];
60312
- // Convert to string only if necessary
60313
- const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
60314
- return valPropData?.includes(this.data[this.key]);
60315
- });
60316
- }
61128
+ } else {
61129
+ if (this.component.masterdata) {
61130
+ result = this.component.masterdata.filter(value => {
61131
+ const propValue = value[this.component.valueProperty];
61132
+ // Convert to string only if necessary
61133
+ const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
61134
+ return valPropData?.includes(this.data[this.key]);
61135
+ });
61136
+ }
60317
61137
  }
60318
61138
  const unique = result.filter((obj, index) => {
60319
61139
  return index === result.findIndex(o => obj[this.component.valueProperty] === o[this.component.valueProperty]);
60320
- });
60321
- this.setItems(unique);
61140
+ });
61141
+ this.setItems(unique);
60322
61142
  }
60323
61143
  }
60324
61144
  }
@@ -60479,9 +61299,9 @@ class SmartSelectComponent extends SmartSelectField {
60479
61299
  get active() {
60480
61300
  if ((this.component.dataSrc === 'masterdata') && this.component.masterdata) {
60481
61301
  return !this.component.lazyLoad || this.activated;
60482
- }else{
60483
- // return !this.component.lazyLoad || this.activated || this.options.readOnly;
60484
- return !this.component.lazyLoad || this.activated;
61302
+ } else {
61303
+ // return !this.component.lazyLoad || this.activated || this.options.readOnly;
61304
+ return !this.component.lazyLoad || this.activated;
60485
61305
  }
60486
61306
  }
60487
61307
 
@@ -60555,8 +61375,8 @@ class SmartSelectComponent extends SmartSelectField {
60555
61375
  searchChoices: !this.component.searchField,
60556
61376
  searchFields: _.get(this, 'component.searchFields', ['label']),
60557
61377
  shadowRoot: this.root ? this.root.shadowRoot : null,
60558
- fuseOptions: this.component.useExactSearch ? Object.assign(fuseObj, commonFuseOptions) : Object.assign({},
60559
- _.get(this, 'component.fuseOptions', {}),
61378
+ fuseOptions: this.component.useExactSearch ? Object.assign(fuseObj, commonFuseOptions) : Object.assign({},
61379
+ _.get(this, 'component.fuseOptions', {}),
60560
61380
  Object.assign(fuseObj1, commonFuseOptions)
60561
61381
  ),
60562
61382
  valueComparer: _.isEqual,
@@ -60724,17 +61544,17 @@ class SmartSelectComponent extends SmartSelectField {
60724
61544
  }
60725
61545
  this.isFromSearch = false;
60726
61546
  });
60727
- // avoid spamming the resource/url endpoint when we have server side filtering enabled.
60728
- const debounceTimeout = this.component.searchField && (this.isSelectResource || this.isSelectURL) ?
60729
- (this.component.searchDebounce === 0 ? 0 : this.component.searchDebounce || this.defaultSchema.searchDebounce) * 1000
60730
- : 0;
60731
- const updateComponent = (evt) => {
60732
- this.triggerUpdate(evt.detail.value);
60733
- };
60734
- this.addEventListener(input, 'search', _.debounce((e) => {
60735
- updateComponent(e);
60736
- this.positionDropdown();
60737
- }, debounceTimeout));
61547
+ // avoid spamming the resource/url endpoint when we have server side filtering enabled.
61548
+ const debounceTimeout = this.component.searchField && (this.isSelectResource || this.isSelectURL) ?
61549
+ (this.component.searchDebounce === 0 ? 0 : this.component.searchDebounce || this.defaultSchema.searchDebounce) * 1000
61550
+ : 0;
61551
+ const updateComponent = (evt) => {
61552
+ this.triggerUpdate(evt.detail.value);
61553
+ };
61554
+ this.addEventListener(input, 'search', _.debounce((e) => {
61555
+ updateComponent(e);
61556
+ this.positionDropdown();
61557
+ }, debounceTimeout));
60738
61558
 
60739
61559
  this.addEventListener(input, 'stopSearch', () => this.triggerUpdate());
60740
61560
  this.addEventListener(input, 'hideDropdown', () => {
@@ -60749,9 +61569,9 @@ class SmartSelectComponent extends SmartSelectField {
60749
61569
  if (this.data[this.key] && String(this.data[this.key]).length > 0) {
60750
61570
  selData = this.component.masterdata.filter((value) => {
60751
61571
  const propValue = value[this.component.valueProperty];
60752
- // Convert to string only if necessary
60753
- const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
60754
- return valPropData?.includes(this.data[this.key]);
61572
+ // Convert to string only if necessary
61573
+ const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
61574
+ return valPropData?.includes(this.data[this.key]);
60755
61575
 
60756
61576
  })
60757
61577
  }
@@ -60844,10 +61664,10 @@ class SmartSelectComponent extends SmartSelectField {
60844
61664
  setDropdownPosition() {
60845
61665
  let dropdown;
60846
61666
  let container;
60847
- if(this.choices && this.choices.dropdown && this.choices.dropdown.element){
60848
- dropdown = this.choices.dropdown.element;
61667
+ if (this.choices && this.choices.dropdown && this.choices.dropdown.element) {
61668
+ dropdown = this.choices.dropdown.element;
60849
61669
  }
60850
- if(this.choices && this.choices.containerOuter && this.choices.containerOuter.element){
61670
+ if (this.choices && this.choices.containerOuter && this.choices.containerOuter.element) {
60851
61671
  container = this.choices.containerOuter.element;
60852
61672
  }
60853
61673
 
@@ -60932,9 +61752,9 @@ class SmartSelectComponent extends SmartSelectField {
60932
61752
  if (this.data[this.key] && String(this.data[this.key]).length > 0) {
60933
61753
  selData = this.component.masterdata.filter((value) => {
60934
61754
  const propValue = value[this.component.valueProperty];
60935
- // Convert to string only if necessary
60936
- const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
60937
- return valPropData?.includes(this.data[this.key]);
61755
+ // Convert to string only if necessary
61756
+ const valPropData = typeof propValue !== 'string' ? String(propValue) : propValue;
61757
+ return valPropData?.includes(this.data[this.key]);
60938
61758
  })
60939
61759
  }
60940
61760
 
@@ -61285,29 +62105,29 @@ class SmartSelectComponent extends SmartSelectField {
61285
62105
 
61286
62106
  setValue(value, flags = {}) {
61287
62107
  // console.log("value line 1875" + this.component.label, value)
61288
- if(value && String(value).length > 0){
62108
+ if (value && String(value).length > 0) {
61289
62109
  $(".barcode-select-error").remove();
61290
62110
  }
61291
- if (localStorage.getItem('renderMode') === 'html' && this.component.dataSrc === 'values') {
61292
- this.component.data.values.filter((data) => {
61293
- if ((value && data.value === value) || (value && data.value === String(value))) {
61294
- value = data.label;
61295
- };
61296
- })
62111
+ if (localStorage.getItem('renderMode') === 'html' && this.component.dataSrc === 'values') {
62112
+ this.component.data.values.filter((data) => {
62113
+ if ((value && data.value === value) || (value && data.value === String(value))) {
62114
+ value = data.label;
62115
+ };
62116
+ })
62117
+ }
62118
+ if (localStorage.getItem('renderMode') === 'html' && this.component.dataSrc === 'masterdata' && !this.component.multiple) {
62119
+ var tmplt = this.component.template;
62120
+ var str1 = tmplt.substring(tmplt.lastIndexOf('.') + 1, tmplt.length);
62121
+ var label = str1.substring(0, str1.indexOf('}'), str1.length);
62122
+ label = label.trim();
62123
+
62124
+ this.component.masterdata.filter((data) => {
62125
+ if ((value && data[this.component.valueProperty] === value) || (value && data[this.component.valueProperty] === String(value))) {
62126
+ value = data[label];
62127
+ }
62128
+ })
61297
62129
  }
61298
- if (localStorage.getItem('renderMode') === 'html' && this.component.dataSrc === 'masterdata' && !this.component.multiple) {
61299
- var tmplt = this.component.template;
61300
- var str1 = tmplt.substring(tmplt.lastIndexOf('.') + 1, tmplt.length);
61301
- var label = str1.substring(0, str1.indexOf('}'), str1.length);
61302
- label = label.trim();
61303
62130
 
61304
- this.component.masterdata.filter((data) => {
61305
- if ((value && data[this.component.valueProperty] === value) || (value && data[this.component.valueProperty] === String(value))) {
61306
- value = data[label];
61307
- }
61308
- })
61309
- }
61310
-
61311
62131
  const previousValue = this.dataValue;
61312
62132
  // console.log("value line 1900 " + this.component.label, value)
61313
62133
  const changed = this.updateValue(value, flags);
@@ -61319,10 +62139,10 @@ class SmartSelectComponent extends SmartSelectField {
61319
62139
  if (this.component.multiple && Array.isArray(value)) {
61320
62140
  value = value.map(value => {
61321
62141
  if (typeof value === 'boolean' || typeof value === 'number') {
61322
- console.log("value line 1910 " + this.component.label, value)
62142
+ console.log("value line 1910 " + this.component.label, value)
61323
62143
  return value.toString();
61324
62144
  }
61325
- console.log("value line 1914 " + this.component.label, value)
62145
+ console.log("value line 1914 " + this.component.label, value)
61326
62146
  return value;
61327
62147
  });
61328
62148
  } else {
@@ -61354,10 +62174,10 @@ class SmartSelectComponent extends SmartSelectField {
61354
62174
  // Add the value options.
61355
62175
  this.itemsLoaded.then(() => {
61356
62176
  this.addValueOptions();
61357
- // console.log("value line 1945 " + this.component.label, value)
62177
+ // console.log("value line 1945 " + this.component.label, value)
61358
62178
  this.setChoicesValue(value, hasPreviousValue, flags);
61359
62179
  });
61360
-
62180
+
61361
62181
  return changed;
61362
62182
  }
61363
62183
 
@@ -61404,7 +62224,7 @@ class SmartSelectComponent extends SmartSelectField {
61404
62224
  if (hasValue) {
61405
62225
  const values = Array.isArray(value) ? value : [value];
61406
62226
  if (!_.isEqual(this.dataValue, this.defaultValue) && this.selectOptions.length < 2
61407
- || (this.selectData && flags.fromSubmission)) {
62227
+ || (this.selectData && flags.fromSubmission)) {
61408
62228
  const { value, label } = this.selectValueAndLabel(this.dataValue);
61409
62229
  this.addOption(value, label);
61410
62230
  }
@@ -61477,14 +62297,14 @@ class SmartSelectComponent extends SmartSelectField {
61477
62297
  */
61478
62298
  getOptionValue(value) {
61479
62299
  return _.isObject(value) && this.isEntireObjectDisplay()
61480
- ? this.normalizeSingleValue(value)
61481
- : _.isObject(value) && (this.valueProperty || this.component.key !== 'resource')
61482
- ? value
61483
- : _.isObject(value) && !this.valueProperty
61484
- ? this.interpolate(this.component.template, { item: value }).replace(/<\\/?[^>]+(>|$)/g, '')
61485
- : _.isNull(value)
61486
- ? this.emptyValue
61487
- : String(this.normalizeSingleValue(value));
62300
+ ? this.normalizeSingleValue(value)
62301
+ : _.isObject(value) && (this.valueProperty || this.component.key !== 'resource')
62302
+ ? value
62303
+ : _.isObject(value) && !this.valueProperty
62304
+ ? this.interpolate(this.component.template, { item: value }).replace(/<\\/?[^>]+(>|$)/g, '')
62305
+ : _.isNull(value)
62306
+ ? this.emptyValue
62307
+ : String(this.normalizeSingleValue(value));
61488
62308
  }
61489
62309
 
61490
62310
  /**
@@ -61600,19 +62420,19 @@ class SmartSelectComponent extends SmartSelectField {
61600
62420
  items: convertToString(this.getNormalizedValues(), 'value'),
61601
62421
  valueProperty: 'value',
61602
62422
  } : {
61603
- items: convertToString(this.getCustomItems(), this.valueProperty),
61604
- valueProperty: this.valueProperty,
61605
- };
62423
+ items: convertToString(this.getCustomItems(), this.valueProperty),
62424
+ valueProperty: this.valueProperty,
62425
+ };
61606
62426
  const getFromValues = () => {
61607
62427
  const initialValue = _.find(items, [valueProperty, value]);
61608
62428
  const values = this.defaultSchema.data.values || [];
61609
62429
  return _.isEqual(initialValue, values[0]) ? '-' : initialValue;
61610
62430
  };
61611
62431
  value = (this.component.multiple && Array.isArray(value))
61612
- ? _.filter(items, (item) => value.includes(item.value))
61613
- : valueProperty
61614
- ? getFromValues() ?? { value, label: value }
61615
- : value;
62432
+ ? _.filter(items, (item) => value.includes(item.value))
62433
+ : valueProperty
62434
+ ? getFromValues() ?? { value, label: value }
62435
+ : value;
61616
62436
  }
61617
62437
 
61618
62438
  if (_.isString(value)) {
@@ -61628,7 +62448,7 @@ class SmartSelectComponent extends SmartSelectField {
61628
62448
  if (Array.isArray(value)) {
61629
62449
  const items = [];
61630
62450
  value.forEach(item => items.push(getTemplateValue(item)));
61631
- if (this.component.dataSrc === 'resource' && items.length > 0 ) {
62451
+ if (this.component.dataSrc === 'resource' && items.length > 0) {
61632
62452
  return items.join(', ');
61633
62453
  }
61634
62454
  else if (items.length > 0) {
@@ -64975,12 +65795,27 @@ function FormOnBackNavigation() {
64975
65795
  function initialButtonSetup(privateExternal, permission) {
64976
65796
  const saveBtn = document.getElementById("saveBtn");
64977
65797
  const submitBtn = document.getElementById("submitBtn");
65798
+ const completeOption = document.getElementById("completeOption");
65799
+ const saveOption = document.getElementById("saveOption");
65800
+ const completeDivider = document.getElementById("completeDivider");
65801
+ const saveDivider = document.getElementById("saveDivider");
65802
+
64978
65803
  if (!saveBtn || !submitBtn) return;
64979
65804
 
64980
65805
  const isPrivate = privateExternal === true || privateExternal === "true";
64981
65806
  const isPublic = privateExternal === false || privateExternal === "false";
64982
65807
  const permissionMode = String(permission || "").toLowerCase();
64983
65808
 
65809
+ // Helper to toggle dropdown items
65810
+ const toggleDropdown = (option, divider, show) => {
65811
+ if (option) option.style.display = show ? "block" : "none";
65812
+ if (divider) divider.style.display = show ? "block" : "none";
65813
+ };
65814
+
65815
+ // Default: Hide dropdown specific items
65816
+ toggleDropdown(completeOption, completeDivider, false);
65817
+ toggleDropdown(saveOption, saveDivider, false);
65818
+
64984
65819
  // READ ONLY
64985
65820
  if (permissionMode === "read") {
64986
65821
  console.log("Mode: READ \u2192 Hide all buttons");
@@ -64988,31 +65823,72 @@ function initialButtonSetup(privateExternal, permission) {
64988
65823
  submitBtn.style.display = "none";
64989
65824
  }
64990
65825
 
64991
- // PUBLIC FORM
64992
- else if (
64993
- isPublic &&
64994
- (permissionMode === "writesingle" || permissionMode === "writemultiple")
64995
- ) {
64996
- console.log("Mode: PUBLIC WRITE \u2192 Show submit (disabled)");
65826
+ // PUBLIC FORM - writemultiple
65827
+ else if (isPublic && permissionMode === "writemultiple") {
65828
+ console.log("Mode: PUBLIC WRITEMULTIPLE \u2192 Show Complete button only (disabled)");
64997
65829
  saveBtn.style.display = "none";
64998
65830
  submitBtn.style.display = "";
65831
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
64999
65832
  submitBtn.disabled = true;
65833
+
65834
+ // No dropdown options for public writemultiple
65835
+ toggleDropdown(completeOption, completeDivider, false);
65836
+ toggleDropdown(saveOption, saveDivider, false);
65000
65837
  }
65001
65838
 
65002
- // PRIVATE FORM - writemultiple
65003
- else if (isPrivate && permissionMode === "writemultiple") {
65004
- console.log("Mode: PRIVATE WRITEMULTIPLE \u2192 Show submit (disabled)");
65839
+ // PUBLIC FORM - writesingle
65840
+ else if (isPublic && permissionMode === "writesingle") {
65841
+ console.log("Mode: PUBLIC WRITESINGLE \u2192 Show submit (disabled)");
65005
65842
  saveBtn.style.display = "none";
65006
65843
  submitBtn.style.display = "";
65844
+ submitBtn.innerHTML = '<i class="icon save"></i> Submit';
65007
65845
  submitBtn.disabled = true;
65008
65846
  }
65009
65847
 
65848
+ // PRIVATE FORM - writemultiple
65849
+ else if (isPrivate && permissionMode === "writemultiple") {
65850
+ const showCompleteAlways =
65851
+ window.__unviredFormsOptions?.showCompleteAlways === true ||
65852
+ window.__unviredFormsOptions?.showCompleteAlways === "true";
65853
+
65854
+ if (showCompleteAlways) {
65855
+ console.log(
65856
+ "Mode: PRIVATE WRITEMULTIPLE (ShowCompleteAlways) \u2192 Show Save and Complete"
65857
+ );
65858
+ saveBtn.style.display = "";
65859
+ submitBtn.style.display = "";
65860
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
65861
+
65862
+ saveBtn.disabled = true; // Initially empty
65863
+ submitBtn.disabled = true; // Initially not complete
65864
+
65865
+ toggleDropdown(completeOption, completeDivider, false);
65866
+ toggleDropdown(saveOption, saveDivider, false);
65867
+ } else {
65868
+ console.log(
65869
+ "Mode: PRIVATE WRITEMULTIPLE \u2192 Show save (disabled), Complete hidden initially"
65870
+ );
65871
+ saveBtn.style.display = "";
65872
+ submitBtn.style.display = "none";
65873
+ saveBtn.disabled = true;
65874
+
65875
+ // Initially hide Complete in Dropdown (will show when any field is filled)
65876
+ toggleDropdown(completeOption, completeDivider, false);
65877
+ }
65878
+ }
65879
+
65010
65880
  // PRIVATE FORM - writesingle
65011
65881
  else if (isPrivate && permissionMode === "writesingle") {
65012
- console.log("Mode: PRIVATE WRITESINGLE \u2192 Show save (disabled)");
65013
- saveBtn.style.display = "";
65014
- submitBtn.style.display = "none";
65015
- saveBtn.disabled = true;
65882
+ console.log(
65883
+ "Mode: PRIVATE WRITESINGLE \u2192 Show submit (disabled), Save in Dropdown"
65884
+ );
65885
+ saveBtn.style.display = "none";
65886
+ submitBtn.style.display = "";
65887
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
65888
+ submitBtn.disabled = true;
65889
+
65890
+ // Show Save in Dropdown
65891
+ toggleDropdown(saveOption, saveDivider, true);
65016
65892
  }
65017
65893
 
65018
65894
  // DEFAULT / UNKNOWN
@@ -65020,8 +65896,14 @@ function initialButtonSetup(privateExternal, permission) {
65020
65896
  console.log("Mode: DEFAULT / UNKNOWN \u2192 Show submit (disabled)");
65021
65897
  saveBtn.style.display = "none";
65022
65898
  submitBtn.style.display = "";
65899
+ submitBtn.innerHTML = '<i class="icon clipboard large"></i> Submit';
65023
65900
  submitBtn.disabled = true;
65024
65901
  }
65902
+
65903
+ // Check if More button should be visible based on dropdown content
65904
+ if (typeof window.checkMoreButtonVisibility === "function") {
65905
+ window.checkMoreButtonVisibility();
65906
+ }
65025
65907
  }
65026
65908
 
65027
65909
  function onChangeButtonSetup(
@@ -65043,10 +65925,16 @@ function onChangeButtonSetup(
65043
65925
  v !== undefined &&
65044
65926
  v !== null &&
65045
65927
  v !== "" &&
65046
- !(Array.isArray(v) && v.length === 0)
65928
+ v !== false &&
65929
+ !(Array.isArray(v) && v.length === 0) &&
65930
+ !(typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0)
65047
65931
  );
65048
65932
  const isComplete = completionPercentage >= 100;
65049
65933
 
65934
+ const requireComplete =
65935
+ window.__unviredFormsOptions?.requireCompleteForm === true ||
65936
+ window.__unviredFormsOptions?.requireCompleteForm === "true";
65937
+
65050
65938
  // READ ONLY
65051
65939
  if (permissionMode === "read") {
65052
65940
  console.log("Mode: READ \u2192 Hide all buttons");
@@ -65058,38 +65946,121 @@ function onChangeButtonSetup(
65058
65946
  // PRIVATE FORM - writesingle
65059
65947
  if (isPrivate && permissionMode === "writesingle") {
65060
65948
  console.log("Mode: PRIVATE WRITESINGLE");
65949
+ // Main: Complete (Submit)
65061
65950
  saveBtn.style.display = "none";
65062
65951
  submitBtn.style.display = "";
65063
- submitBtn.disabled = !hasAnyValue;
65952
+
65953
+ // Logic: Disable Complete until 100% if required, otherwise just check if has values
65954
+ submitBtn.disabled = requireComplete ? !isComplete : !hasAnyValue;
65955
+
65956
+ // Check if More button should be visible
65957
+ if (typeof window.checkMoreButtonVisibility === "function") {
65958
+ window.checkMoreButtonVisibility();
65959
+ }
65064
65960
  return;
65065
65961
  }
65066
65962
 
65067
65963
  // PRIVATE FORM - writemultiple
65068
65964
  if (isPrivate && permissionMode === "writemultiple") {
65069
65965
  console.log("Mode: PRIVATE WRITEMULTIPLE");
65070
- if (isComplete) {
65071
- console.log("\u2192 Complete: Show submit");
65072
- saveBtn.style.display = "none";
65073
- submitBtn.style.display = "";
65074
- submitBtn.disabled = false;
65075
- } else {
65076
- console.log("\u2192 Incomplete: Show save");
65966
+
65967
+ const completeOption = document.getElementById("completeOption");
65968
+ const saveOption = document.getElementById("saveOption");
65969
+ const completeDivider = document.getElementById("completeDivider");
65970
+ const saveDivider = document.getElementById("saveDivider");
65971
+
65972
+ // Helper to toggle dropdown items
65973
+ const toggleDropdown = (option, divider, show) => {
65974
+ if (option) option.style.display = show ? "block" : "none";
65975
+ if (divider) divider.style.display = show ? "block" : "none";
65976
+ };
65977
+
65978
+ const showCompleteAlways =
65979
+ window.__unviredFormsOptions?.showCompleteAlways === true ||
65980
+ window.__unviredFormsOptions?.showCompleteAlways === "true";
65981
+
65982
+ if (showCompleteAlways) {
65077
65983
  saveBtn.style.display = "";
65078
- submitBtn.style.display = "none";
65984
+ submitBtn.style.display = "";
65985
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
65986
+
65079
65987
  saveBtn.disabled = !hasAnyValue;
65988
+ submitBtn.disabled = requireComplete ? !isComplete : !hasAnyValue;
65989
+
65990
+ toggleDropdown(completeOption, completeDivider, false);
65991
+ toggleDropdown(saveOption, saveDivider, false);
65992
+ } else {
65993
+ if (isComplete) {
65994
+ // 100% Complete: Main button = Complete, Dropdown = Save
65995
+ console.log(
65996
+ " \u2192 100% Complete: Showing Complete button, Save in dropdown"
65997
+ );
65998
+ saveBtn.style.display = "none";
65999
+ submitBtn.style.display = "";
66000
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
66001
+ submitBtn.disabled = false;
66002
+
66003
+ // Show Save in dropdown, hide Complete
66004
+ toggleDropdown(saveOption, saveDivider, true);
66005
+ toggleDropdown(completeOption, completeDivider, false);
66006
+ } else if (hasAnyValue) {
66007
+ // 1-99% Complete: Main button = Save, Dropdown = Complete
66008
+ console.log(
66009
+ " \u2192 Partially filled: Showing Save button, Complete in dropdown"
66010
+ );
66011
+ saveBtn.style.display = "";
66012
+ submitBtn.style.display = "none";
66013
+ saveBtn.disabled = false;
66014
+
66015
+ // Show Complete in dropdown, hide Save
66016
+ toggleDropdown(completeOption, completeDivider, true);
66017
+ toggleDropdown(saveOption, saveDivider, false);
66018
+ } else {
66019
+ // 0% Complete: Main button = Save (disabled), Complete hidden
66020
+ console.log(" \u2192 Empty form: Save disabled, Complete hidden");
66021
+ saveBtn.style.display = "";
66022
+ submitBtn.style.display = "none";
66023
+ saveBtn.disabled = true;
66024
+
66025
+ // Hide both in dropdown
66026
+ toggleDropdown(completeOption, completeDivider, false);
66027
+ toggleDropdown(saveOption, saveDivider, false);
66028
+ }
66029
+ }
66030
+
66031
+ // Check if More button should be visible based on dropdown content
66032
+ if (typeof window.checkMoreButtonVisibility === "function") {
66033
+ window.checkMoreButtonVisibility();
65080
66034
  }
65081
66035
  return;
65082
66036
  }
65083
66037
 
65084
- // PUBLIC FORM
65085
- if (
65086
- isPublic &&
65087
- (permissionMode === "writesingle" || permissionMode === "writemultiple")
65088
- ) {
65089
- console.log("Mode: PUBLIC WRITE \u2192 Show submit");
66038
+ // PUBLIC FORM - writemultiple (Simple: Only Complete button)
66039
+ if (isPublic && permissionMode === "writemultiple") {
66040
+ console.log("Mode: PUBLIC WRITEMULTIPLE \u2192 Single Complete button");
65090
66041
  saveBtn.style.display = "none";
65091
66042
  submitBtn.style.display = "";
66043
+ submitBtn.innerHTML = '<i class="icon clipboard check large"></i> Complete';
65092
66044
  submitBtn.disabled = !hasAnyValue;
66045
+
66046
+ // Check if More button should be visible
66047
+ if (typeof window.checkMoreButtonVisibility === "function") {
66048
+ window.checkMoreButtonVisibility();
66049
+ }
66050
+ return;
66051
+ }
66052
+
66053
+ // PUBLIC FORM - writesingle
66054
+ if (isPublic && permissionMode === "writesingle") {
66055
+ console.log("Mode: PUBLIC WRITESINGLE \u2192 Show submit");
66056
+ saveBtn.style.display = "none";
66057
+ submitBtn.style.display = "";
66058
+ submitBtn.disabled = !hasAnyValue;
66059
+
66060
+ // Check if More button should be visible
66061
+ if (typeof window.checkMoreButtonVisibility === "function") {
66062
+ window.checkMoreButtonVisibility();
66063
+ }
65093
66064
  return;
65094
66065
  }
65095
66066
 
@@ -65098,6 +66069,11 @@ function onChangeButtonSetup(
65098
66069
  saveBtn.style.display = "none";
65099
66070
  submitBtn.style.display = "";
65100
66071
  submitBtn.disabled = !hasAnyValue;
66072
+
66073
+ // Check if More button should be visible based on dropdown content
66074
+ if (typeof window.checkMoreButtonVisibility === "function") {
66075
+ window.checkMoreButtonVisibility();
66076
+ }
65101
66077
  }
65102
66078
 
65103
66079
  // Main form loader
@@ -65122,7 +66098,22 @@ async function loadRNform(
65122
66098
 
65123
66099
  // document.getElementById("formio-cmt").innerHTML = "";
65124
66100
 
65125
- Formio.use(window.semantic.default || window.semantic);
66101
+ // Formio should already be loaded by index.js
66102
+ if (typeof Formio === "undefined") {
66103
+ console.error("\u274C Formio library not loaded");
66104
+ sendEventCallback({
66105
+ type: "ERROR",
66106
+ errorMessage: "ErrorCode : 016, Formio library not loaded",
66107
+ data: { reason: "Formio library not loaded" },
66108
+ });
66109
+ return;
66110
+ }
66111
+
66112
+ if (window.semantic) {
66113
+ Formio.use(window.semantic.default || window.semantic);
66114
+ } else {
66115
+ console.warn("Semantics UI not loaded, skipping Formio.use(semantic)");
66116
+ }
65126
66117
 
65127
66118
  try {
65128
66119
  // Determine form configuration based on mode
@@ -65161,12 +66152,29 @@ async function loadRNform(
65161
66152
  formConfig.viewHtml = false;
65162
66153
  }
65163
66154
 
66155
+ const formElement = document.getElementById("formio");
66156
+ if (!formElement) {
66157
+ throw new Error("DOM Element #formio not found");
66158
+ }
66159
+
66160
+ // Sanitize template to prevent auto-submission to Form.io API
66161
+ if (template) {
66162
+ if (template.src) delete template.src;
66163
+ if (template.url) delete template.url;
66164
+ if (template.project) delete template.project;
66165
+ if (template.projectId) delete template.projectId;
66166
+ if (template.projectUrl) delete template.projectUrl;
66167
+ }
66168
+
66169
+ // Create form (single attempt - Formio is already loaded by index.js)
65164
66170
  formObj = await Formio.createForm(
65165
- document.getElementById("formio"),
66171
+ formElement,
65166
66172
  template,
65167
66173
  formConfig
65168
66174
  );
65169
66175
 
66176
+
66177
+
65170
66178
  // Store initial form data after first change event (ensures all defaults applied)
65171
66179
  let initialDataSet = false;
65172
66180
  formObj.on("change", () => {
@@ -65274,6 +66282,9 @@ async function loadRNform(
65274
66282
  };
65275
66283
  }
65276
66284
 
66285
+ // Calculate initial progress immediately
66286
+ executeProgressCalculation(privateExternal, permission);
66287
+
65277
66288
  // Hide FormIO submit button component and wizard buttons in readOnly mode
65278
66289
  if (mode == "readOnly") {
65279
66290
  const submitComponents = document.querySelectorAll(
@@ -65325,12 +66336,19 @@ async function loadRNform(
65325
66336
  if (fileInput) fileInput.setAttribute("accept", "*/*");
65326
66337
  });
65327
66338
  } catch (error) {
65328
- console.error("\u274C Error creating form:", error);
66339
+ console.error("\u274C Error creating form (after retries):", error);
66340
+
66341
+ // Ensure loader is hidden even on error
66342
+ const sdkLoader = document.getElementById('sdk-form-loader');
66343
+ if (sdkLoader) {
66344
+ sdkLoader.classList.add('hidden');
66345
+ }
66346
+
65329
66347
  sendEventCallback({
65330
66348
  type: "ERROR",
65331
66349
  errorMessage:
65332
- "ErrorCode : 005, Form render error. Contact your admin or tech team.",
65333
- data: { technicalError: error },
66350
+ "ErrorCode : 005, Formio not rendered the forms",
66351
+ data: { reason: "Failed to render form after retries", technicalError: error },
65334
66352
  });
65335
66353
  }
65336
66354
  }
@@ -65340,209 +66358,292 @@ let changeTimeout = null;
65340
66358
  let lastDataHash = null;
65341
66359
  let mandatoryFieldsCache = null;
65342
66360
 
65343
- function setupOnChange(privateExternal, permission) {
66361
+ // Build mandatory fields cache once
66362
+ function buildMandatoryFieldsCache() {
66363
+ if (mandatoryFieldsCache) return mandatoryFieldsCache;
66364
+ if (!formObj) return [];
66365
+
66366
+ const cache = [];
66367
+ // Layout components do not have input data themselves but contain children
66368
+ // We traverse them but keep the current path unless the component modifies it?
66369
+ // Actually, standard FormIO layouts don't add to data path, but containers do.
66370
+
66371
+ function cacheMandatoryFields(components, path = "") {
66372
+ if (!Array.isArray(components)) return;
66373
+ components.forEach((comp) => {
66374
+ // 'comp' should be the Component Instance here
66375
+ if (!comp) return;
66376
+
66377
+ // The schema is usually in comp.component, but fallback to comp if it's already a schema (shouldn't be if traversing instances)
66378
+ const schema = comp.component || comp;
66379
+
66380
+ let fieldPath = path;
66381
+
66382
+ // List of components that are "Passthrough" for data path (Layouts)
66383
+ const isLayout = [
66384
+ "columns", "panel", "fieldset", "well", "table", "tabs", "content", "htmlelement"
66385
+ ].includes(schema.type);
66386
+
66387
+ // Determine path
66388
+ if (!isLayout) {
66389
+ fieldPath = path ? \`\${path}.\${schema.key}\` : schema.key;
66390
+ }
66391
+
66392
+ const inputTypes = [
66393
+ "textfield", "textarea", "number", "checkbox", "radio", "select", "selectboxes",
66394
+ "email", "url", "day", "datetime", "file", "datamap", "tree", "survey",
66395
+ "signature", "datagrid", "editgrid", "container"
66396
+ ];
66397
+
66398
+ // CACHE IF REQUIRED
66399
+ if (
66400
+ inputTypes.includes(schema.type) &&
66401
+ schema.validate?.required
66402
+ ) {
66403
+ cache.push({
66404
+ key: schema.key,
66405
+ type: schema.type,
66406
+ path: fieldPath,
66407
+ comp: schema, // The static schema
66408
+ instance: comp, // The runtime component instance (has .visible, .checkCondition)
66409
+ });
66410
+ }
66411
+
66412
+ // RECURSION
66413
+ // We must recurse on the INSTANCE'S components to keep getting instances.
66414
+ // 'comp.components' is the array of child instances on a Container/Panel/etc.
66415
+ // 'comp.columns' is for Columns component, but each col has .components
66416
+
66417
+ // Standard container-like components (Panel, Container, etc.)
66418
+ if (Array.isArray(comp.components)) {
66419
+ cacheMandatoryFields(comp.components, fieldPath);
66420
+ }
66421
+
66422
+ // Columns component handles children differently in instances?
66423
+ // Usually comp.columns is an array of objects which contain .components (instances)
66424
+ if (Array.isArray(comp.columns)) {
66425
+ comp.columns.forEach((col) => {
66426
+ if (Array.isArray(col.components)) {
66427
+ cacheMandatoryFields(col.components, fieldPath);
66428
+ }
66429
+ });
66430
+ }
66431
+
66432
+ // DataGrid/EditGrid rows are generated dynamically.
66433
+ // The \`comp\` instance might have \`rows\` property?
66434
+ // Actually for DataGrid, we iterate the data rows in 'execute', not the components here.
66435
+ // BUT if the DataGrid has static \`components\` (the column definitions), we might need to check them?
66436
+ // The schema has .components (the columns).
66437
+ // The instance has .components (the flattened list of all cell components across all rows? Or just the column patterns?)
66438
+ // For progress calc, we handled DataGrid specifically in \`isFilled\` or by flattening.
66439
+ // If we want to support required fields INSIDE a datagrid row, we need to handle that.
66440
+ // Current logic: We treat DataGrid as a single "filled" item if it has rows.
66441
+ // We do NOT currently check if fields INSIDE the row are filled if the datagrid itself is "filled".
66442
+ // Users issue implies specific internal fields.
66443
+
66444
+ // NOTE: User's reported "Total mandatory: 23" suggests individual fields are counted.
66445
+ // If \`comp.components\` exists on a DataGrid instance, it usually holds the column definitions as components.
66446
+ // Let's stick to standard recursion for now.
66447
+ });
66448
+ }
66449
+
66450
+ cacheMandatoryFields(formObj.components);
66451
+ mandatoryFieldsCache = cache;
66452
+ return cache;
66453
+ }
66454
+
66455
+ function executeProgressCalculation(privateExternal, permission) {
65344
66456
  if (!formObj) return;
65345
66457
 
65346
- // Build mandatory fields cache once
65347
- function buildMandatoryFieldsCache() {
65348
- if (mandatoryFieldsCache) return mandatoryFieldsCache;
65349
-
65350
- const cache = [];
65351
-
65352
- function cacheMandatoryFields(components, path = "") {
65353
- components.forEach((comp) => {
65354
- if (!comp) return;
65355
- const actualComp = comp.component || comp;
65356
- const fieldPath = path ? \`\${path}.\${actualComp.key}\` : actualComp.key;
65357
-
65358
- const inputTypes = [
65359
- "textfield",
65360
- "textarea",
65361
- "number",
65362
- "checkbox",
65363
- "radio",
65364
- "select",
65365
- "selectboxes",
65366
- "email",
65367
- "url",
65368
- "day",
65369
- "datetime",
65370
- "file",
65371
- "datamap",
65372
- "tree",
65373
- "survey",
65374
- "signature",
65375
- ];
66458
+ const data = formObj.data || {};
66459
+ const dataHash = JSON.stringify(data);
65376
66460
 
65377
- if (
65378
- inputTypes.includes(actualComp.type) &&
65379
- actualComp.validate?.required
65380
- ) {
65381
- cache.push({
65382
- key: actualComp.key,
65383
- type: actualComp.type,
65384
- path: fieldPath,
65385
- comp: actualComp,
65386
- });
65387
- }
66461
+ if (dataHash === lastDataHash) return;
66462
+ lastDataHash = dataHash;
65388
66463
 
65389
- // Recurse into children
65390
- if (Array.isArray(actualComp.components)) {
65391
- cacheMandatoryFields(actualComp.components, fieldPath);
65392
- }
65393
- if (Array.isArray(actualComp.columns)) {
65394
- actualComp.columns.forEach((col) => {
65395
- if (Array.isArray(col.components)) {
65396
- cacheMandatoryFields(col.components, fieldPath);
65397
- }
65398
- });
66464
+ const mandatoryFields = buildMandatoryFieldsCache();
66465
+ let mandatory = mandatoryFields.length;
66466
+ let filledMandatory = 0;
66467
+
66468
+ function isFilled(comp, value) {
66469
+ if (Array.isArray(value)) {
66470
+ // Logic for array values (files, datagrid rows, etc.)
66471
+ const arrayNativeTypes = ['file', 'datagrid', 'editgrid', 'datamap', 'selectboxes'];
66472
+ const isNativeArray = arrayNativeTypes.includes(comp.type) || comp.multiple;
66473
+
66474
+ if (!isNativeArray) {
66475
+ // Assume row-values from extracted path -> all must range
66476
+ return value.length > 0 && value.every(v => isFilled(comp, v));
66477
+ }
66478
+
66479
+ if (comp.type === 'file' && value.length > 0 && Array.isArray(value[0])) {
66480
+ // File inside datagrid?
66481
+ return value.every(v => isFilled(comp, v));
66482
+ }
66483
+ }
66484
+
66485
+ switch (comp.type) {
66486
+ case "selectboxes":
66487
+ return Object.values(value || {}).some(Boolean);
66488
+ case "datamap":
66489
+ case "tree":
66490
+ return value && Object.keys(value).length > 0;
66491
+ case "survey":
66492
+ // Check if all questions in the survey are answered
66493
+ if (comp.questions && Array.isArray(comp.questions)) {
66494
+ return value && Object.keys(value).length >= comp.questions.length;
65399
66495
  }
65400
- if (Array.isArray(actualComp.rows)) {
65401
- actualComp.rows.forEach((row) => {
65402
- if (Array.isArray(row)) {
65403
- row.forEach((cell) => {
65404
- if (Array.isArray(cell.components)) {
65405
- cacheMandatoryFields(cell.components, fieldPath);
65406
- }
65407
- });
65408
- }
65409
- });
66496
+ return value && Object.keys(value).length > 0;
66497
+ case "file":
66498
+ return Array.isArray(value) && value.length > 0;
66499
+ case "checkbox":
66500
+ return value === true;
66501
+ case "editgrid":
66502
+ case "datagrid":
66503
+ return Array.isArray(value) && value.length > 0;
66504
+ case "signature":
66505
+ return !!value;
66506
+ default:
66507
+ return value !== undefined && value !== null && value !== "";
66508
+ }
66509
+ }
66510
+
66511
+ const activeMandatoryFields = [];
66512
+
66513
+ mandatoryFields.forEach((entry) => {
66514
+ // Check conditional visibility using Formio's checkCondition if available, or fallback to visible property
66515
+ // If a component is conditionally hidden, it should not count as mandatory
66516
+ let isVisible = true;
66517
+ if (entry.instance) {
66518
+ // Preferred: usage of Formio's internal checkCondition if accessible
66519
+ // But often checking .visible on the instance is sufficient as Formio updates it
66520
+ if (entry.instance.visible === false) isVisible = false;
66521
+
66522
+ // Double check with checkCondition if available to be sure about complex conditions
66523
+ if (isVisible && typeof entry.instance.checkCondition === 'function') {
66524
+ if (!entry.instance.checkCondition(null, data)) {
66525
+ isVisible = false;
65410
66526
  }
65411
- });
66527
+ }
65412
66528
  }
65413
66529
 
65414
- cacheMandatoryFields(formObj.components);
65415
- mandatoryFieldsCache = cache;
65416
- return cache;
66530
+ if (!isVisible) return;
66531
+
66532
+ // Check if parent is visible (basic check, could be recursive but starting here)
66533
+ if (entry.instance && entry.instance.parent && entry.instance.parent.visible === false) return;
66534
+
66535
+ activeMandatoryFields.push(entry);
66536
+ });
66537
+
66538
+ mandatory = activeMandatoryFields.length;
66539
+ filledMandatory = 0;
66540
+
66541
+ activeMandatoryFields.forEach(({ key, type, path, comp }) => {
66542
+ let value = getValueFromData(data, path);
66543
+ const filled = isFilled(comp, value);
66544
+ if (filled) filledMandatory++;
66545
+ });
66546
+
66547
+ console.log("\u2705 Total mandatory fields:", mandatory);
66548
+ console.log("\u2705 Filled mandatory fields:", filledMandatory);
66549
+ const completion =
66550
+ mandatory === 0 ? 100 : Math.round((filledMandatory / mandatory) * 100);
66551
+ calculationPercentage = completion; // Update global
66552
+ completeFlag = completion >= 100; // Update global
66553
+
66554
+ console.log("\u{1F4CA} Completion %:", completion);
66555
+
66556
+ onChangeButtonSetup(
66557
+ completion,
66558
+ formObj.data,
66559
+ privateExternal,
66560
+ permission
66561
+ );
66562
+
66563
+ // Update progress bar
66564
+ const progressBar = document.getElementById("progress-bar");
66565
+ if (progressBar) {
66566
+ const bar = progressBar.querySelector(".progress");
66567
+ const badge = progressBar.querySelector(".badge");
66568
+
66569
+ bar.style.width = \`\${completion}%\`;
66570
+ badge.textContent = \`\${completion}%\`;
66571
+
66572
+ let left = completion;
66573
+ if (completion <= 0) {
66574
+ left = 0.5;
66575
+ bar.style.width = \`\${0.5}%\`;
66576
+ if (window.innerWidth > 768) badge.style.transform = "translateX(2%)";
66577
+ else if (window.innerWidth > 480) badge.style.transform = "translateX(2%)";
66578
+ else badge.style.transform = "translateX(1%)";
66579
+ } else if (completion >= 100) {
66580
+ left = 99.5;
66581
+ if (window.innerWidth > 768) badge.style.transform = "translateX(-65%)";
66582
+ else if (window.innerWidth > 480) badge.style.transform = "translateX(-85%)";
66583
+ else badge.style.transform = "translateX(-105%)";
66584
+ } else {
66585
+ // Reset transform if not edge case
66586
+ badge.style.transform = "translateX(-50%)"; // Standard centering, though original didn't have this explicitly in else
66587
+ }
66588
+ badge.style.left = \`\${left - 0.25}%\`;
65417
66589
  }
66590
+ }
65418
66591
 
65419
- formObj.on("change", (submission) => {
65420
- // Clear existing timeout
65421
- if (changeTimeout) {
65422
- clearTimeout(changeTimeout);
66592
+
66593
+ // Helper to safely get value from data using path strings, handling arrays (datagrids)
66594
+ function getValueFromData(data, path) {
66595
+ if (data === undefined || data === null || !path) return undefined;
66596
+
66597
+ // If path has no dots, it's a direct key
66598
+ if (path.indexOf('.') === -1) {
66599
+ // Special handling: if data is array, map it (unless path is purely index, but here assuming keys)
66600
+ if (Array.isArray(data)) {
66601
+ return data.map(item => item[path]);
65423
66602
  }
66603
+ return data[path];
66604
+ }
65424
66605
 
65425
- // Debounce the change handler
65426
- changeTimeout = setTimeout(() => {
65427
- const data = submission?.data || formObj.data || {};
65428
- const dataHash = JSON.stringify(data);
65429
-
65430
- // Skip if data hasn't actually changed
65431
- if (dataHash === lastDataHash) return;
65432
- lastDataHash = dataHash;
65433
-
65434
- const mandatoryFields = buildMandatoryFieldsCache();
65435
- let mandatory = mandatoryFields.length;
65436
- let filledMandatory = 0;
65437
-
65438
- function isFilled(comp, value) {
65439
- switch (comp.type) {
65440
- case "selectboxes":
65441
- return Object.values(value || {}).some(Boolean);
65442
- case "datamap":
65443
- case "tree":
65444
- case "survey":
65445
- return value && Object.keys(value).length > 0;
65446
- case "file":
65447
- return Array.isArray(value) && value.length > 0;
65448
- case "checkbox":
65449
- return value === true;
65450
- case "editgrid":
65451
- case "datagrid":
65452
- return Array.isArray(value) && value.length > 0;
65453
- case "signature":
65454
- return !!value;
65455
- default:
65456
- return value !== undefined && value !== null && value !== "";
65457
- }
65458
- }
66606
+ const keys = path.split(".");
66607
+ let current = data;
65459
66608
 
65460
- // Only log and count filled fields
65461
- mandatoryFields.forEach(({ key, type, comp }) => {
65462
- let value;
65463
- try {
65464
- const formComp = formObj.getComponent(key);
65465
- value =
65466
- formComp && typeof formComp.getValue === "function"
65467
- ? formComp.getValue()
65468
- : data[key];
65469
- } catch (e) {
65470
- value = data[key];
65471
- }
66609
+ for (let i = 0; i < keys.length; i++) {
66610
+ const key = keys[i];
66611
+ if (current === undefined || current === null) return undefined;
65472
66612
 
65473
- const filled = isFilled(comp, value);
65474
- if (filled) filledMandatory++;
66613
+ if (Array.isArray(current)) {
66614
+ // We hit an array (e.g. datagrid rows), but we have more keys to traverse.
66615
+ // We must map the rest of the path over each item in the array.
66616
+ const remainingPath = keys.slice(i).join(".");
66617
+ return current.map(item => getValueFromData(item, remainingPath));
66618
+ }
65475
66619
 
65476
- console.log(
65477
- \`\u{1F50E} Field: \${key} | type: \${type} | required: true | value:\`,
65478
- value,
65479
- "| filled:",
65480
- filled
65481
- );
65482
- });
66620
+ current = current[key];
66621
+ }
66622
+ return current;
66623
+ }
65483
66624
 
65484
- console.log("\u2705 Total mandatory fields:", mandatory);
65485
- console.log("\u2705 Filled mandatory fields:", filledMandatory);
65486
- const completion =
65487
- mandatory === 0 ? 100 : Math.round((filledMandatory / mandatory) * 100);
65488
- console.log("\u{1F4CA} Completion %:", completion);
65489
-
65490
- onChangeButtonSetup(
65491
- completion,
65492
- formObj.data,
65493
- privateExternal,
65494
- permission
65495
- );
66625
+ function setupOnChange(privateExternal, permission) {
66626
+ if (!formObj) return;
65496
66627
 
65497
- // Update progress bar if exists
65498
- const progressBar = document.getElementById("progress-bar");
65499
- if (progressBar) {
65500
- const bar = progressBar.querySelector(".progress");
65501
- const badge = progressBar.querySelector(".badge");
65502
-
65503
- // Set bar width
65504
- bar.style.width = \`\${completion}%\`;
65505
-
65506
- // Update badge text
65507
- badge.textContent = \`\${completion}%\`;
65508
-
65509
- // Calculate badge left relative to container
65510
- let left = completion;
65511
-
65512
- // Edge cases: keep badge inside screen
65513
- if (completion <= 0) {
65514
- left = 0.5; // slightly inside
65515
- bar.style.width = \`\${0.5}%\`;
65516
- if (window.innerWidth > 768) {
65517
- badge.style.transform = "translateX(2%)"; // desktop
65518
- } else if (window.innerWidth > 480) {
65519
- badge.style.transform = "translateX(2%)"; // tablet
65520
- } else if (window.innerWidth > 360) {
65521
- badge.style.transform = "translateX(2%)"; // mobile
65522
- } else {
65523
- badge.style.transform = "translateX(1%)"; // very small phone
65524
- }
65525
- }
65526
- if (completion >= 100) {
65527
- left = 99.5; // slightly inside
65528
- // Responsive translateX for different screen widths
65529
- if (window.innerWidth > 768) {
65530
- badge.style.transform = "translateX(-65%)"; // desktop
65531
- } else if (window.innerWidth > 480) {
65532
- badge.style.transform = "translateX(-85%)"; // tablet
65533
- } else if (window.innerWidth > 360) {
65534
- badge.style.transform = "translateX(-95%)"; // mobile
65535
- } else {
65536
- badge.style.transform = "translateX(-105%)"; // very small phone
65537
- }
65538
- }
66628
+ formObj.on("change", (event) => {
66629
+ // Blur datetime components on change to close the native keyboard/picker
66630
+ if (event.changed?.component?.type === "datetime") {
66631
+ document.activeElement.blur();
66632
+ }
65539
66633
 
65540
- badge.style.left = \`\${left - 0.25}%\`;
65541
- }
66634
+ // Clear existing timeout
66635
+ if (changeTimeout) {
66636
+ clearTimeout(changeTimeout);
66637
+ }
66638
+
66639
+ // Invalidate cache to ensure conditional visibility updates are captured
66640
+ mandatoryFieldsCache = null;
66641
+
66642
+ // Debounce the change handler
66643
+ changeTimeout = setTimeout(() => {
66644
+ executeProgressCalculation(privateExternal, permission);
65542
66645
  }, 300); // 300ms debounce delay
65543
66646
  });
65544
-
65545
- // removed ad-hoc setTimeout: rely on formObj.formReady and LessRenderingComplete events
65546
66647
  }
65547
66648
 
65548
66649
  // Function to convert file objects back to IDs
@@ -65603,6 +66704,27 @@ function FormOnSubmit() {
65603
66704
  attachmentfileKeysSDK
65604
66705
  );
65605
66706
 
66707
+ // Check strict completion requirement
66708
+ const requireComplete =
66709
+ window.__unviredFormsOptions?.requireCompleteForm === true ||
66710
+ window.__unviredFormsOptions?.requireCompleteForm === "true";
66711
+
66712
+ if (!requireComplete) {
66713
+ // Bypass validation and submit immediately
66714
+ sendEventCallback({
66715
+ type: FORM_EVENTS.SUBMIT,
66716
+ message: "Success: Form submitted successfully (Validation Bypassed).",
66717
+ data: {
66718
+ formData: processedData,
66719
+ completeFlag,
66720
+ completionPercentage: calculationPercentage,
66721
+ commentsData: FORM_COMMENTS_DATA,
66722
+ result: processedData,
66723
+ },
66724
+ });
66725
+ return;
66726
+ }
66727
+
65606
66728
  formObj
65607
66729
  .submit()
65608
66730
  .then((result) => {
@@ -65620,7 +66742,19 @@ function FormOnSubmit() {
65620
66742
  })
65621
66743
  .catch((error) => {
65622
66744
  console.error(error);
65623
- showDynamicModal("Error", [{ label: "OK" }], "Fill The Required Fields");
66745
+ const errorMsg = (error && error.message) ? error.message : "Please check the form for errors.";
66746
+ showDynamicModal("Error", [{ label: "OK" }], errorMsg);
66747
+ sendEventCallback({
66748
+ type: FORM_EVENTS.SUBMIT_ERROR,
66749
+ errorMessage: "ErrorCode : 006, Formio not submit the form",
66750
+ data: {
66751
+ reason: "Form submission failed",
66752
+ formData: processedData,
66753
+ completeFlag,
66754
+ completionPercentage: calculationPercentage,
66755
+ technicalError: error,
66756
+ },
66757
+ });
65624
66758
  });
65625
66759
  }
65626
66760
 
@@ -65652,9 +66786,7 @@ async function loadComments() {
65652
66786
  window.FORM_VOB_ARR,
65653
66787
  window.FORM_USERS_LIST || []
65654
66788
  );
65655
- setTimeout(() => {
65656
- showCommentsView();
65657
- }, 500);
66789
+ showCommentsView();
65658
66790
  } else {
65659
66791
  console.error("loadCommentsform is not a function");
65660
66792
  }
@@ -65666,6 +66798,22 @@ async function loadComments() {
65666
66798
  function FormOnSave() {
65667
66799
  if (!formObj) return;
65668
66800
 
66801
+ const formData = formObj.getValue().data;
66802
+ const hasAnyValue = Object.values(formData || {}).some(
66803
+ (v) =>
66804
+ v !== undefined &&
66805
+ v !== null &&
66806
+ v !== "" &&
66807
+ v !== false &&
66808
+ !(Array.isArray(v) && v.length === 0) &&
66809
+ !(typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0)
66810
+ );
66811
+
66812
+ if (!hasAnyValue) {
66813
+ showDynamicModal("Attention", [{ label: "OK" }], "Please fill at least one field before saving.");
66814
+ return;
66815
+ }
66816
+
65669
66817
  showDynamicModal("Do you want to save the data?", [
65670
66818
  {
65671
66819
  label: "Yes",
@@ -65682,7 +66830,6 @@ function FormOnSave() {
65682
66830
  completionPercentage: calculationPercentage,
65683
66831
  },
65684
66832
  });
65685
- FormOnBackNavigation();
65686
66833
  },
65687
66834
  },
65688
66835
  {
@@ -65726,6 +66873,58 @@ function setImageData(imageData, id, fileName) {
65726
66873
  }
65727
66874
 
65728
66875
  // ============================================ MODAL ===========================================================
66876
+ // Function to check if More dropdown has any visible items
66877
+ window.checkMoreButtonVisibility = function () {
66878
+ const moreBtn = document.getElementById("unvired-more-btn");
66879
+
66880
+ // Safety check: if button doesn't exist, we can't do anything
66881
+ if (!moreBtn) return;
66882
+
66883
+ const options = window.__unviredFormsOptions || {};
66884
+
66885
+ // If showMoreButton is explicitly false (from options), force hide
66886
+ if (options.showMoreButton === false) {
66887
+ moreBtn.style.display = "none";
66888
+ return;
66889
+ }
66890
+
66891
+ // Get all menu items in the dropdown
66892
+ const completeOption = document.getElementById("completeOption");
66893
+ const saveOption = document.getElementById("saveOption");
66894
+ const documentsOption = document.getElementById("documentsOption");
66895
+ const commentsItem = document.getElementById("comments-item");
66896
+ const helpItem = document.getElementById("help-item");
66897
+
66898
+ // Check visibility logic:
66899
+ // 1. Is the item actually displayed (style.display !== 'none')?
66900
+ // 2. Is the item allowed by options (e.g. showComments, showHelp)?
66901
+ // For completeOption/saveOption, their display is managed by logic.
66902
+ // For documentsOption, its display is managed by logic.
66903
+ // For comments/help, they are often just static items toggled.
66904
+
66905
+ const isVisible = (el) => el && el.style.display !== "none";
66906
+
66907
+ // Assuming options.showComments/Help also imply the item SHOULD be visible if logic permits
66908
+ // But logic might hide them. The 'isVisible' check handles dynamic logic.
66909
+ // But for comments/help, they might be hidden initially.
66910
+
66911
+ const hasVisibleItems =
66912
+ isVisible(completeOption) ||
66913
+ isVisible(saveOption) ||
66914
+ isVisible(documentsOption) ||
66915
+ isVisible(commentsItem) ||
66916
+ isVisible(helpItem); // Simplified: check actual DOM state
66917
+
66918
+ // If ANY item is visible, show the button. Otherwise hide.
66919
+ if (hasVisibleItems) {
66920
+ moreBtn.style.display = "";
66921
+ } else {
66922
+ moreBtn.style.display = "none";
66923
+ }
66924
+ };
66925
+
66926
+ window.checkMoreButtonVisibility();
66927
+
65729
66928
  window.loadComments = loadComments;
65730
66929
  window.loadRNform = loadRNform;
65731
66930
  window.FormOnSave = FormOnSave;
@@ -65734,6 +66933,9 @@ window.setImageData = setImageData;
65734
66933
  window.FormOnSubmit = FormOnSubmit;
65735
66934
  window.resetFormCache = resetFormCache;
65736
66935
 
66936
+ // Notify that loadRNform is ready (for event-based coordination)
66937
+ document.dispatchEvent(new CustomEvent('LoadRNformReady'));
66938
+
65737
66939
  })();
65738
66940
 
65739
66941
  // === SCRIPT_SEPARATOR ===
@@ -65789,7 +66991,7 @@ async function loadCommentsform(
65789
66991
  window.formObj.nosubmit = true;
65790
66992
 
65791
66993
  // Listen to form changes (optional, currently empty)
65792
- window.formObj.on("change", () => {});
66994
+ window.formObj.on("change", () => { });
65793
66995
 
65794
66996
  // Initialize Recogito and store globally
65795
66997
  window.commentsResponse = Recogito.init({
@@ -65813,8 +67015,8 @@ async function loadCommentsform(
65813
67015
  });
65814
67016
 
65815
67017
  // Event listeners
65816
- window.commentsResponse.on("createAnnotation", function (annotation) {});
65817
- window.commentsResponse.on("updateAnnotation", function (annotation) {});
67018
+ window.commentsResponse.on("createAnnotation", function (annotation) { });
67019
+ window.commentsResponse.on("updateAnnotation", function (annotation) { });
65818
67020
  window.commentsResponse.on("selectAnnotation", function (annotation) {
65819
67021
  const eventCustom = new CustomEvent("annotateCss", { detail: true });
65820
67022
  document.dispatchEvent(eventCustom);
@@ -65865,6 +67067,13 @@ async function goToformRender() {
65865
67067
  }
65866
67068
  } catch (e) {
65867
67069
  console.error("Error reloading form:", e);
67070
+ if (typeof sendEventCallback === 'function') {
67071
+ sendEventCallback({
67072
+ type: "ERROR",
67073
+ errorMessage: "ErrorCode : 005, Formio not rendered the forms",
67074
+ data: { reason: "Failed to reload forms", technicalError: e }
67075
+ });
67076
+ }
65868
67077
  }
65869
67078
  } else {
65870
67079
  console.error("loadRNform is not defined");
@@ -65937,71 +67146,118 @@ window.CommentOnBack = CommentOnBack;
65937
67146
 
65938
67147
  // Click outside handler for tooltip
65939
67148
  document.addEventListener('click', function (event) {
65940
- const moreBtn = document.getElementById('moreBtn');
67149
+ const moreBtn = document.getElementById('unvired-more-btn');
65941
67150
  const tooltip = document.getElementById('moreTooltip');
65942
67151
  if (moreBtn && tooltip && !moreBtn.contains(event.target) && !tooltip.contains(event.target)) {
65943
67152
  tooltip.style.display = 'none';
65944
67153
  }
65945
67154
  });
65946
- `.split("\n\n// === SCRIPT_SEPARATOR ===\n\n");
65947
- if (scriptContents[0] && scriptContents[0].trim()) {
65948
- const jqueryScript = document.createElement("script");
65949
- jqueryScript.textContent = scriptContents[0];
65950
- document.head.appendChild(jqueryScript);
65951
- }
65952
- const formioScript = document.createElement("script");
65953
- formioScript.src = options.formioLibPath.formioPath;
65954
- document.head.appendChild(formioScript);
65955
- await new Promise((resolve) => {
65956
- const checkFormio = () => {
65957
- if (typeof window.Formio !== "undefined") {
65958
- resolve();
65959
- } else {
65960
- setTimeout(checkFormio, 100);
67155
+ `.split(
67156
+ "\n\n// === SCRIPT_SEPARATOR ===\n\n"
67157
+ );
67158
+ if (!window.__unviredSdkScriptsLoaded && !window.__unviredSdkScriptsLoading) {
67159
+ window.__unviredSdkScriptsLoading = true;
67160
+ console.log("Initializing Unvired SDK scripts...");
67161
+ if (scriptContents[0] && scriptContents[0].trim()) {
67162
+ const jqueryScript = document.createElement("script");
67163
+ jqueryScript.textContent = scriptContents[0];
67164
+ jqueryScript.setAttribute("data-unvired-script", "jquery");
67165
+ document.head.appendChild(jqueryScript);
67166
+ }
67167
+ if (loaderElement) {
67168
+ const loaderText = loaderElement.querySelector(".sdk-loader-text");
67169
+ if (loaderText) loaderText.textContent = "Loading Formio Library...";
67170
+ }
67171
+ try {
67172
+ await new Promise((resolve, reject) => {
67173
+ const formioScript = document.createElement("script");
67174
+ formioScript.src = options.formioLibPath.formioPath;
67175
+ formioScript.async = false;
67176
+ formioScript.onload = () => {
67177
+ if (typeof window.Formio !== "undefined") {
67178
+ resolve();
67179
+ } else {
67180
+ reject(new Error("Formio script loaded but window.Formio is undefined"));
67181
+ }
67182
+ };
67183
+ formioScript.onerror = () => {
67184
+ reject(new Error(`Failed to load Formio script from ${options.formioLibPath.formioPath}`));
67185
+ };
67186
+ document.head.appendChild(formioScript);
67187
+ });
67188
+ } catch (error) {
67189
+ window.__unviredSdkScriptsLoading = false;
67190
+ throw error;
67191
+ }
67192
+ for (let i = 1; i <= 2; i++) {
67193
+ if (scriptContents[i] && scriptContents[i].trim()) {
67194
+ const scriptElement = document.createElement("script");
67195
+ scriptElement.textContent = scriptContents[i];
67196
+ scriptElement.setAttribute("data-unvired-script", `recogito-${i}`);
67197
+ document.head.appendChild(scriptElement);
65961
67198
  }
65962
- };
65963
- if (formioScript.onload !== void 0) {
65964
- formioScript.onload = checkFormio;
65965
- } else {
65966
- checkFormio();
65967
67199
  }
65968
- });
65969
- for (let i = 1; i <= 2; i++) {
65970
- if (scriptContents[i] && scriptContents[i].trim()) {
65971
- const scriptElement = document.createElement("script");
65972
- scriptElement.textContent = scriptContents[i];
65973
- document.head.appendChild(scriptElement);
65974
- }
65975
- }
65976
- if (scriptContents[3] && scriptContents[3].trim()) {
65977
- const lessScript = document.createElement("script");
65978
- lessScript.textContent = scriptContents[3];
65979
- document.head.appendChild(lessScript);
65980
- }
65981
- window.form = window.form || {};
65982
- window.platform = window.platform || {};
65983
- window.ResizeObserver = window.ResizeObserver || ResizeObserver;
65984
- if (typeof browserMD5File !== "undefined") {
65985
- window.form.BMF = new browserMD5File();
65986
- }
65987
- window.less = window.less || {};
65988
- if (typeof window.__Html5QrcodeLibrary__ !== "undefined") {
65989
- window.html5QrCode = window.__Html5QrcodeLibrary__;
65990
- }
65991
- for (let i = 4; i < scriptContents.length; i++) {
65992
- const scriptContent = scriptContents[i];
65993
- if (scriptContent.trim()) {
65994
- const scriptElement = document.createElement("script");
65995
- const isModuleScript = i >= scriptContents.length - 4;
65996
- if (isModuleScript) {
65997
- scriptElement.type = "module";
67200
+ if (scriptContents[3] && scriptContents[3].trim()) {
67201
+ const lessScript = document.createElement("script");
67202
+ lessScript.textContent = scriptContents[3];
67203
+ lessScript.setAttribute("data-unvired-script", "less");
67204
+ document.head.appendChild(lessScript);
67205
+ }
67206
+ window.form = window.form || {};
67207
+ window.platform = window.platform || {};
67208
+ window.ResizeObserver = window.ResizeObserver || ResizeObserver;
67209
+ if (typeof browserMD5File !== "undefined") {
67210
+ window.form.BMF = new browserMD5File();
67211
+ }
67212
+ window.less = window.less || {};
67213
+ if (typeof window.__Html5QrcodeLibrary__ !== "undefined") {
67214
+ window.html5QrCode = window.__Html5QrcodeLibrary__;
67215
+ }
67216
+ for (let i = 4; i < scriptContents.length; i++) {
67217
+ const scriptContent = scriptContents[i];
67218
+ if (scriptContent.trim()) {
67219
+ const scriptElement = document.createElement("script");
67220
+ const isModuleScript = i >= scriptContents.length - 4;
67221
+ if (isModuleScript) {
67222
+ scriptElement.type = "module";
67223
+ }
67224
+ scriptElement.textContent = scriptContent;
67225
+ scriptElement.setAttribute("data-unvired-script", `script-${i}`);
67226
+ document.head.appendChild(scriptElement);
67227
+ }
67228
+ }
67229
+ window.__unviredSdkScriptsLoaded = true;
67230
+ window.__unviredSdkScriptsLoading = false;
67231
+ console.log("\u2705 All Unvired SDK scripts loaded successfully");
67232
+ } else if (window.__unviredSdkScriptsLoading) {
67233
+ console.log("Scripts are being loaded by another instance, checking if already loaded...");
67234
+ if (!window.__unviredSdkScriptsLoaded) {
67235
+ console.warn("\u26A0\uFE0F Scripts marked as loading but not yet loaded - waiting...");
67236
+ window.__unviredSdkScriptsLoaded = true;
67237
+ window.__unviredSdkScriptsLoading = false;
67238
+ }
67239
+ console.log("\u2705 Scripts are available, continuing...");
67240
+ } else {
67241
+ console.log("Unvired SDK scripts already loaded, skipping injection.");
67242
+ if (typeof window.Formio === "undefined") {
67243
+ const errorMsg = `ErrorCode : 005, Formio library not available even though scripts were loaded. Path: ${options.formioLibPath.formioPath}`;
67244
+ console.error(errorMsg);
67245
+ if (loaderElement) {
67246
+ loaderElement.classList.add("hidden");
65998
67247
  }
65999
- scriptElement.textContent = scriptContent;
66000
- document.head.appendChild(scriptElement);
66001
- await new Promise((resolve) => setTimeout(resolve, 50));
67248
+ sendEventCallback2({
67249
+ type: "ERROR",
67250
+ errorMessage: errorMsg,
67251
+ data: {
67252
+ technicalError: new Error(errorMsg),
67253
+ formioPath: options.formioLibPath.formioPath,
67254
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
67255
+ }
67256
+ });
67257
+ throw new Error(errorMsg);
66002
67258
  }
67259
+ console.log("\u2705 Formio verified (previously loaded)");
66003
67260
  }
66004
- await new Promise((resolve) => setTimeout(resolve, 500));
66005
67261
  window.FORM_TEMPLATE = mergedWithMasterData;
66006
67262
  window.FORM_PREVIOUS_DATA = submissionData;
66007
67263
  window.FORM_MODE = options.mode;
@@ -66013,6 +67269,7 @@ window.CommentOnBack = CommentOnBack;
66013
67269
  window.FORM_ENV = options.environmentVariable;
66014
67270
  window.FORM_THEME_DATA = options.themeData;
66015
67271
  window.FORM_USER_DATA = options.userData;
67272
+ window.FORM_USERS_LIST = options.usersList;
66016
67273
  window.FORM_CONTROL_DATA = options.controlData;
66017
67274
  window.FORM_PRIVATE_EXTERNAL = options.privateExternal;
66018
67275
  window.FORM_PERMISSION = options.permission;
@@ -66027,13 +67284,18 @@ window.CommentOnBack = CommentOnBack;
66027
67284
  ONCHANGE: "FORM_ONCHANGE"
66028
67285
  };
66029
67286
  const backBtn = container.querySelector("#form-back-btn");
66030
- const moreBtn = container.querySelector("#moreBtn");
67287
+ const moreBtn = container.querySelector("#unvired-more-btn");
66031
67288
  if (backBtn) {
66032
67289
  backBtn.style.display = options.showBackButton ? "inline-block" : "none";
66033
67290
  }
66034
67291
  if (moreBtn && !options.showMoreButton) {
66035
67292
  moreBtn.style.display = "none";
66036
67293
  }
67294
+ window.__unviredFormsOptions = options;
67295
+ window.checkDocumentsVisibility();
67296
+ if (typeof window.checkMoreButtonVisibility === "function") {
67297
+ window.checkMoreButtonVisibility();
67298
+ }
66037
67299
  if (options.platform === "web") {
66038
67300
  window.platform = { isBrowser: true, isAndroid: false, iosPlatform: false };
66039
67301
  } else if (options.platform === "android") {
@@ -66071,6 +67333,8 @@ window.CommentOnBack = CommentOnBack;
66071
67333
  if (options.language) window.FORMIO_LANGUAGE = options.language;
66072
67334
  if (options.translations) window.FORMIO_I18N = options.translations;
66073
67335
  if (options.userData) {
67336
+ window.firstName = options.userData.firstName;
67337
+ window.lastName = options.userData.lastName;
66074
67338
  window.form = window.form || {};
66075
67339
  Object.assign(window.form, options.userData);
66076
67340
  }
@@ -66088,59 +67352,38 @@ window.CommentOnBack = CommentOnBack;
66088
67352
  sendEventCallback2
66089
67353
  );
66090
67354
  }
66091
- let attempts = 0;
66092
- const maxAttempts = 100;
66093
- const waitForLoadRNform = () => {
66094
- if (typeof window.loadRNform === "function") {
66095
- window.loadRNform(
66096
- mergedWithMasterData,
66097
- processedSubmissionData,
66098
- options.themeData,
66099
- options.mode,
66100
- options.language,
66101
- options.translations,
66102
- options.controlData,
66103
- options.privateExternal,
66104
- options.permission,
66105
- fileKeys
66106
- );
66107
- if (loaderElement && window.formObj) {
66108
- window.formObj.formReady.then(() => {
66109
- if (loaderElement) {
66110
- loaderElement.classList.add("hidden");
66111
- setTimeout(() => {
66112
- if (loaderElement && loaderElement.parentNode) {
66113
- loaderElement.parentNode.removeChild(loaderElement);
66114
- }
66115
- }, 400);
66116
- }
66117
- }).catch(() => {
66118
- setTimeout(() => {
66119
- if (loaderElement) {
66120
- loaderElement.classList.add("hidden");
66121
- setTimeout(() => {
66122
- if (loaderElement && loaderElement.parentNode) {
66123
- loaderElement.parentNode.removeChild(loaderElement);
66124
- }
66125
- }, 400);
66126
- }
66127
- }, 2e3);
66128
- });
66129
- }
66130
- } else if (attempts < maxAttempts) {
66131
- attempts++;
66132
- console.log(`Waiting for loadRNform... attempt ${attempts}`);
66133
- setTimeout(waitForLoadRNform, 500);
66134
- } else {
66135
- console.error("window.loadRNform is not defined after waiting", {
66136
- availableFunctions: Object.keys(window).filter((key) => typeof window[key] === "function")
66137
- });
66138
- if (loaderElement) {
66139
- loaderElement.classList.add("hidden");
67355
+ if (loaderElement) {
67356
+ const loaderText = loaderElement.querySelector(".sdk-loader-text");
67357
+ if (loaderText) loaderText.textContent = "Initializing Form...";
67358
+ }
67359
+ if (typeof window.loadRNform !== "function") {
67360
+ console.log("\u23F3 loadRNform not ready yet, waiting for LoadRNformReady event...");
67361
+ await new Promise((resolve) => {
67362
+ const onReady = () => {
67363
+ document.removeEventListener("LoadRNformReady", onReady);
67364
+ console.log("\u2705 loadRNform is now ready");
67365
+ resolve();
67366
+ };
67367
+ document.addEventListener("LoadRNformReady", onReady);
67368
+ if (typeof window.loadRNform === "function") {
67369
+ onReady();
66140
67370
  }
66141
- }
66142
- };
66143
- waitForLoadRNform();
67371
+ });
67372
+ } else {
67373
+ console.log("\u2705 loadRNform is already available");
67374
+ }
67375
+ await window.loadRNform(
67376
+ mergedWithMasterData,
67377
+ processedSubmissionData,
67378
+ options.themeData,
67379
+ options.mode,
67380
+ options.language,
67381
+ options.translations,
67382
+ options.controlData,
67383
+ options.privateExternal,
67384
+ options.permission,
67385
+ fileKeys
67386
+ );
66144
67387
  return {
66145
67388
  sendAction: (action) => {
66146
67389
  var _a2;
@@ -66158,6 +67401,51 @@ window.CommentOnBack = CommentOnBack;
66158
67401
  }
66159
67402
  };
66160
67403
  }
67404
+ window.closeDocumentsModal = function() {
67405
+ if (typeof $ !== "undefined" && $.fn.modal) {
67406
+ $("#documentsModal").modal("hide");
67407
+ } else {
67408
+ const modal = document.getElementById("documentsModal");
67409
+ if (modal) {
67410
+ modal.style.display = "none";
67411
+ modal.classList.remove("active");
67412
+ }
67413
+ }
67414
+ };
67415
+ window.checkDocumentsVisibility = async function() {
67416
+ const docBtn = document.getElementById("documentsOption");
67417
+ const docDivider = document.getElementById("documentsDivider");
67418
+ const hasDocs = await hasDocuments();
67419
+ docBtn.style.display = hasDocs ? "block" : "none";
67420
+ if (docDivider) docDivider.style.display = hasDocs ? "block" : "none";
67421
+ if (typeof window.checkMoreButtonVisibility === "function") {
67422
+ window.checkMoreButtonVisibility();
67423
+ }
67424
+ };
67425
+ window.insertAppDocument = async function(doc) {
67426
+ try {
67427
+ await insertDocument(doc);
67428
+ console.log("Document inserted");
67429
+ await window.checkDocumentsVisibility();
67430
+ return true;
67431
+ } catch (e) {
67432
+ console.error("Failed to insert document", e);
67433
+ return false;
67434
+ }
67435
+ };
67436
+ window.deleteAppDocument = async function(id) {
67437
+ try {
67438
+ await deleteDocument(id);
67439
+ console.log("Document deleted");
67440
+ await window.checkDocumentsVisibility();
67441
+ return true;
67442
+ } catch (e) {
67443
+ console.error("Failed to delete document", e);
67444
+ return false;
67445
+ }
67446
+ };
67447
+ window.getAllDocuments = getAllDocuments;
67448
+ window.hasDocuments = hasDocuments;
66161
67449
  export {
66162
67450
  loadUnviredForms
66163
67451
  };