pict-section-formeditor 1.0.0

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.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/docs/.nojekyll +0 -0
  4. package/docs/README.md +162 -0
  5. package/docs/_sidebar.md +23 -0
  6. package/docs/_topbar.md +5 -0
  7. package/docs/cover.md +12 -0
  8. package/docs/css/docuserve.css +73 -0
  9. package/docs/index.html +39 -0
  10. package/docs/retold-catalog.json +224 -0
  11. package/docs/retold-keyword-index.json +46846 -0
  12. package/example_applications/form_editor/.quackage.json +10 -0
  13. package/example_applications/form_editor/FormEditor-Example-Application.js +226 -0
  14. package/example_applications/form_editor/html/icon-chooser.html +375 -0
  15. package/example_applications/form_editor/html/index.html +54 -0
  16. package/example_applications/form_editor/package.json +50 -0
  17. package/package.json +55 -0
  18. package/sample_manifests/Complex-Table.json +974 -0
  19. package/sample_manifests/Distill-Example.json +200 -0
  20. package/sample_manifests/Gradebook-Assignment.json +38 -0
  21. package/sample_manifests/Gradebook-Student.json +40 -0
  22. package/sample_manifests/Manyfest-Editor.json +347 -0
  23. package/sample_manifests/Simple-Form.json +232 -0
  24. package/sample_manifests/Simple-Table.json +79 -0
  25. package/source/Pict-Section-FormEditor-DefaultConfiguration.js +3321 -0
  26. package/source/Pict-Section-FormEditor.js +35 -0
  27. package/source/providers/Pict-Provider-ChildPictManager-Application.js +40 -0
  28. package/source/providers/Pict-Provider-ChildPictManager.js +238 -0
  29. package/source/providers/Pict-Provider-FormEditorDocumentation.js +356 -0
  30. package/source/providers/Pict-Provider-FormEditorDragDrop.js +535 -0
  31. package/source/providers/Pict-Provider-FormEditorIconography.js +1002 -0
  32. package/source/providers/Pict-Provider-FormEditorManifestOps.js +1443 -0
  33. package/source/providers/Pict-Provider-FormEditorRendering.js +730 -0
  34. package/source/providers/Pict-Provider-FormEditorUtilities.js +862 -0
  35. package/source/providers/Pict-Provider-PreviewCSS.js +42 -0
  36. package/source/views/PictView-FormEditor-InlineEditing.js +309 -0
  37. package/source/views/PictView-FormEditor-InputTypePicker.js +532 -0
  38. package/source/views/PictView-FormEditor-PropertiesPanel.js +7730 -0
  39. package/source/views/PictView-FormEditor.js +681 -0
  40. package/test/Pict-Section-FormEditor_tests.js +4102 -0
  41. package/user-documentation/.pict_documentation_topics.json +695 -0
  42. package/user-documentation/Getting-Started.md +32 -0
  43. package/user-documentation/Groups.md +52 -0
  44. package/user-documentation/Inputs.md +98 -0
  45. package/user-documentation/Sections.md +36 -0
  46. package/user-documentation/Shortcuts.md +44 -0
  47. package/user-documentation/Solver-Expression-Walkthrough.md +176 -0
  48. package/user-documentation/Solver-Expressions-Advanced.md +344 -0
  49. package/user-documentation/Solver-Functions.md +213 -0
  50. package/user-documentation/Solvers.md +81 -0
  51. package/user-documentation/ToC.md +18 -0
  52. package/user-documentation/solverfunctions/abs.md +84 -0
  53. package/user-documentation/solverfunctions/aggregationhistogram.md +83 -0
  54. package/user-documentation/solverfunctions/aggregationhistogrambyobject.md +64 -0
  55. package/user-documentation/solverfunctions/arrayconcat.md +64 -0
  56. package/user-documentation/solverfunctions/avg.md +81 -0
  57. package/user-documentation/solverfunctions/bucketset.md +69 -0
  58. package/user-documentation/solverfunctions/ceil.md +70 -0
  59. package/user-documentation/solverfunctions/cleanvaluearray.md +66 -0
  60. package/user-documentation/solverfunctions/cleanvalueobject.md +68 -0
  61. package/user-documentation/solverfunctions/colorgroupbackground.md +60 -0
  62. package/user-documentation/solverfunctions/colorinputbackground.md +62 -0
  63. package/user-documentation/solverfunctions/colorinputbackgroundtabular.md +64 -0
  64. package/user-documentation/solverfunctions/colorsectionbackground.md +59 -0
  65. package/user-documentation/solverfunctions/compare.md +72 -0
  66. package/user-documentation/solverfunctions/concat.md +73 -0
  67. package/user-documentation/solverfunctions/concatraw.md +73 -0
  68. package/user-documentation/solverfunctions/cos.md +75 -0
  69. package/user-documentation/solverfunctions/count.md +73 -0
  70. package/user-documentation/solverfunctions/countset.md +65 -0
  71. package/user-documentation/solverfunctions/countsetelements.md +63 -0
  72. package/user-documentation/solverfunctions/createarrayfromabsolutevalues.md +63 -0
  73. package/user-documentation/solverfunctions/createvalueobjectbyhashes.md +69 -0
  74. package/user-documentation/solverfunctions/cumulativesummation.md +96 -0
  75. package/user-documentation/solverfunctions/dateadddays.md +79 -0
  76. package/user-documentation/solverfunctions/dateaddhours.md +74 -0
  77. package/user-documentation/solverfunctions/dateaddmilliseconds.md +65 -0
  78. package/user-documentation/solverfunctions/dateaddminutes.md +72 -0
  79. package/user-documentation/solverfunctions/dateaddmonths.md +74 -0
  80. package/user-documentation/solverfunctions/dateaddseconds.md +66 -0
  81. package/user-documentation/solverfunctions/dateaddweeks.md +73 -0
  82. package/user-documentation/solverfunctions/dateaddyears.md +74 -0
  83. package/user-documentation/solverfunctions/datedaydifference.md +84 -0
  84. package/user-documentation/solverfunctions/datefromparts.md +81 -0
  85. package/user-documentation/solverfunctions/datehourdifference.md +64 -0
  86. package/user-documentation/solverfunctions/datemathadd.md +72 -0
  87. package/user-documentation/solverfunctions/datemilliseconddifference.md +64 -0
  88. package/user-documentation/solverfunctions/dateminutedifference.md +64 -0
  89. package/user-documentation/solverfunctions/datemonthdifference.md +66 -0
  90. package/user-documentation/solverfunctions/dateseconddifference.md +64 -0
  91. package/user-documentation/solverfunctions/dateweekdifference.md +65 -0
  92. package/user-documentation/solverfunctions/dateyeardifference.md +64 -0
  93. package/user-documentation/solverfunctions/differencearrays.md +59 -0
  94. package/user-documentation/solverfunctions/disablesolverordinal.md +58 -0
  95. package/user-documentation/solverfunctions/distributionhistogram.md +96 -0
  96. package/user-documentation/solverfunctions/distributionhistogrambyobject.md +64 -0
  97. package/user-documentation/solverfunctions/enablesolverordinal.md +57 -0
  98. package/user-documentation/solverfunctions/entryinset.md +72 -0
  99. package/user-documentation/solverfunctions/euler.md +77 -0
  100. package/user-documentation/solverfunctions/exp.md +74 -0
  101. package/user-documentation/solverfunctions/findfirstvaluebyexactmatch.md +67 -0
  102. package/user-documentation/solverfunctions/findfirstvaluebystringincludes.md +67 -0
  103. package/user-documentation/solverfunctions/flatten.md +76 -0
  104. package/user-documentation/solverfunctions/floor.md +70 -0
  105. package/user-documentation/solverfunctions/gaussianelimination.md +75 -0
  106. package/user-documentation/solverfunctions/generatearrayofobjectsfromsets.md +70 -0
  107. package/user-documentation/solverfunctions/generatehtmlhexcolor.md +67 -0
  108. package/user-documentation/solverfunctions/getvalue.md +90 -0
  109. package/user-documentation/solverfunctions/getvaluearray.md +64 -0
  110. package/user-documentation/solverfunctions/getvalueobject.md +67 -0
  111. package/user-documentation/solverfunctions/hidesections.md +58 -0
  112. package/user-documentation/solverfunctions/if.md +109 -0
  113. package/user-documentation/solverfunctions/iterativeseries.md +107 -0
  114. package/user-documentation/solverfunctions/join.md +75 -0
  115. package/user-documentation/solverfunctions/joinraw.md +64 -0
  116. package/user-documentation/solverfunctions/largestinset.md +63 -0
  117. package/user-documentation/solverfunctions/leastsquares.md +66 -0
  118. package/user-documentation/solverfunctions/linest.md +58 -0
  119. package/user-documentation/solverfunctions/log.md +74 -0
  120. package/user-documentation/solverfunctions/logvalues.md +65 -0
  121. package/user-documentation/solverfunctions/match.md +71 -0
  122. package/user-documentation/solverfunctions/matrixinverse.md +67 -0
  123. package/user-documentation/solverfunctions/matrixmultiply.md +71 -0
  124. package/user-documentation/solverfunctions/matrixtranspose.md +72 -0
  125. package/user-documentation/solverfunctions/matrixvectormultiply.md +69 -0
  126. package/user-documentation/solverfunctions/max.md +73 -0
  127. package/user-documentation/solverfunctions/mean.md +63 -0
  128. package/user-documentation/solverfunctions/median.md +79 -0
  129. package/user-documentation/solverfunctions/min.md +73 -0
  130. package/user-documentation/solverfunctions/mode.md +66 -0
  131. package/user-documentation/solverfunctions/objectkeystoarray.md +66 -0
  132. package/user-documentation/solverfunctions/objectvaluessortbyexternalobjectarray.md +65 -0
  133. package/user-documentation/solverfunctions/objectvaluestoarray.md +67 -0
  134. package/user-documentation/solverfunctions/percent.md +75 -0
  135. package/user-documentation/solverfunctions/pi.md +77 -0
  136. package/user-documentation/solverfunctions/polynomialregression.md +69 -0
  137. package/user-documentation/solverfunctions/predict.md +71 -0
  138. package/user-documentation/solverfunctions/rad.md +85 -0
  139. package/user-documentation/solverfunctions/randomfloat.md +63 -0
  140. package/user-documentation/solverfunctions/randomfloatbetween.md +72 -0
  141. package/user-documentation/solverfunctions/randomfloatupto.md +65 -0
  142. package/user-documentation/solverfunctions/randominteger.md +56 -0
  143. package/user-documentation/solverfunctions/randomintegerbetween.md +72 -0
  144. package/user-documentation/solverfunctions/randomintegerupto.md +64 -0
  145. package/user-documentation/solverfunctions/refreshtabularsection.md +57 -0
  146. package/user-documentation/solverfunctions/resolvehtmlentities.md +64 -0
  147. package/user-documentation/solverfunctions/round.md +111 -0
  148. package/user-documentation/solverfunctions/runsolvers.md +49 -0
  149. package/user-documentation/solverfunctions/setconcatenate.md +64 -0
  150. package/user-documentation/solverfunctions/setgroupvisibility.md +60 -0
  151. package/user-documentation/solverfunctions/setsectionvisibility.md +59 -0
  152. package/user-documentation/solverfunctions/setsolverordinalenabled.md +59 -0
  153. package/user-documentation/solverfunctions/settabularrowlength.md +57 -0
  154. package/user-documentation/solverfunctions/setvalue.md +65 -0
  155. package/user-documentation/solverfunctions/showsections.md +58 -0
  156. package/user-documentation/solverfunctions/sin.md +83 -0
  157. package/user-documentation/solverfunctions/slice.md +80 -0
  158. package/user-documentation/solverfunctions/smallestinset.md +63 -0
  159. package/user-documentation/solverfunctions/sortarray.md +58 -0
  160. package/user-documentation/solverfunctions/sorthistogram.md +70 -0
  161. package/user-documentation/solverfunctions/sorthistogrambykeys.md +69 -0
  162. package/user-documentation/solverfunctions/sortset.md +75 -0
  163. package/user-documentation/solverfunctions/sqrt.md +85 -0
  164. package/user-documentation/solverfunctions/stdev.md +81 -0
  165. package/user-documentation/solverfunctions/stdeva.md +58 -0
  166. package/user-documentation/solverfunctions/stdevp.md +83 -0
  167. package/user-documentation/solverfunctions/stringcountsegments.md +66 -0
  168. package/user-documentation/solverfunctions/stringgetsegments.md +74 -0
  169. package/user-documentation/solverfunctions/subtractingsummation.md +66 -0
  170. package/user-documentation/solverfunctions/sum.md +78 -0
  171. package/user-documentation/solverfunctions/tan.md +78 -0
  172. package/user-documentation/solverfunctions/tofixed.md +75 -0
  173. package/user-documentation/solverfunctions/unionarrays.md +59 -0
  174. package/user-documentation/solverfunctions/uniquearray.md +58 -0
  175. package/user-documentation/solverfunctions/var.md +67 -0
  176. package/user-documentation/solverfunctions/vara.md +58 -0
  177. package/user-documentation/solverfunctions/varp.md +66 -0
  178. package/user-documentation/solverfunctions/when.md +98 -0
@@ -0,0 +1,532 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class PictViewFormEditorInputTypePicker extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ this._ParentFormEditor = null;
9
+ }
10
+
11
+ /**
12
+ * Open the floating InputType picker for an Input.
13
+ *
14
+ * Renders a categorized, searchable panel anchored near the InputType chip.
15
+ * The first option is always "DataType Default" which clears the InputType.
16
+ *
17
+ * @param {number} pSectionIndex - Index of the section
18
+ * @param {number} pGroupIndex - Index of the group
19
+ * @param {number} pRowIndex - Index of the row
20
+ * @param {number} pInputIndex - Index of the input within the row
21
+ */
22
+ beginEditInputType(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex)
23
+ {
24
+ let tmpHash = this._ParentFormEditor.Hash;
25
+ let tmpViewRef = this._ParentFormEditor._browserViewRef();
26
+ let tmpPickerId = `FormEditor-InputTypePicker-${tmpHash}`;
27
+
28
+ // Close any existing picker first
29
+ this.closeInputTypePicker();
30
+
31
+ // Resolve the current InputType from the Descriptor's PictForm
32
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
33
+ let tmpCurrentValue = '';
34
+ if (tmpManifest && tmpManifest.Sections)
35
+ {
36
+ let tmpSection = tmpManifest.Sections[pSectionIndex];
37
+ let tmpGroup = tmpSection && tmpSection.Groups ? tmpSection.Groups[pGroupIndex] : null;
38
+ let tmpRow = tmpGroup && tmpGroup.Rows ? tmpGroup.Rows[pRowIndex] : null;
39
+ if (tmpRow && Array.isArray(tmpRow.Inputs))
40
+ {
41
+ let tmpAddress = tmpRow.Inputs[pInputIndex];
42
+ if (typeof tmpAddress === 'string' && tmpManifest.Descriptors && tmpManifest.Descriptors[tmpAddress])
43
+ {
44
+ let tmpDescriptor = tmpManifest.Descriptors[tmpAddress];
45
+ if (tmpDescriptor.PictForm && tmpDescriptor.PictForm.InputType)
46
+ {
47
+ tmpCurrentValue = tmpDescriptor.PictForm.InputType;
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ // Store the current edit context so commitEditInputType can use it
54
+ this._InputTypePickerContext =
55
+ {
56
+ SectionIndex: pSectionIndex,
57
+ GroupIndex: pGroupIndex,
58
+ RowIndex: pRowIndex,
59
+ InputIndex: pInputIndex,
60
+ CurrentValue: tmpCurrentValue
61
+ };
62
+
63
+ // Build the picker HTML
64
+ let tmpPickerHTML = this._renderInputTypePicker(tmpCurrentValue, '');
65
+
66
+ // Get the chip element to position the picker near it
67
+ let tmpChipElementId = `FormEditor-InputInputType-${tmpHash}-${pSectionIndex}-${pGroupIndex}-${pRowIndex}-${pInputIndex}`;
68
+
69
+ // Append overlay + picker to document.body so they are not clipped
70
+ // by the form editor container's overflow:hidden.
71
+ if (typeof document !== 'undefined')
72
+ {
73
+ // Create the full-viewport overlay to catch dismiss clicks
74
+ let tmpOverlay = document.createElement('div');
75
+ tmpOverlay.id = tmpPickerId + '-Overlay';
76
+ tmpOverlay.className = 'pict-fe-inputtype-overlay';
77
+ tmpOverlay.onclick = function() { eval(tmpViewRef + '.closeInputTypePicker()'); };
78
+ // Prevent scroll events on the overlay from reaching the page
79
+ tmpOverlay.addEventListener('wheel', function(pEvent) { pEvent.preventDefault(); }, { passive: false });
80
+
81
+ let tmpPickerContainer = document.createElement('div');
82
+ tmpPickerContainer.id = tmpPickerId;
83
+ tmpPickerContainer.className = 'pict-fe-inputtype-picker';
84
+ tmpPickerContainer.innerHTML = tmpPickerHTML;
85
+ tmpPickerContainer.onclick = function(e) { e.stopPropagation(); };
86
+ // Contain scroll within the picker — allow internal scrolling but
87
+ // prevent scroll chaining to the page when at top/bottom boundary
88
+ tmpPickerContainer.addEventListener('wheel', function(pEvent)
89
+ {
90
+ pEvent.stopPropagation();
91
+ // Find the scrollable categories list inside the picker
92
+ let tmpScrollable = tmpPickerContainer.querySelector('.pict-fe-inputtype-picker-categories');
93
+ if (!tmpScrollable)
94
+ {
95
+ pEvent.preventDefault();
96
+ return;
97
+ }
98
+ let tmpAtTop = (tmpScrollable.scrollTop <= 0) && (pEvent.deltaY < 0);
99
+ let tmpAtBottom = (tmpScrollable.scrollTop + tmpScrollable.clientHeight >= tmpScrollable.scrollHeight) && (pEvent.deltaY > 0);
100
+ if (tmpAtTop || tmpAtBottom)
101
+ {
102
+ pEvent.preventDefault();
103
+ }
104
+ }, { passive: false });
105
+
106
+ tmpOverlay.appendChild(tmpPickerContainer);
107
+ document.body.appendChild(tmpOverlay);
108
+
109
+ // Position the picker using fixed viewport coordinates
110
+ // anchored below the InputType chip (or properties panel button as fallback)
111
+ let tmpAnchorEl = document.getElementById(tmpChipElementId);
112
+ // If the inline chip doesn't exist (e.g. picker opened from properties panel), use the panel button
113
+ if (!tmpAnchorEl)
114
+ {
115
+ tmpAnchorEl = document.getElementById(`FormEditor-PropsInputTypeBtn-${tmpHash}`);
116
+ }
117
+ if (tmpAnchorEl && tmpAnchorEl.getBoundingClientRect)
118
+ {
119
+ let tmpAnchorRect = tmpAnchorEl.getBoundingClientRect();
120
+
121
+ tmpPickerContainer.style.position = 'fixed';
122
+ tmpPickerContainer.style.top = (tmpAnchorRect.bottom + 4) + 'px';
123
+ tmpPickerContainer.style.left = tmpAnchorRect.left + 'px';
124
+
125
+ // If the picker would overflow the right edge of the viewport,
126
+ // nudge it left
127
+ let tmpPickerWidth = 340;
128
+ if (tmpAnchorRect.left + tmpPickerWidth > window.innerWidth)
129
+ {
130
+ tmpPickerContainer.style.left = Math.max(8, window.innerWidth - tmpPickerWidth - 8) + 'px';
131
+ }
132
+
133
+ // If the picker would overflow the bottom of the viewport,
134
+ // open it upward instead
135
+ let tmpPickerMaxHeight = 420;
136
+ if (tmpAnchorRect.bottom + 4 + tmpPickerMaxHeight > window.innerHeight)
137
+ {
138
+ tmpPickerContainer.style.top = '';
139
+ tmpPickerContainer.style.bottom = (window.innerHeight - tmpAnchorRect.top + 4) + 'px';
140
+ }
141
+ }
142
+
143
+ // Focus the search input
144
+ let tmpSearchSet = this.pict.ContentAssignment.getElement(`#${tmpPickerId}-Search`);
145
+ let tmpSearch = (Array.isArray(tmpSearchSet) && tmpSearchSet.length > 0) ? tmpSearchSet[0] : tmpSearchSet;
146
+ if (tmpSearch && tmpSearch.focus)
147
+ {
148
+ tmpSearch.focus();
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Render the inner HTML of the InputType picker panel.
155
+ *
156
+ * @param {string} pCurrentValue - The currently selected InputType Hash (empty for default)
157
+ * @param {string} pSearchQuery - Current search/filter text
158
+ * @return {string} The picker innerHTML
159
+ */
160
+ _renderInputTypePicker(pCurrentValue, pSearchQuery)
161
+ {
162
+ let tmpHash = this._ParentFormEditor.Hash;
163
+ let tmpViewRef = this._ParentFormEditor._browserViewRef();
164
+ let tmpPickerId = `FormEditor-InputTypePicker-${tmpHash}`;
165
+
166
+ let tmpHTML = '';
167
+
168
+ // Search bar
169
+ tmpHTML += '<div class="pict-fe-inputtype-picker-search">';
170
+ tmpHTML += `<input type="text" class="pict-fe-inputtype-picker-search-input" id="${tmpPickerId}-Search" placeholder="Search input types\u2026" value="${this._ParentFormEditor._UtilitiesProvider._escapeAttr(pSearchQuery)}" oninput="${tmpViewRef}._onInputTypePickerSearch(this.value)" onkeydown="if(event.key==='Escape'){${tmpViewRef}.closeInputTypePicker();}" />`;
171
+ tmpHTML += '</div>';
172
+
173
+ // Filter definitions by search query
174
+ let tmpDefinitions = this._ParentFormEditor._UtilitiesProvider._filterInputTypeDefinitions(pSearchQuery);
175
+
176
+ // Separate prominent items from the rest
177
+ let tmpProminentItems = [];
178
+ let tmpRemainingItems = [];
179
+ for (let i = 0; i < tmpDefinitions.length; i++)
180
+ {
181
+ if (tmpDefinitions[i].Prominent)
182
+ {
183
+ tmpProminentItems.push(tmpDefinitions[i]);
184
+ }
185
+ else
186
+ {
187
+ tmpRemainingItems.push(tmpDefinitions[i]);
188
+ }
189
+ }
190
+
191
+ // --- Common section: DataType Default + prominent items ---
192
+ tmpHTML += '<div class="pict-fe-inputtype-picker-default">';
193
+
194
+ // DataType Default is always shown (not affected by search)
195
+ let tmpDefaultActive = (pCurrentValue === '') ? ' pict-fe-inputtype-picker-item-active' : '';
196
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item${tmpDefaultActive}" onclick="${tmpViewRef}.commitEditInputType('')">`;
197
+ tmpHTML += '<div class="pict-fe-inputtype-picker-item-name">DataType Default</div>';
198
+ tmpHTML += '<div class="pict-fe-inputtype-picker-item-desc">Use the default template for this DataType</div>';
199
+ tmpHTML += '</div>';
200
+
201
+ // Prominent items appear right after DataType Default
202
+ for (let p = 0; p < tmpProminentItems.length; p++)
203
+ {
204
+ let tmpDef = tmpProminentItems[p];
205
+ let tmpActive = (tmpDef.Hash === pCurrentValue) ? ' pict-fe-inputtype-picker-item-active' : '';
206
+ let tmpPickerIcon = this._ParentFormEditor._IconographyProvider.getInputTypeIcon(tmpDef.Hash, 16);
207
+
208
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item${tmpActive}" onclick="${tmpViewRef}.commitEditInputType('${this._ParentFormEditor._UtilitiesProvider._escapeAttr(tmpDef.Hash)}')">`;
209
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item-name">${tmpPickerIcon ? '<span class="pict-fe-icon pict-fe-icon-picker">' + tmpPickerIcon + '</span>' : ''}${this._ParentFormEditor._UtilitiesProvider._escapeHTML(tmpDef.Name || tmpDef.Hash)}</div>`;
210
+ if (tmpDef.Description)
211
+ {
212
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item-desc">${this._ParentFormEditor._UtilitiesProvider._escapeHTML(tmpDef.Description)}</div>`;
213
+ }
214
+ tmpHTML += '</div>';
215
+ }
216
+
217
+ tmpHTML += '</div>';
218
+
219
+ // --- Categorized section: all non-prominent items ---
220
+ let tmpCategories = [];
221
+ let tmpCategorySeen = {};
222
+ let tmpCategoryItems = {};
223
+
224
+ for (let i = 0; i < tmpRemainingItems.length; i++)
225
+ {
226
+ let tmpDef = tmpRemainingItems[i];
227
+ let tmpCategory = tmpDef.Category || 'Other';
228
+
229
+ if (!tmpCategorySeen[tmpCategory])
230
+ {
231
+ tmpCategorySeen[tmpCategory] = true;
232
+ tmpCategories.push(tmpCategory);
233
+ tmpCategoryItems[tmpCategory] = [];
234
+ }
235
+ tmpCategoryItems[tmpCategory].push(tmpDef);
236
+ }
237
+
238
+ if (tmpCategories.length === 0 && tmpProminentItems.length === 0 && pSearchQuery)
239
+ {
240
+ tmpHTML += '<div class="pict-fe-inputtype-picker-empty">No input types match your search.</div>';
241
+ }
242
+ else if (tmpCategories.length > 0)
243
+ {
244
+ tmpHTML += '<div class="pict-fe-inputtype-picker-categories">';
245
+ for (let c = 0; c < tmpCategories.length; c++)
246
+ {
247
+ let tmpCategory = tmpCategories[c];
248
+ let tmpItems = tmpCategoryItems[tmpCategory];
249
+
250
+ tmpHTML += '<div class="pict-fe-inputtype-picker-category">';
251
+ tmpHTML += `<div class="pict-fe-inputtype-picker-category-label">${this._ParentFormEditor._UtilitiesProvider._escapeHTML(tmpCategory)}</div>`;
252
+
253
+ for (let j = 0; j < tmpItems.length; j++)
254
+ {
255
+ let tmpDef = tmpItems[j];
256
+ let tmpActive = (tmpDef.Hash === pCurrentValue) ? ' pict-fe-inputtype-picker-item-active' : '';
257
+ let tmpCatIcon = this._ParentFormEditor._IconographyProvider.getInputTypeIcon(tmpDef.Hash, 16);
258
+
259
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item${tmpActive}" onclick="${tmpViewRef}.commitEditInputType('${this._ParentFormEditor._UtilitiesProvider._escapeAttr(tmpDef.Hash)}')">`;
260
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item-name">${tmpCatIcon ? '<span class="pict-fe-icon pict-fe-icon-picker">' + tmpCatIcon + '</span>' : ''}${this._ParentFormEditor._UtilitiesProvider._escapeHTML(tmpDef.Name || tmpDef.Hash)}</div>`;
261
+ if (tmpDef.Description)
262
+ {
263
+ tmpHTML += `<div class="pict-fe-inputtype-picker-item-desc">${this._ParentFormEditor._UtilitiesProvider._escapeHTML(tmpDef.Description)}</div>`;
264
+ }
265
+ tmpHTML += '</div>';
266
+ }
267
+
268
+ tmpHTML += '</div>';
269
+ }
270
+ tmpHTML += '</div>';
271
+ }
272
+
273
+ return tmpHTML;
274
+ }
275
+
276
+ /**
277
+ * Handle search input changes in the InputType picker.
278
+ *
279
+ * @param {string} pQuery - The current search query
280
+ */
281
+ _onInputTypePickerSearch(pQuery)
282
+ {
283
+ let tmpHash = this._ParentFormEditor.Hash;
284
+ let tmpPickerId = `FormEditor-InputTypePicker-${tmpHash}`;
285
+
286
+ if (!this._InputTypePickerContext)
287
+ {
288
+ return;
289
+ }
290
+
291
+ let tmpCurrentValue = this._InputTypePickerContext.CurrentValue;
292
+ let tmpPickerHTML = this._renderInputTypePicker(tmpCurrentValue, pQuery);
293
+
294
+ let tmpPickerSet = this.pict.ContentAssignment.getElement(`#${tmpPickerId}`);
295
+ let tmpPicker = (Array.isArray(tmpPickerSet) && tmpPickerSet.length > 0) ? tmpPickerSet[0] : tmpPickerSet;
296
+ if (tmpPicker)
297
+ {
298
+ tmpPicker.innerHTML = tmpPickerHTML;
299
+
300
+ // Re-focus the search input and restore cursor position
301
+ let tmpSearchSet = this.pict.ContentAssignment.getElement(`#${tmpPickerId}-Search`);
302
+ let tmpSearch = (Array.isArray(tmpSearchSet) && tmpSearchSet.length > 0) ? tmpSearchSet[0] : tmpSearchSet;
303
+ if (tmpSearch && tmpSearch.focus)
304
+ {
305
+ tmpSearch.focus();
306
+ // Move cursor to end
307
+ if (tmpSearch.setSelectionRange)
308
+ {
309
+ let tmpLen = tmpSearch.value.length;
310
+ tmpSearch.setSelectionRange(tmpLen, tmpLen);
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Close the floating InputType picker.
318
+ */
319
+ closeInputTypePicker()
320
+ {
321
+ let tmpHash = this._ParentFormEditor.Hash;
322
+ let tmpOverlayId = `FormEditor-InputTypePicker-${tmpHash}-Overlay`;
323
+
324
+ let tmpOverlaySet = this.pict.ContentAssignment.getElement(`#${tmpOverlayId}`);
325
+ let tmpOverlay = (Array.isArray(tmpOverlaySet) && tmpOverlaySet.length > 0) ? tmpOverlaySet[0] : tmpOverlaySet;
326
+ if (tmpOverlay && tmpOverlay.parentNode)
327
+ {
328
+ tmpOverlay.parentNode.removeChild(tmpOverlay);
329
+ }
330
+
331
+ this._InputTypePickerContext = null;
332
+ }
333
+
334
+ /**
335
+ * Commit the InputType selection from the picker, updating the Descriptor
336
+ * and re-rendering the visual editor.
337
+ *
338
+ * @param {string} pInputTypeHash - The selected InputType Hash, or '' for DataType Default
339
+ */
340
+ commitEditInputType(pInputTypeHash)
341
+ {
342
+ if (!this._InputTypePickerContext)
343
+ {
344
+ return;
345
+ }
346
+
347
+ let tmpContext = this._InputTypePickerContext;
348
+
349
+ // Close the picker
350
+ this.closeInputTypePicker();
351
+
352
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
353
+ if (!tmpManifest || !tmpManifest.Sections)
354
+ {
355
+ return;
356
+ }
357
+
358
+ let tmpDescriptor = null;
359
+
360
+ if (tmpContext.IsTabular)
361
+ {
362
+ // Tabular column — resolve descriptor from ReferenceManifest
363
+ let tmpSection = tmpManifest.Sections[tmpContext.SectionIndex];
364
+ let tmpGroup = (tmpSection && Array.isArray(tmpSection.Groups)) ? tmpSection.Groups[tmpContext.GroupIndex] : null;
365
+ if (tmpGroup && tmpGroup.RecordManifest)
366
+ {
367
+ let tmpRefManifest = this._ParentFormEditor._ManifestOpsProvider._resolveReferenceManifest(tmpGroup.RecordManifest);
368
+ if (tmpRefManifest && tmpRefManifest.Descriptors && tmpRefManifest.Descriptors[tmpContext.ColumnAddress])
369
+ {
370
+ tmpDescriptor = tmpRefManifest.Descriptors[tmpContext.ColumnAddress];
371
+ }
372
+ }
373
+ }
374
+ else
375
+ {
376
+ // Regular input — resolve descriptor from main Descriptors
377
+ let tmpSection = tmpManifest.Sections[tmpContext.SectionIndex];
378
+ let tmpGroup = tmpSection && tmpSection.Groups ? tmpSection.Groups[tmpContext.GroupIndex] : null;
379
+ let tmpRow = tmpGroup && tmpGroup.Rows ? tmpGroup.Rows[tmpContext.RowIndex] : null;
380
+ if (tmpRow && Array.isArray(tmpRow.Inputs))
381
+ {
382
+ let tmpAddress = tmpRow.Inputs[tmpContext.InputIndex];
383
+ if (typeof tmpAddress === 'string' && tmpManifest.Descriptors && tmpManifest.Descriptors[tmpAddress])
384
+ {
385
+ tmpDescriptor = tmpManifest.Descriptors[tmpAddress];
386
+ }
387
+ }
388
+ }
389
+
390
+ if (tmpDescriptor)
391
+ {
392
+ if (!tmpDescriptor.PictForm)
393
+ {
394
+ tmpDescriptor.PictForm = {};
395
+ }
396
+
397
+ if (pInputTypeHash === '')
398
+ {
399
+ // "DataType Default" — remove the InputType property
400
+ delete tmpDescriptor.PictForm.InputType;
401
+ }
402
+ else
403
+ {
404
+ tmpDescriptor.PictForm.InputType = pInputTypeHash;
405
+ }
406
+ }
407
+
408
+ this._ParentFormEditor.renderVisualEditor();
409
+ }
410
+
411
+ /**
412
+ * Open the InputType picker for a tabular/RecordSet column descriptor.
413
+ * Reuses the same picker UI as regular inputs but resolves the descriptor
414
+ * from the ReferenceManifest instead of the main Descriptors.
415
+ *
416
+ * @param {number} pSectionIndex
417
+ * @param {number} pGroupIndex
418
+ * @param {string} pColumnAddress - The column address in the ReferenceManifest
419
+ */
420
+ beginEditTabularInputType(pSectionIndex, pGroupIndex, pColumnAddress)
421
+ {
422
+ let tmpHash = this._ParentFormEditor.Hash;
423
+ let tmpViewRef = this._ParentFormEditor._browserViewRef();
424
+ let tmpPickerId = `FormEditor-InputTypePicker-${tmpHash}`;
425
+
426
+ // Close any existing picker first
427
+ this.closeInputTypePicker();
428
+
429
+ // Resolve the current InputType from the ReferenceManifest descriptor
430
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
431
+ let tmpCurrentValue = '';
432
+ if (tmpManifest && tmpManifest.Sections)
433
+ {
434
+ let tmpSection = tmpManifest.Sections[pSectionIndex];
435
+ let tmpGroup = (tmpSection && Array.isArray(tmpSection.Groups)) ? tmpSection.Groups[pGroupIndex] : null;
436
+ if (tmpGroup && tmpGroup.RecordManifest)
437
+ {
438
+ let tmpRefManifest = this._ParentFormEditor._ManifestOpsProvider._resolveReferenceManifest(tmpGroup.RecordManifest);
439
+ if (tmpRefManifest && tmpRefManifest.Descriptors && tmpRefManifest.Descriptors[pColumnAddress])
440
+ {
441
+ let tmpDescriptor = tmpRefManifest.Descriptors[pColumnAddress];
442
+ if (tmpDescriptor.PictForm && tmpDescriptor.PictForm.InputType)
443
+ {
444
+ tmpCurrentValue = tmpDescriptor.PictForm.InputType;
445
+ }
446
+ }
447
+ }
448
+ }
449
+
450
+ // Store context — mark as tabular so commitEditInputType knows to handle it
451
+ this._InputTypePickerContext =
452
+ {
453
+ SectionIndex: pSectionIndex,
454
+ GroupIndex: pGroupIndex,
455
+ ColumnAddress: pColumnAddress,
456
+ IsTabular: true,
457
+ CurrentValue: tmpCurrentValue
458
+ };
459
+
460
+ // Build the picker HTML
461
+ let tmpPickerHTML = this._renderInputTypePicker(tmpCurrentValue, '');
462
+
463
+ // Anchor near the properties panel InputType button
464
+ if (typeof document !== 'undefined')
465
+ {
466
+ let tmpOverlay = document.createElement('div');
467
+ tmpOverlay.id = tmpPickerId + '-Overlay';
468
+ tmpOverlay.className = 'pict-fe-inputtype-overlay';
469
+ tmpOverlay.onclick = function() { eval(tmpViewRef + '.closeInputTypePicker()'); };
470
+ tmpOverlay.addEventListener('wheel', function(pEvent) { pEvent.preventDefault(); }, { passive: false });
471
+
472
+ let tmpPickerContainer = document.createElement('div');
473
+ tmpPickerContainer.id = tmpPickerId;
474
+ tmpPickerContainer.className = 'pict-fe-inputtype-picker';
475
+ tmpPickerContainer.innerHTML = tmpPickerHTML;
476
+ tmpPickerContainer.onclick = function(e) { e.stopPropagation(); };
477
+ tmpPickerContainer.addEventListener('wheel', function(pEvent)
478
+ {
479
+ pEvent.stopPropagation();
480
+ let tmpScrollable = tmpPickerContainer.querySelector('.pict-fe-inputtype-picker-categories');
481
+ if (!tmpScrollable)
482
+ {
483
+ pEvent.preventDefault();
484
+ return;
485
+ }
486
+ let tmpAtTop = (tmpScrollable.scrollTop <= 0) && (pEvent.deltaY < 0);
487
+ let tmpAtBottom = (tmpScrollable.scrollTop + tmpScrollable.clientHeight >= tmpScrollable.scrollHeight) && (pEvent.deltaY > 0);
488
+ if (tmpAtTop || tmpAtBottom)
489
+ {
490
+ pEvent.preventDefault();
491
+ }
492
+ }, { passive: false });
493
+
494
+ tmpOverlay.appendChild(tmpPickerContainer);
495
+ document.body.appendChild(tmpOverlay);
496
+
497
+ // Position anchored to the InputType button in the properties panel
498
+ let tmpAnchorEl = document.getElementById(`FormEditor-PropsInputTypeBtn-${tmpHash}`);
499
+ if (tmpAnchorEl && tmpAnchorEl.getBoundingClientRect)
500
+ {
501
+ let tmpAnchorRect = tmpAnchorEl.getBoundingClientRect();
502
+
503
+ tmpPickerContainer.style.position = 'fixed';
504
+ tmpPickerContainer.style.top = (tmpAnchorRect.bottom + 4) + 'px';
505
+ tmpPickerContainer.style.left = tmpAnchorRect.left + 'px';
506
+
507
+ let tmpPickerWidth = 340;
508
+ if (tmpAnchorRect.left + tmpPickerWidth > window.innerWidth)
509
+ {
510
+ tmpPickerContainer.style.left = Math.max(8, window.innerWidth - tmpPickerWidth - 8) + 'px';
511
+ }
512
+
513
+ let tmpPickerMaxHeight = 420;
514
+ if (tmpAnchorRect.bottom + 4 + tmpPickerMaxHeight > window.innerHeight)
515
+ {
516
+ tmpPickerContainer.style.top = '';
517
+ tmpPickerContainer.style.bottom = (window.innerHeight - tmpAnchorRect.top + 4) + 'px';
518
+ }
519
+ }
520
+
521
+ // Focus the search input
522
+ let tmpSearchSet = this.pict.ContentAssignment.getElement(`#${tmpPickerId}-Search`);
523
+ let tmpSearch = (Array.isArray(tmpSearchSet) && tmpSearchSet.length > 0) ? tmpSearchSet[0] : tmpSearchSet;
524
+ if (tmpSearch && tmpSearch.focus)
525
+ {
526
+ tmpSearch.focus();
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ module.exports = PictViewFormEditorInputTypePicker;