@ankhzet/graph 0.4.0 → 0.4.1

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": "@ankhzet/graph",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -31,9 +31,9 @@
31
31
  "mobx": "^6.15.4",
32
32
  "mobx-react-lite": "^4.1.1",
33
33
  "@ankhzet/eventual": "1.2.0",
34
- "@ankhzet/ui": "0.3.0",
35
34
  "@ankhzet/gcl": "1.2.0",
36
- "@ankhzet/utils": "1.19.0"
35
+ "@ankhzet/utils": "1.19.0",
36
+ "@ankhzet/ui": "0.3.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "react": "^19.2.6"
@@ -239,8 +239,8 @@ export class ManagedGraphNode<Item extends IItem = IItem>
239
239
  }
240
240
 
241
241
  public render(content: Container) {
242
- const width = ~~content.width - 1;
243
- const height = ~~content.height - 2;
242
+ const width = content.width - 1;
243
+ const height = content.height - 1;
244
244
 
245
245
  if (this.#outline) {
246
246
  this.#outline.hotSwap(content);
@@ -251,7 +251,7 @@ export class ManagedGraphNode<Item extends IItem = IItem>
251
251
  height,
252
252
  color: 0x33ffff,
253
253
  lineSize: 1,
254
- radius: 4,
254
+ radius: 6,
255
255
  }),
256
256
  content,
257
257
  );
@@ -267,7 +267,7 @@ export class ManagedGraphNode<Item extends IItem = IItem>
267
267
  sizeY: height,
268
268
  color: 0x3333ff,
269
269
  lineSize: 3,
270
- radius: 4,
270
+ radius: 6,
271
271
  });
272
272
  }
273
273
 
@@ -16,29 +16,36 @@ export type GroupResolver<T extends GroupableItem<any>> = (node: T) => null | un
16
16
  export interface NodeGroupsProps<T extends GroupableItem<any>> extends PropsWithChildren {
17
17
  items: T[];
18
18
  resolver: GroupResolver<T>;
19
- onSelect: (group: ItemsGroup<T['id']>) => void;
19
+ onSelect: (groups: ItemsGroup<T['id']>[]) => void;
20
20
  }
21
21
 
22
- function makeGroups<T extends GroupableItem<any>>(items: T[], resolver: GroupResolver<T>): ItemsGroup<T['id']>[] {
23
- const groups: ItemsGroup<T['id']>[] = [];
22
+ function makeGroups<T extends GroupableItem<any>>(items: T[], resolver: GroupResolver<T>): {
23
+ groups: ItemsGroup<T['id']>[];
24
+ getAll: () => ItemsGroup<T['id']>[];
25
+ } {
26
+ const resolved: ItemsGroup<T['id']>[] = [];
24
27
 
25
28
  for (const node of items) {
26
29
  const group = resolver(node);
27
30
 
28
31
  if (group) {
29
- groups.push(group);
32
+ resolved.push(group);
30
33
  }
31
34
  }
32
35
 
33
- return groups
36
+ const all: ItemsGroup<T['id']> = {
37
+ value: 'all',
38
+ ids: items.map(({ id }) => id),
39
+ label: `All nodes (${items.length})`,
40
+ };
41
+ const groups = resolved
34
42
  .sort((a, b) => a.label.localeCompare(b.label))
35
- .concat([
36
- {
37
- value: 'all',
38
- ids: items.map(({ id }) => id),
39
- label: `All nodes (${items.length})`,
40
- },
41
- ]);
43
+ .concat([all]);
44
+
45
+ return {
46
+ groups,
47
+ getAll: () => [all],
48
+ };
42
49
  }
43
50
 
44
51
  const isSameArray = (a: any[], b: any[]) => {
@@ -57,12 +64,24 @@ const isSameArray = (a: any[], b: any[]) => {
57
64
  return true;
58
65
  };
59
66
 
60
- const isValue = (value: any) => {
61
- const s = String(value);
67
+ const isValue = (values: Array<string | number>) => {
68
+ const mapped = values.map(String);
62
69
 
63
- return (item: { value: any }) => String(item.value) === s;
70
+ return (item: { value: string | number }) => (
71
+ mapped.includes(String(item.value))
72
+ );
64
73
  };
65
74
 
75
+ const isSameSelection = (a: ItemsGroup<any>[], b: ItemsGroup<any>[]) => (
76
+ a.every((group) => b.some((item) => item.value === group.value)) && isSameArray(
77
+ a.flatMap(mapIds),
78
+ b.flatMap(mapIds)
79
+ )
80
+ );
81
+
82
+ const mapValue = (item: { value: string | number }) => item.value;
83
+ const mapIds = (item: ItemsGroup<any>) => item.ids;
84
+
66
85
  export function NodeGroups<Item extends GroupableItem<any>>({
67
86
  items,
68
87
  resolver,
@@ -70,40 +89,39 @@ export function NodeGroups<Item extends GroupableItem<any>>({
70
89
  children,
71
90
  ...rest
72
91
  }: NodeGroupsProps<Item>) {
73
- const groups = useMemo(() => makeGroups(items, resolver), [items, resolver]);
74
- const [group, setGroup] = useState<ItemsGroup<Item>>(groups.find(({ value }) => value === 'all')!);
92
+ const { groups, getAll } = useMemo(() => makeGroups(items, resolver), [items, resolver]);
93
+ const [selected, setSelected] = useState(getAll);
94
+ const currentValue = useMemo(() => selected.map((group) => String(group.value)), [selected]);
75
95
 
76
- const handleGroup = useLastCallback(({ target: { value } }: ChangeEvent<HTMLSelectElement>) => {
77
- const found = groups.find(isValue(value));
96
+ const handleGroup = useLastCallback(({ target: { selectedOptions } }: ChangeEvent<HTMLSelectElement>) => {
97
+ const found = groups.filter(isValue(Array.from(selectedOptions, mapValue)));
78
98
 
79
99
  if (found) {
80
- setGroup(found);
100
+ setSelected(found);
81
101
  }
82
102
  });
83
103
 
84
- useEffect(() => void onSelect(group), [group, onSelect]);
104
+ useEffect(() => void onSelect(selected), [selected, onSelect]);
85
105
 
86
106
  useLayoutEffect(() => {
87
- const found = groups.find(isValue(group.value));
107
+ const found = groups.filter(isValue(selected.map(mapValue)));
88
108
 
89
109
  if (found) {
90
- if (found === group) {
110
+ if (isSameSelection(found, selected)) {
91
111
  return;
92
112
  }
93
113
 
94
- if (found.label !== group.label || !isSameArray(found.ids, group.ids)) {
95
- setGroup(found);
96
- }
114
+ setSelected(found);
97
115
  } else {
98
- setGroup(groups[0]!);
116
+ setSelected(getAll);
99
117
  }
100
- }, [groups, group]);
118
+ }, [groups, getAll, selected]);
101
119
 
102
120
  return (
103
121
  <select
104
122
  multiple
105
123
  size={groups.length}
106
- value={String(group.value)}
124
+ value={currentValue}
107
125
  onChange={handleGroup}
108
126
  {...rest}
109
127
  >
@@ -140,9 +140,9 @@ export abstract class GraphNode<ID, Data = {}, R = any>
140
140
  }
141
141
 
142
142
  move(pos: Position): Position {
143
- this.position = pos;
144
-
145
- return this.position;
143
+ return (
144
+ this.position = pos
145
+ );
146
146
  }
147
147
 
148
148
  onConnect(handler: IEventHandler<NodeConnection<ID>>): Disposer {
@@ -14,7 +14,6 @@ import { PIXIText } from './PIXIText';
14
14
  export const E_NODE_CONTEXT = Symbol.for('E_NODE_CONTEXT');
15
15
  export const E_CONNECTORS_UPDATE = Symbol.for('E_CONNECTORS_UPDATE');
16
16
 
17
- export const NODE_WIDTH = 64;
18
17
  export const LANE_WIDTH = 16;
19
18
  export const LANE_HEIGHT = 16;
20
19
  export const PORT_SIZE = 8;
@@ -48,8 +47,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
48
47
  });
49
48
 
50
49
  readonly #label: PIXIText;
51
- #_shape?: Container;
52
- #_connectors?: ConnectorPoint<Node>[];
50
+ #shape?: Container;
51
+ #connectors?: ConnectorPoint<Node>[];
53
52
  #drag = onDragStart(this);
54
53
 
55
54
  public data: Node;
@@ -79,8 +78,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
79
78
  port.dispose();
80
79
  }
81
80
 
82
- if (this.#_connectors) {
83
- const remove = this.#_connectors.map(({ port }) => `${this.id}-${port}` as ConnectorId<Node>);
81
+ if (this.#connectors) {
82
+ const remove = this.#connectors.map(({ port }) => `${this.id}-${port}` as ConnectorId<Node>);
84
83
 
85
84
  if (remove.length) {
86
85
  this.dispatchEvent(E_CONNECTORS_UPDATE, {
@@ -89,9 +88,9 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
89
88
  }
90
89
  }
91
90
 
92
- if (this.#_shape) {
93
- this.#_shape.removeFromParent();
94
- this.#_shape = undefined;
91
+ if (this.#shape) {
92
+ this.#shape.removeFromParent();
93
+ this.#shape = undefined;
95
94
  }
96
95
 
97
96
  super.dispose();
@@ -113,7 +112,7 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
113
112
  }
114
113
 
115
114
  public get width() {
116
- return Math.max(this.data.width, this.#label.width + 2);
115
+ return Math.max(this.data.width, this.#label.width + 6);
117
116
  }
118
117
 
119
118
  public get height() {
@@ -143,18 +142,19 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
143
142
  }
144
143
 
145
144
  public get connectors(): ConnectorPoint<Node>[] {
146
- if (this.#_connectors) {
147
- return this.#_connectors;
145
+ if (this.#connectors) {
146
+ return this.#connectors;
148
147
  }
149
148
 
150
- const connectors: ConnectorPoint<Node>[] = [];
149
+ const width = this.width;
150
+ this.#connectors = [];
151
151
  let idx1 = 1;
152
152
  let idx2 = 1;
153
153
 
154
154
  for (const joint of this.data.inputs) {
155
155
  const offset = { x: LANE_WIDTH / 2, y: idx1 * LANE_HEIGHT + LANE_HEIGHT / 2 };
156
156
  const pos = addOffset(this.data.position, offset);
157
- connectors.push({
157
+ this.#connectors.push({
158
158
  parent: this.data.id,
159
159
  port: joint.port,
160
160
  name: joint.name,
@@ -168,19 +168,19 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
168
168
  }
169
169
 
170
170
  for (const joint of this.data.outputs) {
171
- connectors.push({
171
+ this.#connectors.push({
172
172
  parent: this.data.id,
173
173
  port: joint.port,
174
174
  name: joint.name,
175
175
  nodes: joint.nodes,
176
176
  input: false,
177
- x: NODE_WIDTH - LANE_WIDTH / 2,
177
+ x: width - LANE_WIDTH / 2,
178
178
  y: idx2 * LANE_HEIGHT + LANE_HEIGHT / 2,
179
179
  });
180
180
  idx2++;
181
181
  }
182
182
 
183
- return (this.#_connectors = connectors);
183
+ return this.#connectors;
184
184
  }
185
185
 
186
186
  public placeConnectors(recursive = true) {
@@ -231,8 +231,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
231
231
  }
232
232
 
233
233
  public get renderable(): Container {
234
- if (this.#_shape) {
235
- return this.#_shape;
234
+ if (this.#shape) {
235
+ return this.#shape;
236
236
  }
237
237
 
238
238
  const width = this.width;
@@ -289,7 +289,7 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
289
289
  base.x = this.data.position.x;
290
290
  base.y = this.data.position.y;
291
291
 
292
- return (this.#_shape = base);
292
+ return (this.#shape = base);
293
293
  }
294
294
 
295
295
  public makeRoundRect({ stroke, fill, x = 0, y = 0, width, height, thickness = 1, radius = 4 }: RoundRectStyle) {