@likec4/diagram 1.32.0 → 1.32.1

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 (52) hide show
  1. package/bundle/{chunk-BvTQaUYl.js → chunk-DlHyKoeo.js} +236 -210
  2. package/bundle/custom.js +50 -49
  3. package/bundle/index.js +144 -121
  4. package/dist/LikeC4Diagram.js +1 -1
  5. package/dist/LikeC4Diagram.props.d.ts +14 -3
  6. package/dist/bundle/LikeC4View.js +16 -15
  7. package/dist/bundle/ReactLikeC4.js +7 -6
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/custom/customNodes.d.ts +5 -0
  10. package/dist/custom/hooks.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/likec4diagram/DiagramXYFlow.js +3 -3
  13. package/dist/likec4diagram/custom/nodes/toolbar/_shared.d.ts +5 -5
  14. package/dist/likec4model/index.d.ts +1 -1
  15. package/dist/likec4model/useLikeC4Model.d.ts +2 -1
  16. package/dist/likec4model/useLikeC4Model.js +13 -2
  17. package/dist/overlays/overlay/Overlay.js +24 -4
  18. package/dist/search/Search.d.ts +2 -3
  19. package/dist/search/Search.js +27 -46
  20. package/dist/search/components/ElementsColumn.js +12 -13
  21. package/dist/search/components/PickView.js +6 -6
  22. package/dist/search/components/SearchByTags.js +5 -2
  23. package/dist/search/components/SearchInput.js +16 -14
  24. package/dist/search/components/ViewsColum.js +16 -9
  25. package/dist/search/components/utils.d.ts +3 -2
  26. package/dist/search/components/utils.js +37 -5
  27. package/dist/search/hooks.d.ts +2 -8
  28. package/dist/search/hooks.js +4 -4
  29. package/dist/state/DiagramActorProvider.js +1 -1
  30. package/dist/state/diagram-machine.d.ts +216 -8
  31. package/dist/state/diagram-machine.js +8 -6
  32. package/package.json +9 -8
  33. package/src/LikeC4Diagram.props.ts +14 -3
  34. package/src/LikeC4Diagram.tsx +1 -1
  35. package/src/bundle/LikeC4View.tsx +12 -19
  36. package/src/bundle/ReactLikeC4.tsx +2 -10
  37. package/src/custom/customNodes.tsx +5 -0
  38. package/src/custom/hooks.ts +1 -0
  39. package/src/likec4diagram/DiagramXYFlow.tsx +3 -3
  40. package/src/likec4model/index.ts +2 -0
  41. package/src/likec4model/useLikeC4Model.ts +16 -2
  42. package/src/overlays/overlay/Overlay.tsx +20 -5
  43. package/src/search/Search.tsx +34 -51
  44. package/src/search/components/ElementsColumn.tsx +12 -15
  45. package/src/search/components/PickView.tsx +41 -47
  46. package/src/search/components/SearchByTags.tsx +5 -2
  47. package/src/search/components/SearchInput.tsx +17 -14
  48. package/src/search/components/ViewsColum.tsx +18 -10
  49. package/src/search/components/utils.ts +41 -4
  50. package/src/search/hooks.ts +5 -8
  51. package/src/state/DiagramActorProvider.tsx +2 -1
  52. package/src/state/diagram-machine.ts +8 -6
@@ -134,7 +134,15 @@ export interface LikeC4DiagramProperties<A extends aux.Any> {
134
134
  */
135
135
  nodesDraggable?: boolean | undefined
136
136
 
137
+ /**
138
+ * Initial width of the diagram
139
+ * (supposed to be used during SSR)
140
+ */
137
141
  initialWidth?: number | undefined
142
+ /**
143
+ * Initial height of the diagram
144
+ * (supposed to be used during SSR)
145
+ */
138
146
  initialHeight?: number | undefined
139
147
 
140
148
  /**
@@ -156,7 +164,9 @@ export interface LikeC4DiagramProperties<A extends aux.Any> {
156
164
  showNavigationButtons?: undefined | boolean
157
165
 
158
166
  /**
159
- * Display notations of the view
167
+ * Display notations in the bottom right corner
168
+ * (Active if only notations are present)
169
+ *
160
170
  * @default true
161
171
  */
162
172
  showNotations?: boolean | undefined
@@ -200,7 +210,7 @@ export interface LikeC4DiagramProperties<A extends aux.Any> {
200
210
 
201
211
  /**
202
212
  * Display element tags in the bottom left corner
203
- * @default true
213
+ * @default false
204
214
  */
205
215
  enableElementTags?: boolean | undefined
206
216
 
@@ -212,8 +222,9 @@ export interface LikeC4DiagramProperties<A extends aux.Any> {
212
222
 
213
223
  /**
214
224
  * Improve performance by hiding certain elements and reducing visual effects (disable mix-blend, shadows, animations)
225
+ * Enable it if you have a large or static view
215
226
  *
216
- * @default 'auto' - will be set to true if view has more then 3000 * 2000 pixels
227
+ * @default 'auto' - will be `true` if view has more then 3000 * 2000 pixels
217
228
  */
218
229
  reduceGraphics?: 'auto' | boolean | undefined
219
230
 
@@ -42,7 +42,7 @@ export function LikeC4Diagram<A extends Any = Any>({
42
42
  pannable = true,
43
43
  zoomable = true,
44
44
  background = 'dots',
45
- enableElementTags = true,
45
+ enableElementTags = false,
46
46
  enableFocusMode = false,
47
47
  enableElementDetails = false,
48
48
  enableRelationshipDetails = enableElementDetails,
@@ -1,4 +1,4 @@
1
- import type { Any, DiagramView, ViewId } from '@likec4/core/types'
1
+ import type { Any, DiagramView } from '@likec4/core/types'
2
2
  import type * as aux from '@likec4/core/types/aux'
3
3
  import { cx } from '@likec4/styles/css'
4
4
  import { ActionIcon, Box } from '@mantine/core'
@@ -20,27 +20,20 @@ export function LikeC4View<A extends aux.Any = aux.UnknownLayouted>({
20
20
  ...props
21
21
  }: LikeC4ViewProps<A>) {
22
22
  const likec4model = useLikeC4Model()
23
- const view = likec4model?.findView(viewId)
23
+ const view = likec4model.findView(viewId)
24
24
 
25
- if (!likec4model) {
26
- return (
27
- <ErrorMessage>
28
- LikeC4Model not found. Make sure you have LikeC4ModelProvider.
29
- </ErrorMessage>
30
- )
25
+ if (!view) {
26
+ return <ViewNotFound viewId={viewId} />
31
27
  }
32
- if (likec4model.stage !== 'layouted') {
28
+
29
+ if (!view.isDiagram()) {
33
30
  return (
34
31
  <ErrorMessage>
35
- LikeC4Model is not layouted. Make sure you have LikeC4ModelProvider with layouted model.
32
+ LikeC4 View "${viewId}" is not layouted. Make sure you have LikeC4ModelProvider with layouted model.
36
33
  </ErrorMessage>
37
34
  )
38
35
  }
39
36
 
40
- if (!view || !view.isDiagram()) {
41
- return <ViewNotFound viewId={viewId} />
42
- }
43
-
44
37
  return <LikeC4ViewInner view={view.$view} {...props} />
45
38
  }
46
39
 
@@ -57,7 +50,7 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<aux.Any>>(({
57
50
  injectFontCss = true,
58
51
  controls = false,
59
52
  fitView = true,
60
- fitViewPadding = '8px',
53
+ fitViewPadding = '16px',
61
54
  background = 'transparent',
62
55
  browser = true,
63
56
  showDiagramTitle = false,
@@ -154,11 +147,11 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<aux.Any>>(({
154
147
  {...props}
155
148
  />
156
149
  {browserView && (
157
- <Overlay onClose={() => onNavigateTo(null)}>
150
+ <Overlay openDelay={0} onClose={() => onNavigateTo(null)}>
158
151
  <LikeC4Diagram
159
152
  view={browserView}
160
153
  background="dots"
161
- onNavigateTo={to => onNavigateTo(to as ViewId)}
154
+ onNavigateTo={onNavigateTo}
162
155
  enableDynamicViewWalkthrough
163
156
  enableFocusMode
164
157
  enableRelationshipBrowser
@@ -169,12 +162,12 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<aux.Any>>(({
169
162
  controls
170
163
  readonly
171
164
  fitView
172
- fitViewPadding={'16px'}
173
- renderNodes={renderNodes}
165
+ fitViewPadding={'32px'}
174
166
  {...props}
175
167
  {...browserProps}
176
168
  showNotations={(browserProps.showNotations ?? true) &&
177
169
  (browserView.notation?.nodes.length ?? 0) > 0}
170
+ renderNodes={renderNodes}
178
171
  />
179
172
  <Box pos="absolute" top={'1rem'} right={'1rem'}>
180
173
  <ActionIcon
@@ -47,15 +47,7 @@ export function ReactLikeC4<A extends aux.Any = aux.UnknownLayouted>({
47
47
  ...props
48
48
  }: ReactLikeC4Props<A>) {
49
49
  const likec4model = useLikeC4Model()
50
- const view = likec4model?.findView(viewId)
51
-
52
- if (!likec4model) {
53
- return (
54
- <ErrorMessage>
55
- LikeC4Model not found. Make sure you have LikeC4ModelProvider.
56
- </ErrorMessage>
57
- )
58
- }
50
+ const view = likec4model.findView(viewId)
59
51
 
60
52
  if (!view) {
61
53
  return <ViewNotFound viewId={viewId} />
@@ -64,7 +56,7 @@ export function ReactLikeC4<A extends aux.Any = aux.UnknownLayouted>({
64
56
  if (!view.isDiagram()) {
65
57
  return (
66
58
  <ErrorMessage>
67
- LikeC4Model is not layouted. Make sure you have LikeC4ModelProvider with layouted model.
59
+ LikeC4 View "${viewId}" is not layouted. Make sure you have LikeC4ModelProvider with layouted model.
68
60
  </ErrorMessage>
69
61
  )
70
62
  }
@@ -45,15 +45,20 @@ export type CustomElementNodeProps<M extends Any = Any> = {
45
45
  * ElementTitle,
46
46
  * ElementActions,
47
47
  * ElementDetailsButtonWithHandler,
48
+ * IfEnabled,
48
49
  * IfNotReadOnly,
49
50
  * ElementToolbar,
50
51
  * DefaultHandles,
52
+ * ElementTags
51
53
  * } from '@likec4/diagram/custom'
52
54
  *
53
55
  * const CustomElementNode = elementNode(({ nodeProps, nodeModel }) => (
54
56
  * <ElementNodeContainer nodeProps={nodeProps}>
55
57
  * <ElementShape {...nodeProps} />
56
58
  * <ElementTitle {...nodeProps} />
59
+ * <IfEnabled feature="ElementTags">
60
+ * <ElementTags {...nodeProps} />
61
+ * </IfEnabled>
57
62
  * <ElementActions
58
63
  * {...nodeProps}
59
64
  * extraButtons={[
@@ -29,6 +29,7 @@ export {
29
29
  export {
30
30
  useLikeC4Model,
31
31
  useLikeC4Specification,
32
+ useLikeC4ViewModel,
32
33
  } from '../likec4model/useLikeC4Model'
33
34
 
34
35
  export { useMantinePortalProps } from '../hooks/useMantinePortalProps'
@@ -92,13 +92,13 @@ export function LikeC4DiagramXYFlow({
92
92
  $isPanning = usePanningAtom(),
93
93
  isPanning = useTimeout(() => {
94
94
  $isPanning.set(true)
95
- }, isReducedGraphics ? 100 : 200),
95
+ }, isReducedGraphics ? 120 : 200),
96
96
  notPanning = useDebouncedCallback(() => {
97
97
  isPanning.clear()
98
98
  if ($isPanning.get()) {
99
99
  $isPanning.set(false)
100
100
  }
101
- }, 200),
101
+ }, isReducedGraphics ? 350 : 200),
102
102
  onMove: OnMove = useCallbackRef((event) => {
103
103
  if (!event) {
104
104
  return
@@ -109,7 +109,7 @@ export function LikeC4DiagramXYFlow({
109
109
  notPanning()
110
110
  }),
111
111
  onMoveEnd: OnMoveEnd = useCallbackRef((event, viewport) => {
112
- if (!!event) {
112
+ if (!!event && !isReducedGraphics) {
113
113
  notPanning.flush()
114
114
  }
115
115
  diagram.send({ type: 'xyflow.viewportMoved', viewport, manually: !!event })
@@ -3,4 +3,6 @@ export {
3
3
  } from './useLikeC4ElementsTree'
4
4
  export {
5
5
  useLikeC4Model,
6
+ useLikeC4Specification,
7
+ useLikeC4ViewModel,
6
8
  } from './useLikeC4Model'
@@ -1,4 +1,4 @@
1
- import type { aux, LikeC4Model } from '@likec4/core/model'
1
+ import type { aux, LikeC4Model, LikeC4ViewModel } from '@likec4/core/model'
2
2
  import { useContext } from 'react'
3
3
  import { isString } from 'remeda'
4
4
  import { LikeC4ModelContext } from './LikeC4ModelContext'
@@ -12,7 +12,7 @@ export function useLikeC4Model(type?: 'layouted' | 'computed') {
12
12
  const model = useContext(LikeC4ModelContext)
13
13
 
14
14
  if (!model) {
15
- throw new Error('No LikeC4Model found in context')
15
+ throw new Error('LikeC4Model not found. Make sure you have LikeC4ModelProvider.')
16
16
  }
17
17
 
18
18
  if (isString(type) && model.stage !== type) {
@@ -22,6 +22,20 @@ export function useLikeC4Model(type?: 'layouted' | 'computed') {
22
22
  return model
23
23
  }
24
24
 
25
+ export function useLikeC4ViewModel<A extends aux.Any = aux.Unknown>(
26
+ viewId: aux.LooseViewId<A>,
27
+ ): LikeC4ViewModel<aux.toLayouted<A>> | null {
28
+ const model = useLikeC4Model<A>('layouted')
29
+ const view = model.findView(viewId)
30
+ if (!view) {
31
+ return null
32
+ }
33
+ if (!view.isDiagram()) {
34
+ throw new Error(`View "${viewId}" is not layouted`)
35
+ }
36
+ return view as any
37
+ }
38
+
25
39
  export function useLikeC4Specification() {
26
40
  const model = useLikeC4Model()
27
41
  return model.$data.specification
@@ -2,7 +2,7 @@ import { cx } from '@likec4/styles/css'
2
2
  import {
3
3
  RemoveScroll,
4
4
  } from '@mantine/core'
5
- import { useMergedRef } from '@mantine/hooks'
5
+ import { useFocusTrap, useMergedRef } from '@mantine/hooks'
6
6
  import { useDebouncedCallback, useTimeoutEffect } from '@react-hookz/web'
7
7
  import { m, useReducedMotionConfig } from 'motion/react'
8
8
  import { type PropsWithChildren, forwardRef, useLayoutEffect, useRef, useState } from 'react'
@@ -39,6 +39,7 @@ export const Overlay = forwardRef<HTMLDialogElement, OverlayProps>(({
39
39
  ...rest
40
40
  }, ref) => {
41
41
  const [opened, setOpened] = useState(openDelay === 0)
42
+ const focusTrapRef = useFocusTrap(opened)
42
43
  const dialogRef = useRef<HTMLDialogElement>(null)
43
44
  const isClosingRef = useRef(false)
44
45
 
@@ -79,8 +80,17 @@ export const Overlay = forwardRef<HTMLDialogElement, OverlayProps>(({
79
80
  }
80
81
  return (
81
82
  <m.dialog
82
- ref={useMergedRef(dialogRef, ref)}
83
- className={cx(RemoveScroll.classNames.fullWidth, classes?.dialog, className, styles.dialog)}
83
+ ref={useMergedRef(
84
+ dialogRef,
85
+ focusTrapRef,
86
+ ref,
87
+ )}
88
+ className={cx(
89
+ classes?.dialog,
90
+ className,
91
+ styles.dialog,
92
+ fullscreen && RemoveScroll.classNames.fullWidth,
93
+ )}
84
94
  layout
85
95
  style={{
86
96
  // @ts-ignore
@@ -143,8 +153,13 @@ export const Overlay = forwardRef<HTMLDialogElement, OverlayProps>(({
143
153
  }}
144
154
  {...rest}
145
155
  >
146
- <RemoveScroll forwardProps removeScrollBar={false}>
147
- <div className={cx(classes?.body, styles.body, 'overlay-body')}>
156
+ <RemoveScroll forwardProps>
157
+ <div
158
+ className={cx(
159
+ classes?.body,
160
+ styles.body,
161
+ 'overlay-body',
162
+ )}>
148
163
  {opened && <>{children}</>}
149
164
  </div>
150
165
  </RemoveScroll>
@@ -1,6 +1,6 @@
1
1
  import { css } from '@likec4/styles/css'
2
- import { VStack } from '@likec4/styles/jsx'
3
- import { FocusTrap, Grid, GridCol, Group, ScrollArea, Title } from '@mantine/core'
2
+ import { Box, VStack } from '@likec4/styles/jsx'
3
+ import { Grid, GridCol, Group, ScrollArea, Title } from '@mantine/core'
4
4
  import {
5
5
  useCallbackRef,
6
6
  useHotkeys,
@@ -8,7 +8,7 @@ import {
8
8
  import { useTimeoutEffect } from '@react-hookz/web'
9
9
  import { useSelector } from '@xstate/react'
10
10
  import { AnimatePresence, LayoutGroup } from 'motion/react'
11
- import { memo, useRef } from 'react'
11
+ import { useRef } from 'react'
12
12
  import { ErrorBoundary } from 'react-error-boundary'
13
13
  import { isTruthy } from 'remeda'
14
14
  import { ErrorFallback } from '../components/ErrorFallback'
@@ -63,27 +63,15 @@ const body = css({
63
63
 
64
64
  const selectIsOpened = (s: SearchActorSnapshot) => !s.matches('inactive')
65
65
 
66
- export const Search = memo<{ searchActorRef: SearchActorRef }>(({ searchActorRef }) => {
66
+ export function Search({ searchActorRef }: { searchActorRef: SearchActorRef }) {
67
67
  const isOpened = useSelector(searchActorRef, selectIsOpened)
68
68
 
69
69
  const openSearch = () => {
70
70
  searchActorRef.send({ type: 'open' })
71
71
  }
72
72
 
73
- const sendClose = () => {
73
+ const close = useCallbackRef(() => {
74
74
  searchActorRef.send({ type: 'close' })
75
- }
76
-
77
- const afterCloseCbRef = useRef<(() => void)>(null)
78
- const close = useCallbackRef((cb?: () => void) => {
79
- afterCloseCbRef.current = cb ?? null
80
- sendClose()
81
- })
82
- const onExitComplete = useCallbackRef(() => {
83
- if (afterCloseCbRef.current) {
84
- afterCloseCbRef.current()
85
- afterCloseCbRef.current = null
86
- }
87
75
  })
88
76
 
89
77
  useHotkeys([
@@ -96,40 +84,34 @@ export const Search = memo<{ searchActorRef: SearchActorRef }>(({ searchActorRef
96
84
  ])
97
85
 
98
86
  return (
99
- <SearchActorContext
100
- value={{
101
- searchActorRef,
102
- close,
103
- }}>
87
+ <SearchActorContext value={searchActorRef}>
104
88
  <DiagramFeatures.Overlays>
105
- <ErrorBoundary FallbackComponent={ErrorFallback} onReset={sendClose}>
106
- <LayoutGroup>
107
- <AnimatePresence onExitComplete={onExitComplete}>
108
- {isOpened && (
109
- <Overlay
110
- fullscreen
111
- withBackdrop={false}
112
- backdrop={{
113
- opacity: 0.9,
114
- }}
115
- classes={{
116
- dialog,
117
- body,
118
- }}
119
- openDelay={0}
120
- onClose={close}
121
- data-likec4-search="true"
122
- >
123
- <SearchOverlay close={close} searchActorRef={searchActorRef} />
124
- </Overlay>
125
- )}
126
- </AnimatePresence>
127
- </LayoutGroup>
89
+ <ErrorBoundary FallbackComponent={ErrorFallback} onReset={close}>
90
+ <AnimatePresence>
91
+ {isOpened && (
92
+ <Overlay
93
+ fullscreen
94
+ withBackdrop={false}
95
+ backdrop={{
96
+ opacity: 0.9,
97
+ }}
98
+ classes={{
99
+ dialog,
100
+ body,
101
+ }}
102
+ openDelay={0}
103
+ onClose={close}
104
+ data-likec4-search="true"
105
+ >
106
+ <SearchOverlay searchActorRef={searchActorRef} />
107
+ </Overlay>
108
+ )}
109
+ </AnimatePresence>
128
110
  </ErrorBoundary>
129
111
  </DiagramFeatures.Overlays>
130
112
  </SearchActorContext>
131
113
  )
132
- })
114
+ }
133
115
 
134
116
  const scrollArea = css({
135
117
  height: [
@@ -145,23 +127,24 @@ const scrollArea = css({
145
127
  },
146
128
  })
147
129
 
148
- const SearchOverlay = ({ close, searchActorRef }: { close: () => void; searchActorRef: SearchActorRef }) => {
130
+ const SearchOverlay = ({ searchActorRef }: { searchActorRef: SearchActorRef }) => {
131
+ const ref = useRef<HTMLDivElement>(null)
149
132
  const pickViewFor = usePickViewFor()
150
133
 
151
134
  useTimeoutEffect(() => {
152
135
  if (isTruthy(searchActorRef.getSnapshot().context.openedWithSearch)) {
153
- focusToFirstFoundElement()
136
+ focusToFirstFoundElement(ref.current)
154
137
  }
155
138
  }, 150)
156
139
 
157
140
  return (
158
- <FocusTrap>
141
+ <Box ref={ref} display={'contents'}>
159
142
  <Group
160
143
  className={'group'}
161
144
  wrap="nowrap"
162
145
  onClick={e => {
163
146
  e.stopPropagation()
164
- moveFocusToSearchInput()
147
+ moveFocusToSearchInput(ref.current)
165
148
  }}>
166
149
  <VStack flex={1} px={'sm'}>
167
150
  <LikeC4SearchInput />
@@ -232,6 +215,6 @@ const SearchOverlay = ({ close, searchActorRef }: { close: () => void; searchAct
232
215
  </GridCol>
233
216
  </Grid>
234
217
  {pickViewFor && <PickView elementFqn={pickViewFor} />}
235
- </FocusTrap>
218
+ </Box>
236
219
  )
237
220
  }
@@ -22,7 +22,7 @@ import {
22
22
  import { useCallbackRef } from '@mantine/hooks'
23
23
  import { IconChevronRight } from '@tabler/icons-react'
24
24
  import * as m from 'motion/react-m'
25
- import { type KeyboardEventHandler, memo, useCallback, useEffect, useMemo } from 'react'
25
+ import { type KeyboardEventHandler, memo, useEffect, useMemo } from 'react'
26
26
  import { first, isEmpty, only, pipe, reduce } from 'remeda'
27
27
  import { IconOrShapeRenderer } from '../../context/IconRenderer'
28
28
  import { useDiagram } from '../../hooks/useDiagram'
@@ -31,7 +31,7 @@ import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
31
31
  import { useNormalizedSearch, useSearchActor } from '../hooks'
32
32
  import { buttonsva } from './_shared.css'
33
33
  import * as styles from './ElementsColumn.css'
34
- import { centerY, moveFocusToSearchInput, stopAndPrevent } from './utils'
34
+ import { centerY, moveFocusToSearchInput, queryAllFocusable, stopAndPrevent } from './utils'
35
35
  import { NothingFound } from './ViewsColum'
36
36
 
37
37
  interface LikeC4ModelTreeNodeData {
@@ -161,7 +161,7 @@ function ElementsTree({
161
161
  if (e.key === 'ArrowUp') {
162
162
  if (id === roots[0]?.value) {
163
163
  stopAndPrevent(e)
164
- moveFocusToSearchInput()
164
+ moveFocusToSearchInput(target)
165
165
  }
166
166
  return
167
167
  }
@@ -172,9 +172,7 @@ function ElementsTree({
172
172
  }
173
173
  const label = (e.target as HTMLLIElement).querySelector<HTMLLIElement>('.mantine-Tree-label') ?? target
174
174
  const maxY = label.getBoundingClientRect().y
175
- const viewButtons = [...document.querySelectorAll<HTMLButtonElement>(
176
- `[data-likec4-search-views] .${styles.focusable}`,
177
- )]
175
+ const viewButtons = queryAllFocusable(target, 'views')
178
176
  let view = viewButtons.length > 1
179
177
  ? viewButtons.find((el, i, all) => centerY(el) > maxY || i === all.length - 1)
180
178
  : null
@@ -299,23 +297,22 @@ function ElementTreeNode(
299
297
 
300
298
  function useHandleElementSelection() {
301
299
  const diagram = useDiagram()
302
- const { searchActorRef } = useSearchActor()
303
- // const naviconst { searchActorRef } = useSearchActor(gateTo = useCloseSearchAndNavigateTo()
304
-
305
- return useCallback((element: LikeC4Model.Element) => {
300
+ const searchActorRef = useSearchActor()
301
+ return useCallbackRef((element: LikeC4Model.Element) => {
306
302
  const views = [...element.views()]
307
303
  if (views.length === 0) {
308
304
  return
309
305
  }
310
306
  const singleView = only(views)
311
307
  if (singleView) {
312
- if (singleView.id === diagram.currentView.id) {
313
- searchActorRef.send({ type: 'close' })
314
- } else {
315
- diagram.navigateTo(singleView.id)
308
+ searchActorRef.send({ type: 'close' })
309
+ if (singleView.id !== diagram.currentView.id) {
310
+ setTimeout(() => {
311
+ diagram.navigateTo(singleView.id)
312
+ }, 100)
316
313
  }
317
314
  return
318
315
  }
319
316
  searchActorRef.send({ type: 'pickview.open', elementFqn: element.id })
320
- }, [])
317
+ })
321
318
  }
@@ -10,14 +10,8 @@ import * as styles from './styles.css'
10
10
  import { ViewButton } from './ViewsColum'
11
11
 
12
12
  export function PickView({ elementFqn }: { elementFqn: Fqn }) {
13
- const { searchActorRef } = useSearchActor()
13
+ const searchActorRef = useSearchActor()
14
14
  const element = useLikeC4Model().element(elementFqn)
15
- // const pickview = usePickView()
16
- // const navigateTo = useCloseSearchAndNavigateTo()
17
- // const focusTrapRef = useFocusTrap(!!pickview)
18
- // useFocusReturn({
19
- // opened: !!pickview,
20
- // })
21
15
  const scoped = [] as LikeC4ViewModel[]
22
16
  const others = [] as LikeC4ViewModel[]
23
17
 
@@ -61,44 +55,44 @@ export function PickView({ elementFqn }: { elementFqn: Fqn }) {
61
55
  closePickView()
62
56
  }}>
63
57
  </m.div>
64
- <m.div
65
- key="pickview"
66
- initial={{
67
- opacity: 0,
68
- scale: 0.95,
69
- originY: 0,
70
- translateX: '-50%',
71
- translateY: -20,
72
- }}
73
- animate={{
74
- opacity: 1,
75
- scale: 1,
76
- translateY: 0,
77
- }}
78
- exit={{
79
- opacity: 0,
80
- scale: 0.98,
81
- translateY: -20,
82
- transition: {
83
- duration: 0.1,
84
- },
85
- }}
86
- className={styles.pickview}
87
- data-likec4-search-views>
88
- <Group px="sm" py="md" justify="space-between">
89
- <Title order={2} lh={1}>Select view</Title>
90
- <ActionIcon
91
- size={'md'}
92
- variant="default"
93
- onClick={(e) => {
94
- e.stopPropagation()
95
- closePickView()
96
- }}>
97
- <IconX />
98
- </ActionIcon>
99
- </Group>
100
- <FocusTrap>
101
- <ScrollAreaAutosize mah={'calc(100vh - 8rem - 50px)'} type="never">
58
+ <FocusTrap>
59
+ <m.div
60
+ key="pickview"
61
+ initial={{
62
+ opacity: 0,
63
+ scale: 0.95,
64
+ originY: 0,
65
+ translateX: '-50%',
66
+ translateY: -20,
67
+ }}
68
+ animate={{
69
+ opacity: 1,
70
+ scale: 1,
71
+ translateY: 0,
72
+ }}
73
+ exit={{
74
+ opacity: 0,
75
+ scale: 0.98,
76
+ translateY: -20,
77
+ transition: {
78
+ duration: 0.1,
79
+ },
80
+ }}
81
+ className={styles.pickview}
82
+ data-likec4-search-views>
83
+ <Group px="sm" py="md" justify="space-between">
84
+ <Title order={2} lh={1}>Select view</Title>
85
+ <ActionIcon
86
+ size={'md'}
87
+ variant="default"
88
+ onClick={(e) => {
89
+ e.stopPropagation()
90
+ closePickView()
91
+ }}>
92
+ <IconX />
93
+ </ActionIcon>
94
+ </Group>
95
+ <ScrollAreaAutosize mah={'calc(100vh - 110px)'} type="never">
102
96
  {scoped.length > 0 && (
103
97
  <Stack gap={'sm'} px={'sm'} className={styles.pickviewGroup}>
104
98
  <Title order={6} c={'dimmed'}>scoped views of the element</Title>
@@ -133,8 +127,8 @@ export function PickView({ elementFqn }: { elementFqn: Fqn }) {
133
127
  </Stack>
134
128
  )}
135
129
  </ScrollAreaAutosize>
136
- </FocusTrap>
137
- </m.div>
130
+ </m.div>
131
+ </FocusTrap>
138
132
  </>
139
133
  )
140
134
  }