pict-section-formeditor 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-formeditor",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Pict visual editor for pict-section-form configurations",
5
5
  "main": "source/Pict-Section-FormEditor.js",
6
6
  "scripts": {
@@ -696,6 +696,42 @@ module.exports = (
696
696
  outline-offset: -2px;
697
697
  background: rgba(158, 107, 71, 0.05);
698
698
  }
699
+ .pict-fe-drag-insert-before
700
+ {
701
+ position: relative;
702
+ background: rgba(158, 107, 71, 0.03);
703
+ }
704
+ .pict-fe-drag-insert-before::before
705
+ {
706
+ content: '';
707
+ position: absolute;
708
+ top: -1px;
709
+ left: 0;
710
+ right: 0;
711
+ height: 3px;
712
+ background: #9E6B47;
713
+ border-radius: 2px;
714
+ z-index: 10;
715
+ pointer-events: none;
716
+ }
717
+ .pict-fe-drag-insert-after
718
+ {
719
+ position: relative;
720
+ background: rgba(158, 107, 71, 0.03);
721
+ }
722
+ .pict-fe-drag-insert-after::after
723
+ {
724
+ content: '';
725
+ position: absolute;
726
+ bottom: -1px;
727
+ left: 0;
728
+ right: 0;
729
+ height: 3px;
730
+ background: #9E6B47;
731
+ border-radius: 2px;
732
+ z-index: 10;
733
+ pointer-events: none;
734
+ }
699
735
 
700
736
  /* ---- Editor Layout: tab content + toggle + properties panel ---- */
701
737
  .pict-fe-editor-layout
@@ -1316,7 +1352,6 @@ module.exports = (
1316
1352
  width: 100%;
1317
1353
  height: 100%;
1318
1354
  z-index: 9999;
1319
- overflow: hidden;
1320
1355
  }
1321
1356
  .pict-fe-inputtype-picker
1322
1357
  {
@@ -87,7 +87,24 @@ class FormEditorDragDrop extends libPictProvider
87
87
 
88
88
  if (pEvent && pEvent.currentTarget)
89
89
  {
90
- pEvent.currentTarget.classList.add('pict-fe-drag-over');
90
+ // Detect whether the cursor is in the top or bottom half of the target
91
+ let tmpRect = pEvent.currentTarget.getBoundingClientRect();
92
+ let tmpMidpoint = tmpRect.top + (tmpRect.height / 2);
93
+ let tmpIsTopHalf = pEvent.clientY < tmpMidpoint;
94
+
95
+ pEvent.currentTarget.classList.remove('pict-fe-drag-insert-before');
96
+ pEvent.currentTarget.classList.remove('pict-fe-drag-insert-after');
97
+
98
+ if (tmpIsTopHalf)
99
+ {
100
+ pEvent.currentTarget.classList.add('pict-fe-drag-insert-before');
101
+ }
102
+ else
103
+ {
104
+ pEvent.currentTarget.classList.add('pict-fe-drag-insert-after');
105
+ }
106
+
107
+ this._ParentFormEditor._DragState.InsertPosition = tmpIsTopHalf ? 'before' : 'after';
91
108
  }
92
109
  }
93
110
 
@@ -101,6 +118,8 @@ class FormEditorDragDrop extends libPictProvider
101
118
  if (pEvent && pEvent.currentTarget)
102
119
  {
103
120
  pEvent.currentTarget.classList.remove('pict-fe-drag-over');
121
+ pEvent.currentTarget.classList.remove('pict-fe-drag-insert-before');
122
+ pEvent.currentTarget.classList.remove('pict-fe-drag-insert-after');
104
123
  }
105
124
  }
106
125
 
@@ -123,6 +142,7 @@ class FormEditorDragDrop extends libPictProvider
123
142
 
124
143
  let tmpTargetIndices = [pIndex0, pIndex1, pIndex2, pIndex3].filter((pVal) => { return typeof pVal === 'number'; });
125
144
  let tmpSourceIndices = this._ParentFormEditor._DragState.Indices;
145
+ let tmpInsertPosition = this._ParentFormEditor._DragState.InsertPosition || 'before';
126
146
  this._ParentFormEditor._DragState = null;
127
147
 
128
148
  // Check if source and target are identical
@@ -148,7 +168,8 @@ class FormEditorDragDrop extends libPictProvider
148
168
  return;
149
169
  }
150
170
  let tmpItem = tmpManifest.Sections.splice(tmpFromIdx, 1)[0];
151
- tmpManifest.Sections.splice(tmpToIdx, 0, tmpItem);
171
+ let tmpInsertIdx = this._computeInsertIndex(tmpFromIdx, tmpToIdx, true, tmpInsertPosition);
172
+ tmpManifest.Sections.splice(tmpInsertIdx, 0, tmpItem);
152
173
  break;
153
174
  }
154
175
  case 'group':
@@ -164,15 +185,9 @@ class FormEditorDragDrop extends libPictProvider
164
185
  tmpTargetSection.Groups = [];
165
186
  }
166
187
 
188
+ let tmpSameContainer = (tmpSourceIndices[0] === tmpTargetIndices[0]);
167
189
  let tmpItem = tmpSourceSection.Groups.splice(tmpSourceIndices[1], 1)[0];
168
-
169
- // If moving within the same section and target index is after source,
170
- // adjust for the removal
171
- let tmpInsertIdx = tmpTargetIndices[1];
172
- if (tmpSourceIndices[0] === tmpTargetIndices[0] && tmpSourceIndices[1] < tmpTargetIndices[1])
173
- {
174
- tmpInsertIdx--;
175
- }
190
+ let tmpInsertIdx = this._computeInsertIndex(tmpSourceIndices[1], tmpTargetIndices[1], tmpSameContainer, tmpInsertPosition);
176
191
  tmpTargetSection.Groups.splice(tmpInsertIdx, 0, tmpItem);
177
192
  break;
178
193
  }
@@ -197,12 +212,8 @@ class FormEditorDragDrop extends libPictProvider
197
212
 
198
213
  let tmpItem = tmpSourceGroup.Rows.splice(tmpSourceIndices[2], 1)[0];
199
214
 
200
- let tmpInsertIdx = tmpTargetIndices[2];
201
215
  let tmpSameContainer = (tmpSourceIndices[0] === tmpTargetIndices[0]) && (tmpSourceIndices[1] === tmpTargetIndices[1]);
202
- if (tmpSameContainer && tmpSourceIndices[2] < tmpTargetIndices[2])
203
- {
204
- tmpInsertIdx--;
205
- }
216
+ let tmpInsertIdx = this._computeInsertIndex(tmpSourceIndices[2], tmpTargetIndices[2], tmpSameContainer, tmpInsertPosition);
206
217
  tmpTargetGroup.Rows.splice(tmpInsertIdx, 0, tmpItem);
207
218
 
208
219
  // Sync row indices on both source and target groups
@@ -240,12 +251,8 @@ class FormEditorDragDrop extends libPictProvider
240
251
 
241
252
  let tmpAddress = tmpSourceRow.Inputs.splice(tmpSourceIndices[3], 1)[0];
242
253
 
243
- let tmpInsertIdx = tmpTargetIndices[3];
244
254
  let tmpSameContainer = (tmpSourceIndices[0] === tmpTargetIndices[0]) && (tmpSourceIndices[1] === tmpTargetIndices[1]) && (tmpSourceIndices[2] === tmpTargetIndices[2]);
245
- if (tmpSameContainer && tmpSourceIndices[3] < tmpTargetIndices[3])
246
- {
247
- tmpInsertIdx--;
248
- }
255
+ let tmpInsertIdx = this._computeInsertIndex(tmpSourceIndices[3], tmpTargetIndices[3], tmpSameContainer, tmpInsertPosition);
249
256
  tmpTargetRow.Inputs.splice(tmpInsertIdx, 0, tmpAddress);
250
257
 
251
258
  // Update the Descriptor's PictForm metadata for the new location
@@ -282,10 +289,12 @@ class FormEditorDragDrop extends libPictProvider
282
289
  let tmpContainer = this.pict.ContentAssignment.getElement(`#FormEditor-Panel-Visual-${this._ParentFormEditor.Hash}`);
283
290
  if (tmpContainer && tmpContainer[0])
284
291
  {
285
- let tmpHighlighted = tmpContainer[0].querySelectorAll('.pict-fe-drag-over');
292
+ let tmpHighlighted = tmpContainer[0].querySelectorAll('.pict-fe-drag-over, .pict-fe-drag-insert-before, .pict-fe-drag-insert-after');
286
293
  for (let i = 0; i < tmpHighlighted.length; i++)
287
294
  {
288
295
  tmpHighlighted[i].classList.remove('pict-fe-drag-over');
296
+ tmpHighlighted[i].classList.remove('pict-fe-drag-insert-before');
297
+ tmpHighlighted[i].classList.remove('pict-fe-drag-insert-after');
289
298
  }
290
299
  }
291
300
  }
@@ -314,6 +323,31 @@ class FormEditorDragDrop extends libPictProvider
314
323
  return true;
315
324
  }
316
325
 
326
+ /**
327
+ * Compute the final insert index for a position-aware drag-and-drop.
328
+ *
329
+ * After removing the source item from its array, this method determines the
330
+ * correct splice index based on the user's cursor position (insert before or
331
+ * after the target).
332
+ *
333
+ * @param {number} pSourceIdx - The source item's index within its container
334
+ * @param {number} pTargetIdx - The target item's index within its container
335
+ * @param {boolean} pSameContainer - Whether source and target share the same parent
336
+ * @param {string} pInsertPosition - 'before' or 'after'
337
+ * @returns {number} The index to use with Array.splice after removing the source
338
+ */
339
+ _computeInsertIndex(pSourceIdx, pTargetIdx, pSameContainer, pInsertPosition)
340
+ {
341
+ let tmpLogicalTarget = (pInsertPosition === 'before') ? pTargetIdx : pTargetIdx + 1;
342
+
343
+ if (pSameContainer && pSourceIdx < tmpLogicalTarget)
344
+ {
345
+ tmpLogicalTarget--;
346
+ }
347
+
348
+ return tmpLogicalTarget;
349
+ }
350
+
317
351
  /**
318
352
  * Build the drag attribute string for a container element.
319
353
  * Returns an empty string when drag-and-drop is disabled.
@@ -167,8 +167,13 @@ class FormEditorManifestOps extends libPictProvider
167
167
  tmpGroup.Rows = [];
168
168
  }
169
169
 
170
- // PictForm.Row is 1-based; pad with empty rows if necessary
171
- let tmpRowNumber = (typeof tmpPictForm.Row === 'number' && tmpPictForm.Row > 0) ? tmpPictForm.Row : 1;
170
+ // PictForm.Row is 1-based; pad with empty rows if necessary.
171
+ // Row may be a number or a string (e.g. "2") in real-world manifests.
172
+ let tmpRowNumber = parseInt(tmpPictForm.Row, 10);
173
+ if (isNaN(tmpRowNumber) || tmpRowNumber < 1)
174
+ {
175
+ tmpRowNumber = 1;
176
+ }
172
177
  let tmpRowIndex = tmpRowNumber - 1;
173
178
  while (tmpGroup.Rows.length <= tmpRowIndex)
174
179
  {
@@ -103,8 +103,10 @@ class PictViewFormEditorInputTypePicker extends libPictView
103
103
  }
104
104
  }, { passive: false });
105
105
 
106
- tmpOverlay.appendChild(tmpPickerContainer);
106
+ // Append overlay and picker as siblings on document.body so the picker
107
+ // is not inside the overlay's stacking context (which can trap pointer events).
107
108
  document.body.appendChild(tmpOverlay);
109
+ document.body.appendChild(tmpPickerContainer);
108
110
 
109
111
  // Position the picker using fixed viewport coordinates
110
112
  // anchored below the InputType chip (or properties panel button as fallback)
@@ -320,7 +322,9 @@ class PictViewFormEditorInputTypePicker extends libPictView
320
322
  {
321
323
  let tmpHash = this._ParentFormEditor.Hash;
322
324
  let tmpOverlayId = `FormEditor-InputTypePicker-${tmpHash}-Overlay`;
325
+ let tmpPickerId = `FormEditor-InputTypePicker-${tmpHash}`;
323
326
 
327
+ // Remove the overlay
324
328
  let tmpOverlaySet = this.pict.ContentAssignment.getElement(`#${tmpOverlayId}`);
325
329
  let tmpOverlay = (Array.isArray(tmpOverlaySet) && tmpOverlaySet.length > 0) ? tmpOverlaySet[0] : tmpOverlaySet;
326
330
  if (tmpOverlay && tmpOverlay.parentNode)
@@ -328,6 +332,14 @@ class PictViewFormEditorInputTypePicker extends libPictView
328
332
  tmpOverlay.parentNode.removeChild(tmpOverlay);
329
333
  }
330
334
 
335
+ // Remove the picker container (now a sibling of the overlay, not a child)
336
+ let tmpPickerSet = this.pict.ContentAssignment.getElement(`#${tmpPickerId}`);
337
+ let tmpPicker = (Array.isArray(tmpPickerSet) && tmpPickerSet.length > 0) ? tmpPickerSet[0] : tmpPickerSet;
338
+ if (tmpPicker && tmpPicker.parentNode)
339
+ {
340
+ tmpPicker.parentNode.removeChild(tmpPicker);
341
+ }
342
+
331
343
  this._InputTypePickerContext = null;
332
344
  }
333
345
 
@@ -491,8 +503,10 @@ class PictViewFormEditorInputTypePicker extends libPictView
491
503
  }
492
504
  }, { passive: false });
493
505
 
494
- tmpOverlay.appendChild(tmpPickerContainer);
506
+ // Append overlay and picker as siblings on document.body so the picker
507
+ // is not inside the overlay's stacking context (which can trap pointer events).
495
508
  document.body.appendChild(tmpOverlay);
509
+ document.body.appendChild(tmpPickerContainer);
496
510
 
497
511
  // Position anchored to the InputType button in the properties panel
498
512
  let tmpAnchorEl = document.getElementById(`FormEditor-PropsInputTypeBtn-${tmpHash}`);
@@ -991,6 +991,24 @@ class PictViewFormEditor extends libPictView
991
991
  {
992
992
  return `${this.pict.browserAddress}.views['${this.Hash}']`;
993
993
  }
994
+
995
+ // Proxy methods for the InputType picker child view.
996
+ // The picker's inline onclick handlers reference the parent form editor's
997
+ // browser view ref, so these must exist on the parent to route calls through.
998
+ commitEditInputType(pInputTypeHash)
999
+ {
1000
+ this._InputTypePickerView.commitEditInputType(pInputTypeHash);
1001
+ }
1002
+
1003
+ closeInputTypePicker()
1004
+ {
1005
+ this._InputTypePickerView.closeInputTypePicker();
1006
+ }
1007
+
1008
+ _onInputTypePickerSearch(pQuery)
1009
+ {
1010
+ this._InputTypePickerView._onInputTypePickerSearch(pQuery);
1011
+ }
994
1012
  }
995
1013
 
996
1014
  module.exports = PictViewFormEditor;
@@ -2192,6 +2192,212 @@ suite
2192
2192
  fDone();
2193
2193
  }
2194
2194
  );
2195
+ test
2196
+ (
2197
+ 'Should insert before target when InsertPosition is before (same container)',
2198
+ function (fDone)
2199
+ {
2200
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2201
+ tmpPict.AppData.FormConfig =
2202
+ {
2203
+ Scope: 'TestInsertBefore',
2204
+ Sections:
2205
+ [
2206
+ { Name: 'A', Hash: 'A', Groups: [] },
2207
+ { Name: 'B', Hash: 'B', Groups: [] },
2208
+ { Name: 'C', Hash: 'C', Groups: [] }
2209
+ ],
2210
+ Descriptors: {}
2211
+ };
2212
+
2213
+ let tmpView = tmpPict.addView('TestInsertBefore',
2214
+ {
2215
+ ViewIdentifier: 'TestInsertBefore',
2216
+ ManifestDataAddress: 'AppData.FormConfig',
2217
+ DefaultDestinationAddress: '#FormEditor-Container',
2218
+ Renderables:
2219
+ [
2220
+ {
2221
+ RenderableHash: 'FormEditor-Container',
2222
+ TemplateHash: 'FormEditor-Container-Template',
2223
+ DestinationAddress: '#FormEditor-Container',
2224
+ RenderMethod: 'replace'
2225
+ }
2226
+ ]
2227
+ }, libPictSectionFormEditor);
2228
+
2229
+ tmpView.initialize();
2230
+ tmpView.render();
2231
+ tmpView._DragAndDropEnabled = true;
2232
+
2233
+ // Drag C(2) to A(0), InsertPosition=before → [C, A, B]
2234
+ tmpView._DragState = { Type: 'section', Indices: [2], InsertPosition: 'before' };
2235
+ tmpView._DragDropProvider.onDrop(
2236
+ { preventDefault: function() {} },
2237
+ 'section', 0
2238
+ );
2239
+
2240
+ var tmpManifest = tmpPict.AppData.FormConfig;
2241
+ Expect(tmpManifest.Sections[0].Hash).to.equal('C');
2242
+ Expect(tmpManifest.Sections[1].Hash).to.equal('A');
2243
+ Expect(tmpManifest.Sections[2].Hash).to.equal('B');
2244
+
2245
+ fDone();
2246
+ }
2247
+ );
2248
+ test
2249
+ (
2250
+ 'Should insert after target when InsertPosition is after (same container)',
2251
+ function (fDone)
2252
+ {
2253
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2254
+ tmpPict.AppData.FormConfig =
2255
+ {
2256
+ Scope: 'TestInsertAfter',
2257
+ Sections:
2258
+ [
2259
+ { Name: 'A', Hash: 'A', Groups: [] },
2260
+ { Name: 'B', Hash: 'B', Groups: [] },
2261
+ { Name: 'C', Hash: 'C', Groups: [] }
2262
+ ],
2263
+ Descriptors: {}
2264
+ };
2265
+
2266
+ let tmpView = tmpPict.addView('TestInsertAfter',
2267
+ {
2268
+ ViewIdentifier: 'TestInsertAfter',
2269
+ ManifestDataAddress: 'AppData.FormConfig',
2270
+ DefaultDestinationAddress: '#FormEditor-Container',
2271
+ Renderables:
2272
+ [
2273
+ {
2274
+ RenderableHash: 'FormEditor-Container',
2275
+ TemplateHash: 'FormEditor-Container-Template',
2276
+ DestinationAddress: '#FormEditor-Container',
2277
+ RenderMethod: 'replace'
2278
+ }
2279
+ ]
2280
+ }, libPictSectionFormEditor);
2281
+
2282
+ tmpView.initialize();
2283
+ tmpView.render();
2284
+ tmpView._DragAndDropEnabled = true;
2285
+
2286
+ // Drag C(2) to A(0), InsertPosition=after → [A, C, B]
2287
+ tmpView._DragState = { Type: 'section', Indices: [2], InsertPosition: 'after' };
2288
+ tmpView._DragDropProvider.onDrop(
2289
+ { preventDefault: function() {} },
2290
+ 'section', 0
2291
+ );
2292
+
2293
+ var tmpManifest = tmpPict.AppData.FormConfig;
2294
+ Expect(tmpManifest.Sections[0].Hash).to.equal('A');
2295
+ Expect(tmpManifest.Sections[1].Hash).to.equal('C');
2296
+ Expect(tmpManifest.Sections[2].Hash).to.equal('B');
2297
+
2298
+ fDone();
2299
+ }
2300
+ );
2301
+ test
2302
+ (
2303
+ 'Should handle forward drag with before and after positions',
2304
+ function (fDone)
2305
+ {
2306
+ // Test forward drag A(0) to C(2) with before → [B, A, C]
2307
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2308
+ tmpPict.AppData.FormConfig =
2309
+ {
2310
+ Scope: 'TestForwardBefore',
2311
+ Sections:
2312
+ [
2313
+ { Name: 'A', Hash: 'A', Groups: [] },
2314
+ { Name: 'B', Hash: 'B', Groups: [] },
2315
+ { Name: 'C', Hash: 'C', Groups: [] }
2316
+ ],
2317
+ Descriptors: {}
2318
+ };
2319
+
2320
+ let tmpView = tmpPict.addView('TestForwardBefore',
2321
+ {
2322
+ ViewIdentifier: 'TestForwardBefore',
2323
+ ManifestDataAddress: 'AppData.FormConfig',
2324
+ DefaultDestinationAddress: '#FormEditor-Container',
2325
+ Renderables:
2326
+ [
2327
+ {
2328
+ RenderableHash: 'FormEditor-Container',
2329
+ TemplateHash: 'FormEditor-Container-Template',
2330
+ DestinationAddress: '#FormEditor-Container',
2331
+ RenderMethod: 'replace'
2332
+ }
2333
+ ]
2334
+ }, libPictSectionFormEditor);
2335
+
2336
+ tmpView.initialize();
2337
+ tmpView.render();
2338
+ tmpView._DragAndDropEnabled = true;
2339
+
2340
+ // Drag A(0) to C(2), InsertPosition=before → [B, A, C]
2341
+ tmpView._DragState = { Type: 'section', Indices: [0], InsertPosition: 'before' };
2342
+ tmpView._DragDropProvider.onDrop(
2343
+ { preventDefault: function() {} },
2344
+ 'section', 2
2345
+ );
2346
+
2347
+ var tmpManifest = tmpPict.AppData.FormConfig;
2348
+ Expect(tmpManifest.Sections[0].Hash).to.equal('B');
2349
+ Expect(tmpManifest.Sections[1].Hash).to.equal('A');
2350
+ Expect(tmpManifest.Sections[2].Hash).to.equal('C');
2351
+
2352
+ // Now test forward drag with after: reset
2353
+ tmpManifest.Sections = [
2354
+ { Name: 'A', Hash: 'A', Groups: [] },
2355
+ { Name: 'B', Hash: 'B', Groups: [] },
2356
+ { Name: 'C', Hash: 'C', Groups: [] }
2357
+ ];
2358
+
2359
+ // Drag A(0) to C(2), InsertPosition=after → [B, C, A]
2360
+ tmpView._DragState = { Type: 'section', Indices: [0], InsertPosition: 'after' };
2361
+ tmpView._DragDropProvider.onDrop(
2362
+ { preventDefault: function() {} },
2363
+ 'section', 2
2364
+ );
2365
+
2366
+ Expect(tmpManifest.Sections[0].Hash).to.equal('B');
2367
+ Expect(tmpManifest.Sections[1].Hash).to.equal('C');
2368
+ Expect(tmpManifest.Sections[2].Hash).to.equal('A');
2369
+
2370
+ fDone();
2371
+ }
2372
+ );
2373
+ test
2374
+ (
2375
+ 'Should compute insert index correctly via _computeInsertIndex helper',
2376
+ function ()
2377
+ {
2378
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2379
+ let tmpView = tmpPict.addView('TestComputeIdx',
2380
+ {
2381
+ ViewIdentifier: 'TestComputeIdx'
2382
+ }, libPictSectionFormEditor);
2383
+ tmpView.initialize();
2384
+
2385
+ let tmpDD = tmpView._DragDropProvider;
2386
+
2387
+ // Same container, backward drag, insert before
2388
+ Expect(tmpDD._computeInsertIndex(2, 0, true, 'before')).to.equal(0);
2389
+ // Same container, backward drag, insert after
2390
+ Expect(tmpDD._computeInsertIndex(2, 0, true, 'after')).to.equal(1);
2391
+ // Same container, forward drag, insert before
2392
+ Expect(tmpDD._computeInsertIndex(0, 2, true, 'before')).to.equal(1);
2393
+ // Same container, forward drag, insert after
2394
+ Expect(tmpDD._computeInsertIndex(0, 2, true, 'after')).to.equal(2);
2395
+ // Cross container, insert before
2396
+ Expect(tmpDD._computeInsertIndex(1, 0, false, 'before')).to.equal(0);
2397
+ // Cross container, insert after
2398
+ Expect(tmpDD._computeInsertIndex(1, 0, false, 'after')).to.equal(1);
2399
+ }
2400
+ );
2195
2401
  }
2196
2402
  );
2197
2403
  suite