@likec4/diagram 1.2.2 → 1.3.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 (56) hide show
  1. package/dist/LikeC4Diagram.d.ts +1 -1
  2. package/dist/LikeC4Diagram.props.d.ts +21 -3
  3. package/dist/index.mjs +1335 -1240
  4. package/dist/state/DiagramContext.d.ts +2263 -175
  5. package/dist/state/diagramStore.d.ts +2272 -180
  6. package/dist/state/useDiagramStore.d.ts +2409 -321
  7. package/dist/style.css +123 -92
  8. package/dist/theme.css.d.ts +0 -1
  9. package/dist/ui/OptionsPanel.d.ts +3 -1
  10. package/dist/xyflow/XYFlow.d.ts +2 -1
  11. package/dist/xyflow/XYFlowInner.d.ts +1 -1
  12. package/dist/xyflow/const.d.ts +1 -1
  13. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  14. package/dist/xyflow/edges/RelationshipEdge.d.ts +3 -4
  15. package/dist/xyflow/edges/utils.d.ts +4 -2
  16. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +3 -1
  17. package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
  18. package/dist/xyflow/nodes/element/index.d.ts +1 -1
  19. package/dist/xyflow/types.d.ts +2 -2
  20. package/dist/xyflow/utils.d.ts +3 -0
  21. package/package.json +15 -14
  22. package/src/LikeC4Diagram.css.ts +4 -4
  23. package/src/LikeC4Diagram.props.ts +23 -2
  24. package/src/LikeC4Diagram.tsx +79 -31
  25. package/src/globals.css.ts +0 -1
  26. package/src/mantine/theme.ts +1 -0
  27. package/src/state/DiagramContext.tsx +0 -1
  28. package/src/state/diagramStore.ts +177 -100
  29. package/src/theme.css.ts +0 -1
  30. package/src/ui/DiagramTitlePanel.tsx +3 -3
  31. package/src/ui/DynamicViewWalkthrough.css.ts +3 -2
  32. package/src/ui/DynamicViewWalkthrough.tsx +36 -17
  33. package/src/ui/OptionsPanel.tsx +12 -12
  34. package/src/ui/options/NodeOptions.tsx +16 -19
  35. package/src/xyflow/FitviewOnDiagramChange.tsx +15 -23
  36. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +3 -6
  37. package/src/xyflow/SyncWithDiagram.tsx +21 -2
  38. package/src/xyflow/XYFlow.tsx +20 -23
  39. package/src/xyflow/XYFlowEvents.tsx +26 -8
  40. package/src/xyflow/XYFlowInner.tsx +5 -4
  41. package/src/xyflow/const.ts +1 -1
  42. package/src/xyflow/diagram-to-xyflow.ts +5 -31
  43. package/src/xyflow/edges/RelationshipEdge.tsx +158 -112
  44. package/src/xyflow/edges/edges.css.ts +32 -21
  45. package/src/xyflow/edges/utils.ts +5 -2
  46. package/src/xyflow/hooks/useLayoutConstraints.ts +17 -3
  47. package/src/xyflow/nodes/compound/CompoundNode.css.ts +40 -32
  48. package/src/xyflow/nodes/compound/CompoundNode.tsx +36 -26
  49. package/src/xyflow/nodes/element/ElementNode.tsx +20 -6
  50. package/src/xyflow/nodes/element/element.css.ts +15 -15
  51. package/src/xyflow/nodes/element/index.ts +1 -1
  52. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +7 -3
  53. package/src/xyflow/types.ts +5 -2
  54. package/src/xyflow/utils.ts +48 -0
  55. package/dist/ui/index.d.ts +0 -2
  56. package/src/ui/index.ts +0 -3
@@ -1,5 +1,6 @@
1
1
  import { rem } from '@mantine/core'
2
- import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
2
+ import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
+ import { calc } from '@vanilla-extract/css-utils'
3
4
  import { mantine } from '../../../mantine.css'
4
5
  import { vars } from '../../../theme.css'
5
6
 
@@ -11,12 +12,18 @@ export const container = style({
11
12
  margin: 0
12
13
  })
13
14
 
14
- export const dimmed = style({
15
- filter: vars.filterDimmed,
16
- transition: 'filter 800ms ease-out'
15
+ export const dimmed = style({})
16
+
17
+ globalStyle(`.react-flow__node-compound:has(${dimmed})`, {
18
+ opacity: 0.25,
19
+ transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
20
+ transitionDelay: '200ms',
21
+ filter: 'grayscale(0.85) blur(1px)',
22
+ willChange: 'opacity, filter'
17
23
  })
18
24
 
19
- export const varTransparency = createVar('transparency')
25
+ export const varOpacity = createVar('opacity')
26
+ export const varBorderTransparency = createVar('border-transparency')
20
27
 
21
28
  const outlineColor = fallbackVar(
22
29
  mantine.colors.primaryColors.outline,
@@ -31,7 +38,6 @@ export const compoundBody = style({
31
38
  borderRadius: 6,
32
39
  boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
33
40
  padding: 8,
34
- background: vars.element.fill,
35
41
  transition: 'all 200ms ease-out',
36
42
  selectors: {
37
43
  [`:where(.react-flow__node.selected) &`]: {
@@ -42,39 +48,47 @@ export const compoundBody = style({
42
48
  outline: `3px solid ${outlineColor}`,
43
49
  outlineOffset: rem(1.5)
44
50
  }
51
+ },
52
+ ':before': {
53
+ content: '" "',
54
+ position: 'absolute',
55
+ top: 0,
56
+ left: 0,
57
+ width: '100%',
58
+ height: '100%',
59
+ borderRadius: 6,
60
+ pointerEvents: 'none',
61
+ transition: 'background 175ms ease-out, opacity 175ms ease-out',
62
+ background: vars.element.fill,
63
+ backgroundClip: 'padding-box'
45
64
  }
46
65
  })
47
66
 
48
- const bgTransparency = createVar('bgTransparency')
49
- const bgTransparencyDelta = createVar('bgTransparencyDelta')
67
+ const opacityDeltaOnHover = createVar('opacityDeltaOnHover')
50
68
  export const transparent = style({
51
69
  borderStyle: 'dashed',
52
70
  borderWidth: 3,
53
71
  padding: 6,
54
- borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent min(${bgTransparency}, 30%))`,
55
- background: `color-mix(in srgb , ${vars.element.fill}, transparent ${bgTransparency})`,
72
+ boxShadow: 'none',
73
+ borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent ${fallbackVar(varBorderTransparency, '10%')})`,
56
74
  backgroundClip: 'padding-box',
57
- // transitionTimingFunction: 'ease-in',
58
- // transition: 'all 150ms ease-in-out',
59
75
  vars: {
60
- [bgTransparency]: `calc(${fallbackVar(varTransparency, '80%')} - ${bgTransparencyDelta})`,
61
- [bgTransparencyDelta]: '0%',
76
+ [opacityDeltaOnHover]: '0',
62
77
  '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
63
78
  },
79
+ ':before': {
80
+ transitionDelay: '200ms',
81
+ opacity: calc.add(fallbackVar(varOpacity, '1'), opacityDeltaOnHover)
82
+ },
64
83
  selectors: {
65
84
  [`:where(.react-flow__node.selected) &`]: {
66
- transitionDelay: '50ms',
67
- // borderColor: 'transparent',
68
- // transitionTimingFunction: 'ease-out',
69
85
  vars: {
70
- [bgTransparencyDelta]: '5%'
86
+ [opacityDeltaOnHover]: '.05'
71
87
  }
72
88
  },
73
89
  [`:where([data-hovered]) &`]: {
74
- transitionDelay: '175ms',
75
- // transitionTimingFunction: 'ease-out',
76
90
  vars: {
77
- [bgTransparencyDelta]: '15%'
91
+ [opacityDeltaOnHover]: '.1'
78
92
  }
79
93
  }
80
94
  }
@@ -93,15 +107,10 @@ export const title = style({
93
107
  lineHeight: 1,
94
108
  opacity: 0.75,
95
109
  color: `var(--_compound-title-color,${vars.compound.titleColor})`,
96
- paddingLeft: 12,
97
- selectors: {
98
- [`:where([data-likec4-navigable='true']) &`]: {
99
- paddingLeft: 26
100
- }
101
- // [`:where([data-mantine-color-scheme='light']) &`]: {
102
- // opacity: 1
103
- // }
104
- }
110
+ paddingLeft: 12
111
+ })
112
+ export const titleWithNavigation = style({
113
+ paddingLeft: 26
105
114
  })
106
115
 
107
116
  globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
@@ -114,8 +123,7 @@ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transpa
114
123
  opacity: 1
115
124
  })
116
125
 
117
- const indicatorKeyframes = generateIdentifier('indicator')
118
- globalKeyframes(indicatorKeyframes, {
126
+ const indicatorKeyframes = keyframes({
119
127
  'from': {
120
128
  opacity: 0.6
121
129
  },
@@ -2,13 +2,14 @@ import { Box, Text } from '@mantine/core'
2
2
  import { assignInlineVars } from '@vanilla-extract/dynamic'
3
3
  import { Handle, type NodeProps, Position } from '@xyflow/react'
4
4
  import clsx from 'clsx'
5
+ import { DEV } from 'esm-env'
5
6
  import { deepEqual as eq } from 'fast-equals'
6
7
  import { memo } from 'react'
7
8
  import { clamp } from 'remeda'
9
+ import useTilg from 'tilg'
8
10
  import { useDiagramState } from '../../../state'
9
11
  import type { CompoundXYFlowNode } from '../../types'
10
12
  import { NavigateToBtn } from '../shared/NavigateToBtn'
11
- import { compoundBody, container, indicator, navigateBtn, title } from './CompoundNode.css'
12
13
  import * as css from './CompoundNode.css'
13
14
 
14
15
  type CompoundNodeProps = Pick<
@@ -16,13 +17,12 @@ type CompoundNodeProps = Pick<
16
17
  'id' | 'data' | 'width' | 'height' | 'selected'
17
18
  >
18
19
 
19
- // @ts-ignore
20
20
  const isEqualProps = (prev: CompoundNodeProps, next: CompoundNodeProps) => (
21
21
  prev.id === next.id
22
- && prev.selected === next.selected
23
- && prev.width === next.width
24
- && prev.height === next.height
25
- && eq(prev.data, next.data)
22
+ // && prev.selected === next.selected
23
+ // && eq(prev.width, next.width)
24
+ // && eq(prev.height, next.height)
25
+ && eq(prev.data.element, next.data.element)
26
26
  )
27
27
 
28
28
  export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function CompoundNode({
@@ -31,11 +31,18 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
31
31
  element
32
32
  }
33
33
  }) {
34
- // useTilg()
34
+ DEV && useTilg()
35
35
  const { color, style, depth = 1, ...compound } = element
36
36
  // const w = toDomPrecision(width ?? compound.width)
37
37
  // const h = toDomPrecision(height ?? compound.height)
38
- const opacity = style.opacity ?? 100
38
+ const opacity = clamp((style.opacity ?? 100) / 100, {
39
+ min: 0,
40
+ max: 1
41
+ })
42
+ const borderTransparency = clamp(50 - opacity * 50, {
43
+ min: 0,
44
+ max: 50
45
+ })
39
46
 
40
47
  const { isHovered, isDimmed, hasOnNavigateTo } = useDiagramState(s => ({
41
48
  isHovered: s.hoveredNodeId === id,
@@ -44,30 +51,19 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
44
51
  }))
45
52
  const isnavigable = !!compound.navigateTo && hasOnNavigateTo
46
53
 
47
- // invert opacity to transparency
48
- const transparency = clamp(100 - opacity, {
49
- min: 0,
50
- max: 99
51
- })
52
-
53
54
  return (
54
55
  <Box
55
56
  className={clsx(
56
57
  css.container,
57
58
  'likec4-compound-node',
58
- opacity < 100 && 'likec4-compound-transparent',
59
+ opacity < 1 && 'likec4-compound-transparent',
59
60
  isDimmed && css.dimmed
60
61
  )}
61
62
  mod={{
62
63
  'compound-depth': depth,
63
64
  'likec4-color': color,
64
- 'likec4-shape': compound.shape,
65
- 'likec4-navigable': isnavigable,
66
65
  hovered: isHovered
67
66
  }}
68
- style={assignInlineVars({
69
- [css.varTransparency]: transparency + '%'
70
- })}
71
67
  >
72
68
  <Handle
73
69
  type="target"
@@ -84,14 +80,28 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
84
80
  />
85
81
  </svg>
86
82
  <div
87
- className={clsx(css.compoundBody, opacity < 100 && css.transparent, 'likec4-compound')}
88
- style={{
89
- borderStyle: opacity < 100 ? style.border : undefined
90
- }}
83
+ className={clsx(
84
+ css.compoundBody,
85
+ opacity < 1 && css.transparent,
86
+ 'likec4-compound'
87
+ )}
88
+ style={opacity < 1
89
+ ? {
90
+ ...assignInlineVars({
91
+ [css.varBorderTransparency]: `${borderTransparency}%`,
92
+ [css.varOpacity]: opacity.toFixed(2)
93
+ }),
94
+ borderStyle: style.border ?? 'dashed'
95
+ }
96
+ : undefined}
91
97
  >
92
98
  <Text
93
99
  component="div"
94
- className={clsx(css.title, 'likec4-compound-title')}>
100
+ className={clsx(
101
+ css.title,
102
+ isnavigable && css.titleWithNavigation,
103
+ 'likec4-compound-title'
104
+ )}>
95
105
  {compound.title}
96
106
  </Text>
97
107
  </div>
@@ -103,4 +113,4 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
103
113
  />
104
114
  </Box>
105
115
  )
106
- })
116
+ }, isEqualProps)
@@ -1,8 +1,12 @@
1
1
  import { Text } from '@mantine/core'
2
2
  import { Handle, type NodeProps, Position } from '@xyflow/react'
3
3
  import clsx from 'clsx'
4
- import { motion, type Variants } from 'framer-motion'
4
+ import { DEV } from 'esm-env'
5
+ import { deepEqual as eq } from 'fast-equals'
6
+ import { m, type Variants } from 'framer-motion'
7
+ import { memo } from 'react'
5
8
  import { isNumber } from 'remeda'
9
+ import useTilg from 'tilg'
6
10
  import { useDiagramState } from '../../../state'
7
11
  import type { ElementXYFlowNode } from '../../types'
8
12
  import { toDomPrecision } from '../../utils'
@@ -64,7 +68,16 @@ const variants = {
64
68
  // }
65
69
  } satisfies Variants
66
70
 
67
- export function ElementNode({
71
+ const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
72
+ prev.id === next.id
73
+ && eq(prev.selected ?? false, next.selected ?? false)
74
+ && eq(prev.dragging ?? false, next.dragging ?? false)
75
+ && eq(prev.width ?? 0, next.width ?? 0)
76
+ && eq(prev.height ?? 0, next.height ?? 0)
77
+ && eq(prev.data.element, next.data.element)
78
+ )
79
+
80
+ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
68
81
  id,
69
82
  data: {
70
83
  element
@@ -73,7 +86,8 @@ export function ElementNode({
73
86
  selected = false,
74
87
  width,
75
88
  height
76
- }: ElementNodeProps) {
89
+ }) {
90
+ DEV && useTilg()
77
91
  const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
78
92
  isHovered: s.hoveredNodeId === id,
79
93
  isDimmed: s.dimmed.has(id),
@@ -109,7 +123,7 @@ export function ElementNode({
109
123
  // useTilg(animate, isHovered, isInteractive)
110
124
 
111
125
  return (
112
- <motion.div
126
+ <m.div
113
127
  id={id}
114
128
  className={clsx([
115
129
  css.container,
@@ -225,6 +239,6 @@ export function ElementNode({
225
239
  }
226
240
  {isHovercards && element.links && <ElementLink element={element} />}
227
241
  {isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
228
- </motion.div>
242
+ </m.div>
229
243
  )
230
- }
244
+ }, isEqualProps)
@@ -1,5 +1,5 @@
1
1
  import { rem } from '@mantine/core'
2
- import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
2
+ import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
3
  import { mantine } from '../../../mantine.css'
4
4
  import { vars } from '../../../theme.css'
5
5
 
@@ -8,12 +8,12 @@ const elPadding = createVar('el-padding')
8
8
  // assignVars
9
9
 
10
10
  export const container = style({
11
- position: 'relative',
12
11
  width: '100%',
13
12
  height: '100%',
14
13
  padding: 0,
15
14
  margin: 0,
16
- backfaceVisibility: 'hidden',
15
+ display: 'flex',
16
+ alignItems: 'stretch',
17
17
  vars: {
18
18
  [stokeFillMix]: `color-mix(in srgb, ${vars.element.stroke} 90%, ${vars.element.fill})`
19
19
  }
@@ -21,13 +21,15 @@ export const container = style({
21
21
 
22
22
  export const dimmed = style({})
23
23
 
24
- globalStyle(`.react-flow__node:has(${dimmed})`, {
25
- filter: vars.filterDimmed,
26
- transition: 'filter 800ms ease-out'
24
+ globalStyle(`.react-flow__node-element:has(${dimmed})`, {
25
+ opacity: 0.25,
26
+ transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
27
+ transitionDelay: '200ms',
28
+ filter: 'grayscale(0.85) blur(2px)',
29
+ willChange: 'opacity, filter'
27
30
  })
28
31
 
29
- const indicatorKeyframes = generateIdentifier('indicator')
30
- globalKeyframes(indicatorKeyframes, {
32
+ const indicatorKeyframes = keyframes({
31
33
  'from': {
32
34
  opacity: 0.6
33
35
  },
@@ -63,6 +65,9 @@ export const indicator = style({
63
65
  },
64
66
  ':where([data-likec4-shape="queue"], [data-likec4-shape="cylinder"], [data-likec4-shape="storage"]) &': {
65
67
  strokeWidth: 10
68
+ },
69
+ [`${dimmed} &`]: {
70
+ visibility: 'hidden'
66
71
  }
67
72
  }
68
73
  })
@@ -128,9 +133,7 @@ export const technology = style({
128
133
  })
129
134
 
130
135
  export const element = style({
131
- position: 'relative',
132
- width: '100%',
133
- height: '100%',
136
+ flex: '1',
134
137
  padding: elPadding,
135
138
  display: 'flex',
136
139
  flexDirection: 'column',
@@ -165,10 +168,6 @@ export const elementIcon = style({
165
168
  flex: `0 1 ${iconMaxH}`,
166
169
  maxHeight: iconMaxH,
167
170
  marginTop: -8,
168
- // display: 'flex',
169
- // justifyContent: 'flex-end',
170
- // flexDirection: 'column',
171
- // alignItems: 'center',
172
171
  position: 'relative',
173
172
  userSelect: 'none',
174
173
  pointerEvents: 'none',
@@ -203,6 +202,7 @@ export const cssShapeSvg = style({
203
202
  filter: fallbackVar(filterShadowHovered, filterShadow),
204
203
  transition: 'filter 300ms ease-out',
205
204
  transitionDelay: '0ms',
205
+ zIndex: -1,
206
206
  vars: {
207
207
  [filterShadow]: `
208
208
  drop-shadow(0 2px 1px rgba(0, 0, 0, 0.21))
@@ -1 +1 @@
1
- export { ElementNode } from './ElementNode'
1
+ export { ElementNodeMemo as ElementNode } from './ElementNode'
@@ -1,6 +1,6 @@
1
1
  import { ActionIcon } from '@mantine/core'
2
+ import { IconZoomScan } from '@tabler/icons-react'
2
3
  import clsx from 'clsx'
3
- import { ZoomIn } from '../../../icons'
4
4
  import { useDiagramStoreApi } from '../../../state'
5
5
 
6
6
  export type NavigateToBtnProps = {
@@ -13,7 +13,7 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
13
13
  return (
14
14
  <ActionIcon
15
15
  className={clsx('nodrag nopan', className)}
16
- radius="xl"
16
+ radius="md"
17
17
  onPointerDownCapture={e => e.stopPropagation()}
18
18
  onClick={event => {
19
19
  event.stopPropagation()
@@ -21,7 +21,11 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
21
21
  }}
22
22
  onDoubleClick={event => event.stopPropagation()}
23
23
  >
24
- <ZoomIn />
24
+ <IconZoomScan
25
+ style={{
26
+ width: '75%',
27
+ height: '75%'
28
+ }} />
25
29
  </ActionIcon>
26
30
  )
27
31
  }
@@ -1,4 +1,4 @@
1
- import type { BBox, DiagramEdge, DiagramNode, Fqn, Point } from '@likec4/core'
1
+ import type { BBox, DiagramEdge, DiagramNode, Fqn, Point, ViewID, XYPosition } from '@likec4/core'
2
2
  import type {
3
3
  Edge,
4
4
  EdgeMouseHandler,
@@ -57,7 +57,10 @@ export namespace XYFlowNode {
57
57
 
58
58
  export type RelationshipData = {
59
59
  edge: DiagramEdge
60
- controlPoints: Point[]
60
+ // to replace edges on view change
61
+ // viewId: ViewID
62
+ // if set - edge was changed by user
63
+ controlPoints: XYPosition[] | null
61
64
  headPoint: Point | null
62
65
  tailPoint: Point | null
63
66
  type: 'bezier' | 'poly'
@@ -1,4 +1,8 @@
1
+ import { hasAtLeast, invariant } from '@likec4/core'
2
+ import type { DiagramEdge, NonEmptyArray, Point } from '@likec4/core/types'
1
3
  import type { XYPosition } from '@xyflow/react'
4
+ import { Bezier } from 'bezier-js'
5
+ import { isArray } from 'remeda'
2
6
 
3
7
  export function toDomPrecision(v: number | null) {
4
8
  if (v === null) {
@@ -10,3 +14,47 @@ export function toDomPrecision(v: number | null) {
10
14
  export function distance(a: XYPosition, b: XYPosition) {
11
15
  return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2))
12
16
  }
17
+
18
+ export function bezierControlPoints(diagramEdge: DiagramEdge) {
19
+ let [start, ...bezierPoints] = diagramEdge.points
20
+ invariant(start, 'start should be defined')
21
+ const handles = [
22
+ // start
23
+ ] as XYPosition[]
24
+
25
+ while (hasAtLeast(bezierPoints, 3)) {
26
+ const [cp1, cp2, end, ...rest] = bezierPoints
27
+ const bezier = new Bezier(start[0], start[1], cp1[0], cp1[1], cp2[0], cp2[1], end[0], end[1])
28
+ // TODO: temporary, we need correcly derive catmull-rom from bezier. Actually, from poly-bezier
29
+ const inflections = bezier.inflections()
30
+ if (inflections.length === 0) {
31
+ inflections.push(0.5)
32
+ }
33
+ inflections.forEach(t => {
34
+ const { x, y } = bezier.get(t)
35
+ handles.push({
36
+ x: toDomPrecision(x),
37
+ y: toDomPrecision(y)
38
+ })
39
+ })
40
+ bezierPoints = rest
41
+ start = end
42
+ }
43
+ invariant(bezierPoints.length === 0, 'all points should be consumed')
44
+ if (diagramEdge.dir === 'back' && handles.length > 0) {
45
+ handles.reverse()
46
+ }
47
+
48
+ return handles
49
+ }
50
+
51
+ // If points are within 3px, consider them the same
52
+ const isClose = (a: number, b: number) => {
53
+ return Math.abs(a - b) < 3.1
54
+ }
55
+
56
+ export function isSamePoint(a: XYPosition | Point, b: XYPosition | Point) {
57
+ const [ax, ay] = isArray(a) ? a : [a.x, a.y]
58
+ const [bx, by] = isArray(b) ? b : [b.x, b.y]
59
+ return isClose(ax, bx) && isClose(ay, by)
60
+ }
@@ -1,2 +0,0 @@
1
- export * from './DynamicViewWalkthrough';
2
- export { default as OptionsPanel } from './OptionsPanel';
package/src/ui/index.ts DELETED
@@ -1,3 +0,0 @@
1
- // export { default as Camera } from './Camera'
2
- export * from './DynamicViewWalkthrough'
3
- export { default as OptionsPanel } from './OptionsPanel'