@elicdavis/node-flow 0.1.19 → 0.2.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 (55) hide show
  1. package/dist/index.d.mts +386 -227
  2. package/dist/index.d.ts +386 -227
  3. package/dist/index.js +1884 -1076
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1867 -1075
  6. package/dist/index.mjs.map +1 -1
  7. package/index.html +1134 -329
  8. package/package.json +1 -1
  9. package/src/connection.ts +30 -5
  10. package/src/contextMenu.ts +66 -34
  11. package/src/graph.ts +12 -10
  12. package/src/index.ts +26 -12
  13. package/src/markdown/entry.ts +11 -15
  14. package/src/markdown/syntaxParser.ts +2 -1
  15. package/src/node.ts +162 -111
  16. package/src/nodes/subsystem.ts +123 -29
  17. package/src/notes/note.ts +9 -1
  18. package/src/organize.ts +540 -111
  19. package/src/popup.ts +4 -2
  20. package/src/port.ts +83 -6
  21. package/src/styles/bevel.ts +31 -0
  22. package/src/styles/cursor.ts +1 -0
  23. package/src/styles/text.ts +4 -4
  24. package/src/theme.ts +179 -92
  25. package/src/types/text.ts +5 -1
  26. package/src/utils/color.ts +1 -1
  27. package/src/widgets/button.ts +52 -107
  28. package/src/widgets/color.ts +91 -150
  29. package/src/widgets/factory.ts +89 -46
  30. package/src/widgets/field.ts +99 -0
  31. package/src/widgets/image.ts +107 -165
  32. package/src/widgets/inplace.ts +84 -0
  33. package/src/widgets/number.ts +122 -154
  34. package/src/widgets/row.ts +138 -0
  35. package/src/widgets/slider.ts +101 -216
  36. package/src/widgets/string.ts +39 -130
  37. package/src/widgets/text.ts +81 -92
  38. package/src/widgets/toggle.ts +62 -207
  39. package/src/widgets/value.ts +127 -0
  40. package/src/widgets/widget.ts +180 -34
  41. package/test/context_menu.test.ts +82 -0
  42. package/test/cursor.test.ts +17 -0
  43. package/test/hidden_port.test.ts +125 -0
  44. package/test/hover_after_removal.test.ts +72 -0
  45. package/test/markdown.test.ts +2 -2
  46. package/test/markdown_code_block.test.ts +58 -0
  47. package/test/multi_input.test.ts +124 -0
  48. package/test/organize.test.ts +117 -1
  49. package/test/property_binding.test.ts +49 -0
  50. package/test/support.ts +175 -0
  51. package/test/theme.test.ts +86 -0
  52. package/test/widget_click.test.ts +111 -97
  53. package/test/widget_construction.test.ts +5 -4
  54. package/test/widgets.test.ts +333 -0
  55. package/test/wire_follows_camera.test.ts +57 -0
@@ -0,0 +1,138 @@
1
+ import { Theme } from "../theme";
2
+ import { CursorStyle } from "../styles/cursor";
3
+ import { Box, InBox } from "../types/box";
4
+ import { Vector2 } from "../types/vector2";
5
+ import { Widget, WidgetBase, WidgetPointer } from "./widget";
6
+
7
+ export interface RowWidgetConfig {
8
+ /**
9
+ * How the room beyond each child's own width is shared out. Left out,
10
+ * every child grows the same; 0 keeps a child at its own width.
11
+ */
12
+ grow?: Array<number>;
13
+
14
+ gap?: number;
15
+ }
16
+
17
+ export class RowWidget extends WidgetBase {
18
+
19
+ #children: Array<Widget>;
20
+
21
+ #grow: Array<number>;
22
+
23
+ #gap: number;
24
+
25
+ #boxes: Array<Box | null> = [];
26
+
27
+ #captured: Widget | null = null;
28
+
29
+ #capturedBox: Box | null = null;
30
+
31
+ #mouse: Vector2 | undefined;
32
+
33
+ constructor(children: Array<Widget>, config?: RowWidgetConfig) {
34
+ super();
35
+ this.#children = children;
36
+ this.#grow = config?.grow ?? children.map(() => 1);
37
+ this.#gap = config?.gap ?? Theme.Widget.Gap;
38
+ }
39
+
40
+ children(): ReadonlyArray<Widget> {
41
+ return this.#children;
42
+ }
43
+
44
+ Size(): Vector2 {
45
+ const shown = this.#shown();
46
+ let x = 0;
47
+ let y = 0;
48
+ for (const child of shown) {
49
+ const size = child.Size();
50
+ x += size.x;
51
+ y = Math.max(y, size.y);
52
+ }
53
+ x += Math.max(0, shown.length - 1) * this.#gap;
54
+ return { x, y };
55
+ }
56
+
57
+ Cursor(): CursorStyle {
58
+ const over = this.#captured ?? this.#childAt(this.#mouse);
59
+ return over?.Cursor?.() ?? CursorStyle.Default;
60
+ }
61
+
62
+ Draw(ctx: CanvasRenderingContext2D, box: Box, scale: number, mousePosition: Vector2 | undefined): void {
63
+ this.#mouse = mousePosition;
64
+ super.Draw(ctx, box, scale, mousePosition);
65
+ }
66
+
67
+ protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
68
+ const shown = this.#shown();
69
+ const gap = this.#gap * scale;
70
+ const natural = shown.reduce((sum, child) => sum + (child.Size().x * scale), 0);
71
+ const extra = Math.max(0, box.Size.x - natural - (gap * Math.max(0, shown.length - 1)));
72
+ const totalGrow = shown.reduce((sum, child) => sum + this.#growOf(child), 0);
73
+
74
+ this.#boxes = this.#children.map(() => null);
75
+ let x = box.Position.x;
76
+ for (const child of shown) {
77
+ const size = child.Size();
78
+ const share = totalGrow > 0 ? extra * (this.#growOf(child) / totalGrow) : 0;
79
+ const childBox: Box = {
80
+ Position: { x, y: box.Position.y + ((box.Size.y - (size.y * scale)) / 2) },
81
+ Size: { x: (size.x * scale) + share, y: size.y * scale },
82
+ };
83
+ this.#boxes[this.#children.indexOf(child)] = childBox;
84
+ child.Draw(ctx, childBox, scale, this.#captured === null || this.#captured === child ? this.#mouse : undefined);
85
+ x += childBox.Size.x + gap;
86
+ }
87
+ }
88
+
89
+ protected onPointerDown(pointer: WidgetPointer): void {
90
+ const child = this.#childAt(pointer.position);
91
+ if (child === null || child.Disabled?.()) {
92
+ return;
93
+ }
94
+ this.#captured = child;
95
+ this.#capturedBox = this.#boxes[this.#children.indexOf(child)];
96
+ child.PointerDown?.(this.#forChild(pointer));
97
+ }
98
+
99
+ protected onPointerMove(pointer: WidgetPointer): void {
100
+ this.#captured?.PointerMove?.(this.#forChild(pointer));
101
+ }
102
+
103
+ protected onPointerUp(pointer: WidgetPointer): void {
104
+ const child = this.#captured;
105
+ const forChild = this.#forChild(pointer);
106
+ this.#captured = null;
107
+ this.#capturedBox = null;
108
+ child?.PointerUp?.(forChild);
109
+ }
110
+
111
+ #forChild(pointer: WidgetPointer): WidgetPointer {
112
+ return {
113
+ position: pointer.position,
114
+ inside: this.#capturedBox !== null && InBox(this.#capturedBox, pointer.position),
115
+ };
116
+ }
117
+
118
+ #childAt(position: Vector2 | undefined): Widget | null {
119
+ if (position === undefined) {
120
+ return null;
121
+ }
122
+ for (let i = 0; i < this.#children.length; i++) {
123
+ const box = this.#boxes[i];
124
+ if (box && InBox(box, position)) {
125
+ return this.#children[i];
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+
131
+ #shown(): Array<Widget> {
132
+ return this.#children.filter((child) => !child.Hidden?.());
133
+ }
134
+
135
+ #growOf(child: Widget): number {
136
+ return this.#grow[this.#children.indexOf(child)] ?? 1;
137
+ }
138
+ }
@@ -1,216 +1,101 @@
1
- import { Theme } from "../theme";
2
- import { TextAlign } from "../styles/canvasTextAlign";
3
- import { TextStyle, TextStyleConfig, TextStyleFallback } from "../styles/text";
4
- import { Box } from '../types/box';
5
- import { CopyVector2, Vector2, Zero } from "../types/vector2";
6
- import { Clamp, Clamp01 } from "../utils/math";
7
- import { height, startingWidgetValue, width } from "./widget";
8
- import { TextBaseline } from "../styles/canvasTextBaseline";
9
- import { FlowNode } from "../node";
10
-
11
- export interface SliderWidgetConfig {
12
-
13
- property?: string;
14
-
15
- min?: number;
16
-
17
- max?: number;
18
-
19
- value?: number;
20
-
21
- backgroundColor?: string;
22
-
23
- borderColor?: string;
24
-
25
- fillColor?: string;
26
-
27
- textStyle?: TextStyleConfig;
28
-
29
- change?: (newValue: number) => void;
30
-
31
- release?: (newValue: number) => void;
32
-
33
- snapIncrement?: number;
34
-
35
- }
36
-
37
- export class SliderWidget {
38
-
39
- // Data Variables =========================================================
40
-
41
- #min: number;
42
-
43
- #max: number;
44
-
45
- #value: number;
46
-
47
- #text: string;
48
-
49
- #snapIncrement?: number;
50
-
51
- #node: FlowNode;
52
-
53
- #nodeProperty: string | undefined;
54
-
55
- // Callbacks ==============================================================
56
-
57
- #change?: (newValue: number) => void;
58
-
59
- #release?: (newValue: number) => void;
60
-
61
- // Styling Variables ======================================================
62
-
63
- #textStyle: TextStyle
64
-
65
- #backgroundColor: string;
66
-
67
- #borderColor: string;
68
-
69
- #fillColor: string;
70
-
71
- // Runtime Variables ======================================================
72
-
73
- #lastMousePosition: Vector2;
74
-
75
- #clickStartMousePosition: Vector2;
76
-
77
- #clicking: boolean;
78
-
79
- constructor(node: FlowNode, config?: SliderWidgetConfig) {
80
- this.#node = node;
81
- this.#value = 0;
82
- this.#min = config?.min === undefined ? 0 : config?.min;
83
- this.#max = config?.max === undefined ? 1 : config?.max;
84
- this.#snapIncrement = config?.snapIncrement;
85
- this.#nodeProperty = config?.property;
86
- this.#text = "";
87
-
88
- this.#release = config?.release;
89
-
90
- this.#backgroundColor = config?.backgroundColor === undefined ? Theme.Widget.BackgroundColor : config?.backgroundColor;
91
- this.#fillColor = config?.fillColor === undefined ? Theme.Widget.Slider.FillColor : config?.fillColor;
92
- this.#borderColor = config?.borderColor === undefined ? Theme.Widget.Border.Color : config?.borderColor;
93
- this.#textStyle = new TextStyle(TextStyleFallback(config?.textStyle, {
94
- color: Theme.Widget.FontColor,
95
- }));
96
- this.#lastMousePosition = Zero();
97
- this.#clickStartMousePosition = Zero();
98
- this.#clicking = false;
99
-
100
-
101
- if (config?.property !== undefined && config?.property !== null) {
102
- node.addPropertyChangeListener(config.property, (oldVal, newVal) => {
103
- this.SetValue(newVal);
104
- });
105
- }
106
-
107
- this.SetValue(startingWidgetValue(node, this.#nodeProperty, config?.value, 0));
108
-
109
- // Setup change callback after we set the initial value to prevent the callback from being
110
- this.#change = config?.change;
111
- }
112
-
113
- SetValue(newValue: number): void {
114
- const cleanedValue = Clamp(newValue, this.#min, this.#max);
115
- if (this.#value === cleanedValue) {
116
- return;
117
- }
118
-
119
- this.#value = cleanedValue;
120
-
121
- if (this.#nodeProperty) {
122
- this.#node.setProperty(this.#nodeProperty, this.#value);
123
- }
124
-
125
- this.#text = this.#value.toFixed(3);
126
- if (this.#change !== undefined) {
127
- this.#change(this.#value);
128
- }
129
- }
130
-
131
- Size(): Vector2 {
132
- return { "x": width, "y": height }
133
- }
134
-
135
- ClickStart(): void {
136
- this.#clicking = true;
137
- CopyVector2(this.#clickStartMousePosition, this.#lastMousePosition)
138
- }
139
-
140
- ClickEnd(): void {
141
- this.#clicking = false;
142
- if (this.#release !== undefined) {
143
- this.#release(this.#value);
144
- }
145
- }
146
-
147
- Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
148
- const scaledWidth = width * scale;
149
- const scaledHeight = height * scale;
150
-
151
- const scaledBorderThickness = Theme.Widget.Border.Size * scale;
152
-
153
- // Render background
154
- ctx.fillStyle = this.#backgroundColor;
155
- ctx.strokeStyle = this.#borderColor;
156
- ctx.lineWidth = scaledBorderThickness;
157
- ctx.beginPath();
158
- ctx.roundRect(
159
- position.x,
160
- position.y,
161
- scaledWidth,
162
- scaledHeight,
163
- Theme.Widget.Border.Radius * scale
164
- );
165
- ctx.fill();
166
- ctx.stroke();
167
-
168
- // Render Fill
169
- const fill = Clamp01((this.#value - this.#min) / (this.#max - this.#min));
170
- ctx.fillStyle = this.#fillColor;
171
- ctx.beginPath();
172
- ctx.roundRect(
173
- position.x + (scaledBorderThickness / 2),
174
- position.y + (scaledBorderThickness / 2),
175
- (scaledWidth * fill) - scaledBorderThickness,
176
- scaledHeight - scaledBorderThickness,
177
- Theme.Widget.Border.Radius * scale
178
- );
179
- ctx.fill();
180
-
181
- // Render Number
182
- ctx.textAlign = TextAlign.Center;
183
- ctx.textBaseline = TextBaseline.Middle;
184
- this.#textStyle.setupStyle(ctx, scale);
185
- ctx.fillText(
186
- this.#text,
187
- position.x + (scaledWidth / 2),
188
- position.y + (scaledHeight / 2),
189
- );
190
-
191
- // Update value based on mouse position
192
- if (mousePosition !== undefined) {
193
- CopyVector2(this.#lastMousePosition, mousePosition);
194
- if (this.#clicking) {
195
- const min = position.x + (scaledBorderThickness / 2);
196
- const max = min + scaledWidth - scaledBorderThickness;
197
- const p = Clamp01((this.#lastMousePosition.x - min) / (max - min));
198
-
199
- let value = (p * (this.#max - this.#min)) + this.#min;
200
- if (this.#snapIncrement !== undefined) {
201
- value = Math.round(value / this.#snapIncrement) * this.#snapIncrement;
202
- }
203
-
204
- this.SetValue(value);
205
- }
206
- }
207
-
208
- return {
209
- Position: { x: position.x, y: position.y },
210
- Size: {
211
- x: scaledWidth,
212
- y: scaledHeight
213
- }
214
- };
215
- }
216
- }
1
+ import { Theme } from "../theme";
2
+ import { Box } from "../types/box";
3
+ import { Clamp, Clamp01 } from "../utils/math";
4
+ import { FlowNode } from "../node";
5
+ import { CursorStyle } from "../styles/cursor";
6
+ import { drawFieldBox, drawFieldText } from "./field";
7
+ import { formatNumber } from "./number";
8
+ import { ValueWidget, ValueWidgetConfig } from "./value";
9
+ import { WidgetPointer } from "./widget";
10
+
11
+ export interface SliderWidgetConfig extends ValueWidgetConfig<number> {
12
+ min?: number;
13
+
14
+ max?: number;
15
+
16
+ snapIncrement?: number;
17
+
18
+ fillColor?: string;
19
+ }
20
+
21
+ export class SliderWidget extends ValueWidget<number> {
22
+
23
+ #min: number;
24
+
25
+ #max: number;
26
+
27
+ #snapIncrement?: number;
28
+
29
+ #fillColor: string | undefined;
30
+
31
+ #startValue = 0;
32
+
33
+ constructor(node: FlowNode, config?: SliderWidgetConfig) {
34
+ const min = config?.min ?? 0;
35
+ const max = config?.max ?? 1;
36
+ super(node, { ...config, value: config?.value === undefined ? undefined : Clamp(config.value, min, max) }, min);
37
+ this.#min = min;
38
+ this.#max = max;
39
+ this.#snapIncrement = config?.snapIncrement;
40
+ this.#fillColor = config?.fillColor;
41
+ }
42
+
43
+ Cursor(): CursorStyle {
44
+ return CursorStyle.EWResize;
45
+ }
46
+
47
+ setValue(value: number): void {
48
+ super.setValue(Clamp(value, this.#min, this.#max));
49
+ }
50
+
51
+ protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
52
+ drawFieldBox(ctx, box, scale, { input: true, hovered: this.hovered() || this.pressed(), value: "" });
53
+
54
+ const border = Theme.Widget.Border.Size * scale;
55
+ const fill = Clamp01((this.value() - this.#min) / (this.#max - this.#min));
56
+ ctx.fillStyle = this.#fillColor ?? Theme.Widget.Slider.FillColor;
57
+ ctx.beginPath();
58
+ ctx.roundRect(
59
+ box.Position.x + (border / 2),
60
+ box.Position.y + (border / 2),
61
+ Math.max(0, (box.Size.x * fill) - border),
62
+ box.Size.y - border,
63
+ Theme.Widget.Border.Radius * scale
64
+ );
65
+ ctx.fill();
66
+
67
+ drawFieldText(ctx, box, scale, {
68
+ input: true,
69
+ hovered: false,
70
+ label: this.label(),
71
+ value: formatNumber(Number(this.value().toFixed(3))),
72
+ });
73
+ }
74
+
75
+ protected onPointerDown(pointer: WidgetPointer): void {
76
+ this.#startValue = this.value();
77
+ this.input(this.#valueAt(pointer.position.x));
78
+ }
79
+
80
+ protected onPointerMove(pointer: WidgetPointer): void {
81
+ this.input(this.#valueAt(pointer.position.x));
82
+ }
83
+
84
+ protected onPointerUp(): void {
85
+ this.commit(this.value(), this.#startValue);
86
+ }
87
+
88
+ #valueAt(x: number): number {
89
+ const box = this.box();
90
+ const border = Theme.Widget.Border.Size * this.scale();
91
+ const min = box.Position.x + (border / 2);
92
+ const max = min + box.Size.x - border;
93
+ const p = Clamp01((x - min) / (max - min));
94
+
95
+ let value = (p * (this.#max - this.#min)) + this.#min;
96
+ if (this.#snapIncrement !== undefined) {
97
+ value = Math.round(value / this.#snapIncrement) * this.#snapIncrement;
98
+ }
99
+ return Clamp(value, this.#min, this.#max);
100
+ }
101
+ }
@@ -1,130 +1,39 @@
1
- import { Theme } from "../theme";
2
- import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../styles/textBox";
3
- import { Box, InBox } from "../types/box";
4
- import { CopyVector2, Vector2 } from "../types/vector2";
5
- import { fitString } from "../utils/string";
6
- import { height, startingWidgetValue, width } from "./widget";
7
- import { FlowNode } from '../node';
8
- import { SetStringPopup } from "../popups/string";
9
-
10
- export interface StringWidgetConfig {
11
- property?: string;
12
-
13
- value?: string;
14
-
15
- textBoxStyle?: TextBoxStyleConfig;
16
-
17
- callback?: (newString: string) => void;
18
- }
19
-
20
- export class StringWidget {
21
-
22
- #value: string;
23
-
24
- #idleStyle: TextBoxStyle;
25
-
26
- #hoverStyle: TextBoxStyle;
27
-
28
- #callback?: (newString: string) => void;
29
-
30
- #node: FlowNode
31
-
32
- #nodeProperty: string | undefined;
33
-
34
- constructor(node: FlowNode, config?: StringWidgetConfig) {
35
- this.#value = "";
36
-
37
- this.#node = node;
38
- this.#nodeProperty = config?.property;
39
- this.#idleStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.textBoxStyle, {
40
- box: {
41
- color: Theme.Widget.BackgroundColor,
42
- border: {
43
- size: Theme.Widget.Border.Size,
44
- color: Theme.Widget.Border.Color,
45
- },
46
- radius: Theme.Widget.Border.Radius
47
- },
48
- text: { color: Theme.Widget.FontColor },
49
- }));
50
-
51
- this.#hoverStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.textBoxStyle, {
52
- box: {
53
- color: Theme.Widget.Hover.BackgroundColor,
54
- border: {
55
- size: Theme.Widget.Border.Size,
56
- color: Theme.Widget.Border.Color,
57
- },
58
- radius: Theme.Widget.Border.Radius
59
- },
60
- text: { color: Theme.Widget.FontColor },
61
- }));
62
-
63
- this.Set(startingWidgetValue(node, this.#nodeProperty, config?.value, ""));
64
-
65
- this.#callback = config?.callback;
66
-
67
- if (this.#nodeProperty !== undefined) {
68
- this.#node.addPropertyChangeListener(this.#nodeProperty, (oldVal, newVal) => {
69
- this.Set(newVal);
70
- });
71
- }
72
- }
73
-
74
- Size(): Vector2 {
75
- return { "x": width, "y": height }
76
- }
77
-
78
- Set(value: string): void {
79
- if (this.#value === value) {
80
- return;
81
- }
82
- this.#value = value;
83
-
84
- if (this.#nodeProperty !== undefined) {
85
- this.#node.setProperty(this.#nodeProperty, this.#value)
86
- }
87
-
88
- if (this.#callback !== undefined) {
89
- this.#callback(this.#value);
90
- }
91
- }
92
-
93
- ClickStart(): void {
94
- }
95
-
96
- ClickEnd(): void {
97
- const popup = SetStringPopup({
98
- title: "Set String",
99
- startingValue: this.#value,
100
- onUpdate: (value: string): void => {
101
- this.Set(value);
102
- },
103
- });
104
- popup.Show();
105
- }
106
-
107
- Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
108
- const box = {
109
- Position: { x: 0, y: 0 },
110
- Size: {
111
- x: width * scale,
112
- y: height * scale
113
- }
114
- };
115
- CopyVector2(box.Position, position);
116
-
117
-
118
- let style: TextBoxStyle = this.#idleStyle;
119
-
120
- if (mousePosition !== undefined) {
121
- if (InBox(box, mousePosition)) {
122
- style = this.#hoverStyle;
123
- }
124
- }
125
-
126
- style.DrawUnderline(ctx, box, scale, fitString(ctx, this.#value, box.Size.x - (20 * scale)))
127
-
128
- return box;
129
- }
130
- }
1
+ import { FlowNode } from "../node";
2
+ import { CursorStyle } from "../styles/cursor";
3
+ import { Box } from "../types/box";
4
+ import { drawField } from "./field";
5
+ import { editInPlace } from "./inplace";
6
+ import { ValueWidget, ValueWidgetConfig } from "./value";
7
+
8
+ export interface StringWidgetConfig extends ValueWidgetConfig<string> { }
9
+
10
+ export class StringWidget extends ValueWidget<string> {
11
+
12
+ constructor(node: FlowNode, config?: StringWidgetConfig) {
13
+ super(node, config, "");
14
+ }
15
+
16
+ Cursor(): CursorStyle {
17
+ return CursorStyle.Text;
18
+ }
19
+
20
+ protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
21
+ drawField(ctx, box, scale, {
22
+ input: true,
23
+ hovered: this.hovered(),
24
+ label: this.label(),
25
+ value: this.value(),
26
+ });
27
+ }
28
+
29
+ protected onClick(): void {
30
+ const before = this.value();
31
+ editInPlace({
32
+ canvas: this.canvas(),
33
+ box: this.box(),
34
+ scale: this.scale(),
35
+ value: before,
36
+ onCommit: (text) => this.commit(text, before),
37
+ });
38
+ }
39
+ }