Skip to content

Commit 4bf89d2

Browse files
committed
Bug fixes
1 parent 4b490ca commit 4bf89d2

3 files changed

Lines changed: 99 additions & 45 deletions

File tree

src/components/pg/node/node.ts

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export default class PgNode extends HTMLElement {
1717
@Prop() y: number = 0;
1818
@Prop() width: number = 12;
1919
@Prop() height: number = 3;
20+
// The node type's declared width; nodes cannot be resized below it.
21+
@Prop() minWidth: number = 6;
2022
@Prop() itemId: number = 0;
2123
@Prop() label: string = '';
2224
@Prop() fields: any = [];
@@ -42,42 +44,15 @@ export default class PgNode extends HTMLElement {
4244
},
4345
create: ($item: any, item) => {
4446
this.height += $item.height;
47+
this.#fieldHeights.set(item.itemKey, $item.height);
4548
$item.addEventListener('input', (e: any) => {
46-
this.dispatchEvent(new CustomEvent('input', {
47-
detail: {
48-
type: 'arg',
49-
id: this.itemId,
50-
key: item.itemKey,
51-
value: e.detail.value,
52-
}
53-
}));
49+
this.#dispatchArg('input', item.itemKey, e.detail.value);
5450
});
5551
$item.addEventListener('change', (e: any) => {
56-
this.dispatchEvent(new CustomEvent('change', {
57-
detail: {
58-
type: 'arg',
59-
id: this.itemId,
60-
key: item.itemKey,
61-
value: e.detail.value,
62-
}
63-
}));
64-
// todo: cache height by key and only trigger when height changes
65-
// as this is currently bad for performance.
66-
if (Array.isArray(e.detail.value)) {
67-
const $outputs = this.$outputs.children;
68-
this.outputs.forEach((output, outputIndex) => {
69-
const $output = $outputs[outputIndex];
70-
const top = this.$node.getBoundingClientRect().top;
71-
this.dispatchEvent(new CustomEvent('registernodeoutput', {
72-
detail: {
73-
node: this.itemId,
74-
key: output.key,
75-
label: output.label,
76-
offset: $output.getBoundingClientRect().top - top + 9,
77-
},
78-
}));
79-
});
80-
}
52+
// Editors can grow or shrink on change (e.g. TextArray rows);
53+
// reflow first so change listeners see the final geometry.
54+
this.#syncFieldHeight(item.itemKey, $item);
55+
this.#dispatchArg('change', item.itemKey, e.detail.value);
8156
});
8257
},
8358
});
@@ -92,23 +67,71 @@ export default class PgNode extends HTMLElement {
9267
this.height += $item.height;
9368
},
9469
connect: ($item: any, item) => {
70+
// Measured mid-setup, before render() applies the host styles, so
71+
// the adjust constant differs from the post-layout one below.
9572
const top = this.$node.getBoundingClientRect().top;
96-
this.dispatchEvent(new CustomEvent('registernodeoutput', {
97-
detail: {
98-
node: this.itemId,
99-
key: item.key,
100-
label: item.label,
101-
offset: $item.getBoundingClientRect().top - top + 31,
102-
}
103-
}));
73+
this.#registerOutputPin(item.key, item.label, $item.getBoundingClientRect().top - top + 31);
10474
},
10575
});
76+
this.#intrinsicHeight = this.height;
10677
if (requestedHeight > this.height) {
10778
this.height = requestedHeight;
10879
}
10980
this.$node.addEventListener('pointerover', this.#handlePointerOver.bind(this));
11081
}
11182

83+
// Editor heights are cached by field key so a change event only triggers a
84+
// reflow (node height + output pin offsets) when a height actually changed.
85+
#fieldHeights = new Map<string, number>();
86+
#intrinsicHeight = 2;
87+
#syncFieldHeight(key: string, $item: any) {
88+
const previous = this.#fieldHeights.get(key) ?? 0;
89+
if ($item.height === previous) return;
90+
this.#fieldHeights.set(key, $item.height);
91+
// Auto-sized nodes track their content; manually resized nodes only grow
92+
// when the content no longer fits.
93+
const wasAuto = this.height === this.#intrinsicHeight;
94+
this.#intrinsicHeight += $item.height - previous;
95+
this.height = wasAuto ? this.#intrinsicHeight : Math.max(this.height, this.#intrinsicHeight);
96+
this.#registerOutputPins();
97+
}
98+
99+
#dispatchArg(type: 'input' | 'change', key: string, value: any) {
100+
this.dispatchEvent(new CustomEvent(type, {
101+
detail: {
102+
type: 'arg',
103+
id: this.itemId,
104+
key,
105+
value,
106+
}
107+
}));
108+
}
109+
110+
#registerOutputPin(key: string, label: string, offset: number) {
111+
this.dispatchEvent(new CustomEvent('registernodeoutput', {
112+
detail: {
113+
node: this.itemId,
114+
key,
115+
label,
116+
offset,
117+
}
118+
}));
119+
}
120+
121+
// Re-measures every rendered output row. 'then' outputs render no row
122+
// (their pin stays on the header line), so the DOM index advances only
123+
// for outputs that produced an element.
124+
#registerOutputPins() {
125+
const top = this.$node.getBoundingClientRect().top;
126+
let domIndex = 0;
127+
this.outputs.forEach((output: any) => {
128+
if (output.key === 'then') return;
129+
const $output = this.$outputs.children[domIndex++];
130+
if (!$output) return;
131+
this.#registerOutputPin(output.key, output.label, $output.getBoundingClientRect().top - top + 9);
132+
});
133+
}
134+
112135
render(changes: any) {
113136
if (changes.outputs) {
114137
this.outputs.forEach(({ key, label }: any) => {
@@ -230,7 +253,7 @@ export default class PgNode extends HTMLElement {
230253
}
231254

232255
getMinWidth() {
233-
return this.width || 6;
256+
return this.minWidth;
234257
}
235258

236259
focus() {

src/components/pg/nodeEditorTextArray/nodeEditorTextArray.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ export default class PgNodeEditorTextArray extends HTMLElement {
110110
key: uuid(),
111111
value: '',
112112
});
113+
// Emit updated array
114+
this.dispatchEvent(new CustomEvent('change', {
115+
detail: {
116+
value: this.#inputs.map(x => x.value),
117+
},
118+
}));
113119
});
114120
$item.addEventListener('remove', (e: any) => {
115121
const { index } = e.detail;
@@ -138,9 +144,12 @@ export default class PgNodeEditorTextArray extends HTMLElement {
138144
key: uuid(),
139145
value: '',
140146
});
141-
// Emit updated array
147+
// Emit updated array (#inputs is the live list; this.value is only the
148+
// initial prop and goes stale after the first edit)
142149
this.dispatchEvent(new CustomEvent('change', {
143-
detail: { value: [...this.value, ''] },
150+
detail: {
151+
value: this.#inputs.map(x => x.value),
152+
},
144153
}));
145154
});
146155
}

src/components/pg/nodes/nodes.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import PgMenuItem from '../menuItem/menuItem';
88

99
import template from './nodes.html';
1010
import style from './nodes.css';
11+
import PgMenuDivider from '../menuDivider/menuDivider';
1112

1213
type NodeState = { x: number; y: number; width: number; height: number };
1314
type UndoTransform = { type: 'transform'; nodeId: number; before: NodeState; after: NodeState };
@@ -254,6 +255,9 @@ export default class PgNodes extends HTMLElement {
254255
y: e.clientY,
255256
items: [
256257
{ label: 'Copy', value: 'copyNode', type: PgMenuItem },
258+
{ type: PgMenuDivider },
259+
{ label: 'Enable Breakpoint', value: 'breakpoint', type: PgMenuItem },
260+
{ type: PgMenuDivider },
257261
{ label: 'Delete Node', value: 'deleteNode', type: PgMenuItem },
258262
],
259263
});
@@ -291,8 +295,9 @@ export default class PgNodes extends HTMLElement {
291295
const nodeType = this.nodes.find((n: any) => n.name === item.node);
292296
if (nodeType) {
293297
$item.label = nodeType.label;
298+
$item.minWidth = nodeType.width ?? 6;
294299
if (!item.width) {
295-
$item.width = nodeType.width ?? $item.getMinWidth();
300+
$item.width = $item.getMinWidth();
296301
}
297302
if (nodeType.args) {
298303
$item.fields = nodeType.args.map((arg: any) => ({
@@ -637,9 +642,14 @@ export default class PgNodes extends HTMLElement {
637642
if (item) {
638643
item.x = cx;
639644
item.y = cy;
645+
// Sizes matching the type default (minWidth) / content height stay
646+
// implicit so nodes follow the registry on reload.
640647
if (node) {
641648
if (cw === node.getMinWidth()) { delete item.width; } else { item.width = cw; }
642649
if (ch === node.getMinHeight()) { delete item.height; } else { item.height = ch; }
650+
} else {
651+
item.width = cw;
652+
item.height = ch;
643653
}
644654
}
645655
this.dispatchEvent(new CustomEvent('change', {
@@ -651,6 +661,18 @@ export default class PgNodes extends HTMLElement {
651661
if (!item) return;
652662
const args = item.args ?? (item.args = {});
653663
args[key] = value;
664+
// Editors can change the node's height on value changes (TextArray
665+
// rows); pg-node reflows before dispatching, so sync the cached state
666+
// and the connector's box to the element here.
667+
const node = this.getNodeById(id) as any;
668+
if (node) {
669+
const state = this.#nodeStates.get(id);
670+
if (state && (state.width !== node.width || state.height !== node.height)) {
671+
this.#nodeStates.set(id, { ...state, width: node.width, height: node.height });
672+
this.#updatePins(id);
673+
this.#updateScrollExtent();
674+
}
675+
}
654676
this.dispatchEvent(new CustomEvent('change', { detail: e.detail }));
655677
}
656678
}

0 commit comments

Comments
 (0)