pict-section-flow 0.0.18 → 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.
@@ -50,7 +50,11 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
50
50
  let tmpPict = this._FlowView.pict || this._FlowView.fable;
51
51
  let tmpTitle = pPanelData.Title || 'Properties';
52
52
  let tmpChromeHTML = tmpPict.parseTemplateByHash('Flow-PanelChrome-Template',
53
- { Hash: pPanelData.Hash, Title: tmpTitle });
53
+ {
54
+ Hash: pPanelData.Hash,
55
+ Title: tmpTitle,
56
+ FlowViewIdentifier: this._FlowView.options.ViewIdentifier
57
+ });
54
58
 
55
59
  tmpFO.innerHTML = tmpChromeHTML;
56
60
 
@@ -65,22 +69,8 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
65
69
  tmpCloseIcon.textContent = '\u2715';
66
70
  }
67
71
 
68
- // Attach event isolation to the scrollable content area so
69
- // pointer/wheel events inside the panel do not trigger SVG interactions
70
- let tmpContent = tmpFO.querySelector('.pict-flow-panel-content');
71
- if (tmpContent)
72
- {
73
- tmpContent.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
74
- tmpContent.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
75
- }
76
-
77
- // Isolate events on the tab bar
78
- let tmpTabbar = tmpFO.querySelector('.pict-flow-panel-tabbar');
79
- if (tmpTabbar)
80
- {
81
- tmpTabbar.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
82
- tmpTabbar.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
83
- }
72
+ // Pointer/wheel isolation lives on the inline onpointerdown/onwheel
73
+ // attributes in Flow-PanelChrome-Template see PictView-Flow.js.
84
74
 
85
75
  pPanelsLayer.appendChild(tmpFO);
86
76
 
@@ -104,61 +104,58 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
104
104
 
105
105
  if (!this._SVGElement) return;
106
106
 
107
- // Use pointer events for unified mouse/touch handling
108
- this._SVGElement.addEventListener('pointerdown', this._boundOnPointerDown);
109
- this._SVGElement.addEventListener('pointermove', this._boundOnPointerMove);
110
- this._SVGElement.addEventListener('pointerup', this._boundOnPointerUp);
111
- this._SVGElement.addEventListener('pointerleave', this._boundOnPointerUp);
112
- this._SVGElement.addEventListener('wheel', this._boundOnWheel, { passive: false });
113
-
114
- // Keyboard events for delete
107
+ // Pointer/wheel/contextmenu live as inline attributes on the SVG
108
+ // template (see Flow-Container-Template in PictView-Flow.js); they
109
+ // route to InteractionManager via the FlowView's bridges. The
110
+ // keydown listener stays document-level because keyboard input
111
+ // has no element-level inline equivalent for the canvas-as-a-whole.
115
112
  document.addEventListener('keydown', this._boundOnKeyDown);
113
+ }
116
114
 
117
- // Handle right-click: add/remove bezier handles on connections
118
- this._SVGElement.addEventListener('contextmenu', (pEvent) =>
119
- {
120
- pEvent.preventDefault();
115
+ /**
116
+ * Inline-handler bridge — invoked from the SVG template's
117
+ * `oncontextmenu` attribute. Prevents the browser context menu and
118
+ * dispatches to the right bezier-handle action based on what was
119
+ * right-clicked.
120
+ *
121
+ * @param {Event} pEvent
122
+ */
123
+ handleContextMenu(pEvent)
124
+ {
125
+ pEvent.preventDefault();
121
126
 
122
- let tmpTarget = pEvent.target;
123
- let tmpElementType = this._getElementType(tmpTarget);
127
+ let tmpTarget = pEvent.target;
128
+ let tmpElementType = this._getElementType(tmpTarget);
124
129
 
125
- switch (tmpElementType)
126
- {
127
- case 'connection':
128
- case 'connection-hitarea':
129
- this._addBezierHandle(tmpTarget, pEvent);
130
- break;
130
+ switch (tmpElementType)
131
+ {
132
+ case 'connection':
133
+ case 'connection-hitarea':
134
+ this._addBezierHandle(tmpTarget, pEvent);
135
+ break;
131
136
 
132
- case 'connection-handle':
133
- this._removeBezierHandle(tmpTarget);
134
- break;
137
+ case 'connection-handle':
138
+ this._removeBezierHandle(tmpTarget);
139
+ break;
135
140
 
136
- case 'tether':
137
- case 'tether-hitarea':
138
- this._addTetherBezierHandle(tmpTarget, pEvent);
139
- break;
141
+ case 'tether':
142
+ case 'tether-hitarea':
143
+ this._addTetherBezierHandle(tmpTarget, pEvent);
144
+ break;
140
145
 
141
- case 'tether-handle':
142
- this._removeTetherBezierHandle(tmpTarget);
143
- break;
144
- }
145
- });
146
+ case 'tether-handle':
147
+ this._removeTetherBezierHandle(tmpTarget);
148
+ break;
149
+ }
146
150
  }
147
151
 
148
152
  /**
149
- * Remove all event listeners
153
+ * Remove all event listeners. Pointer/wheel/contextmenu listeners live
154
+ * as inline template attributes and don't need explicit teardown — they
155
+ * disappear with the SVG element when the FlowView is destroyed.
150
156
  */
151
157
  destroy()
152
158
  {
153
- if (this._SVGElement)
154
- {
155
- this._SVGElement.removeEventListener('pointerdown', this._boundOnPointerDown);
156
- this._SVGElement.removeEventListener('pointermove', this._boundOnPointerMove);
157
- this._SVGElement.removeEventListener('pointerup', this._boundOnPointerUp);
158
- this._SVGElement.removeEventListener('pointerleave', this._boundOnPointerUp);
159
- this._SVGElement.removeEventListener('wheel', this._boundOnWheel);
160
- }
161
-
162
159
  document.removeEventListener('keydown', this._boundOnKeyDown);
163
160
  }
164
161
 
@@ -283,31 +283,17 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
283
283
  */
284
284
  _wirePortHintHover(pHoverEl, pPortHash, pNodeHash)
285
285
  {
286
- if (!pHoverEl || typeof pHoverEl.addEventListener !== 'function') return;
286
+ if (!pHoverEl || typeof pHoverEl.setAttribute !== 'function') return;
287
+ if (!this._FlowView || !this._FlowView.options) return;
287
288
 
288
- let tmpFlowView = this._FlowView;
289
- let tmpSelector = pPortHash
290
- ? `.pict-flow-port-hint[data-port-hash="${pPortHash}"]`
291
- : `.pict-flow-port-hint[data-node-hash="${pNodeHash}"]`;
292
-
293
- pHoverEl.addEventListener('mouseenter', function ()
294
- {
295
- let tmpScope = tmpFlowView._SVGElement || document;
296
- let tmpHints = tmpScope.querySelectorAll(tmpSelector);
297
- for (let i = 0; i < tmpHints.length; i++)
298
- {
299
- tmpHints[i].setAttribute('data-active', 'true');
300
- }
301
- });
302
- pHoverEl.addEventListener('mouseleave', function ()
303
- {
304
- let tmpScope = tmpFlowView._SVGElement || document;
305
- let tmpHints = tmpScope.querySelectorAll(tmpSelector);
306
- for (let i = 0; i < tmpHints.length; i++)
307
- {
308
- tmpHints[i].removeAttribute('data-active');
309
- }
310
- });
289
+ // Inline attribute handlers — survive the connection-layer rebuild
290
+ // without leaving stale listeners on detached DOM nodes.
291
+ let tmpFlowViewIdentifier = this._FlowView.options.ViewIdentifier;
292
+ let tmpHashArg = pPortHash ? ('"' + pPortHash + '", null') : ('null, "' + pNodeHash + '"');
293
+ pHoverEl.setAttribute('onmouseenter',
294
+ "_Pict.views['" + tmpFlowViewIdentifier + "']._activatePortHints(" + tmpHashArg + ")");
295
+ pHoverEl.setAttribute('onmouseleave',
296
+ "_Pict.views['" + tmpFlowViewIdentifier + "']._deactivatePortHints(" + tmpHashArg + ")");
311
297
  }
312
298
 
313
299
  /**
@@ -17,42 +17,56 @@ const _DefaultConfiguration =
17
17
  [
18
18
  {
19
19
  Hash: 'Flow-FloatingToolbar-Template',
20
+ // Inline handlers route to the floating toolbar view via _Pict.views
21
+ // (see _handleButtonClick / _startDrag / _toggleCollapse).
20
22
  Template: /*html*/`
21
23
  <div class="pict-flow-floating-toolbar" id="Flow-FloatingToolbar-{~D:Record.FlowViewIdentifier~}">
22
- <div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse">
24
+ <div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse"
25
+ onmousedown="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._startDrag(event)"
26
+ ondblclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleGripDoubleClick(event)">
23
27
  <span id="Flow-FloatingIcon-grip-{~D:Record.FlowViewIdentifier~}"></span>
24
28
  </div>
25
- <button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node">
29
+ <button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node"
30
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('add-node')">
26
31
  <span id="Flow-FloatingIcon-plus-{~D:Record.FlowViewIdentifier~}"></span>
27
32
  </button>
28
- <button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards">
33
+ <button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards"
34
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('cards-popup')">
29
35
  <span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
30
36
  </button>
31
- <button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected">
37
+ <button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected"
38
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('delete-selected')">
32
39
  <span id="Flow-FloatingIcon-trash-{~D:Record.FlowViewIdentifier~}"></span>
33
40
  </button>
34
41
  <div class="pict-flow-floating-separator"></div>
35
- <button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In">
42
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In"
43
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-in')">
36
44
  <span id="Flow-FloatingIcon-zoom-in-{~D:Record.FlowViewIdentifier~}"></span>
37
45
  </button>
38
- <button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out">
46
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out"
47
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-out')">
39
48
  <span id="Flow-FloatingIcon-zoom-out-{~D:Record.FlowViewIdentifier~}"></span>
40
49
  </button>
41
- <button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View">
50
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View"
51
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-fit')">
42
52
  <span id="Flow-FloatingIcon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
43
53
  </button>
44
54
  <div class="pict-flow-floating-separator"></div>
45
- <button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout">
55
+ <button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout"
56
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('auto-layout')">
46
57
  <span id="Flow-FloatingIcon-auto-layout-{~D:Record.FlowViewIdentifier~}"></span>
47
58
  </button>
48
- <button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout">
59
+ <button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout"
60
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('layout-popup')">
49
61
  <span id="Flow-FloatingIcon-layout-{~D:Record.FlowViewIdentifier~}"></span>
50
62
  </button>
51
- <button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen">
63
+ <button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen"
64
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('fullscreen')">
52
65
  <span id="Flow-FloatingIcon-fullscreen-{~D:Record.FlowViewIdentifier~}"></span>
53
66
  </button>
54
67
  <div class="pict-flow-floating-separator"></div>
55
- <button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar">
68
+ <button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar"
69
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('dock-toolbar')">
56
70
  <span id="Flow-FloatingIcon-dock-{~D:Record.FlowViewIdentifier~}"></span>
57
71
  </button>
58
72
  </div>
@@ -104,58 +118,13 @@ class PictViewFlowFloatingToolbar extends libPictView
104
118
  {
105
119
  let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
106
120
 
107
- // Bind click delegation for action buttons
108
- let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
109
- if (tmpFloatingToolbar.length > 0)
110
- {
111
- tmpFloatingToolbar[0].addEventListener('click', (pEvent) =>
112
- {
113
- let tmpTarget = pEvent.target;
114
- if (!tmpTarget) return;
115
-
116
- let tmpButton = tmpTarget.closest('[data-flow-action]');
117
- if (!tmpButton) return;
118
-
119
- let tmpAction = tmpButton.getAttribute('data-flow-action');
120
- if (tmpAction === 'dock-toolbar')
121
- {
122
- if (this._ToolbarView)
123
- {
124
- this._ToolbarView._setToolbarMode('docked');
125
- }
126
- return;
127
- }
128
-
129
- // Delegate all other actions to the docked toolbar
130
- if (this._ToolbarView)
131
- {
132
- this._ToolbarView._handleToolbarAction(tmpAction);
133
- }
134
- });
135
- }
136
-
137
- // Bind drag behavior on the grip
138
- let tmpGrip = this.pict.ContentAssignment.getElement(`#Flow-FloatingGrip-${tmpFlowViewIdentifier}`);
139
- if (tmpGrip.length > 0)
140
- {
141
- tmpGrip[0].addEventListener('mousedown', (pEvent) =>
142
- {
143
- this._startDrag(pEvent);
144
- });
145
-
146
- // Double-click grip to toggle collapsed state
147
- tmpGrip[0].addEventListener('dblclick', (pEvent) =>
148
- {
149
- pEvent.preventDefault();
150
- pEvent.stopPropagation();
151
- this._toggleCollapse();
152
- });
153
- }
154
-
155
- // Populate icons
121
+ // Button clicks and grip drag/dblclick are wired inline in
122
+ // Flow-FloatingToolbar-Template — see _handleButtonClick / _startDrag /
123
+ // _handleGripDoubleClick. Only icon population and option-based
124
+ // pruning happen here.
156
125
  this._populateIcons();
157
126
 
158
- // Remove buttons from DOM based on options
127
+ let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
159
128
  if (tmpFloatingToolbar.length > 0)
160
129
  {
161
130
  if (this.options.EnableAddNode === false)
@@ -179,6 +148,45 @@ class PictViewFlowFloatingToolbar extends libPictView
179
148
  return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
180
149
  }
181
150
 
151
+ /**
152
+ * Handle a click on a floating toolbar action button. Called from the
153
+ * inline `onclick` handler on each button.
154
+ *
155
+ * @param {string} pAction - Value of the button's data-flow-action attr
156
+ */
157
+ _handleButtonClick(pAction)
158
+ {
159
+ if (pAction === 'dock-toolbar')
160
+ {
161
+ if (this._ToolbarView)
162
+ {
163
+ this._ToolbarView._setToolbarMode('docked');
164
+ }
165
+ return;
166
+ }
167
+ if (this._ToolbarView)
168
+ {
169
+ this._ToolbarView._handleToolbarAction(pAction);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Handle a double-click on the grip. Toggles collapsed state and
175
+ * prevents the event from reaching the surrounding canvas. Called
176
+ * from the inline `ondblclick` handler.
177
+ *
178
+ * @param {Event} pEvent
179
+ */
180
+ _handleGripDoubleClick(pEvent)
181
+ {
182
+ if (pEvent && typeof pEvent.preventDefault === 'function')
183
+ {
184
+ pEvent.preventDefault();
185
+ pEvent.stopPropagation();
186
+ }
187
+ this._toggleCollapse();
188
+ }
189
+
182
190
  /**
183
191
  * Populate SVG icons into all floating toolbar button spans.
184
192
  */
@@ -40,6 +40,18 @@ class PictViewFlowNode extends libPictView
40
40
  if (pNodeTypeConfig.PortLabelsOnHover) tmpClassList += ' pict-flow-node-port-labels-hover';
41
41
  if (pNodeTypeConfig.PortLabelsVertical) tmpClassList += ' pict-flow-node-port-labels-vertical';
42
42
  }
43
+
44
+ // Resolve theme-aware ColorRole. Per-node override wins; falls back
45
+ // to the node type's role. A role of 'none' (or empty string)
46
+ // explicitly opts out — useful when a card wants to keep its
47
+ // hex BodyStyle/TitleBarColor regardless of the host theme.
48
+ let tmpColorRole = (typeof pNodeData.ColorRole !== 'undefined')
49
+ ? pNodeData.ColorRole
50
+ : (pNodeTypeConfig && pNodeTypeConfig.ColorRole);
51
+ if (tmpColorRole && tmpColorRole !== 'none')
52
+ {
53
+ tmpClassList += ' pict-flow-node-color-' + tmpColorRole;
54
+ }
43
55
  tmpGroup.setAttribute('class', tmpClassList);
44
56
  tmpGroup.setAttribute('transform', `translate(${pNodeData.X}, ${pNodeData.Y})`);
45
57
  tmpGroup.setAttribute('data-node-hash', pNodeData.Hash);
@@ -430,9 +442,10 @@ class PictViewFlowNode extends libPictView
430
442
  tmpDiv.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
431
443
  tmpDiv.setAttribute('class', 'pict-flow-node-body-content-html');
432
444
 
433
- // Pointer event isolation — prevent node drag/canvas pan
434
- tmpDiv.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
435
- tmpDiv.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
445
+ // Pointer event isolation — prevent node drag/canvas pan.
446
+ // Inline attribute handlers (visible in DOM, no closure leak).
447
+ tmpDiv.setAttribute('onpointerdown', 'event.stopPropagation()');
448
+ tmpDiv.setAttribute('onwheel', 'event.stopPropagation()');
436
449
 
437
450
  // Render template content
438
451
  let tmpRenderedContent = this._resolveBodyTemplate(pBodyContent, pNodeData, pPict);
@@ -471,9 +484,9 @@ class PictViewFlowNode extends libPictView
471
484
  tmpCanvas.style.width = '100%';
472
485
  tmpCanvas.style.height = '100%';
473
486
 
474
- // Pointer event isolation
475
- tmpCanvas.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
476
- tmpCanvas.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
487
+ // Pointer event isolation — inline attribute handlers.
488
+ tmpCanvas.setAttribute('onpointerdown', 'event.stopPropagation()');
489
+ tmpCanvas.setAttribute('onwheel', 'event.stopPropagation()');
477
490
 
478
491
  // Invoke render callback (the primary rendering path for canvas)
479
492
  if (typeof pBodyContent.RenderCallback === 'function')
@@ -74,7 +74,17 @@ const _DefaultConfiguration =
74
74
  },
75
75
  {
76
76
  Hash: 'Flow-NodeProps-Editor',
77
- Template: '<div class="pict-flow-node-props-fields"><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title</label><input type="text" class="pict-flow-node-props-input" data-prop="Title" value="{~D:Record.Title~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Width" value="{~D:Record.Width~}" min="60" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Height</label><input type="number" class="pict-flow-node-props-input" data-prop="Height" value="{~D:Record.Height~}" min="40" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Fill</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyFill" value="{~D:Record.BodyFillValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Stroke</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyStroke" value="{~D:Record.BodyStrokeValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Stroke Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Style.BodyStrokeWidth" value="{~D:Record.BodyStrokeWidthValue~}" min="0" max="10" step="0.5" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title Bar</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.TitleBarColor" value="{~D:Record.TitleBarColorValue~}" /></div></div>'
77
+ // Inline oninput/onpointerdown wired to the FlowView's helper
78
+ // see PictViewFlow._applyNodePropChange / _stopEvent.
79
+ Template: '<div class="pict-flow-node-props-fields" data-flow-view="{~D:Record.FlowViewIdentifier~}" data-node-hash="{~D:Record.NodeHash~}">'
80
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title</label><input type="text" class="pict-flow-node-props-input" data-prop="Title" value="{~D:Record.Title~}" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
81
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Width" value="{~D:Record.Width~}" min="60" step="10" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
82
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Height</label><input type="number" class="pict-flow-node-props-input" data-prop="Height" value="{~D:Record.Height~}" min="40" step="10" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
83
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Fill</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyFill" value="{~D:Record.BodyFillValue~}" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
84
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Stroke</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyStroke" value="{~D:Record.BodyStrokeValue~}" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
85
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Stroke Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Style.BodyStrokeWidth" value="{~D:Record.BodyStrokeWidthValue~}" min="0" max="10" step="0.5" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
86
+ + '<div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title Bar</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.TitleBarColor" value="{~D:Record.TitleBarColorValue~}" oninput="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._applyNodePropChange(\'{~D:Record.NodeHash~}\', this.getAttribute(\'data-prop\'), this.value, this.type, event)" onpointerdown="event.stopPropagation()" /></div>'
87
+ + '</div>'
78
88
  }
79
89
  ]
80
90
  };
@@ -216,12 +226,11 @@ class PictViewFlowPropertiesPanel extends libPictView
216
226
  let tmpFO = pPanelsLayer.querySelector(`[data-panel-hash="${pPanelData.Hash}"]`);
217
227
  if (tmpFO)
218
228
  {
219
- // Render appearance and help tabs
229
+ // Render appearance and help tabs. Tab-switching click handlers
230
+ // are inline `onclick=` attributes in Flow-PanelChrome-Template
231
+ // that call FlowView._handlePanelTabClick → switchPanelTab.
220
232
  this._renderAppearanceTab(pPanelData, tmpFO);
221
233
  this._renderHelpTab(pPanelData, tmpFO);
222
-
223
- // Wire up tab switching
224
- this._wireTabSwitching(tmpFO);
225
234
  }
226
235
  }
227
236
 
@@ -559,27 +568,16 @@ class PictViewFlowPropertiesPanel extends libPictView
559
568
  BodyFillValue: tmpStyle.BodyFill || tmpDefaultBodyFill,
560
569
  BodyStrokeValue: tmpStyle.BodyStroke || tmpDefaultBodyStroke,
561
570
  BodyStrokeWidthValue: tmpStyle.BodyStrokeWidth || 1,
562
- TitleBarColorValue: tmpStyle.TitleBarColor || tmpDefaultTitleBarColor
571
+ TitleBarColorValue: tmpStyle.TitleBarColor || tmpDefaultTitleBarColor,
572
+ NodeHash: pPanelData.NodeHash,
573
+ FlowViewIdentifier: this._FlowView.options.ViewIdentifier
563
574
  };
564
575
 
565
- tmpAppearancePane.innerHTML = this.pict.parseTemplateByHash('Flow-NodeProps-Editor', tmpRecord);
566
-
567
- // Wire up live change handlers on all input fields
568
- let tmpInputs = tmpAppearancePane.querySelectorAll('.pict-flow-node-props-input');
569
- for (let i = 0; i < tmpInputs.length; i++)
570
- {
571
- let tmpInput = tmpInputs[i];
572
- let tmpProp = tmpInput.getAttribute('data-prop');
573
-
574
- tmpInput.addEventListener('input', (pEvent) =>
575
- {
576
- pEvent.stopPropagation();
577
- this._applyNodePropChange(pPanelData.NodeHash, tmpProp, tmpInput.value, tmpInput.type);
578
- });
579
-
580
- // Prevent pointer events from propagating to SVG drag handler
581
- tmpInput.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
582
- }
576
+ // Inputs in the rendered template carry inline oninput/onpointerdown
577
+ // handlers that call the FlowView's _applyNodePropChange — no
578
+ // addEventListener wiring needed here.
579
+ this.pict.ContentAssignment.assignContent(tmpAppearancePane,
580
+ this.pict.parseTemplateByHash('Flow-NodeProps-Editor', tmpRecord));
583
581
  }
584
582
 
585
583
  /**
@@ -619,42 +617,37 @@ class PictViewFlowPropertiesPanel extends libPictView
619
617
  }
620
618
 
621
619
  /**
622
- * Wire up tab switching on all tab buttons within a panel foreignObject.
620
+ * Switch the visible tab pane within a panel foreignObject. Invoked
621
+ * from the inline `onclick` handler on tab buttons in
622
+ * Flow-PanelChrome-Template via FlowView._handlePanelTabClick.
623
623
  *
624
- * @param {Element} pForeignObject - The panel's SVG foreignObject element
624
+ * @param {Element} pTabElement - The clicked tab button
625
625
  */
626
- _wireTabSwitching(pForeignObject)
626
+ switchPanelTab(pTabElement)
627
627
  {
628
- let tmpTabs = pForeignObject.querySelectorAll('.pict-flow-panel-tab');
629
- let tmpPanes = pForeignObject.querySelectorAll('.pict-flow-panel-tab-pane');
628
+ let tmpForeignObject = pTabElement.closest('foreignObject');
629
+ if (!tmpForeignObject) return;
630
+
631
+ let tmpTarget = pTabElement.getAttribute('data-tab-target');
632
+ let tmpTabs = tmpForeignObject.querySelectorAll('.pict-flow-panel-tab');
633
+ let tmpPanes = tmpForeignObject.querySelectorAll('.pict-flow-panel-tab-pane');
630
634
 
631
635
  for (let i = 0; i < tmpTabs.length; i++)
632
636
  {
633
- tmpTabs[i].addEventListener('click', (pEvent) =>
634
- {
635
- pEvent.stopPropagation();
636
- let tmpTarget = pEvent.currentTarget.getAttribute('data-tab-target');
637
-
638
- // Deactivate all tabs and panes
639
- for (let j = 0; j < tmpTabs.length; j++)
640
- {
641
- tmpTabs[j].classList.remove('active');
642
- }
643
- for (let j = 0; j < tmpPanes.length; j++)
644
- {
645
- tmpPanes[j].classList.remove('active');
646
- tmpPanes[j].style.display = 'none';
647
- }
637
+ tmpTabs[i].classList.remove('active');
638
+ }
639
+ for (let i = 0; i < tmpPanes.length; i++)
640
+ {
641
+ tmpPanes[i].classList.remove('active');
642
+ tmpPanes[i].style.display = 'none';
643
+ }
648
644
 
649
- // Activate the selected tab and pane
650
- pEvent.currentTarget.classList.add('active');
651
- let tmpTargetPane = pForeignObject.querySelector('.pict-flow-panel-tab-pane[data-tab="' + tmpTarget + '"]');
652
- if (tmpTargetPane)
653
- {
654
- tmpTargetPane.classList.add('active');
655
- tmpTargetPane.style.display = 'block';
656
- }
657
- });
645
+ pTabElement.classList.add('active');
646
+ let tmpTargetPane = tmpForeignObject.querySelector('.pict-flow-panel-tab-pane[data-tab="' + tmpTarget + '"]');
647
+ if (tmpTargetPane)
648
+ {
649
+ tmpTargetPane.classList.add('active');
650
+ tmpTargetPane.style.display = 'block';
658
651
  }
659
652
  }
660
653