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,862 @@
1
+ const libPictProvider = require('pict-provider');
2
+
3
+ class FormEditorUtilities extends libPictProvider
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+
9
+ this.serviceType = 'PictProvider';
10
+
11
+ // Back-reference to the parent FormEditor view (set after construction)
12
+ this._ParentFormEditor = null;
13
+ }
14
+
15
+ /**
16
+ * Sanitize a string into a valid object key.
17
+ *
18
+ * Strips non-alphanumeric characters (except underscore), collapses runs of
19
+ * underscores, and trims leading/trailing underscores so the result is a
20
+ * clean, human-readable identifier.
21
+ *
22
+ * @param {string} pString - The string to sanitize
23
+ * @return {string} A sanitized key, or 'INVALID' if the input is unusable
24
+ */
25
+ sanitizeObjectKey(pString)
26
+ {
27
+ if (typeof pString !== 'string' || pString.length < 1)
28
+ {
29
+ return 'INVALID';
30
+ }
31
+ return pString
32
+ .replace(/[^a-zA-Z0-9_]/g, '_')
33
+ .replace(/_+/g, '_')
34
+ .replace(/^_|_$/g, '');
35
+ }
36
+
37
+ /**
38
+ * Check whether a hash is an auto-generated section hash (S{n}).
39
+ */
40
+ _isAutoGeneratedSectionHash(pHash)
41
+ {
42
+ if (typeof pHash !== 'string')
43
+ {
44
+ return false;
45
+ }
46
+ return /^S\d+$/.test(pHash);
47
+ }
48
+
49
+ /**
50
+ * Check whether a hash is an auto-generated input hash.
51
+ * Auto-generated input hashes end with _Input{n}.
52
+ */
53
+ _isAutoGeneratedInputHash(pHash)
54
+ {
55
+ if (typeof pHash !== 'string')
56
+ {
57
+ return false;
58
+ }
59
+ return /_Input\d+$/.test(pHash);
60
+ }
61
+
62
+ _escapeHTML(pString)
63
+ {
64
+ if (typeof pString !== 'string')
65
+ {
66
+ return '';
67
+ }
68
+ return pString
69
+ .replace(/&/g, '&amp;')
70
+ .replace(/</g, '&lt;')
71
+ .replace(/>/g, '&gt;')
72
+ .replace(/"/g, '&quot;');
73
+ }
74
+
75
+ _escapeAttr(pString)
76
+ {
77
+ if (typeof pString !== 'string')
78
+ {
79
+ return '';
80
+ }
81
+ return pString
82
+ .replace(/&/g, '&amp;')
83
+ .replace(/"/g, '&quot;')
84
+ .replace(/'/g, '&#39;');
85
+ }
86
+
87
+ _truncateMiddle(pString, pMaxLength)
88
+ {
89
+ if (typeof pString !== 'string')
90
+ {
91
+ return '';
92
+ }
93
+ if (!pMaxLength || pString.length <= pMaxLength)
94
+ {
95
+ return pString;
96
+ }
97
+ // Show more of the beginning than the end
98
+ let tmpEndLength = Math.floor((pMaxLength - 1) / 3);
99
+ let tmpStartLength = pMaxLength - 1 - tmpEndLength;
100
+ return pString.substring(0, tmpStartLength) + '\u2026' + pString.substring(pString.length - tmpEndLength);
101
+ }
102
+
103
+ /**
104
+ * Build the merged list of InputType definitions from built-in defaults and
105
+ * any embedder-provided overrides/additions in options.InputTypeDefinitions.
106
+ *
107
+ * Each definition is an object with:
108
+ * Hash — The InputType value stored in PictForm.InputType
109
+ * Name — A human-readable display name
110
+ * Description — A short description of what this type does
111
+ * Category — A grouping category for the picker UI
112
+ *
113
+ * Embedders can extend or override by passing InputTypeDefinitions in the
114
+ * view options. Entries with matching Hash values override the default;
115
+ * entries with new Hash values are appended.
116
+ *
117
+ * @param {object} pOptions - The merged view options
118
+ * @return {Array} An array of InputType definition objects
119
+ */
120
+ _buildInputTypeDefinitions(pOptions)
121
+ {
122
+ // Built-in InputType definitions categorized by function.
123
+ // Each definition can include a Manifest with Descriptors for
124
+ // InputType-specific PictForm properties, rendered in the properties panel.
125
+ let tmpDefaults =
126
+ [
127
+ // Text & Content
128
+ { Hash: 'TextArea', Name: 'Text Area', Description: 'Multi-line text input', Category: 'Text & Content' },
129
+ { Hash: 'Markdown', Name: 'Markdown', Description: 'Markdown-formatted text editor', Category: 'Text & Content' },
130
+ { Hash: 'HTML', Name: 'HTML', Description: 'Rich HTML content block', Category: 'Text & Content' },
131
+
132
+ // Selection
133
+ {
134
+ Hash: 'Option', Name: 'Option', Description: 'Dropdown select from a set of choices', Category: 'Selection',
135
+ Manifest:
136
+ {
137
+ Descriptors:
138
+ {
139
+ 'SelectOptions': { Name: 'Select Options', Hash: 'SelectOptions', DataType: 'String', Description: 'JSON array of {id, text} option objects' },
140
+ 'SelectOptionsPickList': { Name: 'Pick List Name', Hash: 'SelectOptionsPickList', DataType: 'String', Description: 'Dynamic pick list name from AppData' }
141
+ }
142
+ }
143
+ },
144
+ { Hash: 'Boolean', Name: 'Boolean', Description: 'Checkbox or toggle for true/false values', Category: 'Selection' },
145
+ { Hash: 'Color', Name: 'Color', Description: 'Color picker input', Category: 'Selection' },
146
+
147
+ // Display
148
+ { Hash: 'DisplayOnly', Name: 'Display Only', Description: 'Read-only display of the value with no input control', Category: 'Display', Prominent: true },
149
+ { Hash: 'ReadOnly', Name: 'Read Only', Description: 'Input-styled read-only field', Category: 'Display', Prominent: true },
150
+ {
151
+ Hash: 'PreciseNumberReadOnly', Name: 'Precise Number (Read Only)', Description: 'Formatted precise number display with optional prefix/postfix', Category: 'Display', Prominent: true,
152
+ Manifest:
153
+ {
154
+ Descriptors:
155
+ {
156
+ 'DecimalPrecision': { Name: 'Decimal Precision', Hash: 'DecimalPrecision', DataType: 'Number', Description: 'Number of decimal places to display' },
157
+ 'AddCommas': { Name: 'Add Commas', Hash: 'AddCommas', DataType: 'Boolean', Description: 'Add thousand-separator commas to the number' },
158
+ 'DigitsPrefix': { Name: 'Prefix', Hash: 'DigitsPrefix', DataType: 'String', Description: 'Prefix string prepended to the value (e.g. "$")' },
159
+ 'DigitsPostfix': { Name: 'Postfix', Hash: 'DigitsPostfix', DataType: 'String', Description: 'Postfix string appended to the value (e.g. " USD")' }
160
+ }
161
+ }
162
+ },
163
+ { Hash: 'Hidden', Name: 'Hidden', Description: 'Hidden input, not visible to the user', Category: 'Display' },
164
+ {
165
+ Hash: 'Chart', Name: 'Chart', Description: 'Data visualization chart', Category: 'Display',
166
+ Manifest:
167
+ {
168
+ Descriptors:
169
+ {
170
+ 'ChartType': { Name: 'Chart Type', Hash: 'ChartType', DataType: 'String', Description: 'Chart.js type (bar, line, pie, doughnut, radar, polarArea)' },
171
+ 'ChartLabelsAddress': { Name: 'Labels Address', Hash: 'ChartLabelsAddress', DataType: 'String', Description: 'AppData address to resolve chart labels from' },
172
+ 'ChartLabelsSolver': { Name: 'Labels Solver', Hash: 'ChartLabelsSolver', DataType: 'String', Description: 'Fable solver expression for chart labels' },
173
+ 'ChartDatasetsAddress': { Name: 'Datasets Address', Hash: 'ChartDatasetsAddress', DataType: 'String', Description: 'AppData address to resolve datasets from' }
174
+ }
175
+ }
176
+ },
177
+ { Hash: 'Link', Name: 'Link', Description: 'Clickable hyperlink display', Category: 'Display' },
178
+
179
+ // Navigation
180
+ {
181
+ Hash: 'TabSectionSelector', Name: 'Tab Section Selector', Description: 'Selector that controls which sections are displayed as tabs', Category: 'Navigation',
182
+ Manifest:
183
+ {
184
+ Descriptors:
185
+ {
186
+ 'TabSectionSet': { Name: 'Section Set', Hash: 'TabSectionSet', DataType: 'String', Description: 'JSON array of section hashes to show as tabs' },
187
+ 'DefaultTabSectionHash': { Name: 'Default Tab', Hash: 'DefaultTabSectionHash', DataType: 'String', Description: 'Hash of the initially selected tab' },
188
+ 'DefaultFromData': { Name: 'Default From Data', Hash: 'DefaultFromData', DataType: 'Boolean', Description: 'Use the data value to determine the default tab' }
189
+ }
190
+ }
191
+ },
192
+ {
193
+ Hash: 'TabGroupSelector', Name: 'Tab Group Selector', Description: 'Selector that controls which groups are displayed as tabs', Category: 'Navigation',
194
+ Manifest:
195
+ {
196
+ Descriptors:
197
+ {
198
+ 'TabGroupSet': { Name: 'Group Set', Hash: 'TabGroupSet', DataType: 'String', Description: 'JSON array of group hashes to show as tabs' },
199
+ 'DefaultTabGroupHash': { Name: 'Default Tab', Hash: 'DefaultTabGroupHash', DataType: 'String', Description: 'Hash of the initially selected tab' },
200
+ 'DefaultFromData': { Name: 'Default From Data', Hash: 'DefaultFromData', DataType: 'Boolean', Description: 'Use the data value to determine the default tab' }
201
+ }
202
+ }
203
+ },
204
+
205
+ // Advanced
206
+ {
207
+ Hash: 'Templated', Name: 'Templated', Description: 'Custom template-driven input rendering', Category: 'Advanced',
208
+ Manifest:
209
+ {
210
+ Descriptors:
211
+ {
212
+ 'Template': { Name: 'Template', Hash: 'Template', DataType: 'String', Description: 'Template string for custom rendering' }
213
+ }
214
+ }
215
+ },
216
+ {
217
+ Hash: 'TemplatedEntityLookup', Name: 'Templated Entity Lookup', Description: 'Template-driven entity search and selection', Category: 'Advanced',
218
+ Manifest:
219
+ {
220
+ Descriptors:
221
+ {
222
+ 'Template': { Name: 'Template', Hash: 'Template', DataType: 'String', Description: 'Template string for rendering the entity display' }
223
+ }
224
+ }
225
+ }
226
+ ];
227
+
228
+ // If the embedder provided custom InputTypeDefinitions, merge them
229
+ let tmpCustomDefinitions = (pOptions && Array.isArray(pOptions.InputTypeDefinitions)) ? pOptions.InputTypeDefinitions : [];
230
+
231
+ if (tmpCustomDefinitions.length === 0)
232
+ {
233
+ return tmpDefaults;
234
+ }
235
+
236
+ // Index defaults by Hash for quick lookup
237
+ let tmpDefaultMap = {};
238
+ for (let i = 0; i < tmpDefaults.length; i++)
239
+ {
240
+ tmpDefaultMap[tmpDefaults[i].Hash] = i;
241
+ }
242
+
243
+ // Merge: override existing entries by Hash, append new ones
244
+ for (let i = 0; i < tmpCustomDefinitions.length; i++)
245
+ {
246
+ let tmpCustom = tmpCustomDefinitions[i];
247
+ if (!tmpCustom || !tmpCustom.Hash)
248
+ {
249
+ continue;
250
+ }
251
+
252
+ if (tmpDefaultMap.hasOwnProperty(tmpCustom.Hash))
253
+ {
254
+ // Override the default entry
255
+ let tmpIndex = tmpDefaultMap[tmpCustom.Hash];
256
+ tmpDefaults[tmpIndex] = Object.assign({}, tmpDefaults[tmpIndex], tmpCustom);
257
+ }
258
+ else
259
+ {
260
+ // Append as a new entry
261
+ tmpDefaults.push(tmpCustom);
262
+ tmpDefaultMap[tmpCustom.Hash] = tmpDefaults.length - 1;
263
+ }
264
+ }
265
+
266
+ return tmpDefaults;
267
+ }
268
+
269
+ /**
270
+ * Get the distinct list of InputType categories in display order.
271
+ *
272
+ * @return {Array} An array of category name strings
273
+ */
274
+ _getInputTypeCategories()
275
+ {
276
+ let tmpCategories = [];
277
+ let tmpSeen = {};
278
+
279
+ for (let i = 0; i < this._ParentFormEditor._InputTypeDefinitions.length; i++)
280
+ {
281
+ let tmpCategory = this._ParentFormEditor._InputTypeDefinitions[i].Category || 'Other';
282
+ if (!tmpSeen[tmpCategory])
283
+ {
284
+ tmpSeen[tmpCategory] = true;
285
+ tmpCategories.push(tmpCategory);
286
+ }
287
+ }
288
+
289
+ return tmpCategories;
290
+ }
291
+
292
+ /**
293
+ * Get the Manifest for a given InputType hash.
294
+ *
295
+ * Returns the Manifest object (with Descriptors) or null if the InputType
296
+ * has no configurable PictForm properties.
297
+ *
298
+ * @param {string} pInputTypeHash - The InputType hash (e.g. 'Option', 'Chart')
299
+ * @return {object|null} The Manifest object or null
300
+ */
301
+ _getInputTypeManifest(pInputTypeHash)
302
+ {
303
+ if (!pInputTypeHash || typeof pInputTypeHash !== 'string')
304
+ {
305
+ return null;
306
+ }
307
+
308
+ for (let i = 0; i < this._ParentFormEditor._InputTypeDefinitions.length; i++)
309
+ {
310
+ if (this._ParentFormEditor._InputTypeDefinitions[i].Hash === pInputTypeHash)
311
+ {
312
+ return this._ParentFormEditor._InputTypeDefinitions[i].Manifest || null;
313
+ }
314
+ }
315
+
316
+ return null;
317
+ }
318
+
319
+ /**
320
+ * Get InputType definitions filtered by a search query.
321
+ *
322
+ * The search is case-insensitive and matches against Hash, Name,
323
+ * Description, and Category.
324
+ *
325
+ * @param {string} pQuery - The search query (empty string returns all)
326
+ * @return {Array} Filtered InputType definition objects
327
+ */
328
+ _filterInputTypeDefinitions(pQuery)
329
+ {
330
+ if (!pQuery || typeof pQuery !== 'string' || pQuery.trim().length === 0)
331
+ {
332
+ return this._ParentFormEditor._InputTypeDefinitions;
333
+ }
334
+
335
+ let tmpQuery = pQuery.trim().toLowerCase();
336
+
337
+ return this._ParentFormEditor._InputTypeDefinitions.filter(function(pDef)
338
+ {
339
+ return (
340
+ (pDef.Hash && pDef.Hash.toLowerCase().indexOf(tmpQuery) >= 0) ||
341
+ (pDef.Name && pDef.Name.toLowerCase().indexOf(tmpQuery) >= 0) ||
342
+ (pDef.Description && pDef.Description.toLowerCase().indexOf(tmpQuery) >= 0) ||
343
+ (pDef.Category && pDef.Category.toLowerCase().indexOf(tmpQuery) >= 0)
344
+ );
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Select an input to open it in the properties panel.
350
+ *
351
+ * @param {number} pSectionIndex - Index of the section
352
+ * @param {number} pGroupIndex - Index of the group
353
+ * @param {number} pRowIndex - Index of the row
354
+ * @param {number} pInputIndex - Index of the input within the row
355
+ */
356
+ selectInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex)
357
+ {
358
+ this._ParentFormEditor._SelectedInputIndices = [pSectionIndex, pGroupIndex, pRowIndex, pInputIndex];
359
+ // Clear submanifest column selection
360
+ this._ParentFormEditor._SelectedTabularColumn = null;
361
+ // Also select the containing section and group
362
+ this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
363
+ this._ParentFormEditor._SelectedGroupIndices = { SectionIndex: pSectionIndex, GroupIndex: pGroupIndex };
364
+
365
+ if (this._ParentFormEditor._PropertiesPanelView)
366
+ {
367
+ this._ParentFormEditor._PropertiesPanelView.selectInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex);
368
+ }
369
+
370
+ // Auto-switch to properties tab and expand panel
371
+ this._ParentFormEditor._PanelActiveTab = 'properties';
372
+ if (this._ParentFormEditor._PanelCollapsed)
373
+ {
374
+ this._ParentFormEditor._PanelCollapsed = false;
375
+ }
376
+
377
+ this._ParentFormEditor.renderVisualEditor();
378
+
379
+ // Align the properties panel with the selected input after layout settles
380
+ let tmpSelf = this;
381
+ setTimeout(function () { tmpSelf._alignPanelToSelection(); }, 0);
382
+ }
383
+
384
+ /**
385
+ * Deselect the current input. The panel stays open.
386
+ */
387
+ deselectInput()
388
+ {
389
+ this._ParentFormEditor._SelectedInputIndices = null;
390
+ this._ParentFormEditor._SelectedTabularColumn = null;
391
+ this._ParentFormEditor._SelectedSectionIndex = null;
392
+ this._ParentFormEditor._SelectedGroupIndices = null;
393
+
394
+ if (this._ParentFormEditor._PropertiesPanelView)
395
+ {
396
+ this._ParentFormEditor._PropertiesPanelView.deselectAll();
397
+ }
398
+
399
+ this._ParentFormEditor.renderVisualEditor();
400
+ }
401
+
402
+ /**
403
+ * Select a section to open it in the properties panel.
404
+ *
405
+ * @param {number} pSectionIndex - Index of the section
406
+ */
407
+ selectSection(pSectionIndex)
408
+ {
409
+ this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
410
+ // Clear input, tabular, and group selections
411
+ this._ParentFormEditor._SelectedInputIndices = null;
412
+ this._ParentFormEditor._SelectedTabularColumn = null;
413
+ this._ParentFormEditor._SelectedGroupIndices = null;
414
+
415
+ if (this._ParentFormEditor._PropertiesPanelView)
416
+ {
417
+ this._ParentFormEditor._PropertiesPanelView.selectSection(pSectionIndex);
418
+ }
419
+
420
+ // Auto-switch to section tab and expand panel
421
+ this._ParentFormEditor._PanelActiveTab = 'section';
422
+ if (this._ParentFormEditor._PanelCollapsed)
423
+ {
424
+ this._ParentFormEditor._PanelCollapsed = false;
425
+ }
426
+
427
+ this._ParentFormEditor.renderVisualEditor();
428
+ }
429
+
430
+ /**
431
+ * Select a group to open it in the properties panel.
432
+ *
433
+ * @param {number} pSectionIndex - Index of the section
434
+ * @param {number} pGroupIndex - Index of the group
435
+ */
436
+ selectGroup(pSectionIndex, pGroupIndex)
437
+ {
438
+ this._ParentFormEditor._SelectedGroupIndices = { SectionIndex: pSectionIndex, GroupIndex: pGroupIndex };
439
+ // Clear input and tabular selections
440
+ this._ParentFormEditor._SelectedInputIndices = null;
441
+ this._ParentFormEditor._SelectedTabularColumn = null;
442
+ // Also select the containing section
443
+ this._ParentFormEditor._SelectedSectionIndex = pSectionIndex;
444
+
445
+ if (this._ParentFormEditor._PropertiesPanelView)
446
+ {
447
+ this._ParentFormEditor._PropertiesPanelView.selectGroup(pSectionIndex, pGroupIndex);
448
+ }
449
+
450
+ // Auto-switch to group tab and expand panel
451
+ this._ParentFormEditor._PanelActiveTab = 'group';
452
+ if (this._ParentFormEditor._PanelCollapsed)
453
+ {
454
+ this._ParentFormEditor._PanelCollapsed = false;
455
+ }
456
+
457
+ this._ParentFormEditor.renderVisualEditor();
458
+ }
459
+
460
+ /**
461
+ * Set the input display mode for all input cards.
462
+ *
463
+ * @param {string} pMode - 'name' or 'hash'
464
+ */
465
+ setInputDisplayMode(pMode)
466
+ {
467
+ if (pMode === 'name' || pMode === 'hash')
468
+ {
469
+ this._ParentFormEditor._InputDisplayMode = pMode;
470
+ this._ParentFormEditor.renderVisualEditor();
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Toggle the properties panel collapsed/expanded state.
476
+ */
477
+ togglePropertiesPanel()
478
+ {
479
+ this._ParentFormEditor._PanelCollapsed = !this._ParentFormEditor._PanelCollapsed;
480
+
481
+ if (typeof document !== 'undefined')
482
+ {
483
+ let tmpPanelEl = document.getElementById('FormEditor-PropertiesPanel-' + this._ParentFormEditor.Hash);
484
+ if (tmpPanelEl)
485
+ {
486
+ if (this._ParentFormEditor._PanelCollapsed)
487
+ {
488
+ tmpPanelEl.className = 'pict-fe-properties-panel';
489
+ tmpPanelEl.style.width = '';
490
+ }
491
+ else
492
+ {
493
+ tmpPanelEl.className = 'pict-fe-properties-panel pict-fe-properties-panel-open';
494
+ tmpPanelEl.style.width = this._ParentFormEditor._PanelWidth + 'px';
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Begin resizing the properties panel by dragging the toggle bar.
502
+ *
503
+ * @param {MouseEvent} pEvent
504
+ */
505
+ onPanelResizeStart(pEvent)
506
+ {
507
+ if (typeof document === 'undefined')
508
+ {
509
+ return;
510
+ }
511
+
512
+ // Don't start resize if panel is collapsed — just toggle instead
513
+ if (this._ParentFormEditor._PanelCollapsed)
514
+ {
515
+ this.togglePropertiesPanel();
516
+ return;
517
+ }
518
+
519
+ pEvent.preventDefault();
520
+ this._ParentFormEditor._PanelResizing = true;
521
+
522
+ let tmpSelf = this;
523
+ let tmpStartX = pEvent.clientX;
524
+ let tmpStartWidth = this._ParentFormEditor._PanelWidth;
525
+ let tmpPanelEl = document.getElementById('FormEditor-PropertiesPanel-' + this._ParentFormEditor.Hash);
526
+
527
+ if (!tmpPanelEl)
528
+ {
529
+ this._ParentFormEditor._PanelResizing = false;
530
+ return;
531
+ }
532
+
533
+ // Find the content area to enforce its minimum width
534
+ let tmpContentEl = document.querySelector('.pict-fe-editor-content');
535
+ let tmpContainerEl = document.querySelector('.pict-fe-editor-layout');
536
+
537
+ let tmpOnMouseMove = function(pMoveEvent)
538
+ {
539
+ if (!tmpSelf._ParentFormEditor._PanelResizing)
540
+ {
541
+ return;
542
+ }
543
+
544
+ // Moving left increases panel width, moving right decreases it
545
+ let tmpDelta = tmpStartX - pMoveEvent.clientX;
546
+ let tmpNewWidth = Math.max(240, tmpStartWidth + tmpDelta);
547
+
548
+ // Limit so the content area doesn't shrink below its min-width (300px)
549
+ if (tmpContainerEl)
550
+ {
551
+ let tmpAvailable = tmpContainerEl.clientWidth - 10; // 10px for the grip
552
+ let tmpContentMin = 300;
553
+ if (tmpNewWidth > tmpAvailable - tmpContentMin)
554
+ {
555
+ tmpNewWidth = tmpAvailable - tmpContentMin;
556
+ }
557
+ }
558
+
559
+ tmpSelf._ParentFormEditor._PanelWidth = tmpNewWidth;
560
+ tmpPanelEl.style.width = tmpNewWidth + 'px';
561
+ };
562
+
563
+ let tmpOnMouseUp = function()
564
+ {
565
+ tmpSelf._ParentFormEditor._PanelResizing = false;
566
+ document.removeEventListener('mousemove', tmpOnMouseMove);
567
+ document.removeEventListener('mouseup', tmpOnMouseUp);
568
+
569
+ // Persist the panel width preference
570
+ if (typeof localStorage !== 'undefined')
571
+ {
572
+ try
573
+ {
574
+ localStorage.setItem('pict-fe-panel-width', String(tmpSelf._ParentFormEditor._PanelWidth));
575
+ }
576
+ catch (pError)
577
+ {
578
+ // localStorage may throw in restrictive environments
579
+ }
580
+ }
581
+ };
582
+
583
+ document.addEventListener('mousemove', tmpOnMouseMove);
584
+ document.addEventListener('mouseup', tmpOnMouseUp);
585
+ }
586
+
587
+ /**
588
+ * Switch the active tab in the properties panel.
589
+ *
590
+ * @param {string} pTabName - 'form', 'section', 'group', 'properties', 'options', or 'help'
591
+ */
592
+ setPanelTab(pTabName)
593
+ {
594
+ if (pTabName === 'form' || pTabName === 'properties' || pTabName === 'section' || pTabName === 'group' || pTabName === 'options' || pTabName === 'help')
595
+ {
596
+ this._ParentFormEditor._PanelActiveTab = pTabName;
597
+ // Re-render only the panel content, not the entire visual editor
598
+ if (this._ParentFormEditor._PropertiesPanelView)
599
+ {
600
+ this._ParentFormEditor._PropertiesPanelView.renderPanel();
601
+ }
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Compute summary statistics about the form manifest.
607
+ *
608
+ * @returns {object} { Sections, Groups, Inputs, Descriptors }
609
+ */
610
+ getFormStats()
611
+ {
612
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
613
+ let tmpStats =
614
+ {
615
+ Sections: 0,
616
+ Groups: 0,
617
+ Inputs: 0,
618
+ Descriptors: 0,
619
+ ReferenceManifests: 0,
620
+ TabularColumns: 0,
621
+ DataTypes: {},
622
+ InputTypes: {}
623
+ };
624
+
625
+ if (!tmpManifest)
626
+ {
627
+ return tmpStats;
628
+ }
629
+
630
+ if (Array.isArray(tmpManifest.Sections))
631
+ {
632
+ tmpStats.Sections = tmpManifest.Sections.length;
633
+ for (let i = 0; i < tmpManifest.Sections.length; i++)
634
+ {
635
+ let tmpSection = tmpManifest.Sections[i];
636
+ if (Array.isArray(tmpSection.Groups))
637
+ {
638
+ tmpStats.Groups += tmpSection.Groups.length;
639
+ for (let j = 0; j < tmpSection.Groups.length; j++)
640
+ {
641
+ let tmpGroup = tmpSection.Groups[j];
642
+ if (Array.isArray(tmpGroup.Rows))
643
+ {
644
+ for (let k = 0; k < tmpGroup.Rows.length; k++)
645
+ {
646
+ let tmpRow = tmpGroup.Rows[k];
647
+ if (Array.isArray(tmpRow.Inputs))
648
+ {
649
+ tmpStats.Inputs += tmpRow.Inputs.length;
650
+ }
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ if (tmpManifest.Descriptors && typeof tmpManifest.Descriptors === 'object')
659
+ {
660
+ let tmpDescriptorKeys = Object.keys(tmpManifest.Descriptors);
661
+ tmpStats.Descriptors = tmpDescriptorKeys.length;
662
+
663
+ // Build DataType and InputType histograms
664
+ for (let d = 0; d < tmpDescriptorKeys.length; d++)
665
+ {
666
+ let tmpDesc = tmpManifest.Descriptors[tmpDescriptorKeys[d]];
667
+ if (tmpDesc)
668
+ {
669
+ let tmpDataType = tmpDesc.DataType || 'String';
670
+ tmpStats.DataTypes[tmpDataType] = (tmpStats.DataTypes[tmpDataType] || 0) + 1;
671
+
672
+ if (tmpDesc.PictForm && tmpDesc.PictForm.InputType)
673
+ {
674
+ let tmpInputType = tmpDesc.PictForm.InputType;
675
+ tmpStats.InputTypes[tmpInputType] = (tmpStats.InputTypes[tmpInputType] || 0) + 1;
676
+ }
677
+ }
678
+ }
679
+ }
680
+
681
+ if (tmpManifest.ReferenceManifests && typeof tmpManifest.ReferenceManifests === 'object')
682
+ {
683
+ let tmpRefNames = Object.keys(tmpManifest.ReferenceManifests);
684
+ tmpStats.ReferenceManifests = tmpRefNames.length;
685
+ for (let r = 0; r < tmpRefNames.length; r++)
686
+ {
687
+ let tmpRefManifest = tmpManifest.ReferenceManifests[tmpRefNames[r]];
688
+ if (tmpRefManifest && tmpRefManifest.Descriptors && typeof tmpRefManifest.Descriptors === 'object')
689
+ {
690
+ tmpStats.TabularColumns += Object.keys(tmpRefManifest.Descriptors).length;
691
+ }
692
+ }
693
+ }
694
+
695
+ return tmpStats;
696
+ }
697
+
698
+ /**
699
+ * Enumerate all inputs in the manifest with their indices and labels.
700
+ *
701
+ * @returns {Array} Array of { SectionIndex, GroupIndex, RowIndex, InputIndex, Address, Label, SectionName }
702
+ */
703
+ getAllInputEntries()
704
+ {
705
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
706
+ let tmpEntries = [];
707
+
708
+ if (!tmpManifest || !Array.isArray(tmpManifest.Sections))
709
+ {
710
+ return tmpEntries;
711
+ }
712
+
713
+ for (let s = 0; s < tmpManifest.Sections.length; s++)
714
+ {
715
+ let tmpSection = tmpManifest.Sections[s];
716
+ if (!Array.isArray(tmpSection.Groups))
717
+ {
718
+ continue;
719
+ }
720
+ for (let g = 0; g < tmpSection.Groups.length; g++)
721
+ {
722
+ let tmpGroup = tmpSection.Groups[g];
723
+ let tmpGroupLayout = tmpGroup.Layout || 'Record';
724
+
725
+ // Record layout: enumerate rows and inputs
726
+ if (tmpGroupLayout === 'Record' && Array.isArray(tmpGroup.Rows))
727
+ {
728
+ for (let r = 0; r < tmpGroup.Rows.length; r++)
729
+ {
730
+ let tmpRow = tmpGroup.Rows[r];
731
+ if (!Array.isArray(tmpRow.Inputs))
732
+ {
733
+ continue;
734
+ }
735
+ for (let i = 0; i < tmpRow.Inputs.length; i++)
736
+ {
737
+ let tmpAddress = tmpRow.Inputs[i];
738
+ let tmpDescriptor = (tmpManifest.Descriptors && tmpManifest.Descriptors[tmpAddress]) ? tmpManifest.Descriptors[tmpAddress] : null;
739
+ let tmpLabel = tmpDescriptor ? (tmpDescriptor.Name || tmpDescriptor.Hash || tmpAddress) : tmpAddress;
740
+ let tmpHash = tmpDescriptor ? (tmpDescriptor.Hash || '') : '';
741
+ let tmpDataType = tmpDescriptor ? (tmpDescriptor.DataType || '') : '';
742
+
743
+ tmpEntries.push(
744
+ {
745
+ SectionIndex: s,
746
+ GroupIndex: g,
747
+ RowIndex: r,
748
+ InputIndex: i,
749
+ Address: tmpAddress,
750
+ Hash: tmpHash,
751
+ Label: tmpLabel,
752
+ DataType: tmpDataType,
753
+ SectionName: tmpSection.Name || tmpSection.Hash || ('Section ' + (s + 1)),
754
+ GroupName: tmpGroup.Name || tmpGroup.Hash || ('Group ' + (g + 1)),
755
+ RowNumber: r + 1
756
+ });
757
+ }
758
+ }
759
+ }
760
+ // Tabular / RecordSet: enumerate submanifest columns
761
+ else if ((tmpGroupLayout === 'Tabular' || tmpGroupLayout === 'RecordSet') && tmpGroup.RecordManifest)
762
+ {
763
+ let tmpRefManifest = this._ParentFormEditor._ManifestOpsProvider._resolveReferenceManifest(tmpGroup.RecordManifest);
764
+ if (tmpRefManifest && tmpRefManifest.Descriptors && typeof tmpRefManifest.Descriptors === 'object')
765
+ {
766
+ let tmpColKeys = Object.keys(tmpRefManifest.Descriptors);
767
+ for (let c = 0; c < tmpColKeys.length; c++)
768
+ {
769
+ let tmpColDescriptor = tmpRefManifest.Descriptors[tmpColKeys[c]];
770
+ let tmpLabel = tmpColDescriptor ? (tmpColDescriptor.Name || tmpColDescriptor.Hash || tmpColKeys[c]) : tmpColKeys[c];
771
+ let tmpHash = tmpColDescriptor ? (tmpColDescriptor.Hash || '') : '';
772
+ let tmpDataType = tmpColDescriptor ? (tmpColDescriptor.DataType || '') : '';
773
+
774
+ tmpEntries.push(
775
+ {
776
+ SectionIndex: s,
777
+ GroupIndex: g,
778
+ Address: tmpColKeys[c],
779
+ Hash: tmpHash,
780
+ Label: tmpLabel,
781
+ DataType: tmpDataType,
782
+ SectionName: tmpSection.Name || tmpSection.Hash || ('Section ' + (s + 1)),
783
+ GroupName: tmpGroup.Name || tmpGroup.Hash || ('Group ' + (g + 1)),
784
+ IsTabular: true
785
+ });
786
+ }
787
+ }
788
+ }
789
+ }
790
+ }
791
+
792
+ return tmpEntries;
793
+ }
794
+
795
+ /**
796
+ * Scroll a specific input card into view in the visual editor.
797
+ *
798
+ * @param {number} pSectionIndex
799
+ * @param {number} pGroupIndex
800
+ * @param {number} pRowIndex
801
+ * @param {number} pInputIndex
802
+ */
803
+ scrollToInput(pSectionIndex, pGroupIndex, pRowIndex, pInputIndex)
804
+ {
805
+ if (typeof document === 'undefined')
806
+ {
807
+ return;
808
+ }
809
+
810
+ let tmpInputId = `FormEditor-Input-${this._ParentFormEditor.Hash}-${pSectionIndex}-${pGroupIndex}-${pRowIndex}-${pInputIndex}`;
811
+ let tmpElement = document.getElementById(tmpInputId);
812
+
813
+ if (tmpElement)
814
+ {
815
+ tmpElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Align the properties panel vertically with the currently selected input
821
+ * or tabular column. Also scrolls the selected element into view in the
822
+ * main content area if it is not already visible.
823
+ *
824
+ * Should be called after renderVisualEditor() inside a setTimeout so the
825
+ * browser has computed layout before we measure positions.
826
+ */
827
+ _alignPanelToSelection()
828
+ {
829
+ // No-op: the properties panel now has a fixed header and tab bar with
830
+ // independently scrolling tab content. The previous approach of adding
831
+ // paddingTop to align the panel with the selected input caused the panel
832
+ // to jump. The searchable selector dropdowns provide easy navigation.
833
+ }
834
+
835
+ _updateCodeEditor()
836
+ {
837
+ let tmpManifest = this._ParentFormEditor._resolveManifestData();
838
+ let tmpJSON = JSON.stringify(tmpManifest, null, '\t');
839
+
840
+ if (this._ParentFormEditor._CodeEditorView)
841
+ {
842
+ if (this._ParentFormEditor._CodeEditorView.codeJar)
843
+ {
844
+ // Code editor already initialized — just update the code
845
+ this._ParentFormEditor._CodeEditorView.setCode(tmpJSON);
846
+ }
847
+ else
848
+ {
849
+ // First time switching to JSON tab — render the code editor
850
+ this._ParentFormEditor._CodeEditorView.render();
851
+ // After render, setCode with current manifest
852
+ if (this._ParentFormEditor._CodeEditorView.codeJar)
853
+ {
854
+ this._ParentFormEditor._CodeEditorView.setCode(tmpJSON);
855
+ }
856
+ }
857
+ }
858
+ }
859
+ }
860
+
861
+ module.exports = FormEditorUtilities;
862
+ module.exports.default_configuration = {};