@likec4/diagram 1.7.1 → 1.7.3

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 (69) hide show
  1. package/dist/LikeC4Diagram.d.ts +1 -1
  2. package/dist/LikeC4Diagram.props.d.ts +38 -22
  3. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  4. package/dist/globals.css.d.ts +0 -1
  5. package/dist/hooks/index.d.ts +2 -0
  6. package/dist/{state/useDiagramStore.d.ts → hooks/useDiagramState.d.ts} +2 -3
  7. package/dist/{xyflow/hooks → hooks}/useXYFlow.d.ts +10 -10
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.mjs +755 -401
  10. package/dist/state/DiagramContext.d.ts +74 -30
  11. package/dist/state/diagram-to-xyflow.d.ts +2 -0
  12. package/dist/state/diagramStore.d.ts +71 -22
  13. package/dist/style.css +655 -35
  14. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +3 -0
  15. package/dist/ui/top-left/TopLeftPanel.d.ts +4 -0
  16. package/dist/ui/top-left/_shared.d.ts +5 -0
  17. package/dist/ui/top-left/styles.css.d.ts +3 -0
  18. package/dist/xyflow/XYFlowInner.d.ts +0 -1
  19. package/dist/xyflow/edges/edges.css.d.ts +1 -1
  20. package/dist/xyflow/{hooks/useLayoutConstraints.d.ts → useLayoutConstraints.d.ts} +1 -1
  21. package/dist/xyflow/utils.d.ts +11 -1
  22. package/package.json +6 -6
  23. package/src/LikeC4Diagram.props.ts +66 -32
  24. package/src/LikeC4Diagram.tsx +19 -12
  25. package/src/StaticLikeC4Diagram.tsx +2 -0
  26. package/src/globals.css.ts +0 -2
  27. package/src/hooks/index.ts +2 -0
  28. package/src/{state/useDiagramStore.ts → hooks/useDiagramState.ts} +3 -4
  29. package/src/{xyflow/hooks → hooks}/useXYFlow.ts +1 -1
  30. package/src/index.ts +4 -2
  31. package/src/state/DiagramContext.tsx +15 -7
  32. package/src/state/diagram-to-xyflow.ts +17 -3
  33. package/src/state/diagramStore.ts +73 -38
  34. package/src/ui/DiagramTitlePanel.tsx +1 -1
  35. package/src/ui/DynamicViewWalkthrough.tsx +1 -2
  36. package/src/ui/OptionsPanel.tsx +1 -1
  37. package/src/ui/options/EdgeOptions.tsx +1 -16
  38. package/src/ui/options/NodeOptions.tsx +38 -13
  39. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +131 -0
  40. package/src/ui/top-left/TopLeftPanel.tsx +213 -0
  41. package/src/ui/top-left/_shared.ts +19 -0
  42. package/src/ui/top-left/styles.css.ts +38 -0
  43. package/src/xyflow/FitviewOnDiagramChange.tsx +2 -2
  44. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +87 -59
  45. package/src/xyflow/XYFlow.tsx +7 -8
  46. package/src/xyflow/XYFlowEvents.tsx +39 -19
  47. package/src/xyflow/XYFlowInner.tsx +5 -7
  48. package/src/xyflow/edges/RelationshipEdge.tsx +60 -27
  49. package/src/xyflow/edges/edges.css.ts +29 -25
  50. package/src/xyflow/nodes/compound/CompoundNode.tsx +1 -1
  51. package/src/xyflow/nodes/element/ElementLink.tsx +1 -2
  52. package/src/xyflow/nodes/element/ElementNode.tsx +7 -5
  53. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  54. package/src/xyflow/{hooks/useLayoutConstraints.ts → useLayoutConstraints.ts} +3 -3
  55. package/src/xyflow/utils.ts +25 -1
  56. package/dist/state/hooks.d.ts +0 -4
  57. package/dist/state/index.d.ts +0 -2
  58. package/dist/state/useNavigationHistory.d.ts +0 -5
  59. package/dist/types.d.ts +0 -20
  60. package/dist/ui/BackwardForwardPanel.css.d.ts +0 -1
  61. package/dist/ui/BackwardForwardPanel.d.ts +0 -1
  62. package/dist/xyflow/hooks/index.d.ts +0 -2
  63. package/src/state/hooks.ts +0 -5
  64. package/src/state/index.ts +0 -2
  65. package/src/state/useNavigationHistory.ts +0 -39
  66. package/src/types.ts +0 -22
  67. package/src/ui/BackwardForwardPanel.css.ts +0 -22
  68. package/src/ui/BackwardForwardPanel.tsx +0 -76
  69. package/src/xyflow/hooks/index.ts +0 -3
@@ -16,10 +16,10 @@ import { curveCatmullRomOpen, line as d3line } from 'd3-shape'
16
16
  import { deepEqual, deepEqual as eq } from 'fast-equals'
17
17
  import { memo, type PointerEvent as ReactPointerEvent, useEffect, useRef, useState } from 'react'
18
18
  import { first, hasAtLeast, isArray, isTruthy, last } from 'remeda'
19
- import { useDiagramState, useDiagramStoreApi } from '../../state/useDiagramStore'
19
+ import { useDiagramState, useDiagramStoreApi } from '../../hooks/useDiagramState'
20
+ import { useXYStoreApi } from '../../hooks/useXYFlow'
20
21
  import { ZIndexes } from '../const'
21
22
  import { EdgeMarkers, type EdgeMarkerType } from '../EdgeMarkers'
22
- import { useXYStoreApi } from '../hooks/useXYFlow'
23
23
  import { type RelationshipData, type XYFlowEdge } from '../types'
24
24
  import { bezierControlPoints } from '../utils'
25
25
  import * as edgesCss from './edges.css'
@@ -97,9 +97,9 @@ function bezierPath(bezierSpline: NonEmptyArray<Point>) {
97
97
  return path
98
98
  }
99
99
 
100
- // If points are within 3px, consider them the same
100
+ // If points are within 1px, consider them the same
101
101
  const isSame = (a: number, b: number) => {
102
- return Math.abs(a - b) < 3.1
102
+ return Math.abs(a - b) < 2.5
103
103
  }
104
104
 
105
105
  const isSamePoint = (a: XYPosition | Point, b: XYPosition | Point) => {
@@ -150,12 +150,14 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
150
150
  const [isControlPointDragging, setIsControlPointDragging] = useState(false)
151
151
  const diagramStore = useDiagramStoreApi()
152
152
  const xyflowStore = useXYStoreApi()
153
- const { isActive, isEdgePathEditable, isHovered, isDimmed } = useDiagramState(s => ({
154
- isEdgePathEditable: s.readonly !== true && s.experimentalEdgeEditing === true,
153
+ const { isActive, isEdgePathEditable, isHovered, isDimmed, hasOpenSourceRelation } = useDiagramState(s => ({
154
+ isEdgePathEditable: s.readonly !== true && s.experimentalEdgeEditing === true && s.focusedNodeId === null
155
+ && s.activeDynamicViewStep === null,
155
156
  isActive: s.focusedNodeId === source || s.focusedNodeId === target
156
157
  || (s.activeDynamicViewStep !== null && s.activeDynamicViewStep === data.stepNum),
157
158
  isHovered: s.hoveredEdgeId === id,
158
- isDimmed: s.dimmed.has(id)
159
+ isDimmed: s.dimmed.has(id),
160
+ hasOpenSourceRelation: !!s.onOpenSourceRelation
159
161
  }))
160
162
  const { nodeLookup, edgeLookup } = xyflowStore.getState()
161
163
  const sourceNode = nonNullable(nodeLookup.get(source)!, `source node ${source} not found`)
@@ -200,8 +202,6 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
200
202
  let edgePath: string
201
203
 
202
204
  if (isModified) {
203
- labelX = labelPos.x
204
- labelY = labelPos.y
205
205
  const sourceCenterPos = { x: sourceX, y: sourceY }
206
206
  const targetCenterPos = { x: targetX, y: targetY }
207
207
 
@@ -254,10 +254,15 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
254
254
  })
255
255
  },
256
256
  [labelPos],
257
- 100,
257
+ 50,
258
258
  300
259
259
  )
260
260
 
261
+ if (isModified || isControlPointDragging) {
262
+ labelX = labelPos.x
263
+ labelY = labelPos.y
264
+ }
265
+
261
266
  const onControlPointerDown = (index: number, e: ReactPointerEvent<SVGCircleElement>) => {
262
267
  const { domNode } = xyflowStore.getState()
263
268
  if (!domNode || e.pointerType !== 'mouse') {
@@ -281,30 +286,47 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
281
286
  e.stopPropagation()
282
287
  let hasMoved = false
283
288
  let pointer = { x: e.clientX, y: e.clientY }
289
+
284
290
  const onPointerMove = (e: PointerEvent) => {
285
- if (!isSamePoint(pointer, [e.clientX, e.clientY])) {
291
+ if (hasMoved || !isSamePoint(pointer, [e.clientX, e.clientY])) {
286
292
  setIsControlPointDragging(true)
287
293
  hasMoved = true
288
294
  pointer = { x: e.clientX, y: e.clientY }
289
295
  const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: false })
290
- const newControlPoints = controlPoints.slice()
291
- newControlPoints[index] = {
292
- x: Math.round(x),
293
- y: Math.round(y)
294
- }
295
- xyflow.updateEdgeData(id, { controlPoints: newControlPoints })
296
+ xyflow.updateEdgeData(id, xyedge => {
297
+ const cp = (xyedge.data.controlPoints ?? controlPoints).slice()
298
+ cp[index] = {
299
+ x: Math.round(x),
300
+ y: Math.round(y)
301
+ }
302
+ return {
303
+ controlPoints: cp
304
+ }
305
+ })
296
306
  }
307
+ e.stopPropagation()
297
308
  }
298
- const onPointerUp = () => {
299
- domNode.removeEventListener('pointermove', onPointerMove)
300
- domNode.removeEventListener('pointerup', onPointerUp)
309
+
310
+ const onPointerUp = (e: PointerEvent) => {
311
+ domNode.removeEventListener('pointermove', onPointerMove, {
312
+ capture: true
313
+ })
314
+ if (hasMoved) {
315
+ e.stopPropagation()
316
+ }
301
317
  if (hasMoved || wasCanceled) {
302
318
  diagramStore.getState().scheduleSaveManualLayout()
303
319
  }
304
320
  setIsControlPointDragging(false)
305
321
  }
306
- domNode.addEventListener('pointermove', onPointerMove)
307
- domNode.addEventListener('pointerup', onPointerUp, { once: true })
322
+
323
+ domNode.addEventListener('pointermove', onPointerMove, {
324
+ capture: true
325
+ })
326
+ domNode.addEventListener('pointerup', onPointerUp, {
327
+ once: true,
328
+ capture: true
329
+ })
308
330
  }
309
331
 
310
332
  let markerStartName = toMarker(diagramEdge.tail)
@@ -318,6 +340,9 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
318
340
 
319
341
  const labelZIndex = 1 + (isHovered ? ZIndexes.Element : (edgeLookup.get(id)!.zIndex ?? ZIndexes.Edge))
320
342
 
343
+ const relationId = first(data.edge.relations)
344
+ const showOpenSourceRelation = hasOpenSourceRelation && relationId !== undefined
345
+
321
346
  return (
322
347
  <g
323
348
  className={clsx(
@@ -393,7 +418,12 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
393
418
  isHovered,
394
419
  isActive,
395
420
  isStepEdge
396
- })} />
421
+ })}
422
+ onClick={showOpenSourceRelation && ((e) => {
423
+ e.stopPropagation()
424
+ diagramStore.getState().onOpenSourceRelation?.(relationId)
425
+ })}
426
+ />
397
427
  )}
398
428
  </g>
399
429
  )
@@ -412,9 +442,10 @@ type EdgeLabelProps = {
412
442
  isHovered: boolean
413
443
  isActive: boolean
414
444
  zIndex: number
445
+ onClick: false | ((e: ReactPointerEvent<HTMLDivElement>) => void)
415
446
  }
416
447
 
417
- const EdgeLabel = memo<EdgeLabelProps>(({
448
+ const EdgeLabel = ({
418
449
  isDimmed,
419
450
  color,
420
451
  isModified,
@@ -426,8 +457,9 @@ const EdgeLabel = memo<EdgeLabelProps>(({
426
457
  stepNum,
427
458
  isHovered,
428
459
  isActive,
429
- zIndex
430
- }) => {
460
+ zIndex,
461
+ onClick
462
+ }: EdgeLabelProps) => {
431
463
  return (
432
464
  <EdgeLabelRenderer>
433
465
  <Box
@@ -455,6 +487,7 @@ const EdgeLabel = memo<EdgeLabelProps>(({
455
487
  'data-edge-hovered': isHovered,
456
488
  'data-edge-active': isActive
457
489
  }}
490
+ onClick={onClick || undefined}
458
491
  >
459
492
  {stepNum !== null && (
460
493
  <Box className={edgesCss.stepEdgeNumber}>
@@ -469,4 +502,4 @@ const EdgeLabel = memo<EdgeLabelProps>(({
469
502
  </Box>
470
503
  </EdgeLabelRenderer>
471
504
  )
472
- }, deepEqual)
505
+ }
@@ -10,7 +10,7 @@ export const container = style({
10
10
  [mixColor]: `black`,
11
11
  [xyvars.edge.stroke]: vars.relation.lineColor,
12
12
  [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
13
- [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 20%)`,
13
+ [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 40%)`,
14
14
  [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 40%)`,
15
15
  [xyvars.edge.strokeWidth]: '3'
16
16
  }
@@ -159,6 +159,24 @@ export const cssEdgePath = style({
159
159
  }
160
160
  })
161
161
 
162
+ const labelBorderRadius = 2
163
+ export const stepEdgeNumber = style({
164
+ flex: '0 0 auto',
165
+ fontWeight: 600,
166
+ fontSize: rem(14),
167
+ lineHeight: 1,
168
+ padding: '5px 5px',
169
+ textAlign: 'center',
170
+ minWidth: 22,
171
+ borderTopLeftRadius: labelBorderRadius,
172
+ borderBottomLeftRadius: labelBorderRadius,
173
+ backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, ${mixColor} 5%)`,
174
+ fontVariantNumeric: 'tabular-nums'
175
+ })
176
+ globalStyle(`:where([data-mantine-color-scheme="dark"]) :where([data-likec4-color="gray"]) ${stepEdgeNumber}`, {
177
+ backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, ${mixColor} 15%)`
178
+ })
179
+
162
180
  export const varLabelX = createVar('label-x')
163
181
  export const varLabelY = createVar('label-y')
164
182
 
@@ -169,6 +187,7 @@ export const edgeLabel = style({
169
187
  left: 0,
170
188
  padding: '2px 4px 4px 4px',
171
189
  fontFamily: vars.likec4.font,
190
+ display: 'flex',
172
191
  position: 'absolute',
173
192
  pointerEvents: 'all',
174
193
  cursor: 'pointer',
@@ -177,7 +196,7 @@ export const edgeLabel = style({
177
196
  mixBlendMode: 'plus-lighter',
178
197
  color: xyvars.edge.labelColor,
179
198
  backgroundColor: xyvars.edge.labelBgColor,
180
- borderRadius: 3,
199
+ borderRadius: labelBorderRadius,
181
200
  transform: varTranslate,
182
201
  vars: {
183
202
  [varTranslate]: `translate(${fallbackVar(varLabelX, '-50%')}, ${fallbackVar(varLabelY, '-50%')})`
@@ -187,6 +206,10 @@ export const edgeLabel = style({
187
206
  transition: 'all 140ms ease-in-out',
188
207
  transform: `${varTranslate} scale(1.12)`
189
208
  },
209
+ [`&:has(${stepEdgeNumber})`]: {
210
+ padding: 0,
211
+ gap: 2
212
+ },
190
213
  [`&:is(${dimmed})`]: {
191
214
  opacity: 0.3,
192
215
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
@@ -197,32 +220,13 @@ export const edgeLabel = style({
197
220
  }
198
221
  })
199
222
 
200
- export const stepEdgeNumber = style({
201
- position: 'absolute',
202
- top: -2,
203
- left: -2,
204
- fontWeight: 500,
205
- fontSize: rem(16),
206
- lineHeight: 0.9,
207
- padding: '4px 6px',
208
- borderRadius: 99999,
209
- textAlign: 'center',
210
- minWidth: 22,
211
- backgroundColor: xyvars.edge.labelBgColor,
212
- transition: 'all 120ms ease-out',
213
- transform: 'translateX(-100%)',
214
- fontVariantNumeric: 'tabular-nums',
215
- selectors: {
216
- ':where([data-edge-hovered="true"],[data-edge-active="true"]) &': {
217
- fontWeight: 600,
218
- backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 5%)`
219
- }
220
- }
221
- })
222
-
223
223
  export const edgeLabelText = style({
224
224
  textAlign: 'left',
225
225
  whiteSpaceCollapse: 'preserve-breaks',
226
226
  fontSize: rem(14),
227
227
  lineHeight: 1.185
228
228
  })
229
+
230
+ globalStyle(`${edgeLabel}:has(${stepEdgeNumber}) ${edgeLabelText}`, {
231
+ padding: '2px 5px 4px 2px'
232
+ })
@@ -5,7 +5,7 @@ import clsx from 'clsx'
5
5
  import { deepEqual as eq } from 'fast-equals'
6
6
  import { memo } from 'react'
7
7
  import { clamp } from 'remeda'
8
- import { useDiagramState } from '../../../state/useDiagramStore'
8
+ import { useDiagramState } from '../../../hooks/useDiagramState'
9
9
  import type { CompoundXYFlowNode } from '../../types'
10
10
  import { NavigateToBtn } from '../shared/NavigateToBtn'
11
11
  import * as css from './CompoundNode.css'
@@ -7,7 +7,6 @@ import {
7
7
  Group,
8
8
  HoverCard,
9
9
  HoverCardDropdown,
10
- type HoverCardProps,
11
10
  HoverCardTarget,
12
11
  Stack,
13
12
  UnstyledButton
@@ -16,8 +15,8 @@ import { IconLink } from '@tabler/icons-react'
16
15
  import clsx from 'clsx'
17
16
  import { useId } from 'react'
18
17
  import { clamp } from 'remeda'
18
+ import { useDiagramState } from '../../../hooks/useDiagramState'
19
19
  import { type DiagramState } from '../../../state/diagramStore'
20
- import { useDiagramState } from '../../../state/useDiagramStore'
21
20
  import type { XYFlowNode } from '../../types'
22
21
  import { elementLink, trigger } from './ElementLink.css'
23
22
 
@@ -1,12 +1,13 @@
1
1
  import type { DiagramNode } from '@likec4/core/types'
2
- import { Text as MantineText } from '@mantine/core'
3
- import { Handle, type NodeProps, Position } from '@xyflow/react'
2
+ import { ActionIcon, Text as MantineText, Tooltip } from '@mantine/core'
3
+ import { IconFileSymlink } from '@tabler/icons-react'
4
+ import { Handle, type NodeProps, NodeToolbar, Position } from '@xyflow/react'
4
5
  import clsx from 'clsx'
5
6
  import { deepEqual as eq } from 'fast-equals'
6
7
  import { m, type Variants } from 'framer-motion'
7
8
  import { memo } from 'react'
8
9
  import { isNumber, isTruthy } from 'remeda'
9
- import { type DiagramStoreApi, useDiagramState, useDiagramStoreApi } from '../../../state/useDiagramStore'
10
+ import { type DiagramStoreApi, useDiagramState, useDiagramStoreApi } from '../../../hooks/useDiagramState'
10
11
  import type { ElementXYFlowNode } from '../../types'
11
12
  import { toDomPrecision } from '../../utils'
12
13
  import { NavigateToBtn } from '../shared/NavigateToBtn'
@@ -89,12 +90,13 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
89
90
  height
90
91
  }) {
91
92
  const store = useDiagramStoreApi()
92
- const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
93
+ const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive, hasGoToSource } = useDiagramState(s => ({
93
94
  isHovered: s.hoveredNodeId === id,
94
95
  isDimmed: s.dimmed.has(id),
95
96
  isInteractive: s.nodesDraggable || s.nodesSelectable || !!s.onNavigateTo,
96
97
  isHovercards: s.showElementLinks,
97
- hasOnNavigateTo: !!s.onNavigateTo
98
+ hasOnNavigateTo: !!s.onNavigateTo,
99
+ hasGoToSource: !!s.onOpenSourceElement
98
100
  }))
99
101
  const isNavigable = hasOnNavigateTo && !!element.navigateTo
100
102
 
@@ -1,7 +1,7 @@
1
1
  import { ActionIcon } from '@mantine/core'
2
2
  import { IconZoomScan } from '@tabler/icons-react'
3
3
  import clsx from 'clsx'
4
- import { useDiagramStoreApi } from '../../../state/useDiagramStore'
4
+ import { useDiagramStoreApi } from '../../../hooks/useDiagramState'
5
5
 
6
6
  export type NavigateToBtnProps = {
7
7
  xynodeId: string
@@ -4,9 +4,9 @@ import type { InternalNode, NodeChange, ReactFlowProps, XYPosition } from '@xyfl
4
4
  import { getNodeDimensions } from '@xyflow/system'
5
5
  import { useMemo, useRef } from 'react'
6
6
  import { isNullish } from 'remeda'
7
- import { useDiagramStoreApi } from '../../state/useDiagramStore'
8
- import type { XYFlowNode } from '../types'
9
- import { type XYStoreApi } from './useXYFlow'
7
+ import { useDiagramStoreApi } from '../hooks'
8
+ import { type XYStoreApi } from '../hooks/useXYFlow'
9
+ import type { XYFlowNode } from './types'
10
10
 
11
11
  abstract class Rect {
12
12
  id!: string
@@ -1,8 +1,9 @@
1
1
  import { hasAtLeast, invariant } from '@likec4/core'
2
2
  import type { DiagramEdge, Point } from '@likec4/core/types'
3
- import type { XYPosition } from '@xyflow/react'
3
+ import type { Rect, XYPosition } from '@xyflow/react'
4
4
  import { Bezier } from 'bezier-js'
5
5
  import { isArray } from 'remeda'
6
+ import type { InternalXYFlowNode } from './types'
6
7
 
7
8
  export function toDomPrecision(v: number | null) {
8
9
  if (v === null) {
@@ -15,6 +16,29 @@ export function distance(a: XYPosition, b: XYPosition) {
15
16
  return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2))
16
17
  }
17
18
 
19
+ export const nodeToRect = (nd: InternalXYFlowNode): Rect => ({
20
+ x: nd.internals.positionAbsolute.x,
21
+ y: nd.internals.positionAbsolute.y,
22
+ width: nd.width ?? nd.data.element.width,
23
+ height: nd.height ?? nd.data.element.height
24
+ })
25
+
26
+ /**
27
+ * Checks if a rectangle is completely inside another rectangle.
28
+ *
29
+ * @param test - The rectangle to test.
30
+ * @param target - The target rectangle.
31
+ * @returns `true` if the `test` rectangle is completely inside the `target` rectangle, otherwise `false`.
32
+ */
33
+ export const isInside = (test: Rect, target: Rect) => {
34
+ return (
35
+ test.x >= target.x
36
+ && test.y >= target.y
37
+ && test.x + test.width <= target.x + target.width
38
+ && test.y + test.height <= target.y + target.height
39
+ )
40
+ }
41
+
18
42
  export function bezierControlPoints(diagramEdge: DiagramEdge) {
19
43
  let [start, ...bezierPoints] = diagramEdge.points
20
44
  invariant(start, 'start should be defined')
@@ -1,4 +0,0 @@
1
- export { useDiagramState, useDiagramStoreApi } from './useDiagramStore';
2
- export { useNavigationHistory } from './useNavigationHistory';
3
- export type * from './diagramStore';
4
- export type * from './useDiagramStore';
@@ -1,2 +0,0 @@
1
- export type * from './diagramStore';
2
- export type * from './useDiagramStore';
@@ -1,5 +0,0 @@
1
- export declare function useNavigationHistory(): {
2
- hasBack: boolean;
3
- hasForward: boolean;
4
- ops: import('@mantine/hooks').UseStateHistoryHandlers<import('@likec4/core').ViewID>;
5
- };
package/dist/types.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import { Fqn, RelationID, ViewID } from '@likec4/core';
2
- import { Simplify } from 'type-fest';
3
-
4
- type TaggedUnion<TagKey extends string, UnionMembers extends Record<string, Record<string, unknown>>> = {
5
- [Name in keyof UnionMembers]: Simplify<{
6
- [Key in TagKey]: Name;
7
- } & UnionMembers[Name]>;
8
- }[keyof UnionMembers];
9
- export type DiagramEditorCommand = TaggedUnion<'type', {
10
- showElement: {
11
- element: Fqn;
12
- };
13
- showRelation: {
14
- relation: RelationID;
15
- };
16
- showView: {
17
- view: ViewID;
18
- };
19
- }>;
20
- export {};
@@ -1 +0,0 @@
1
- export declare const panel: string;
@@ -1 +0,0 @@
1
- export declare function BackwardForwardPanel(): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +0,0 @@
1
- export { useXYFlow, useXYNodesData, useXYStore, useXYStoreApi } from './useXYFlow';
2
- export type * from './useXYFlow';
@@ -1,5 +0,0 @@
1
- export { useDiagramState, useDiagramStoreApi } from './useDiagramStore'
2
- export { useNavigationHistory } from './useNavigationHistory'
3
-
4
- export type * from './diagramStore'
5
- export type * from './useDiagramStore'
@@ -1,2 +0,0 @@
1
- export type * from './diagramStore'
2
- export type * from './useDiagramStore'
@@ -1,39 +0,0 @@
1
- import { useStateHistory } from '@mantine/hooks'
2
- import { useMemo } from 'react'
3
- import { useUpdateEffect } from '../hooks/useUpdateEffect'
4
- import { useDiagramState, useDiagramStoreApi } from './useDiagramStore'
5
-
6
- export function useNavigationHistory() {
7
- const store = useDiagramStoreApi()
8
- const viewId = useDiagramState(s => s.view.id)
9
-
10
- const [historyViewId, historyOps, {
11
- history,
12
- current: historyIndex
13
- }] = useStateHistory(viewId)
14
-
15
- const hasBack = historyIndex > 0
16
- const hasForward = historyIndex < history.length - 1
17
-
18
- useUpdateEffect(() => {
19
- if (viewId !== historyViewId) {
20
- historyOps.set(viewId)
21
- }
22
- }, [viewId])
23
-
24
- useUpdateEffect(() => {
25
- if (viewId !== historyViewId) {
26
- store.getState().onNavigateTo?.(historyViewId)
27
- }
28
- }, [historyViewId])
29
-
30
- return useMemo(() => ({
31
- hasBack,
32
- hasForward,
33
- ops: historyOps
34
- }), [
35
- hasBack,
36
- hasForward,
37
- historyOps
38
- ])
39
- }
package/src/types.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { Fqn, RelationID, ViewID } from '@likec4/core'
2
- import type { Simplify } from 'type-fest'
3
-
4
- type TaggedUnion<
5
- TagKey extends string,
6
- UnionMembers extends Record<string, Record<string, unknown>>
7
- > = {
8
- [Name in keyof UnionMembers]: Simplify<{ [Key in TagKey]: Name } & UnionMembers[Name]>
9
- }[keyof UnionMembers]
10
-
11
- export type DiagramEditorCommand = TaggedUnion<'type', {
12
- // Sources
13
- showElement: {
14
- element: Fqn
15
- }
16
- showRelation: {
17
- relation: RelationID
18
- }
19
- showView: {
20
- view: ViewID
21
- }
22
- }>
@@ -1,22 +0,0 @@
1
- import { fallbackVar, globalStyle, style } from '@vanilla-extract/css'
2
- import { mantine } from '../mantine.css'
3
- import { vars } from '../theme.css'
4
-
5
- export const panel = style({
6
- top: fallbackVar(vars.navigationPanel.top, '0.8rem'),
7
- left: fallbackVar(vars.navigationPanel.left, '1rem'),
8
- margin: 0
9
- })
10
-
11
- globalStyle(`${panel} .mantine-ActionIcon-root`, {
12
- vars: {
13
- ['--ai-size']: 'var(--ai-size-lg)'
14
- }
15
- // '@media': {
16
- // [mantine.largerThan('lg')]: {
17
- // vars: {
18
- // ['--ai-size']: 'var(--ai-size-xl)'
19
- // }
20
- // }
21
- // }
22
- })
@@ -1,76 +0,0 @@
1
- import { ActionIcon, Group } from '@mantine/core'
2
- import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'
3
- import clsx from 'clsx'
4
- import { AnimatePresence, m } from 'framer-motion'
5
- import { type DiagramState, useDiagramState } from '../state/hooks'
6
- import * as css from './BackwardForwardPanel.css'
7
-
8
- const historySelector = (s: DiagramState) => ({
9
- hasStepBack: s.navigationHistoryIndex > 0,
10
- hasStepForward: s.navigationHistoryIndex < s.navigationHistory.length - 1,
11
- goBack: s.goBack,
12
- goForward: s.goForward
13
- })
14
-
15
- export function BackwardForwardPanel() {
16
- const {
17
- hasStepBack,
18
- hasStepForward,
19
- goBack,
20
- goForward
21
- } = useDiagramState(historySelector)
22
- return (
23
- <Group
24
- className={clsx(
25
- 'react-flow__panel',
26
- css.panel,
27
- 'likec4-navigation-panel'
28
- )}
29
- gap={'xs'}>
30
- <AnimatePresence>
31
- {hasStepBack && (
32
- <m.div
33
- layout
34
- initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
35
- animate={{ opacity: 1, transform: 'translateX(0)' }}
36
- exit={{
37
- opacity: 0.05,
38
- transform: 'translateX(-10px)'
39
- }}
40
- key={'back'}>
41
- <ActionIcon
42
- variant="light"
43
- color="gray"
44
- onClick={e => {
45
- e.stopPropagation()
46
- goBack()
47
- }}>
48
- <IconChevronLeft />
49
- </ActionIcon>
50
- </m.div>
51
- )}
52
- {hasStepForward && (
53
- <m.div
54
- layout
55
- initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
56
- animate={{ opacity: 1, transform: 'translateX(0)' }}
57
- exit={{
58
- opacity: 0,
59
- transform: 'translateX(10px)'
60
- }}
61
- key={'forward'}>
62
- <ActionIcon
63
- variant="light"
64
- color="gray"
65
- onClick={e => {
66
- e.stopPropagation()
67
- goForward()
68
- }}>
69
- <IconChevronRight />
70
- </ActionIcon>
71
- </m.div>
72
- )}
73
- </AnimatePresence>
74
- </Group>
75
- )
76
- }
@@ -1,3 +0,0 @@
1
- export { useXYFlow, useXYNodesData, useXYStore, useXYStoreApi } from './useXYFlow'
2
-
3
- export type * from './useXYFlow'