@likec4/diagram 1.39.4 → 1.40.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 (123) hide show
  1. package/bundle/{chunk-CW_KClZe.js → chunk-CDr-4Ymp.js} +936 -903
  2. package/bundle/custom.js +61 -60
  3. package/bundle/index.js +2667 -539
  4. package/dist/LikeC4Diagram.d.ts +1 -1
  5. package/dist/LikeC4Diagram.js +7 -4
  6. package/dist/LikeC4Diagram.props.d.ts +6 -1
  7. package/dist/base/const.d.ts +9 -0
  8. package/dist/base/const.js +10 -1
  9. package/dist/base/index.d.ts +1 -0
  10. package/dist/base/primitives/edge/EdgeLabel.js +2 -1
  11. package/dist/base/primitives/edge/EdgeLabelContainer.js +10 -4
  12. package/dist/base/primitives/edge/EdgePath.js +17 -8
  13. package/dist/base/primitives/edge/edge.css.d.ts +0 -1
  14. package/dist/base/primitives/edge/edge.css.js +6 -14
  15. package/dist/base/primitives/element/ElementActionButtons.js +1 -3
  16. package/dist/base/primitives/element/ElementNodeContainer.d.ts +0 -6
  17. package/dist/base/primitives/element/ElementNodeContainer.js +3 -26
  18. package/dist/base/primitives/element/ElementTitle.js +2 -2
  19. package/dist/base/types.d.ts +3 -3
  20. package/dist/bundle/LikeC4View.js +4 -12
  21. package/dist/bundle/LikeC4View.props.d.ts +12 -3
  22. package/dist/bundle/custom.js +1 -1
  23. package/dist/custom/customNodes.d.ts +6 -0
  24. package/dist/custom/customNodes.js +2 -1
  25. package/dist/custom/index.js +1 -1
  26. package/dist/hooks/useDiagram.d.ts +5 -1
  27. package/dist/hooks/useDiagram.js +3 -0
  28. package/dist/hooks/useXYFlow.d.ts +4 -439
  29. package/dist/likec4diagram/DiagramUI.js +1 -1
  30. package/dist/likec4diagram/DiagramXYFlow.js +8 -4
  31. package/dist/likec4diagram/convert-to-xyflow.d.ts +14 -0
  32. package/dist/likec4diagram/convert-to-xyflow.js +29 -0
  33. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
  34. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +0 -1
  35. package/dist/likec4diagram/custom/edges/SequenceStepEdge.d.ts +3 -0
  36. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +71 -0
  37. package/dist/likec4diagram/custom/index.js +7 -2
  38. package/dist/likec4diagram/custom/nodes/ElementActions.d.ts +2 -1
  39. package/dist/likec4diagram/custom/nodes/SequenceActorNode.d.ts +4 -0
  40. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +157 -0
  41. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  42. package/dist/likec4diagram/custom/nodes/nodes.d.ts +1 -2
  43. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +31 -27
  44. package/dist/likec4diagram/types.d.ts +41 -7
  45. package/dist/likec4diagram/xyflow-diagram/diagram-view.d.ts +11 -0
  46. package/dist/likec4diagram/{useViewToNodesEdges.js → xyflow-diagram/diagram-view.js} +4 -13
  47. package/dist/likec4diagram/xyflow-sequence/_types.d.ts +41 -0
  48. package/dist/likec4diagram/xyflow-sequence/const.d.ts +17 -0
  49. package/dist/likec4diagram/xyflow-sequence/const.js +16 -0
  50. package/dist/likec4diagram/xyflow-sequence/layouter.d.ts +46 -0
  51. package/dist/likec4diagram/xyflow-sequence/layouter.js +408 -0
  52. package/dist/likec4diagram/xyflow-sequence/sequence-view.d.ts +7 -0
  53. package/dist/likec4diagram/xyflow-sequence/sequence-view.js +273 -0
  54. package/dist/likec4diagram/xyflow-sequence/utils.d.ts +14 -0
  55. package/dist/likec4diagram/xyflow-sequence/utils.js +92 -0
  56. package/dist/navigationpanel/NavigationPanelControls.js +2 -2
  57. package/dist/navigationpanel/controls/ViewDetailsCard.js +5 -2
  58. package/dist/navigationpanel/dropdown/ProjectsMenu.js +53 -44
  59. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.d.ts +1 -2
  60. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.js +10 -41
  61. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +292 -0
  62. package/dist/navigationpanel/walkthrough/DynamicViewControls.js +119 -0
  63. package/dist/navigationpanel/walkthrough/index.d.ts +2 -1
  64. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -1
  65. package/dist/state/DiagramActorProvider.d.ts +3 -2
  66. package/dist/state/DiagramActorProvider.js +19 -11
  67. package/dist/state/assign.js +11 -4
  68. package/dist/state/diagram-machine.d.ts +224 -194
  69. package/dist/state/diagram-machine.js +170 -63
  70. package/dist/state/utils.d.ts +7 -1
  71. package/dist/state/utils.js +67 -7
  72. package/dist/styles-min.css +1 -1
  73. package/dist/styles.css +1 -1
  74. package/dist/styles.css2.js +1 -1
  75. package/package.json +8 -7
  76. package/panda.buildinfo.json +30 -9
  77. package/src/LikeC4Diagram.props.ts +14 -1
  78. package/src/LikeC4Diagram.tsx +7 -4
  79. package/src/base/const.ts +12 -0
  80. package/src/base/index.ts +2 -0
  81. package/src/base/primitives/edge/EdgeLabel.tsx +23 -19
  82. package/src/base/primitives/edge/EdgeLabelContainer.tsx +12 -7
  83. package/src/base/primitives/edge/EdgePath.tsx +25 -14
  84. package/src/base/primitives/edge/edge.css.ts +5 -14
  85. package/src/base/primitives/element/ElementActionButtons.tsx +1 -3
  86. package/src/base/primitives/element/ElementNodeContainer.tsx +3 -30
  87. package/src/base/primitives/element/ElementTitle.tsx +2 -3
  88. package/src/base/types.ts +7 -5
  89. package/src/bundle/LikeC4View.props.ts +14 -3
  90. package/src/bundle/LikeC4View.tsx +4 -14
  91. package/src/bundle/ReactLikeC4.tsx +1 -2
  92. package/src/custom/customNodes.ts +7 -0
  93. package/src/hooks/useDiagram.ts +10 -0
  94. package/src/likec4diagram/DiagramUI.tsx +1 -1
  95. package/src/likec4diagram/DiagramXYFlow.tsx +10 -5
  96. package/src/likec4diagram/convert-to-xyflow.ts +45 -0
  97. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
  98. package/src/likec4diagram/custom/edges/SequenceStepEdge.tsx +65 -0
  99. package/src/likec4diagram/custom/index.ts +7 -1
  100. package/src/likec4diagram/custom/nodes/ElementActions.tsx +4 -2
  101. package/src/likec4diagram/custom/nodes/SequenceActorNode.tsx +175 -0
  102. package/src/likec4diagram/custom/nodes/index.ts +1 -0
  103. package/src/likec4diagram/custom/nodes/nodes.tsx +1 -1
  104. package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +36 -30
  105. package/src/likec4diagram/types.ts +96 -13
  106. package/src/likec4diagram/{useViewToNodesEdges.ts → xyflow-diagram/diagram-view.ts} +7 -28
  107. package/src/likec4diagram/xyflow-sequence/_types.ts +45 -0
  108. package/src/likec4diagram/xyflow-sequence/const.ts +37 -0
  109. package/src/likec4diagram/xyflow-sequence/layouter.ts +567 -0
  110. package/src/likec4diagram/xyflow-sequence/sequence-view.ts +337 -0
  111. package/src/likec4diagram/xyflow-sequence/utils.ts +112 -0
  112. package/src/navigationpanel/NavigationPanelControls.tsx +2 -2
  113. package/src/navigationpanel/controls/ViewDetailsCard.tsx +5 -2
  114. package/src/navigationpanel/dropdown/ProjectsMenu.tsx +5 -1
  115. package/src/navigationpanel/walkthrough/ActiveWalkthroughControls.tsx +5 -40
  116. package/src/navigationpanel/walkthrough/DynamicViewControls.tsx +125 -0
  117. package/src/navigationpanel/walkthrough/index.tsx +2 -1
  118. package/src/overlays/relationship-details/RelationshipDetails.tsx +5 -2
  119. package/src/state/DiagramActorProvider.tsx +21 -10
  120. package/src/state/assign.ts +18 -5
  121. package/src/state/diagram-machine.ts +207 -75
  122. package/src/state/utils.ts +81 -7
  123. package/dist/likec4diagram/useViewToNodesEdges.d.ts +0 -12
@@ -0,0 +1,567 @@
1
+ import {
2
+ type BBox,
3
+ type DiagramNode,
4
+ type NonEmptyArray,
5
+ } from '@likec4/core/types'
6
+ import { invariant, nonexhaustive, nonNullable } from '@likec4/core/utils'
7
+ import * as kiwi from '@lume/kiwi'
8
+ import { last, map, only } from 'remeda'
9
+ import type { Compound, ParallelRect, Step } from './_types'
10
+ import {
11
+ ACTOR_GAP,
12
+ COLUMN_GAP,
13
+ FIRST_STEP_OFFSET,
14
+ MIN_ROW_HEIGHT,
15
+ PORT_HEIGHT,
16
+ STEP_LABEL_MARGIN,
17
+ } from './const'
18
+ import { findParallelRects } from './utils'
19
+
20
+ // const SELF_LOOP_ADDITIONAL_HEIGHT = 50
21
+
22
+ type CompareOperator = '<=' | '==' | '>='
23
+ type Operator = CompareOperator | `${CompareOperator} 0`
24
+
25
+ interface CompoundRect {
26
+ node: DiagramNode
27
+ from: ActorBox
28
+ to: ActorBox
29
+ depth: number
30
+ // Top-left
31
+ x1: kiwi.Expression | kiwi.Variable
32
+ y1: kiwi.Expression | kiwi.Variable
33
+ // Bottom-right
34
+ x2: kiwi.Expression | kiwi.Variable
35
+ y2: kiwi.Expression | kiwi.Variable
36
+ // bottowRow
37
+ bottom: kiwi.Variable
38
+ }
39
+
40
+ interface ActorBox {
41
+ readonly column: number // The column index of the actor box
42
+ readonly actor: DiagramNode
43
+ // The X and Y position of the actor node
44
+ readonly x: kiwi.Variable
45
+ readonly y: kiwi.Variable
46
+ readonly width: number
47
+ readonly height: number
48
+ // Derived properties
49
+ readonly centerX: kiwi.Expression
50
+ readonly centerY: kiwi.Expression
51
+ readonly right: kiwi.Expression
52
+ readonly bottom: kiwi.Expression
53
+ /**
54
+ * The offset of the actor box
55
+ * This is updated if actor box is inside a compound
56
+ */
57
+ readonly offset: {
58
+ top: kiwi.Expression | kiwi.Variable
59
+ left: kiwi.Expression | kiwi.Variable
60
+ right: kiwi.Expression | kiwi.Variable
61
+ bottom: kiwi.Expression | kiwi.Variable
62
+ }
63
+ /**
64
+ * The minimum and maximum row of the steps assigned to this actor box
65
+ */
66
+ minRow: number
67
+ maxRow: number
68
+ }
69
+
70
+ export class SequenceViewLayouter {
71
+ #solver = new kiwi.Solver()
72
+
73
+ #actors: NonEmptyArray<ActorBox>
74
+
75
+ #compounds = [] as Array<CompoundRect>
76
+
77
+ #viewportRight: kiwi.Variable
78
+ #viewportBottom: kiwi.Variable
79
+ #rowsTop: kiwi.Variable
80
+ #rows = [] as Array<{
81
+ y: kiwi.Variable
82
+ height: kiwi.Variable
83
+ bottom: kiwi.Expression
84
+ lastHeight: number
85
+ }>
86
+
87
+ #parallelBoxes = [] as Array<{
88
+ parallelPrefix: string
89
+ x1: kiwi.Expression
90
+ y1: kiwi.Expression | kiwi.Variable
91
+ x2: kiwi.Expression
92
+ y2: kiwi.Expression | kiwi.Variable
93
+ }>
94
+
95
+ constructor({
96
+ actors,
97
+ steps,
98
+ compounds,
99
+ }: {
100
+ actors: NonEmptyArray<DiagramNode>
101
+ steps: Array<Step>
102
+ compounds: Array<Compound>
103
+ }) {
104
+ this.#rowsTop = this.newVar(FIRST_STEP_OFFSET)
105
+ this.#viewportRight = this.newVar(0)
106
+ this.#viewportBottom = this.newVar(0)
107
+
108
+ this.#actors = this.addActors(actors)
109
+
110
+ for (const compound of compounds) {
111
+ const result = this.addCompound(compound)
112
+ const toplevel = result[0]
113
+ // ensure that the top level compound is at the top
114
+ this.constraint(toplevel.y1, '==', 0, kiwi.Strength.strong)
115
+ this.put(this.#viewportBottom).after(toplevel.bottom)
116
+ this.put(this.#rowsTop).after(toplevel.y2)
117
+ this.#compounds.push(...result)
118
+ }
119
+
120
+ for (const step of steps) {
121
+ this.addStep(step)
122
+ }
123
+
124
+ for (const parallelRect of findParallelRects(steps)) {
125
+ this.addParallelRect(parallelRect)
126
+ }
127
+
128
+ const firstActor = this.#actors[0]
129
+ this.constraint(firstActor.offset.left, '==', 0, kiwi.Strength.strong)
130
+
131
+ const lastActor = this.#actors.reduce((prev, actor) => {
132
+ this.put(actor.x).after(prev.right, ACTOR_GAP)
133
+ this.put(actor.offset.left, kiwi.Strength.strong).after(prev.offset.right, COLUMN_GAP)
134
+ this.constraint(actor.centerY, '==', prev.centerY, kiwi.Strength.strong)
135
+ this.put(this.#rowsTop).after(actor.offset.bottom)
136
+ return actor
137
+ })
138
+
139
+ this.put(this.#viewportRight).after(lastActor.offset.right)
140
+ this.put(this.#viewportBottom).after(last(this.#rows)?.bottom ?? this.#rowsTop)
141
+
142
+ if (compounds.length > 0) {
143
+ for (const compound of this.#compounds) {
144
+ const from = compound.from.column
145
+ const to = compound.to.column
146
+ let maxRow = Math.max(compound.from.maxRow, compound.to.maxRow)
147
+ for (let i = from + 1; i < to; i++) {
148
+ const actorBox = this.actorBox(i)
149
+ maxRow = Math.max(maxRow, actorBox.maxRow)
150
+ }
151
+ const lastRow = nonNullable(this.#rows[maxRow], `row ${maxRow} not found`)
152
+ this.put(compound.bottom).after(lastRow.bottom, 16)
153
+ this.put(this.#viewportBottom).after(compound.bottom)
154
+ }
155
+ }
156
+
157
+ this.#solver.updateVariables()
158
+ }
159
+
160
+ getParallelBoxes(): Array<BBox & { parallelPrefix: string }> {
161
+ return this.#parallelBoxes.map(({ parallelPrefix, x1, y1, x2, y2 }) => ({
162
+ parallelPrefix,
163
+ x: x1.value(),
164
+ y: y1.value(),
165
+ width: x2.value() - x1.value(),
166
+ height: y2.value() - y1.value(),
167
+ }))
168
+ }
169
+
170
+ getActorBox(actor: DiagramNode): BBox {
171
+ const actorBox = this.actorBox(actor)
172
+ return {
173
+ x: actorBox.x.value(),
174
+ y: actorBox.y.value(),
175
+ width: actorBox.width,
176
+ height: actorBox.height,
177
+ }
178
+ }
179
+
180
+ getCompoundBoxes(): Array<BBox & { node: DiagramNode; depth: number }> {
181
+ return this.#compounds.map(({ node, depth, x1, y1, x2, y2, bottom }) => ({
182
+ node,
183
+ depth,
184
+ x: x1.value(),
185
+ y: y1.value(),
186
+ width: x2.value() - x1.value(),
187
+ height: bottom.value() - y1.value(),
188
+ }))
189
+ }
190
+
191
+ getPortCenter(step: Step, type: 'source' | 'target') {
192
+ const { column, row } = type === 'source' ? step.from : step.to
193
+ const x = this.actorBox(column).centerX
194
+ const { y } = nonNullable(this.#rows[row])
195
+
196
+ return {
197
+ cx: x.value(),
198
+ cy: y.value() + PORT_HEIGHT / 2 + step.offset,
199
+ height: type === 'source' ? 40 : 24,
200
+ }
201
+ }
202
+
203
+ getViewBounds(): {
204
+ x: number
205
+ y: number
206
+ width: number
207
+ height: number
208
+ } {
209
+ return {
210
+ x: 0,
211
+ y: 0,
212
+ width: this.#viewportRight.value(),
213
+ height: this.#viewportBottom.value(), // Max Y,
214
+ }
215
+ }
216
+
217
+ private actorBox(actor: DiagramNode | string | number): ActorBox {
218
+ if (typeof actor !== 'number') {
219
+ const id = typeof actor === 'string' ? actor : actor.id
220
+ actor = this.#actors.findIndex(a => a.actor.id === id)
221
+ invariant(actor >= 0, `actor ${id} not found`)
222
+ }
223
+ return nonNullable(this.#actors[actor], `actor at index ${actor} not found`)
224
+ }
225
+
226
+ private addActors(actors: NonEmptyArray<DiagramNode>): NonEmptyArray<ActorBox> {
227
+ let accX = 0
228
+ return map(actors, (actor, column) => {
229
+ const x = this.newVar(accX)
230
+ accX += actor.width + ACTOR_GAP
231
+
232
+ const y = this.newVar(0)
233
+
234
+ const actorBox = {
235
+ column,
236
+ actor,
237
+ x,
238
+ y,
239
+ centerX: x.plus(Math.round(actor.width / 2)),
240
+ centerY: y.plus(Math.round(actor.height / 2)),
241
+ width: actor.width,
242
+ height: actor.height,
243
+ right: x.plus(actor.width),
244
+ bottom: y.plus(actor.height),
245
+ minRow: Infinity,
246
+ maxRow: -Infinity,
247
+ }
248
+
249
+ // Create variables for offsets
250
+ const top = this.newVar(0),
251
+ left = this.newVar(0),
252
+ right = this.newVar(0),
253
+ bottom = this.newVar(0)
254
+
255
+ this.put(top, kiwi.Strength.strong).before(y)
256
+ this.put(left, kiwi.Strength.strong).before(x)
257
+ this.put(right, kiwi.Strength.strong).after(actorBox.right)
258
+ this.put(bottom, kiwi.Strength.strong).after(actorBox.bottom)
259
+
260
+ return {
261
+ ...actorBox,
262
+ offset: {
263
+ top,
264
+ left,
265
+ right,
266
+ bottom,
267
+ },
268
+ }
269
+ })
270
+ }
271
+
272
+ private addStep(step: Step): this {
273
+ const source = this.actorBox(step.source)
274
+ const target = this.actorBox(step.target)
275
+
276
+ source.minRow = Math.min(source.minRow, step.from.row)
277
+ source.maxRow = Math.max(source.maxRow, step.from.row)
278
+
279
+ target.minRow = Math.min(target.minRow, step.to.row)
280
+ target.maxRow = Math.max(target.maxRow, step.to.row)
281
+
282
+ const [left, right] = source.column <= target.column
283
+ ? [source, target]
284
+ : [target, source]
285
+
286
+ const width = (step.label?.width ?? 100) + STEP_LABEL_MARGIN
287
+
288
+ if (left !== right) {
289
+ this.constraint(left.centerX.plus(width), '<=', right.centerX)
290
+ } else {
291
+ this.constraint(left.centerX.plus(width), '<=', left.offset.right)
292
+ }
293
+
294
+ let height = step.label?.height ? step.label.height + STEP_LABEL_MARGIN + PORT_HEIGHT / 2 : MIN_ROW_HEIGHT
295
+ height = Math.max(height, MIN_ROW_HEIGHT) + step.offset
296
+
297
+ this.ensureRow(step.from.row, height)
298
+ if (step.isSelfLoop) {
299
+ this.ensureRow(step.to.row, MIN_ROW_HEIGHT)
300
+ }
301
+
302
+ return this
303
+ }
304
+
305
+ private addParallelRect({
306
+ parallelPrefix,
307
+ min,
308
+ max,
309
+ }: ParallelRect) {
310
+ const x1 = this.actorBox(min.column).centerX.minus(30)
311
+ const x2 = this.actorBox(max.column).centerX.plus(30)
312
+ const firstRow = this.#rows[min.row]
313
+ const lastRow = this.#rows[max.row]
314
+ invariant(firstRow && lastRow, `parallel box invalid x1${x1} x2${x2} y1${firstRow} y2${lastRow}`)
315
+
316
+ const y1 = this.newVar(0)
317
+ this.put(y1).before(firstRow.y, 40)
318
+ const y2 = lastRow.bottom
319
+
320
+ // margin top
321
+ const rowBefore = min.row > 0 && this.#rows[min.row - 1]
322
+ if (rowBefore) {
323
+ this.put(y1).after(rowBefore.bottom, 16)
324
+ }
325
+
326
+ const rowAfter = max.row < this.#rows.length - 1 && this.#rows[max.row + 1]
327
+ if (rowAfter) {
328
+ this.put(y2).before(rowAfter.y, 16)
329
+ }
330
+
331
+ this.#parallelBoxes.push({
332
+ parallelPrefix,
333
+ x1,
334
+ y1,
335
+ x2,
336
+ y2,
337
+ })
338
+ }
339
+
340
+ private addCompound(compound: Compound): NonEmptyArray<CompoundRect> {
341
+ const PADDING = 32
342
+ const PADDING_TOP = 40
343
+ const PADDING_TOP_FROM_ACTOR = 52
344
+
345
+ const children = [] as CompoundRect[]
346
+ const nested = compound.nested.flatMap(c => {
347
+ const result = this.addCompound(c)
348
+ // first is the direct child
349
+ children.push(result[0])
350
+ return result
351
+ })
352
+ const depth = Math.max(...nested.map(c => c.depth + 1), 0)
353
+
354
+ const from = this.actorBox(compound.from)
355
+ const to = this.actorBox(compound.to)
356
+
357
+ const x1 = from.offset.left.minus(PADDING)
358
+ from.offset.left = x1 // change offset
359
+
360
+ const x2 = to.offset.right.plus(PADDING)
361
+ to.offset.right = x2 // change offset
362
+
363
+ const bottom = this.newVar(0)
364
+
365
+ const onlyChild = only(children)
366
+
367
+ let y1, y2
368
+ switch (true) {
369
+ case !!onlyChild: {
370
+ y1 = onlyChild.y1.minus(PADDING_TOP)
371
+ y2 = onlyChild.y2.plus(PADDING)
372
+ this.put(bottom).after(onlyChild.bottom, PADDING)
373
+ break
374
+ }
375
+ // Compound with single actor
376
+ case to === from: {
377
+ y1 = this.newVar(0)
378
+ y2 = this.newVar(0)
379
+ this.put(y1).before(from.offset.top, PADDING_TOP_FROM_ACTOR)
380
+ this.put(y2).after(from.offset.bottom, PADDING)
381
+ this.put(bottom).after(y2)
382
+ break
383
+ }
384
+ // Compound nested compound, offset from it
385
+ case children.length > 0: {
386
+ y1 = this.newVar(0)
387
+ y2 = this.newVar(0)
388
+ for (const child of children) {
389
+ this.put(y1).before(child.y1, PADDING)
390
+ this.put(y2).after(child.y2, PADDING)
391
+ this.put(bottom).after(child.bottom, PADDING)
392
+ }
393
+ break
394
+ }
395
+ default: {
396
+ y1 = this.newVar(0)
397
+ y2 = this.newVar(0)
398
+ for (var col = from.column; col <= to.column; col++) {
399
+ const offset = this.actorBox(col).offset
400
+ this.put(y1).before(offset.top, PADDING_TOP_FROM_ACTOR)
401
+ this.put(y2).after(offset.bottom, PADDING)
402
+ }
403
+ this.put(bottom).after(y2)
404
+ break
405
+ }
406
+ }
407
+
408
+ for (var col = from.column; col <= to.column; col++) {
409
+ const offset = this.actorBox(col).offset
410
+ offset.top = y1
411
+ offset.bottom = y2
412
+ }
413
+
414
+ return [
415
+ {
416
+ node: compound.node,
417
+ depth,
418
+ from,
419
+ to,
420
+ x1,
421
+ y1,
422
+ x2,
423
+ y2,
424
+ bottom,
425
+ },
426
+ ...nested,
427
+ ]
428
+ }
429
+
430
+ private ensureRow(row: number, rowHeight: number) {
431
+ while (row >= this.#rows.length) {
432
+ const prevRowY = this.#rows.length > 0 && this.#rows[this.#rows.length - 1]?.bottom ||
433
+ this.#rowsTop.plus(FIRST_STEP_OFFSET)
434
+
435
+ const y = this.newVar(this.#rows.length * MIN_ROW_HEIGHT)
436
+ this.put(y).after(prevRowY)
437
+
438
+ const height = this.newVar(MIN_ROW_HEIGHT)
439
+ this.require(height, '>=', MIN_ROW_HEIGHT)
440
+
441
+ this.#rows.push({
442
+ y,
443
+ height,
444
+ bottom: y.plus(height),
445
+ lastHeight: MIN_ROW_HEIGHT,
446
+ })
447
+ }
448
+ const rowVar = nonNullable(this.#rows[row])
449
+ if (rowHeight > rowVar.lastHeight) {
450
+ rowVar.lastHeight = rowHeight
451
+ this.require(rowVar.height, '>=', rowHeight)
452
+ this.#solver.suggestValue(rowVar.height, rowHeight)
453
+ }
454
+ }
455
+
456
+ private newVar(initialValue?: number) {
457
+ const v = new kiwi.Variable()
458
+ this.#solver.addEditVariable(v, kiwi.Strength.weak)
459
+ if (initialValue) {
460
+ this.#solver.suggestValue(v, initialValue)
461
+ this.constraint(v, '>=', 0, kiwi.Strength.strong)
462
+ }
463
+ return v
464
+ }
465
+
466
+ /**
467
+ * Adds a required constraint:
468
+ * Also adds a weak constraint == if the operator is <= or >=
469
+ */
470
+ private require(
471
+ left: kiwi.Expression | kiwi.Variable,
472
+ op: Operator,
473
+ right: kiwi.Expression | kiwi.Variable | number | undefined = undefined,
474
+ ) {
475
+ this.constraint(left, op, right, kiwi.Strength.required)
476
+ switch (op) {
477
+ case '<=':
478
+ case '>=':
479
+ this.constraint(left, '==', right, kiwi.Strength.weak)
480
+ break
481
+ case '<= 0':
482
+ case '>= 0':
483
+ this.constraint(left, '== 0', undefined, kiwi.Strength.weak)
484
+ break
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Adds a constraint with medium strength by default
490
+ */
491
+ private constraint(
492
+ left: kiwi.Expression | kiwi.Variable,
493
+ op: Operator,
494
+ right: kiwi.Expression | kiwi.Variable | number | undefined = undefined,
495
+ strength = kiwi.Strength.medium,
496
+ ) {
497
+ let operator: kiwi.Operator
498
+ switch (op) {
499
+ case '==':
500
+ operator = kiwi.Operator.Eq
501
+ break
502
+ case '>=':
503
+ operator = kiwi.Operator.Ge
504
+ break
505
+ case '<=':
506
+ operator = kiwi.Operator.Le
507
+ break
508
+ case '== 0': {
509
+ operator = kiwi.Operator.Eq
510
+ right = 0
511
+ break
512
+ }
513
+ case '>= 0': {
514
+ operator = kiwi.Operator.Ge
515
+ right = 0
516
+ break
517
+ }
518
+ case '<= 0': {
519
+ operator = kiwi.Operator.Le
520
+ right = 0
521
+ break
522
+ }
523
+ default:
524
+ nonexhaustive(op)
525
+ }
526
+ this.#solver.addConstraint(new kiwi.Constraint(left, operator, right ?? 0, strength))
527
+ }
528
+
529
+ private put(variable: kiwi.Variable | kiwi.Expression, strength = kiwi.Strength.required) {
530
+ const eqStrength = strength === kiwi.Strength.required ? kiwi.Strength.medium : kiwi.Strength.weak
531
+ return {
532
+ before: (other: kiwi.Variable | kiwi.Expression, gap?: number) => {
533
+ if (gap) {
534
+ other = other.minus(gap)
535
+ }
536
+ this.constraint(variable, '<=', other, strength)
537
+ this.constraint(variable, '==', other, eqStrength)
538
+ },
539
+ after: (other: kiwi.Variable | kiwi.Expression, gap?: number) => {
540
+ if (gap) {
541
+ other = other.plus(gap)
542
+ }
543
+ this.constraint(variable, '>=', other, strength)
544
+ this.constraint(variable, '==', other, eqStrength)
545
+ },
546
+ }
547
+ }
548
+
549
+ // private recalcActorsRect() {
550
+ // this.#actorsRect = this.#columnRects.reduce((acc, rect, index, all) => {
551
+ // if (index === 0) {
552
+ // acc.minX = rect.min.x.value()
553
+ // }
554
+ // if (index === all.length - 1) {
555
+ // acc.maxX = rect.max.x.value()
556
+ // }
557
+ // acc.minY = Math.min(acc.minY, rect.min.y.value())
558
+ // acc.maxY = Math.max(acc.maxY, rect.max.y.value())
559
+ // return acc
560
+ // }, {
561
+ // minX: Infinity,
562
+ // minY: Infinity,
563
+ // maxX: -Infinity,
564
+ // maxY: -Infinity,
565
+ // })
566
+ // }
567
+ }