@unvired/turboforms-embed-sdk 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -251,7 +251,9 @@ document.addEventListener('deviceready', function() {
251
251
  | `GET_ATTACHMENT` | Attachment request |
252
252
  | `ERROR` | Error occurred |
253
253
 
254
- ## ✅ Button Visibility & Enablement
254
+ ## ✅ Button Visibility & Enablement (Normal Forms)
255
+
256
+ The following rules apply to standard (non-wizard) forms:
255
257
 
256
258
  | privateExternal | Permission | Main Toolbar Button | More Options (Dropdown) | Enable / Disable Rule |
257
259
  | ------------------ | --------------- | -------------------------- | ------------------------------- | -------------------------------------------------- |
@@ -261,6 +263,18 @@ document.addEventListener('deviceready', function() {
261
263
  | **true (Private)** | `writesingle` | **Complete** (`submitBtn`) | **Save** (`saveOption`) | Complete enabled **ONLY when form is 100% filled** |
262
264
  | **Any** | `read` | None | None | N/A (Read-only mode) |
263
265
 
266
+ ## 🧙 Wizard Form Navigation & Visibility
267
+
268
+ For Wizard forms, the standard action buttons are managed per-page to prioritize navigation:
269
+
270
+ | Page Status | Action Buttons (Save/Complete) | Navigation Buttons | Action Menu (More Options) |
271
+ | :--- | :--- | :--- | :--- |
272
+ | **Intermediate Steps** | **Hidden** | **Previous**, **Next** | **Help** / **Comments** (if enabled) |
273
+ | **Final Step** | **Visible** (Follows standard rules above) | **Previous** | **All Actions** (Follows standard rules above) |
274
+
275
+ * **Intermediate Steps**: To ensure a clean experience, the SDK automatically hides all final submission actions (buttons, icons, and "More" dropdown items) until the user reaches the end of the wizard.
276
+ * **Final Step**: Once the user reaches the last page, the standard logic defined in the table above is applied. The **Next** button is replaced by the appropriate **Save** or **Complete** action based on permissions and form state.
277
+
264
278
  ## 🧭 “More Options” Dropdown Logic
265
279
 
266
280
  ### ▶ **Complete appears in More Options**
@@ -513,12 +513,16 @@ body {
513
513
  box-sizing: border-box;
514
514
  background-color: #f5f5f5;
515
515
  font-family: Arial, sans-serif;
516
+ overflow: hidden; /* Prevent window from scrolling */
516
517
  }
517
518
 
518
519
  .formio-wrapper {
519
520
  border: 1px solid #ccc;
520
521
  background-color: #fff;
521
522
  border-radius: 8px;
523
+ overflow-y: auto; /* Enable internal scrolling */
524
+ -webkit-overflow-scrolling: touch;
525
+ height: 100%; /* Fill space */
522
526
  }
523
527
 
524
528
  #progressContainer {
@@ -1084,15 +1088,34 @@ body {
1084
1088
  }
1085
1089
 
1086
1090
  /* Add buffer at bottom to allow last components to scroll above the keyboard */
1087
- #form-container {
1088
- padding-bottom: 500px !important;
1091
+ #formio-wrapper {
1092
+ padding-bottom: 800px !important;
1089
1093
  }
1090
1094
 
1091
- /* Ensure DataGrid rows allow dropdowns to overflow now that we disabled fixed positioning on iOS */
1092
- .formio-component-datagrid .ui.table tr,
1093
- .formio-component-datagrid .ui.table td {
1095
+ /* Logically add space below the select when opened to allow scrolling the last row higher */
1096
+ .choices.is-open {
1097
+ margin-bottom: 800px !important;
1094
1098
  overflow: visible !important;
1095
1099
  }
1100
+
1101
+ /* Prevent dropdown from being layered behind table headers or other rows */
1102
+ .choices__list--dropdown {
1103
+ z-index: 10000 !important;
1104
+ overflow-y: auto !important;
1105
+ -webkit-overflow-scrolling: touch;
1106
+ }
1107
+
1108
+ /* Ensure the internal list doesn't have its own scrollbar/max-height that clips items */
1109
+ .choices__list--dropdown .choices__list {
1110
+ max-height: none !important;
1111
+ overflow: visible !important;
1112
+ padding-bottom: 20px !important;
1113
+ }
1114
+
1115
+ /* Add extra space for the last item to ensure it's not clipped on iOS */
1116
+ .choices__list--dropdown .choices__item:last-child {
1117
+ padding-bottom: 20px !important;
1118
+ }
1096
1119
  }</style>
1097
1120
  <style>.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}
1098
1121
  .r6o-btn{background-color:#4483c4;border:1px solid #4483c4;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-size:14px;margin:0;outline:none;text-decoration:none;white-space:nowrap;padding:6px 18px;min-width:70px;vertical-align:middle;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.r6o-btn *{vertical-align:middle;cursor:pointer}.r6o-btn .r6o-icon{margin-right:4px}.r6o-btn:disabled{border-color:#a3c2e2 !important;background-color:#a3c2e2 !important}.r6o-btn:hover{background-color:#4f92d7;border-color:#4f92d7}.r6o-btn.outline{border:1px solid #4483c4;color:#4483c4;background-color:transparent;text-shadow:none}.r6o-autocomplete{display:inline;position:relative}.r6o-autocomplete div[role=combobox]{display:inline}.r6o-autocomplete input{outline:none;border:none;width:80px;height:100%;line-height:14px;white-space:pre;box-sizing:border-box;background-color:transparent;font-size:14px;color:#3f3f3f}.r6o-autocomplete ul{position:absolute;margin:0;padding:0;list-style-type:none;background-color:#fff;border-radius:3px;border:1px solid #d6d7d9;box-sizing:border-box;box-shadow:0 0 20px rgba(0,0,0,.25)}.r6o-autocomplete ul:empty{display:none}.r6o-autocomplete li{box-sizing:border-box;padding:2px 12px;width:100%;cursor:pointer}.r6o-editable-text{max-height:120px;overflow:auto;outline:none;min-height:2em;font-size:14px;font-family:"Lato",sans-serif}.r6o-editable-text:empty:not(:focus):before{content:attr(data-placeholder);color:#c2c2c2}.r6o-widget.comment{font-size:14px;min-height:3em;background-color:#fff;position:relative}.r6o-widget.comment .r6o-editable-text,.r6o-widget.comment .r6o-readonly-comment{padding:10px;width:100%;box-sizing:border-box;outline:none;border:none;background-color:transparent;resize:none}.r6o-widget.comment .r6o-readonly-comment{white-space:pre}.r6o-widget.comment .r6o-editable-text::-webkit-input-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text::-moz-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text:-moz-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text:-ms-input-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-lastmodified{border:1px solid #e5e5e5;display:inline-block;border-radius:2px;margin:0 10px 8px 10px;padding:4px 5px;line-height:100%;font-size:12px}.r6o-widget.comment .r6o-lastmodified .r6o-lastmodified-at{color:#757575;padding-left:3px}.r6o-widget.comment .r6o-arrow-down{position:absolute;height:20px;width:20px;top:9px;right:9px;line-height:22px;background-color:#fff;text-align:center;-webkit-font-smoothing:antialiased;border:1px solid #e5e5e5;cursor:pointer;-webkit-border-radius:1px;-khtml-border-radius:1px;-moz-border-radius:1px;border-radius:1px}.r6o-widget.comment .r6o-arrow-down.r6o-menu-open{border-color:#4483c4}.r6o-widget.comment .r6o-comment-dropdown-menu{position:absolute;top:32px;right:8px;background-color:#fff;border:1px solid #e5e5e5;list-style-type:none;margin:0;padding:5px 0;z-index:9999;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.2);-moz-box-shadow:0 2px 4px rgba(0,0,0,.2);box-shadow:0 2px 4px rgba(0,0,0,.2)}.r6o-widget.comment .r6o-comment-dropdown-menu li{padding:0 15px;cursor:pointer}.r6o-widget.comment .r6o-comment-dropdown-menu li:hover{background-color:#ecf0f1}.r6o-widget.comment .r6o-purposedropdown{position:relative;z-index:2}.r6o-widget.comment.editable{background-color:#ecf0f1}.r6o-widget.r6o-tag:empty{display:none}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.r6o-widget.tag .r6o-taglist li{height:27px}.r6o-widget.tag .r6o-taglist li .r6o-delete-wrapper .r6o-delete{position:relative;top:-4px}}.r6o-widget.r6o-tag{background-color:#ecf0f1;border-bottom:1px solid #e5e5e5;padding:1px 3px;display:flex}.r6o-widget.r6o-tag ul{margin:0;padding:0;list-style-type:none;z-index:1}.r6o-widget.r6o-tag ul.r6o-taglist{flex:0;white-space:nowrap}.r6o-widget.r6o-tag ul.r6o-taglist li{margin:0;display:inline-block;margin:1px 1px 1px 0;padding:0;vertical-align:middle;overflow:hidden;font-size:12px;background-color:#fff;border:1px solid #d6d7d9;cursor:pointer;position:relative;line-height:180%;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 4px rgba(0,0,0,.1);-moz-box-shadow:0 0 4px rgba(0,0,0,.1);box-shadow:0 0 4px rgba(0,0,0,.1)}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-label{padding:2px 8px;display:inline-block}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper{display:inline-block;padding:2px 0;color:#fff;width:0;height:100%;background-color:#4483c4;-webkit-border-top-right-radius:2px;-webkit-border-bottom-right-radius:2px;-khtml-border-radius-topright:2px;-khtml-border-radius-bottomright:2px;-moz-border-radius-topright:2px;-moz-border-radius-bottomright:2px;border-top-right-radius:2px;border-bottom-right-radius:2px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper .r6o-delete{padding:2px 6px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper svg{vertical-align:text-top}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-enter-active{width:24px;transition:width 200ms}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-enter-done{width:24px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-exit{width:24px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-exit-active{width:0;transition:width 200ms}.r6o-widget.r6o-tag .r6o-autocomplete{flex:1;position:relative}.r6o-widget.r6o-tag .r6o-autocomplete li{font-size:14px}.r6o-widget.r6o-tag input{width:100%;padding:0 3px;min-width:80px;outline:none;border:none;line-height:170%;background-color:transparent;color:#3f3f3f}.r6o-widget.r6o-tag input::-webkit-input-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input::-moz-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input:-moz-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input:-ms-input-placeholder{color:#c2c2c2}.r6o-editor{position:absolute;z-index:99999;width:400px;color:#3f3f3f;opacity:0;font-family:"Lato",sans-serif;font-size:17px;line-height:27px;-webkit-transition:opacity .2s ease-in;-moz-transition:opacity .2s ease-in;transition:opacity .2s ease-in}.r6o-editor .r6o-arrow{position:absolute;overflow:hidden;top:-12px;left:12px;width:28px;height:12px;display:none}.r6o-editor .r6o-arrow:after{content:"";position:absolute;top:5px;left:5px;width:18px;height:18px;background-color:#fff;-webkit-backface-visibility:hidden;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.r6o-editor .r6o-editor-inner{background-color:#fff;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:2px 2px 42px rgba(0,0,0,.4);-moz-box-shadow:2px 2px 42px rgba(0,0,0,.4);box-shadow:2px 2px 42px rgba(0,0,0,.4)}.r6o-editor .r6o-editor-inner .r6o-widget:first-child{-webkit-border-top-left-radius:2px;-webkit-border-top-right-radius:2px;-khtml-border-radius-topleft:2px;-khtml-border-radius-topright:2px;-moz-border-radius-topleft:2px;-moz-border-radius-topright:2px;border-top-left-radius:2px;border-top-right-radius:2px}.r6o-editor .r6o-editor-inner .r6o-widget{border-bottom:1px solid #e5e5e5}.r6o-editor .r6o-footer{position:relative;text-align:right;padding:8px 0}.r6o-editor .r6o-footer .r6o-btn{margin-right:8px}.r6o-editor .r6o-footer .r6o-btn.delete-annotation{position:absolute;top:7px;left:7px;background-color:transparent;border:none;color:#4483c4;width:32px;height:32px;min-width:0;border-radius:100%;padding:0;display:flex;justify-content:center;align-items:center;-webkit-transition:all .1s ease-in;-moz-transition:all .1s ease-in;-o-transition:all .1s ease-in;transition:all .1s ease-in}.r6o-editor .r6o-footer .r6o-btn.delete-annotation:hover{color:#fff;background-color:#ef352c}@media(max-width: 640px){.r6o-editor{width:260px}}.r6o-editor.r6o-arrow-top .r6o-arrow{display:block}.r6o-editor.r6o-arrow-right{margin-left:8px}.r6o-editor.r6o-arrow-right .r6o-arrow{left:auto;right:12px}.r6o-editor.r6o-arrow-bottom .r6o-arrow{display:block;top:auto;bottom:-12px}.r6o-editor.r6o-arrow-bottom .r6o-arrow::after{top:-11px;box-shadow:none}.r6o-editor.pushed .r6o-arrow,.r6o-editor.dragged .r6o-arrow{display:none}.r6o-editor .r6o-draggable{cursor:move}.r6o-purposedropdown{width:150px;display:inline-block}.r6o-noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.r6o-editor{margin-left:-12px}.r6o-annotation{background-color:rgba(255,165,0,.2);border-bottom:2px solid orange;cursor:pointer}.r6o-selection{background-color:rgba(207,207,255,.63);cursor:pointer}.r6o-hide-selection::selection,.r6o-hide-selection ::selection{background:transparent}.r6o-hide-selection::-moz-selection .r6o-hide-selection ::-moz-selection{background:transparent}.r6o-relation-editor{position:absolute;font-family:"Lato",sans-serif;font-size:17px;line-height:27px;-webkit-box-shadow:0 1px 14px rgba(0,0,0,.4);-moz-box-shadow:0 1px 14px rgba(0,0,0,.4);box-shadow:0 1px 14px rgba(0,0,0,.4);-webkit-border-radius:3px;-khtml-border-radius:3px;-moz-border-radius:3px;border-radius:3px;transform:translate(-50%, -50%);background-color:#fff}.r6o-relation-editor svg{vertical-align:middle;shape-rendering:geometricPrecision}.r6o-relation-editor *{box-sizing:border-box}.r6o-relation-editor .input-wrapper{height:34px;padding:0 6px;margin-right:68px;font-size:14px;background-color:#ecf0f1;cursor:text;-webkit-border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-khtml-border-radius-topleft:3px;-khtml-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-moz-border-radius-bottomleft:3px;border-top-left-radius:3px;border-bottom-left-radius:3px}.r6o-relation-editor .input-wrapper .r6o-autocomplete ul{position:relative;left:-6px}.r6o-relation-editor .buttons{position:absolute;display:inline-flex;top:0;right:0}.r6o-relation-editor .buttons span{height:34px;display:inline-block;width:34px;text-align:center;font-size:14px;cursor:pointer;padding:1px 0}.r6o-relation-editor .buttons .delete{background-color:#fff;color:#9ca4b1;border-left:1px solid #e5e5e5}.r6o-relation-editor .buttons .delete:hover{background-color:#f6f6f6}.r6o-relation-editor .buttons .ok{background-color:#4483c4;color:#fff;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-khtml-border-radius-topright:3px;-khtml-border-radius-bottomright:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.r6o-relation-editor .buttons .ok:hover{background-color:#4f92d7}
@@ -40578,12 +40601,16 @@ body {
40578
40601
  box-sizing: border-box;
40579
40602
  background-color: #f5f5f5;
40580
40603
  font-family: Arial, sans-serif;
40604
+ overflow: hidden; /* Prevent window from scrolling */
40581
40605
  }
40582
40606
 
40583
40607
  .formio-wrapper {
40584
40608
  border: 1px solid #ccc;
40585
40609
  background-color: #fff;
40586
40610
  border-radius: 8px;
40611
+ overflow-y: auto; /* Enable internal scrolling */
40612
+ -webkit-overflow-scrolling: touch;
40613
+ height: 100%; /* Fill space */
40587
40614
  }
40588
40615
 
40589
40616
  #progressContainer {
@@ -41149,15 +41176,34 @@ body {
41149
41176
  }
41150
41177
 
41151
41178
  /* Add buffer at bottom to allow last components to scroll above the keyboard */
41152
- #form-container {
41153
- padding-bottom: 500px !important;
41179
+ #formio-wrapper {
41180
+ padding-bottom: 800px !important;
41154
41181
  }
41155
41182
 
41156
- /* Ensure DataGrid rows allow dropdowns to overflow now that we disabled fixed positioning on iOS */
41157
- .formio-component-datagrid .ui.table tr,
41158
- .formio-component-datagrid .ui.table td {
41183
+ /* Logically add space below the select when opened to allow scrolling the last row higher */
41184
+ .choices.is-open {
41185
+ margin-bottom: 800px !important;
41159
41186
  overflow: visible !important;
41160
41187
  }
41188
+
41189
+ /* Prevent dropdown from being layered behind table headers or other rows */
41190
+ .choices__list--dropdown {
41191
+ z-index: 10000 !important;
41192
+ overflow-y: auto !important;
41193
+ -webkit-overflow-scrolling: touch;
41194
+ }
41195
+
41196
+ /* Ensure the internal list doesn't have its own scrollbar/max-height that clips items */
41197
+ .choices__list--dropdown .choices__list {
41198
+ max-height: none !important;
41199
+ overflow: visible !important;
41200
+ padding-bottom: 20px !important;
41201
+ }
41202
+
41203
+ /* Add extra space for the last item to ensure it's not clipped on iOS */
41204
+ .choices__list--dropdown .choices__item:last-child {
41205
+ padding-bottom: 20px !important;
41206
+ }
41161
41207
  }</style>
41162
41208
 
41163
41209
  <script>
@@ -47063,16 +47109,9 @@ var Choices = /** @class */function () {
47063
47109
  requestAnimationFrame(function () {
47064
47110
  _this.dropdown.show();
47065
47111
  _this.containerOuter.open(_this.dropdown.distanceFromTopWindow);
47066
- if (!preventInputFocus && _this._canSearch) {
47067
- // On iOS, delay focus slightly to ensure the keyboard has time to reset/close from previous field
47068
- if (window.platform && window.platform.iosPlatform) {
47069
- setTimeout(() => {
47070
- _this.input.focus({ preventScroll: true });
47071
- }, 150);
47072
- } else {
47112
+ if (!preventInputFocus && _this._canSearch) {
47073
47113
  _this.input.focus({ preventScroll: true });
47074
47114
  }
47075
- }
47076
47115
  _this.passedElement.triggerEvent(constants_1.EVENTS.showDropdown, {});
47077
47116
  });
47078
47117
  return this;
@@ -54697,12 +54736,16 @@ class SmartSelectComponent extends SmartSelectField {
54697
54736
  if (this.component.multiple) {
54698
54737
  this.focusableElement = this.choices.input.element;
54699
54738
  } else {
54700
- this.focusableElement = this.choices.containerInner.element;
54739
+ // On iOS, focus the search input even for single select to ensure keyboard opens and centering logic works
54740
+ if (choicesOptions.searchEnabled && window.platform && window.platform.iosPlatform) {
54741
+ this.focusableElement = this.choices.input.element;
54742
+ } else {
54743
+ this.focusableElement = this.choices.containerInner.element;
54744
+ }
54701
54745
  this.choices.containerOuter.element.setAttribute('tabIndex', '-1');
54702
54746
  if (choicesOptions.searchEnabled) {
54703
54747
  // Proactively position on mousedown so it's ready before focus happens
54704
54748
  this.addEventListener(this.choices.containerOuter.element, 'mousedown', () => this.positionDropdown());
54705
- this.addEventListener(this.choices.containerOuter.element, 'focus', () => this.focusableElement.focus({ preventScroll: true }));
54706
54749
  }
54707
54750
  }
54708
54751
  this.addFocusBlurEvents(this.focusableElement);
@@ -54719,6 +54762,12 @@ class SmartSelectComponent extends SmartSelectField {
54719
54762
  }
54720
54763
  }
54721
54764
  if (window && this.choices && this.shouldPositionDropdown) {
54765
+ const wrapper = document.getElementById('formio-wrapper');
54766
+ if (wrapper) {
54767
+ this.addEventListener(wrapper, 'scroll', () => {
54768
+ this.positionDropdown(true);
54769
+ }, false, true);
54770
+ }
54722
54771
  this.addEventListener(window.document, 'scroll', () => {
54723
54772
  this.positionDropdown(true);
54724
54773
  }, false, true);
@@ -54877,11 +54926,8 @@ class SmartSelectComponent extends SmartSelectField {
54877
54926
 
54878
54927
  // Ensure select is visible above keyboard on iOS
54879
54928
  if (window.platform && window.platform.iosPlatform) {
54880
- setTimeout(() => {
54881
- if (this.focusableElement) {
54882
- this.focusableElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
54883
- }
54884
- }, 300);
54929
+ // We removed native scrollIntoView as it interferes with manual scroll control in focus.js
54930
+ // focus.js handles the centering now.
54885
54931
  }
54886
54932
 
54887
54933
  // Restore and lock scroll on iPad to prevent "flicker" shifts during keyboard toggles
@@ -54999,14 +55045,29 @@ class SmartSelectComponent extends SmartSelectField {
54999
55045
  const containerPosition = container.getBoundingClientRect();
55000
55046
  const isFlipped = container.classList.contains('is-flipped');
55001
55047
 
55002
- _.assign(dropdown.style, {
55003
- top: `${isFlipped ? containerPosition.top - dropdown.offsetHeight : containerPosition.top + containerPosition.height}px`,
55048
+ const vpH = window.visualViewport ? window.visualViewport.height : window.innerHeight;
55049
+ const top = isFlipped ? containerPosition.top - dropdown.offsetHeight : containerPosition.top + containerPosition.height;
55050
+
55051
+ const style = {
55004
55052
  left: `${containerPosition.left}px`,
55005
55053
  width: `${containerPosition.width}px`,
55006
55054
  position: 'fixed',
55007
55055
  bottom: 'unset',
55008
55056
  right: 'unset',
55009
- });
55057
+ top: `${top}px`,
55058
+ };
55059
+
55060
+ // On iOS, we limit the max-height of the fixed dropdown to stay within the viewport.
55061
+ // We also use a scroll listener on the wrapper (added in attach()) to ensure this fixed
55062
+ // element re-positions during scrolling, making it behave like an absolute element.
55063
+ if (window.platform && window.platform.iosPlatform) {
55064
+ // Limit max-height to available space but ensure at least 300px for a good scroll area.
55065
+ // We subtract 30px to leave a small gap at the bottom for better visibility.
55066
+ const availableHeight = isFlipped ? top - 20 : vpH - top - 30;
55067
+ style.maxHeight = `${Math.max(availableHeight, 300)}px`;
55068
+ }
55069
+
55070
+ _.assign(dropdown.style, style);
55010
55071
  }
55011
55072
 
55012
55073
  hasDataGridAncestor(comp) {
@@ -56304,6 +56365,11 @@ class SignatureComponent extends InputComponentForSignature {
56304
56365
  }
56305
56366
  if (this.refs.clicktoSign) {
56306
56367
  this.refs.clicktoSign.disabled = true;
56368
+ this.refs.clicktoSign.style.pointerEvents = 'none';
56369
+ this.refs.clicktoSign.style.display = 'none';
56370
+
56371
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
56372
+ if(clickBox) clickBox.style.display = 'none';
56307
56373
  }
56308
56374
  if (this.refs.signatureImage && this.dataValue) {
56309
56375
  this.refs.signatureImage.setAttribute('src', this.dataValue);
@@ -56315,6 +56381,12 @@ class SignatureComponent extends InputComponentForSignature {
56315
56381
  }
56316
56382
  if (this.refs.clicktoSign) {
56317
56383
  this.refs.clicktoSign.disabled = false;
56384
+ this.refs.clicktoSign.style.pointerEvents = 'auto';
56385
+ if (this.component.isEnableClicktoSign) {
56386
+ this.refs.clicktoSign.style.display = 'inline-flex';
56387
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
56388
+ if(clickBox) clickBox.style.display = '';
56389
+ }
56318
56390
  }
56319
56391
  }
56320
56392
  }
@@ -56386,6 +56458,11 @@ class SignatureComponent extends InputComponentForSignature {
56386
56458
  this.refs.refresh.classList.add('disabled');
56387
56459
  if (this.refs.clicktoSign) {
56388
56460
  this.refs.clicktoSign.disabled = true;
56461
+ this.refs.clicktoSign.style.pointerEvents = 'none';
56462
+ this.refs.clicktoSign.style.display = 'none';
56463
+
56464
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
56465
+ if(clickBox) clickBox.style.display = 'none';
56389
56466
  }
56390
56467
  }
56391
56468
 
@@ -59935,7 +60012,7 @@ async function renderRNform(
59935
60012
  }
59936
60013
 
59937
60014
  // Hide FormIO submit button component and wizard buttons in readOnly mode
59938
- if (mode == "readOnly") {
60015
+ if (mode == "readOnly" || permission == "read") {
59939
60016
  const submitComponents = document.querySelectorAll(
59940
60017
  ".formio-component-submit"
59941
60018
  );
@@ -59943,6 +60020,18 @@ async function renderRNform(
59943
60020
  component.style.display = "none";
59944
60021
  });
59945
60022
 
60023
+ // Disable signature pads robustly via CSS
60024
+ if (!document.getElementById("readonly-signature-style")) {
60025
+ const style = document.createElement("style");
60026
+ style.id = "readonly-signature-style";
60027
+ style.innerHTML = `
60028
+ .formio-component-signature { pointer-events: none !important; }
60029
+ .formio-component-signature [ref="clicktoSign"],
60030
+ .formio-component-signature .click-to-sign-box { display: none !important; }
60031
+ `;
60032
+ document.head.appendChild(style);
60033
+ }
60034
+
59946
60035
  // Hide wizard navigation buttons
59947
60036
  const wizardNextBtns = document.querySelectorAll(
59948
60037
  ".btn-wizard-nav-next"
@@ -60626,7 +60715,7 @@ window.CommentOnBack = CommentOnBack;
60626
60715
 
60627
60716
 
60628
60717
  <div id="sticky-footer">
60629
- <div class="build-version">SDK v1.0.21</div>
60718
+ <div class="build-version">SDK v1.0.23</div>
60630
60719
  <div class="relative-position">
60631
60720
  <button id="unvired-more-btn" class="ui button primary dataGrid-addRow" onclick="toggleTooltip()">
60632
60721
  <i class="icon options"></i>
@@ -1489,12 +1489,16 @@ body {
1489
1489
  box-sizing: border-box;
1490
1490
  background-color: #f5f5f5;
1491
1491
  font-family: Arial, sans-serif;
1492
+ overflow: hidden; /* Prevent window from scrolling */
1492
1493
  }
1493
1494
 
1494
1495
  .formio-wrapper {
1495
1496
  border: 1px solid #ccc;
1496
1497
  background-color: #fff;
1497
1498
  border-radius: 8px;
1499
+ overflow-y: auto; /* Enable internal scrolling */
1500
+ -webkit-overflow-scrolling: touch;
1501
+ height: 100%; /* Fill space */
1498
1502
  }
1499
1503
 
1500
1504
  #progressContainer {
@@ -2060,15 +2064,34 @@ body {
2060
2064
  }
2061
2065
 
2062
2066
  /* Add buffer at bottom to allow last components to scroll above the keyboard */
2063
- #form-container {
2064
- padding-bottom: 500px !important;
2067
+ #formio-wrapper {
2068
+ padding-bottom: 800px !important;
2065
2069
  }
2066
2070
 
2067
- /* Ensure DataGrid rows allow dropdowns to overflow now that we disabled fixed positioning on iOS */
2068
- .formio-component-datagrid .ui.table tr,
2069
- .formio-component-datagrid .ui.table td {
2071
+ /* Logically add space below the select when opened to allow scrolling the last row higher */
2072
+ .choices.is-open {
2073
+ margin-bottom: 800px !important;
2070
2074
  overflow: visible !important;
2071
2075
  }
2076
+
2077
+ /* Prevent dropdown from being layered behind table headers or other rows */
2078
+ .choices__list--dropdown {
2079
+ z-index: 10000 !important;
2080
+ overflow-y: auto !important;
2081
+ -webkit-overflow-scrolling: touch;
2082
+ }
2083
+
2084
+ /* Ensure the internal list doesn't have its own scrollbar/max-height that clips items */
2085
+ .choices__list--dropdown .choices__list {
2086
+ max-height: none !important;
2087
+ overflow: visible !important;
2088
+ padding-bottom: 20px !important;
2089
+ }
2090
+
2091
+ /* Add extra space for the last item to ensure it's not clipped on iOS */
2092
+ .choices__list--dropdown .choices__item:last-child {
2093
+ padding-bottom: 20px !important;
2094
+ }
2072
2095
  }
2073
2096
 
2074
2097
  /* === STYLE_SEPARATOR === */
@@ -27899,14 +27922,20 @@ select.ui.dropdown {
27899
27922
  <div id="formio-cmt" style="margin-bottom: 20px;"></div>
27900
27923
  </div>
27901
27924
  <div id="sticky-footer">
27902
- <div class="build-version">SDK v1.0.21</div>
27925
+ <div class="build-version">SDK v1.0.23</div>
27903
27926
  <button class="ui button primary dataGrid-addRow" id="saveBtn" disabled="true" onclick="FormOnSave()">
27904
27927
  <i class="icon save large"></i>Save
27905
27928
  </button>
27929
+ <button class="ui button secondary dataGrid-addRow" id="prevBtn" style="display:none" onclick="FormOnPrevious()">
27930
+ <i class="icon arrow left"></i>Previous
27931
+ </button>
27932
+ <button class="ui button primary dataGrid-addRow" id="nextBtn" style="display:none" onclick="FormOnNext()">
27933
+ Next<i class="icon arrow right"></i>
27934
+ </button>
27906
27935
  <button class="ui button primary dataGrid-addRow" id="submitBtn" style="display:none" onclick="FormOnSubmit()">
27907
27936
  <i class="icon save large"></i>Submit
27908
27937
  </button>
27909
-
27938
+
27910
27939
  <!-- Quick Action Buttons -->
27911
27940
  <div id="quick-actions" style="display: flex; align-items: center;">
27912
27941
  <i id="quickCompleteBtn" class="icon clipboard check large" style="display:none; cursor: pointer; margin: 0 10px;" onclick="FormOnSubmit()" title="Complete"></i>
@@ -28055,6 +28084,16 @@ select.ui.dropdown {
28055
28084
  window.formOnSubmitFunction();
28056
28085
  }
28057
28086
  };
28087
+ window.FormOnNext = window.FormOnNext || function() {
28088
+ if (typeof window.formOnNextFunction === "function") {
28089
+ window.formOnNextFunction();
28090
+ }
28091
+ };
28092
+ window.FormOnPrevious = window.FormOnPrevious || function() {
28093
+ if (typeof window.formOnPreviousFunction === "function") {
28094
+ window.formOnPreviousFunction();
28095
+ }
28096
+ };
28058
28097
  const scriptContents = `
28059
28098
  // Global variable initialization
28060
28099
  window.form = window.form || {};
@@ -54315,16 +54354,9 @@ var Choices = /** @class */function () {
54315
54354
  requestAnimationFrame(function () {
54316
54355
  _this.dropdown.show();
54317
54356
  _this.containerOuter.open(_this.dropdown.distanceFromTopWindow);
54318
- if (!preventInputFocus && _this._canSearch) {
54319
- // On iOS, delay focus slightly to ensure the keyboard has time to reset/close from previous field
54320
- if (window.platform && window.platform.iosPlatform) {
54321
- setTimeout(() => {
54322
- _this.input.focus({ preventScroll: true });
54323
- }, 150);
54324
- } else {
54357
+ if (!preventInputFocus && _this._canSearch) {
54325
54358
  _this.input.focus({ preventScroll: true });
54326
54359
  }
54327
- }
54328
54360
  _this.passedElement.triggerEvent(constants_1.EVENTS.showDropdown, {});
54329
54361
  });
54330
54362
  return this;
@@ -61950,12 +61982,16 @@ class SmartSelectComponent extends SmartSelectField {
61950
61982
  if (this.component.multiple) {
61951
61983
  this.focusableElement = this.choices.input.element;
61952
61984
  } else {
61953
- this.focusableElement = this.choices.containerInner.element;
61985
+ // On iOS, focus the search input even for single select to ensure keyboard opens and centering logic works
61986
+ if (choicesOptions.searchEnabled && window.platform && window.platform.iosPlatform) {
61987
+ this.focusableElement = this.choices.input.element;
61988
+ } else {
61989
+ this.focusableElement = this.choices.containerInner.element;
61990
+ }
61954
61991
  this.choices.containerOuter.element.setAttribute('tabIndex', '-1');
61955
61992
  if (choicesOptions.searchEnabled) {
61956
61993
  // Proactively position on mousedown so it's ready before focus happens
61957
61994
  this.addEventListener(this.choices.containerOuter.element, 'mousedown', () => this.positionDropdown());
61958
- this.addEventListener(this.choices.containerOuter.element, 'focus', () => this.focusableElement.focus({ preventScroll: true }));
61959
61995
  }
61960
61996
  }
61961
61997
  this.addFocusBlurEvents(this.focusableElement);
@@ -61972,6 +62008,12 @@ class SmartSelectComponent extends SmartSelectField {
61972
62008
  }
61973
62009
  }
61974
62010
  if (window && this.choices && this.shouldPositionDropdown) {
62011
+ const wrapper = document.getElementById('formio-wrapper');
62012
+ if (wrapper) {
62013
+ this.addEventListener(wrapper, 'scroll', () => {
62014
+ this.positionDropdown(true);
62015
+ }, false, true);
62016
+ }
61975
62017
  this.addEventListener(window.document, 'scroll', () => {
61976
62018
  this.positionDropdown(true);
61977
62019
  }, false, true);
@@ -62130,11 +62172,8 @@ class SmartSelectComponent extends SmartSelectField {
62130
62172
 
62131
62173
  // Ensure select is visible above keyboard on iOS
62132
62174
  if (window.platform && window.platform.iosPlatform) {
62133
- setTimeout(() => {
62134
- if (this.focusableElement) {
62135
- this.focusableElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
62136
- }
62137
- }, 300);
62175
+ // We removed native scrollIntoView as it interferes with manual scroll control in focus.js
62176
+ // focus.js handles the centering now.
62138
62177
  }
62139
62178
 
62140
62179
  // Restore and lock scroll on iPad to prevent "flicker" shifts during keyboard toggles
@@ -62252,14 +62291,29 @@ class SmartSelectComponent extends SmartSelectField {
62252
62291
  const containerPosition = container.getBoundingClientRect();
62253
62292
  const isFlipped = container.classList.contains('is-flipped');
62254
62293
 
62255
- _.assign(dropdown.style, {
62256
- top: \`\${isFlipped ? containerPosition.top - dropdown.offsetHeight : containerPosition.top + containerPosition.height}px\`,
62294
+ const vpH = window.visualViewport ? window.visualViewport.height : window.innerHeight;
62295
+ const top = isFlipped ? containerPosition.top - dropdown.offsetHeight : containerPosition.top + containerPosition.height;
62296
+
62297
+ const style = {
62257
62298
  left: \`\${containerPosition.left}px\`,
62258
62299
  width: \`\${containerPosition.width}px\`,
62259
62300
  position: 'fixed',
62260
62301
  bottom: 'unset',
62261
62302
  right: 'unset',
62262
- });
62303
+ top: \`\${top}px\`,
62304
+ };
62305
+
62306
+ // On iOS, we limit the max-height of the fixed dropdown to stay within the viewport.
62307
+ // We also use a scroll listener on the wrapper (added in attach()) to ensure this fixed
62308
+ // element re-positions during scrolling, making it behave like an absolute element.
62309
+ if (window.platform && window.platform.iosPlatform) {
62310
+ // Limit max-height to available space but ensure at least 300px for a good scroll area.
62311
+ // We subtract 30px to leave a small gap at the bottom for better visibility.
62312
+ const availableHeight = isFlipped ? top - 20 : vpH - top - 30;
62313
+ style.maxHeight = \`\${Math.max(availableHeight, 300)}px\`;
62314
+ }
62315
+
62316
+ _.assign(dropdown.style, style);
62263
62317
  }
62264
62318
 
62265
62319
  hasDataGridAncestor(comp) {
@@ -63560,6 +63614,11 @@ class SignatureComponent extends InputComponentForSignature {
63560
63614
  }
63561
63615
  if (this.refs.clicktoSign) {
63562
63616
  this.refs.clicktoSign.disabled = true;
63617
+ this.refs.clicktoSign.style.pointerEvents = 'none';
63618
+ this.refs.clicktoSign.style.display = 'none';
63619
+
63620
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
63621
+ if(clickBox) clickBox.style.display = 'none';
63563
63622
  }
63564
63623
  if (this.refs.signatureImage && this.dataValue) {
63565
63624
  this.refs.signatureImage.setAttribute('src', this.dataValue);
@@ -63571,6 +63630,12 @@ class SignatureComponent extends InputComponentForSignature {
63571
63630
  }
63572
63631
  if (this.refs.clicktoSign) {
63573
63632
  this.refs.clicktoSign.disabled = false;
63633
+ this.refs.clicktoSign.style.pointerEvents = 'auto';
63634
+ if (this.component.isEnableClicktoSign) {
63635
+ this.refs.clicktoSign.style.display = 'inline-flex';
63636
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
63637
+ if(clickBox) clickBox.style.display = '';
63638
+ }
63574
63639
  }
63575
63640
  }
63576
63641
  }
@@ -63642,6 +63707,11 @@ class SignatureComponent extends InputComponentForSignature {
63642
63707
  this.refs.refresh.classList.add('disabled');
63643
63708
  if (this.refs.clicktoSign) {
63644
63709
  this.refs.clicktoSign.disabled = true;
63710
+ this.refs.clicktoSign.style.pointerEvents = 'none';
63711
+ this.refs.clicktoSign.style.display = 'none';
63712
+
63713
+ const clickBox = this.refs.clicktoSign.closest('.click-to-sign-box');
63714
+ if(clickBox) clickBox.style.display = 'none';
63645
63715
  }
63646
63716
  }
63647
63717
 
@@ -66348,6 +66418,161 @@ window.showDynamicModal = showDynamicModal;
66348
66418
 
66349
66419
  // === SCRIPT_SEPARATOR ===
66350
66420
 
66421
+ (function(){
66422
+ let isKeyboardOpen = false;
66423
+ let activeElement = null;
66424
+ const INITIAL_WINDOW_HEIGHT = window.innerHeight;
66425
+
66426
+ console.log("[Focus] Script initialized. Initial Height:", INITIAL_WINDOW_HEIGHT);
66427
+
66428
+ // SOFT LOCK
66429
+ window.addEventListener('scroll', () => {
66430
+ if (isKeyboardOpen && Math.abs(window.scrollY) > 20) {
66431
+ window.scrollTo(0, 0);
66432
+ }
66433
+ }, { passive: true });
66434
+
66435
+ function updateFooterVisibility(keyboardOpen) {
66436
+ // Only hide the footer on iOS to make room for the software keyboard.
66437
+ // On Windows/Desktop, we should keep the footer visible at all times.
66438
+ const isIOS = (window.platform && window.platform.iosPlatform);
66439
+ if (!isIOS) return;
66440
+
66441
+ const footer = document.getElementById('sticky-footer');
66442
+ if (!footer) return;
66443
+
66444
+ if (keyboardOpen) {
66445
+ footer.style.display = 'none';
66446
+ } else {
66447
+ footer.style.display = 'flex';
66448
+ }
66449
+ }
66450
+
66451
+ // Track focused input
66452
+ document.addEventListener('focusin', (e) => {
66453
+ const el = e.target;
66454
+ if (!el || !['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName)) return;
66455
+
66456
+ console.log("[Focus] \u{1F3AF} focusin on:", el.name || el.id || el.tagName);
66457
+ updateFooterVisibility(true);
66458
+
66459
+ activeElement = el;
66460
+
66461
+ // Special handling for Choices.js: if an input inside choices is focused,
66462
+ // we want to ensure the whole container is considered the active element for centering
66463
+ if (el.classList.contains('choices__input')) {
66464
+ const container = el.closest('.choices');
66465
+ if (container) {
66466
+ activeElement = container;
66467
+ console.log("[Focus] \u{1F4E6} Choices.js detected, centering container.");
66468
+ }
66469
+ }
66470
+
66471
+ if (isKeyboardOpen) {
66472
+ console.log("[Focus] \u26A1 Already open, centering now.");
66473
+ centerActiveElement();
66474
+ }
66475
+ });
66476
+
66477
+ // Restore footer when focus is lost and keyboard is not open
66478
+ document.addEventListener('focusout', (e) => {
66479
+ setTimeout(() => {
66480
+ const currentlyFocused = document.activeElement;
66481
+ const isInputFocused = currentlyFocused && ['INPUT', 'TEXTAREA', 'SELECT'].includes(currentlyFocused.tagName);
66482
+
66483
+ if (!isInputFocused && !isKeyboardOpen) {
66484
+ console.log("[Focus] \u{1F6AA} focusout and keyboard closed, restoring footer.");
66485
+ updateFooterVisibility(false);
66486
+ }
66487
+ }, 200);
66488
+ });
66489
+
66490
+ // Detect keyboard open/close
66491
+ if (window.visualViewport) {
66492
+ window.visualViewport.addEventListener('resize', () => {
66493
+ const isIOS = (window.platform && window.platform.iosPlatform);
66494
+ if (!isIOS) return;
66495
+
66496
+ const viewport = window.visualViewport;
66497
+ const vpH = viewport.height;
66498
+ const isNowOpen = vpH < INITIAL_WINDOW_HEIGHT - 100;
66499
+
66500
+ if (isNowOpen) {
66501
+ const wasAlreadyOpen = isKeyboardOpen;
66502
+ isKeyboardOpen = true;
66503
+ updateFooterVisibility(true);
66504
+
66505
+ if (activeElement && !wasAlreadyOpen) {
66506
+ console.log("[Focus] \u{1F3A2} Keyboard sliding. Following...");
66507
+ startFollowingElement();
66508
+ }
66509
+ } else {
66510
+ isKeyboardOpen = false;
66511
+ updateFooterVisibility(false);
66512
+ window.scrollTo(0, 0);
66513
+ }
66514
+ });
66515
+ }
66516
+
66517
+ // Manually follow the element for a short duration to ensure it stays centered during animation
66518
+ function startFollowingElement() {
66519
+ let start = Date.now();
66520
+ const duration = 400; // Follow for 400ms (typical iOS keyboard duration)
66521
+
66522
+ function step() {
66523
+ centerActiveElement();
66524
+ if (Date.now() - start < duration) {
66525
+ requestAnimationFrame(step);
66526
+ }
66527
+ }
66528
+ requestAnimationFrame(step);
66529
+ }
66530
+
66531
+ function centerActiveElement() {
66532
+ if (!activeElement) return;
66533
+ const wrapper = document.getElementById('formio-wrapper');
66534
+ if (!wrapper) return;
66535
+
66536
+ const vpH = window.visualViewport ? window.visualViewport.height : window.innerHeight;
66537
+
66538
+ // Calculate absolute offset of element relative to wrapper
66539
+ let offsetTop = 0;
66540
+ let curr = activeElement;
66541
+ while (curr && curr !== wrapper && curr !== null) {
66542
+ offsetTop += curr.offsetTop;
66543
+ curr = curr.offsetParent;
66544
+ }
66545
+
66546
+ // For Choices.js, if the dropdown is open, we might want to adjust centering
66547
+ // to ensure the dropdown is visible. However, centering the container is usually best.
66548
+ const elementHeight = activeElement.offsetHeight;
66549
+
66550
+ // targetScroll = (distance from top) - (half of available space) + (half of element height)
66551
+ const targetScroll = offsetTop - (vpH / 2) + (elementHeight / 2);
66552
+
66553
+ // Apply instantly but accurately
66554
+ if (Math.abs(wrapper.scrollTop - targetScroll) > 5) {
66555
+ wrapper.scrollTop = targetScroll;
66556
+ }
66557
+ }
66558
+
66559
+ // Listen for Choices.js events to re-center when dropdown opens/closes
66560
+ document.addEventListener('showDropdown', () => {
66561
+ if (isKeyboardOpen) {
66562
+ console.log("[Focus] \u{1F53D} showDropdown detected, re-centering.");
66563
+ setTimeout(centerActiveElement, 100);
66564
+ }
66565
+ }, true);
66566
+
66567
+ document.addEventListener('hideDropdown', () => {
66568
+ console.log("[Focus] \u{1F53C} hideDropdown detected, resetting view.");
66569
+ // When dropdown hides, we might want to center the closed select again
66570
+ setTimeout(centerActiveElement, 100);
66571
+ }, true);
66572
+ })();
66573
+
66574
+ // === SCRIPT_SEPARATOR ===
66575
+
66351
66576
  (function(){
66352
66577
  var formObj = null;
66353
66578
  var initialFormData = null;
@@ -66374,6 +66599,8 @@ function FormOnBackNavigation() {
66374
66599
  function initialButtonSetup(privateExternal, permission) {
66375
66600
  const saveBtn = document.getElementById("saveBtn");
66376
66601
  const submitBtn = document.getElementById("submitBtn");
66602
+ const prevBtn = document.getElementById("prevBtn");
66603
+ const nextBtn = document.getElementById("nextBtn");
66377
66604
  const completeOption = document.getElementById("completeOption");
66378
66605
  const saveOption = document.getElementById("saveOption");
66379
66606
  const completeDivider = document.getElementById("completeDivider");
@@ -66479,6 +66706,7 @@ function initialButtonSetup(privateExternal, permission) {
66479
66706
  }
66480
66707
  }
66481
66708
 
66709
+
66482
66710
  function onChangeButtonSetup(
66483
66711
  completionPercentage,
66484
66712
  formData,
@@ -66487,6 +66715,8 @@ function onChangeButtonSetup(
66487
66715
  ) {
66488
66716
  const saveBtn = document.getElementById("saveBtn");
66489
66717
  const submitBtn = document.getElementById("submitBtn");
66718
+ const prevBtn = document.getElementById("prevBtn");
66719
+ const nextBtn = document.getElementById("nextBtn");
66490
66720
  if (!saveBtn || !submitBtn) return;
66491
66721
 
66492
66722
  const isPrivate = privateExternal === true || privateExternal === "true";
@@ -66689,6 +66919,7 @@ async function loadRNform(
66689
66919
  // Determine form configuration based on mode
66690
66920
  let formConfig = {
66691
66921
  noAlerts: true,
66922
+ scroll: false, // DISABLE Formio internal scrolling
66692
66923
  language: window.FORMIO_LANGUAGE || "en",
66693
66924
  i18n: window.FORMIO_I18N,
66694
66925
  };
@@ -66848,6 +67079,11 @@ async function loadRNform(
66848
67079
  .then(() => {
66849
67080
  console.log("[FormIO] \u{1F680} formReady.then() triggered");
66850
67081
 
67082
+ // Handle Navigation and Button Hiding (Wizard-specific logic separated)
67083
+ if (typeof window.initializeWizardLogic === "function") {
67084
+ window.initializeWizardLogic(formObj, privateExternal, permission);
67085
+ }
67086
+
66851
67087
  if (formPreviousData) {
66852
67088
  console.log("[FormIO] \u{1F4E6} Previous form data found:", formPreviousData);
66853
67089
 
@@ -66869,7 +67105,7 @@ async function loadRNform(
66869
67105
  console.log("[FormIO] \u2705 Progress calculation completed");
66870
67106
 
66871
67107
  // Hide FormIO submit button component and wizard buttons in readOnly mode
66872
- if (mode == "readOnly") {
67108
+ if (mode == "readOnly" || permission == "read") {
66873
67109
  console.log("[FormIO] \u{1F512} ReadOnly mode detected");
66874
67110
 
66875
67111
  const submitComponents = document.querySelectorAll(
@@ -66884,41 +67120,17 @@ async function loadRNform(
66884
67120
  console.log(\`[FormIO] Hidden submit component \${index + 1}\`);
66885
67121
  });
66886
67122
 
66887
- const wizardNextBtns = document.querySelectorAll(
66888
- ".btn-wizard-nav-next"
66889
- );
66890
- console.log(
66891
- \`[FormIO] Found \${wizardNextBtns.length} wizard next buttons\`
66892
- );
66893
-
66894
- wizardNextBtns.forEach((btn, index) => {
66895
- btn.style.display = "none";
66896
- console.log(\`[FormIO] Hidden wizard next button \${index + 1}\`);
66897
- });
66898
-
66899
- const wizardSubmitBtns = document.querySelectorAll(
66900
- ".btn-wizard-nav-submit"
66901
- );
66902
- console.log(
66903
- \`[FormIO] Found \${wizardSubmitBtns.length} wizard submit buttons\`
66904
- );
66905
-
66906
- wizardSubmitBtns.forEach((btn, index) => {
66907
- btn.style.display = "none";
66908
- console.log(\`[FormIO] Hidden wizard submit button \${index + 1}\`);
66909
- });
66910
-
66911
- const wizardPrevBtns = document.querySelectorAll(
66912
- ".btn-wizard-nav-previous"
66913
- );
66914
- console.log(
66915
- \`[FormIO] Found \${wizardPrevBtns.length} wizard previous buttons\`
66916
- );
66917
-
66918
- wizardPrevBtns.forEach((btn, index) => {
66919
- btn.style.display = "none";
66920
- console.log(\`[FormIO] Hidden wizard previous button \${index + 1}\`);
66921
- });
67123
+ // Disable signature components robustly via CSS
67124
+ if (!document.getElementById("readonly-signature-style")) {
67125
+ const style = document.createElement("style");
67126
+ style.id = "readonly-signature-style";
67127
+ style.innerHTML = \`
67128
+ .formio-component-signature { pointer-events: none !important; }
67129
+ .formio-component-signature [ref="clicktoSign"],
67130
+ .formio-component-signature .click-to-sign-box { display: none !important; }
67131
+ \`;
67132
+ document.head.appendChild(style);
67133
+ }
66922
67134
  } else {
66923
67135
  console.log("[FormIO] \u{1F513} Editable mode detected");
66924
67136
  }
@@ -67096,13 +67308,16 @@ function buildMandatoryFieldsCache() {
67096
67308
  return cache;
67097
67309
  }
67098
67310
 
67099
- function executeProgressCalculation(privateExternal, permission) {
67311
+ window.executeProgressCalculation = function(privateExternal, permission) {
67100
67312
  if (!formObj) return;
67101
67313
 
67102
67314
  const data = formObj.data || {};
67103
67315
  const dataHash = JSON.stringify(data);
67316
+ const isWizard = formObj && !!formObj.wizard;
67104
67317
 
67105
- if (dataHash === lastDataHash) return;
67318
+ // For wizards, we must always proceed even if data is same,
67319
+ // because button visibility depends on the current page.
67320
+ if (!isWizard && dataHash === lastDataHash) return;
67106
67321
  lastDataHash = dataHash;
67107
67322
 
67108
67323
  const mandatoryFields = buildMandatoryFieldsCache();
@@ -67204,6 +67419,11 @@ function executeProgressCalculation(privateExternal, permission) {
67204
67419
  permission
67205
67420
  );
67206
67421
 
67422
+ // After setting up standard button visibility, override with wizard rules if applicable
67423
+ if (typeof window.updateWizardNav === "function") {
67424
+ window.updateWizardNav(formObj);
67425
+ }
67426
+
67207
67427
  // Update progress bar
67208
67428
  const progressBar = document.getElementById("progress-bar");
67209
67429
  if (progressBar) {
@@ -67546,18 +67766,19 @@ window.checkMoreButtonVisibility = function () {
67546
67766
  // For documentsOption, its display is managed by logic.
67547
67767
  // For comments/help, they are often just static items toggled.
67548
67768
 
67549
- const isVisible = (el) => el && el.style.display !== "none";
67550
-
67551
- // Assuming options.showComments/Help also imply the item SHOULD be visible if logic permits
67552
- // But logic might hide them. The 'isVisible' check handles dynamic logic.
67553
- // But for comments/help, they might be hidden initially.
67769
+ const isVisible = (el, optionKey) => {
67770
+ if (!el) return false;
67771
+ // If it's a static item like Help or Comments, check the options as well
67772
+ if (optionKey && options[optionKey] === true) return true;
67773
+ return el.style.display !== "none";
67774
+ };
67554
67775
 
67555
67776
  const hasVisibleItems =
67556
67777
  isVisible(completeOption) ||
67557
67778
  isVisible(saveOption) ||
67558
67779
  isVisible(documentsOption) ||
67559
- isVisible(commentsItem) ||
67560
- isVisible(helpItem); // Simplified: check actual DOM state
67780
+ isVisible(commentsItem, "showComments") ||
67781
+ isVisible(helpItem, "showHelp"); // Simplified: check actual DOM state + options
67561
67782
 
67562
67783
  // If ANY item is visible, show the button. Otherwise hide.
67563
67784
  if (hasVisibleItems) {
@@ -67575,6 +67796,28 @@ window.FormOnSave = FormOnSave;
67575
67796
  window.FormOnBack = FormOnBack;
67576
67797
  window.setImageData = setImageData;
67577
67798
  window.FormOnSubmit = FormOnSubmit;
67799
+ window.FormOnNext = function () {
67800
+ if (formObj && typeof formObj.nextPage === "function") {
67801
+ formObj.nextPage().then(() => {
67802
+ if (typeof window.updateWizardNav === "function") {
67803
+ window.updateWizardNav(formObj);
67804
+ }
67805
+ });
67806
+ }
67807
+ };
67808
+
67809
+ window.FormOnPrevious = function () {
67810
+ if (formObj && typeof formObj.prevPage === "function") {
67811
+ formObj.prevPage().then(() => {
67812
+ if (typeof window.updateWizardNav === "function") {
67813
+ window.updateWizardNav(formObj);
67814
+ }
67815
+ });
67816
+ }
67817
+ };
67818
+
67819
+ window.formOnSubmitFunction = FormOnSubmit;
67820
+
67578
67821
  window.resetFormCache = resetFormCache;
67579
67822
 
67580
67823
  // Notify that loadRNform is ready (for event-based coordination)
@@ -67584,6 +67827,174 @@ document.dispatchEvent(new CustomEvent('LoadRNformReady'));
67584
67827
 
67585
67828
  // === SCRIPT_SEPARATOR ===
67586
67829
 
67830
+ (function(){
67831
+ /**
67832
+ * Wizard Navigation Logic for Unvired Forms
67833
+ * This file handles all logic specific to Form.io wizard forms,
67834
+ * including navigation button visibility and event handling.
67835
+ */
67836
+
67837
+ /**
67838
+ * Updates the visibility of navigation buttons (Next, Previous, Submit)
67839
+ * based on the current wizard page and form type.
67840
+ *
67841
+ * @param {Object} formObj - The Form.io form instance
67842
+ */
67843
+ function updateWizardNav(formObj) {
67844
+ if (!formObj) return;
67845
+
67846
+ // 1. Hide default navigation and buttons (Next, Previous, Cancel, Submit)
67847
+ // This ensures that we only use our own footer buttons
67848
+ const selectorsToHide = [
67849
+ '.formio-component-wizard_nav',
67850
+ '.btn-wizard-nav-next',
67851
+ '.btn-wizard-nav-previous',
67852
+ '.btn-wizard-nav-cancel',
67853
+ '.btn-wizard-nav-submit'
67854
+ ];
67855
+
67856
+ selectorsToHide.forEach(selector => {
67857
+ const elements = formObj.element.querySelectorAll(selector);
67858
+ elements.forEach(el => {
67859
+ el.style.display = "none";
67860
+ });
67861
+ });
67862
+
67863
+ // 2. Hide components from JSON that match navigation actions
67864
+ if (typeof formObj.eachComponent === 'function') {
67865
+ formObj.eachComponent((component) => {
67866
+ if (component.type === 'button') {
67867
+ const action = (component.component.action || '').toLowerCase();
67868
+ const label = (component.component.label || '').toLowerCase();
67869
+
67870
+ if (action === 'next' || action === 'previous' || action === 'cancel' ||
67871
+ label === 'next' || label === 'previous' || label === 'cancel') {
67872
+ // Hide both the component instance and its DOM element
67873
+ component.visible = false;
67874
+ if (component.element) {
67875
+ component.element.style.display = 'none';
67876
+ }
67877
+ }
67878
+ }
67879
+ });
67880
+ }
67881
+
67882
+ // 3. Handle Footer Navigation Toggling (Only for Wizards)
67883
+ if (!formObj.wizard) return;
67884
+
67885
+ const prevBtn = document.getElementById("prevBtn");
67886
+ const nextBtn = document.getElementById("nextBtn");
67887
+ const submitBtn = document.getElementById("submitBtn");
67888
+ const saveBtn = document.getElementById("saveBtn");
67889
+
67890
+ const currentPage = formObj.page || 0;
67891
+ const numPages = (formObj.pages && formObj.pages.length) || 0;
67892
+ const isLastPage = (currentPage >= numPages - 1);
67893
+
67894
+ console.log(\`[WizardLogic] \u{1F9ED} Page: \${currentPage + 1}/\${numPages}, isLastPage: \${isLastPage}\`);
67895
+
67896
+ if (prevBtn) {
67897
+ prevBtn.style.display = currentPage > 0 ? "inline-block" : "none";
67898
+ }
67899
+
67900
+ if (nextBtn) {
67901
+ nextBtn.style.display = currentPage < numPages - 1 ? "inline-block" : "none";
67902
+ }
67903
+
67904
+ // If it's a wizard, we only want the save and submit buttons on the last page.
67905
+ // If it's NOT the last page, we force hide them.
67906
+ // If it IS the last page, we let the normal logic (onChangeButtonSetup) decide.
67907
+ if (!isLastPage) {
67908
+ console.log("[WizardLogic] \u{1F512} Intermediate page: Hiding save/complete actions");
67909
+ if (submitBtn) submitBtn.style.display = "none";
67910
+ if (saveBtn) saveBtn.style.display = "none";
67911
+
67912
+ // Hide dropdown options as well
67913
+ const completeOption = document.getElementById("completeOption");
67914
+ const saveOption = document.getElementById("saveOption");
67915
+ const completeDivider = document.getElementById("completeDivider");
67916
+ const saveDivider = document.getElementById("saveDivider");
67917
+
67918
+ if (completeOption) completeOption.style.display = "none";
67919
+ if (saveOption) saveOption.style.display = "none";
67920
+ if (completeDivider) completeDivider.style.display = "none";
67921
+ if (saveDivider) saveDivider.style.display = "none";
67922
+
67923
+ // Also hide quick action buttons if they exist
67924
+ const quickCompleteBtn = document.getElementById("quickCompleteBtn");
67925
+ const quickSaveBtn = document.getElementById("quickSaveBtn");
67926
+ if (quickCompleteBtn) quickCompleteBtn.style.display = "none";
67927
+ if (quickSaveBtn) quickSaveBtn.style.display = "none";
67928
+
67929
+ // Re-check More button visibility after hiding items
67930
+ if (typeof window.checkMoreButtonVisibility === "function") {
67931
+ window.checkMoreButtonVisibility();
67932
+ }
67933
+ } else {
67934
+ console.log("[WizardLogic] \u{1F513} Last page: Allowing normal button logic to take over");
67935
+ // On the last page, we MUST ensure the More button visibility is re-evaluated
67936
+ // because it might have been hidden on the previous page.
67937
+ if (typeof window.checkMoreButtonVisibility === "function") {
67938
+ window.checkMoreButtonVisibility();
67939
+ }
67940
+ }
67941
+ }
67942
+
67943
+ /**
67944
+ * Sets up event listeners for wizard-specific events like page changes.
67945
+ *
67946
+ * @param {Object} formObj - The Form.io form instance
67947
+ * @param {Boolean} privateExternal - Private/External flag
67948
+ * @param {String} permission - User permission level
67949
+ */
67950
+ function setupWizardEvents(formObj, privateExternal, permission) {
67951
+ if (!formObj) return;
67952
+
67953
+ if (formObj.wizard) {
67954
+ formObj.on('nextPage', () => {
67955
+ updateWizardNav(formObj);
67956
+ // Trigger progress calculation on page change
67957
+ if (typeof window.executeProgressCalculation === 'function') {
67958
+ window.executeProgressCalculation(privateExternal, permission);
67959
+ }
67960
+ });
67961
+ formObj.on('prevPage', () => {
67962
+ updateWizardNav(formObj);
67963
+ // Trigger progress calculation on page change
67964
+ if (typeof window.executeProgressCalculation === 'function') {
67965
+ window.executeProgressCalculation(privateExternal, permission);
67966
+ }
67967
+ });
67968
+ }
67969
+
67970
+ // Always handle render event to ensure buttons stay hidden
67971
+ formObj.on('render', () => {
67972
+ updateWizardNav(formObj);
67973
+ });
67974
+ }
67975
+
67976
+ window.updateWizardNav = updateWizardNav;
67977
+
67978
+ /**
67979
+ * Main initialization function for wizard logic.
67980
+ * Should be called after the Form.io form is created.
67981
+ */
67982
+ window.initializeWizardLogic = function(formObj, privateExternal, permission) {
67983
+ console.log("[WizardLogic] \u{1F9D9} Initializing wizard-specific logic...");
67984
+ setupWizardEvents(formObj, privateExternal, permission);
67985
+
67986
+ // Also listen for change events to update nav (e.g. if pages are conditionally hidden)
67987
+ formObj.on('change', () => {
67988
+ updateWizardNav(formObj);
67989
+ });
67990
+
67991
+ updateWizardNav(formObj);
67992
+ };
67993
+
67994
+ })();
67995
+
67996
+ // === SCRIPT_SEPARATOR ===
67997
+
67587
67998
  (function(){
67588
67999
  async function loadCommentsform(
67589
68000
  template,
@@ -68106,7 +68517,7 @@ window.deleteAppDocument = async function(id) {
68106
68517
  window.getAllDocuments = getAllDocuments;
68107
68518
  window.hasDocuments = hasDocuments;
68108
68519
  function getBuildVersion() {
68109
- return "1.0.21";
68520
+ return "1.0.23";
68110
68521
  }
68111
68522
  export {
68112
68523
  getBuildVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unvired/turboforms-embed-sdk",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Reusable vanilla JS form library that works with React, Angular, Ionic, etc.",
5
5
  "main": "dist/unvired-forms-sdk.js",
6
6
  "types": "dist/index.d.ts",