@likec4/diagram 1.7.2 → 1.7.4

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 (70) 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/hooks/index.d.ts +2 -0
  5. package/dist/{state/useDiagramStore.d.ts → hooks/useDiagramState.d.ts} +2 -3
  6. package/dist/{xyflow/hooks → hooks}/useXYFlow.d.ts +10 -10
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.mjs +818 -448
  9. package/dist/state/DiagramContext.d.ts +74 -30
  10. package/dist/state/diagram-to-xyflow.d.ts +2 -0
  11. package/dist/state/diagramStore.d.ts +71 -22
  12. package/dist/style.css +58 -37
  13. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +3 -0
  14. package/dist/ui/top-left/TopLeftPanel.d.ts +4 -0
  15. package/dist/ui/top-left/_shared.d.ts +5 -0
  16. package/dist/ui/top-left/styles.css.d.ts +3 -0
  17. package/dist/xyflow/XYFlowInner.d.ts +0 -1
  18. package/dist/xyflow/const.d.ts +1 -0
  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/hooks/index.ts +2 -0
  27. package/src/{state/useDiagramStore.ts → hooks/useDiagramState.ts} +3 -4
  28. package/src/{xyflow/hooks → hooks}/useXYFlow.ts +1 -1
  29. package/src/index.ts +2 -2
  30. package/src/state/DiagramContext.tsx +15 -7
  31. package/src/state/diagram-to-xyflow.ts +17 -3
  32. package/src/state/diagramStore.ts +73 -38
  33. package/src/ui/DiagramTitlePanel.css.ts +2 -2
  34. package/src/ui/DiagramTitlePanel.tsx +62 -44
  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 +11 -10
  46. package/src/xyflow/XYFlowEvents.tsx +44 -18
  47. package/src/xyflow/XYFlowInner.tsx +5 -7
  48. package/src/xyflow/const.ts +1 -0
  49. package/src/xyflow/edges/RelationshipEdge.tsx +60 -27
  50. package/src/xyflow/edges/edges.css.ts +29 -25
  51. package/src/xyflow/nodes/compound/CompoundNode.tsx +1 -1
  52. package/src/xyflow/nodes/element/ElementLink.tsx +1 -2
  53. package/src/xyflow/nodes/element/ElementNode.tsx +7 -5
  54. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  55. package/src/xyflow/{hooks/useLayoutConstraints.ts → useLayoutConstraints.ts} +3 -3
  56. package/src/xyflow/utils.ts +25 -1
  57. package/dist/state/hooks.d.ts +0 -4
  58. package/dist/state/index.d.ts +0 -2
  59. package/dist/state/useNavigationHistory.d.ts +0 -5
  60. package/dist/types.d.ts +0 -20
  61. package/dist/ui/BackwardForwardPanel.css.d.ts +0 -1
  62. package/dist/ui/BackwardForwardPanel.d.ts +0 -1
  63. package/dist/xyflow/hooks/index.d.ts +0 -2
  64. package/src/state/hooks.ts +0 -5
  65. package/src/state/index.ts +0 -2
  66. package/src/state/useNavigationHistory.ts +0 -39
  67. package/src/types.ts +0 -22
  68. package/src/ui/BackwardForwardPanel.css.ts +0 -22
  69. package/src/ui/BackwardForwardPanel.tsx +0 -76
  70. package/src/xyflow/hooks/index.ts +0 -3
@@ -1,20 +1,73 @@
1
1
  import type {
2
- BorderStyle,
3
2
  DiagramEdge,
4
3
  DiagramNode,
5
4
  DiagramView,
6
- ElementShape,
7
5
  Fqn,
8
6
  NonEmptyArray,
9
- ThemeColor,
10
- ViewID,
11
- ViewManualLayout
7
+ RelationID,
8
+ ViewChange,
9
+ ViewID
12
10
  } from '@likec4/core'
13
11
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'
14
- import type { DiagramEditorCommand } from './types'
15
12
  import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
16
13
  import type { XYBackground } from './xyflow/XYFlowBackground'
17
14
 
15
+ type EqualOperator<V> = {
16
+ eq: V
17
+ neq?: never
18
+ } | {
19
+ eq?: never
20
+ neq: V
21
+ }
22
+
23
+ type AllNever = {
24
+ not?: never
25
+ and?: never
26
+ or?: never
27
+ tag?: never
28
+ kind?: never
29
+ }
30
+
31
+ type TagKindOperator<Tag, Kind> =
32
+ & Omit<AllNever, 'tag' | 'kind'>
33
+ & ({
34
+ tag: EqualOperator<Tag>
35
+ kind?: never
36
+ } | {
37
+ tag?: never
38
+ kind: EqualOperator<Kind>
39
+ })
40
+
41
+ type NotOperator<Tag, Kind> = Omit<AllNever, 'not'> & {
42
+ not: TagKindOperator<Tag, Kind> | AndOperator<Tag, Kind> | OrOperator<Tag, Kind>
43
+ }
44
+
45
+ type AndOperator<Tag, Kind> = Omit<AllNever, 'and'> & {
46
+ and: NonEmptyArray<
47
+ | TagKindOperator<Tag, Kind>
48
+ | OrOperator<Tag, Kind>
49
+ | NotOperator<Tag, Kind>
50
+ >
51
+ }
52
+
53
+ type OrOperator<Tag, Kind> = Omit<AllNever, 'or'> & {
54
+ or: NonEmptyArray<
55
+ | TagKindOperator<Tag, Kind>
56
+ | AndOperator<Tag, Kind>
57
+ | NotOperator<Tag, Kind>
58
+ >
59
+ }
60
+
61
+ // export const isOrOperator = (operator: WhereOperator): operator is OrOperator<WhereOperator> => {
62
+ // return 'or' in operator
63
+ // }
64
+
65
+ export type WhereOperator<Tag extends string, Kind extends string> =
66
+ | TagKindOperator<Tag, Kind>
67
+ | NotOperator<Tag, Kind>
68
+ | AndOperator<Tag, Kind>
69
+ | OrOperator<Tag, Kind>
70
+
18
71
  export type DiagramNodeWithNavigate = Omit<DiagramNode, 'navigateTo'> & {
19
72
  navigateTo: ViewID
20
73
  }
@@ -53,37 +106,13 @@ export type OnEdgeClick = (
53
106
  }
54
107
  ) => void
55
108
 
56
- export type OnEditorCommand = (
57
- cmd: DiagramEditorCommand
58
- ) => void
59
-
60
109
  /**
61
110
  * On pane/canvas click (not on any node or edge)
62
111
  */
63
112
  export type OnCanvasClick = (event: ReactMouseEvent) => void
64
113
 
65
- export namespace Changes {
66
- export interface ChangeElementStyle {
67
- op: 'change-element-style'
68
- style: {
69
- border?: BorderStyle
70
- opacity?: number
71
- shape?: ElementShape
72
- color?: ThemeColor
73
- }
74
- targets: NonEmptyArray<Fqn>
75
- }
76
-
77
- export interface SaveManualLayout {
78
- op: 'save-manual-layout'
79
- layout: ViewManualLayout
80
- }
81
- }
82
-
83
- export type Change = Changes.ChangeElementStyle | Changes.SaveManualLayout
84
-
85
114
  export type ChangeEvent = {
86
- change: Change
115
+ change: ViewChange
87
116
  }
88
117
  export type OnChange = {
89
118
  (event: ChangeEvent): void
@@ -197,6 +226,8 @@ export interface LikeC4DiagramProperties {
197
226
  * By default, if icon is http:// or https://, it will be rendered as an image
198
227
  */
199
228
  renderIcon?: ElementIconRenderer | undefined
229
+
230
+ where?: WhereOperator<string, string> | undefined
200
231
  }
201
232
 
202
233
  export interface LikeC4DiagramEventHandlers {
@@ -210,5 +241,8 @@ export interface LikeC4DiagramEventHandlers {
210
241
  onCanvasClick?: OnCanvasClick | null | undefined
211
242
  onCanvasDblClick?: OnCanvasClick | null | undefined
212
243
 
213
- onEditorCommand?: OnEditorCommand | null | undefined
244
+ onBurgerMenuClick?: null | undefined | (() => void)
245
+ onOpenSourceView?: null | undefined | (() => void)
246
+ onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void)
247
+ onOpenSourceRelation?: null | undefined | ((id: RelationID) => void)
214
248
  }
@@ -4,11 +4,11 @@ import { shallowEqual } from 'fast-equals'
4
4
  import { domAnimation, LazyMotion } from 'framer-motion'
5
5
  import { memo, useRef } from 'react'
6
6
  import { rootClassName } from './globals.css'
7
+ import { useDiagramState } from './hooks/useDiagramState'
7
8
  import * as css from './LikeC4Diagram.css'
8
9
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
9
10
  import { EnsureMantine } from './mantine/EnsureMantine'
10
11
  import { DiagramContextProvider } from './state/DiagramContext'
11
- import { useDiagramState } from './state/useDiagramStore'
12
12
  import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
13
13
  import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
14
14
  import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
@@ -30,9 +30,8 @@ export function LikeC4Diagram({
30
30
  controls = false,
31
31
  showElementLinks = true,
32
32
  showDiagramTitle = true,
33
- showNavigationButtons = false,
34
33
  enableDynamicViewWalkthrough = false,
35
- enableFocusMode = readonly,
34
+ enableFocusMode = false,
36
35
  initialWidth,
37
36
  initialHeight,
38
37
  keepAspectRatio = false,
@@ -46,8 +45,13 @@ export function LikeC4Diagram({
46
45
  onNavigateTo,
47
46
  onNodeClick,
48
47
  onNodeContextMenu,
49
- onEditorCommand,
50
- renderIcon
48
+ onOpenSourceElement,
49
+ onOpenSourceView,
50
+ onOpenSourceRelation,
51
+ onBurgerMenuClick,
52
+ renderIcon,
53
+ where,
54
+ showNavigationButtons = !!onNavigateTo
51
55
  }: LikeC4DiagramProps) {
52
56
  const initialRef = useRef({
53
57
  defaultNodes: [] as XYFlowNode[],
@@ -55,7 +59,9 @@ export function LikeC4Diagram({
55
59
  initialWidth: initialWidth ?? view.bounds.width,
56
60
  initialHeight: initialHeight ?? view.bounds.height
57
61
  })
58
- // useLogger('LikeC4Diagram', [{view}])
62
+ if (readonly !== true && !!where) {
63
+ console.warn('where filter is only supported in readonly mode')
64
+ }
59
65
  return (
60
66
  <EnsureMantine>
61
67
  <LazyMotion features={domAnimation} strict>
@@ -73,11 +79,13 @@ export function LikeC4Diagram({
73
79
  fitViewEnabled={fitView}
74
80
  fitViewPadding={fitViewPadding}
75
81
  showElementLinks={showElementLinks}
82
+ showNavigationButtons={showNavigationButtons}
76
83
  nodesDraggable={nodesDraggable}
77
84
  nodesSelectable={nodesSelectable}
78
85
  experimentalEdgeEditing={experimentalEdgeEditing}
79
86
  enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
80
87
  enableFocusMode={enableFocusMode}
88
+ whereFilter={where ?? null}
81
89
  renderIcon={renderIcon ?? null}
82
90
  onCanvasClick={onCanvasClick ?? null}
83
91
  onCanvasContextMenu={onCanvasContextMenu ?? null}
@@ -88,13 +96,15 @@ export function LikeC4Diagram({
88
96
  onChange={onChange ?? null}
89
97
  onNavigateTo={onNavigateTo ?? null}
90
98
  onCanvasDblClick={onCanvasDblClick ?? null}
91
- onEditorCommand={onEditorCommand ?? null}
99
+ onOpenSourceView={onOpenSourceView ?? null}
100
+ onOpenSourceElement={onOpenSourceElement ?? null}
101
+ onOpenSourceRelation={onOpenSourceRelation ?? null}
102
+ onBurgerMenuClick={onBurgerMenuClick ?? null}
92
103
  >
93
104
  <LikeC4DiagramInnerMemo
94
105
  controls={controls}
95
106
  background={background}
96
107
  showDiagramTitle={showDiagramTitle}
97
- showNavigationButtons={showNavigationButtons}
98
108
  />
99
109
  </DiagramContextProvider>
100
110
  </XYFlowProvider>
@@ -107,13 +117,11 @@ type LikeC4DiagramInnerProps = {
107
117
  background: NonNullable<LikeC4DiagramProperties['background']>
108
118
  controls: boolean
109
119
  showDiagramTitle: boolean
110
- showNavigationButtons: boolean
111
120
  }
112
121
  const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
113
122
  background,
114
123
  controls,
115
- showDiagramTitle,
116
- showNavigationButtons
124
+ showDiagramTitle
117
125
  }) {
118
126
  const {
119
127
  isInitialized,
@@ -140,7 +148,6 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
140
148
  )}
141
149
  >
142
150
  <XYFlowInner
143
- showNavigationButtons={showNavigationButtons}
144
151
  showDiagramTitle={showDiagramTitle}
145
152
  background={background}
146
153
  controls={controls}
@@ -12,6 +12,7 @@ export type StaticLikeC4DiagramProps = Pick<
12
12
  | 'initialWidth'
13
13
  | 'initialHeight'
14
14
  | 'renderIcon'
15
+ | 'where'
15
16
  >
16
17
 
17
18
  export function StaticLikeC4Diagram({
@@ -34,6 +35,7 @@ export function StaticLikeC4Diagram({
34
35
  showElementLinks
35
36
  showDiagramTitle={false}
36
37
  enableDynamicViewWalkthrough={false}
38
+ enableFocusMode={false}
37
39
  nodesSelectable={false}
38
40
  nodesDraggable={false}
39
41
  {...rest}
@@ -1,2 +1,4 @@
1
+ export * from './useDiagramState'
1
2
  export * from './useSetState'
2
3
  export * from './useUpdateEffect'
4
+ export * from './useXYFlow'
@@ -2,8 +2,8 @@ import { shallowEqual } from 'fast-equals'
2
2
  import { useContext } from 'react'
3
3
  import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'
4
4
 
5
- import { DiagramContext, type DiagramZustandStore } from './DiagramContext'
6
- import type { DiagramState } from './diagramStore'
5
+ import { DiagramContext } from '../state/DiagramContext'
6
+ import type { DiagramState, DiagramStoreApi } from '../state/diagramStore'
7
7
 
8
8
  function useDiagramState<StateSlice = unknown>(
9
9
  selector: (state: DiagramState) => StateSlice,
@@ -18,7 +18,6 @@ function useDiagramState<StateSlice = unknown>(
18
18
  return useZustandStore(store, selector, equalityFn ?? shallowEqual)
19
19
  }
20
20
 
21
- export type DiagramStoreApi = Readonly<Pick<DiagramZustandStore, 'getState' | 'setState' | 'subscribe'>>
22
21
  function useDiagramStoreApi(): DiagramStoreApi {
23
22
  const store = useContext(DiagramContext)
24
23
 
@@ -28,5 +27,5 @@ function useDiagramStoreApi(): DiagramStoreApi {
28
27
 
29
28
  return store
30
29
  }
31
-
30
+ export type { DiagramState, DiagramStoreApi }
32
31
  export { useDiagramState, useDiagramStoreApi }
@@ -1,7 +1,7 @@
1
1
  import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
2
2
  import { deepEqual, shallowEqual } from 'fast-equals'
3
3
  import { useCallback } from 'react'
4
- import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../types'
4
+ import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../xyflow/types'
5
5
 
6
6
  export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
7
7
 
package/src/index.ts CHANGED
@@ -3,9 +3,9 @@ import '@xyflow/react/dist/style.css'
3
3
  export { LikeC4Diagram } from './LikeC4Diagram'
4
4
  export { StaticLikeC4Diagram } from './StaticLikeC4Diagram'
5
5
 
6
- export { useSetState, useUpdateEffect } from './hooks'
6
+ export { useSetState } from './hooks/useSetState'
7
+ export { useUpdateEffect } from './hooks/useUpdateEffect'
7
8
  export type * from './LikeC4Diagram'
8
9
  export type * from './LikeC4Diagram.props'
9
10
  export type * from './StaticLikeC4Diagram'
10
- export type * from './types'
11
11
  export type * from './xyflow/types'
@@ -1,10 +1,10 @@
1
+ import { invariant } from '@likec4/core'
1
2
  import { deepEqual, shallowEqual } from 'fast-equals'
2
- import { createContext, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'
3
- import { useUpdateEffect } from '../hooks'
4
- import { useXYFlow, useXYStoreApi } from '../xyflow/hooks/useXYFlow'
5
- import { createDiagramStore, type DiagramInitialState } from './diagramStore'
3
+ import { createContext, type PropsWithChildren, useCallback, useRef } from 'react'
4
+ import { useUpdateEffect } from '../hooks/useUpdateEffect'
5
+ import { useXYFlow, useXYStoreApi } from '../hooks/useXYFlow'
6
+ import { createDiagramStore, type DiagramInitialState, type DiagramZustandStore } from './diagramStore'
6
7
 
7
- export type DiagramZustandStore = ReturnType<typeof createDiagramStore>
8
8
  export const DiagramContext = createContext<DiagramZustandStore | null>(null)
9
9
 
10
10
  export type DiagramContextProviderProps = PropsWithChildren<
@@ -19,6 +19,7 @@ export function DiagramContextProvider({
19
19
  view,
20
20
  className,
21
21
  keepAspectRatio,
22
+ whereFilter,
22
23
  ...props
23
24
  }: DiagramContextProviderProps) {
24
25
  const containerRef = useRef<HTMLDivElement>(null)
@@ -34,6 +35,7 @@ export function DiagramContextProvider({
34
35
  xyflow,
35
36
  view,
36
37
  getContainer,
38
+ whereFilter: structuredClone(whereFilter),
37
39
  ...props
38
40
  })
39
41
  }
@@ -50,9 +52,15 @@ export function DiagramContextProvider({
50
52
 
51
53
  useUpdateEffect(
52
54
  () => {
53
- store.current?.getState().updateView(view)
55
+ const current = store.current
56
+ invariant(current, 'DiagramContext.store.current is not defined')
57
+ if (!deepEqual(whereFilter, current.getState().whereFilter)) {
58
+ current.setState({ whereFilter: structuredClone(whereFilter) }, false, 'update where filter')
59
+ }
60
+ current.getState().updateView(view)
54
61
  },
55
- [view]
62
+ [view, whereFilter] as const,
63
+ (a, b) => shallowEqual(a[0], b[0]) && deepEqual(a[1], b[1])
56
64
  )
57
65
 
58
66
  return (
@@ -1,6 +1,7 @@
1
- import { extractStep, invariant, nonNullable } from '@likec4/core'
2
- import type { DiagramNode, DiagramView, Fqn } from '@likec4/core/types'
1
+ import { extractStep, invariant, nonNullable, whereOperatorAsPredicate } from '@likec4/core'
2
+ import type { DiagramEdge, DiagramNode, DiagramView, Fqn } from '@likec4/core/types'
3
3
  import { hasAtLeast } from 'remeda'
4
+ import type { WhereOperator } from '../LikeC4Diagram.props'
4
5
  import { ZIndexes } from '../xyflow/const'
5
6
  import type { XYFlowEdge, XYFlowNode } from '../xyflow/types'
6
7
 
@@ -9,6 +10,7 @@ import type { XYFlowEdge, XYFlowNode } from '../xyflow/types'
9
10
  export function diagramViewToXYFlowData(
10
11
  view: Pick<DiagramView, 'id' | 'nodes' | 'edges' | '__'>,
11
12
  opts: {
13
+ where: WhereOperator<string, string> | null
12
14
  draggable: boolean
13
15
  selectable: boolean
14
16
  }
@@ -35,6 +37,17 @@ export function diagramViewToXYFlowData(
35
37
  }>()
36
38
  )
37
39
 
40
+ let visiblePredicate = (_nodeOrEdge: DiagramNode | DiagramEdge): boolean => true
41
+ if (opts.where) {
42
+ try {
43
+ visiblePredicate = whereOperatorAsPredicate(opts.where)
44
+ } catch (e) {
45
+ console.error('Error in where filter:', e)
46
+ }
47
+ }
48
+
49
+ // const visiblePredicate = opts.where ? whereOperatorAsPredicate(opts.where) : () => true
50
+
38
51
  // namespace to force unique ids
39
52
  const ns = ''
40
53
  const nodeById = (id: Fqn) => nonNullable(nodeLookup.get(id), `Node not found: ${id}`)
@@ -72,9 +85,9 @@ export function diagramViewToXYFlowData(
72
85
  deletable: false,
73
86
  position,
74
87
  zIndex: isCompound ? ZIndexes.Compound : ZIndexes.Element,
75
- hidden: false,
76
88
  width: node.width,
77
89
  height: node.height,
90
+ hidden: !visiblePredicate(node),
78
91
  // parentId: parent ? ns + parent.id : null,
79
92
  ...(parent && {
80
93
  parentId: ns + parent.id
@@ -109,6 +122,7 @@ export function diagramViewToXYFlowData(
109
122
  target: ns + target,
110
123
  zIndex: ZIndexes.Edge,
111
124
  selectable: opts.selectable,
125
+ hidden: !visiblePredicate(edge),
112
126
  deletable: false,
113
127
  data: {
114
128
  edge,