@likec4/diagram 1.31.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 (269) hide show
  1. package/bundle/{chunk-BBzMXzCg.js → chunk-DlHyKoeo.js} +1700 -1206
  2. package/bundle/custom.js +51 -47
  3. package/bundle/index.js +5495 -5428
  4. package/dist/LikeC4CustomColors.d.ts +2 -7
  5. package/dist/LikeC4CustomColors.js +17 -6
  6. package/dist/LikeC4Diagram.d.ts +3 -2
  7. package/dist/LikeC4Diagram.js +10 -14
  8. package/dist/LikeC4Diagram.props.d.ts +41 -24
  9. package/dist/LikeC4ModelProvider.d.ts +5 -4
  10. package/dist/StaticLikeC4Diagram.d.ts +3 -2
  11. package/dist/StaticLikeC4Diagram.js +2 -2
  12. package/dist/base/BaseXYFlow.js +5 -5
  13. package/dist/base/const.d.ts +1 -1
  14. package/dist/base/const.js +1 -1
  15. package/dist/base/index.d.ts +2 -1
  16. package/dist/base/primitives/compound/CompoundActionButton.js +2 -1
  17. package/dist/base/primitives/compound/CompoundDetailsButton.js +2 -1
  18. package/dist/base/primitives/compound/CompoundTitle.d.ts +1 -1
  19. package/dist/base/primitives/compound/CompoundTitle.js +2 -1
  20. package/dist/base/primitives/edge/EdgeContainer.d.ts +1 -1
  21. package/dist/base/primitives/edge/EdgeLabel.css.js +1 -0
  22. package/dist/base/primitives/edge/EdgeLabel.d.ts +3 -3
  23. package/dist/base/primitives/edge/EdgeLabel.js +6 -4
  24. package/dist/base/primitives/edge/EdgeLabelContainer.d.ts +1 -1
  25. package/dist/base/primitives/edge/EdgeMarkers.d.ts +1 -1
  26. package/dist/base/primitives/edge/EdgePath.d.ts +1 -1
  27. package/dist/base/primitives/element/ElementActionButtons.js +10 -9
  28. package/dist/base/primitives/element/ElementDetailsButton.js +2 -1
  29. package/dist/base/primitives/element/ElementShape.d.ts +1 -1
  30. package/dist/base/primitives/element/ElementTags.d.ts +14 -0
  31. package/dist/base/primitives/element/ElementTags.js +176 -0
  32. package/dist/base/primitives/element/ElementTitle.d.ts +5 -4
  33. package/dist/base/primitives/element/ElementTitle.js +6 -6
  34. package/dist/base/primitives/index.d.ts +1 -0
  35. package/dist/base/types.Base.d.ts +54 -0
  36. package/dist/base/types.Base.js +48 -0
  37. package/dist/base/types.d.ts +2 -52
  38. package/dist/bundle/LikeC4View.d.ts +2 -1
  39. package/dist/bundle/LikeC4View.js +22 -19
  40. package/dist/bundle/LikeC4View.props.d.ts +15 -4
  41. package/dist/bundle/ReactLikeC4.d.ts +4 -7
  42. package/dist/bundle/ReactLikeC4.js +10 -8
  43. package/dist/bundle/index.js +1 -1
  44. package/dist/components/ErrorFallback.d.ts +2 -0
  45. package/dist/components/ErrorFallback.js +29 -0
  46. package/dist/context/DiagramEventHandlers.d.ts +6 -5
  47. package/dist/context/DiagramFeatures.d.ts +2 -2
  48. package/dist/context/DiagramFeatures.js +3 -31
  49. package/dist/context/EnsureMantine.js +1 -1
  50. package/dist/context/IconRenderer.js +1 -4
  51. package/dist/context/RootContainer.js +5 -1
  52. package/dist/context/TagStylesContext.d.ts +7 -0
  53. package/dist/context/TagStylesContext.js +85 -0
  54. package/dist/custom/components.d.ts +1 -1
  55. package/dist/custom/customNodes.d.ts +32 -17
  56. package/dist/custom/hooks.d.ts +1 -1
  57. package/dist/hooks/index.d.ts +1 -1
  58. package/dist/hooks/useDiagram.d.ts +73 -38
  59. package/dist/hooks/useDiagram.js +7 -3
  60. package/dist/hooks/useMantinePortalProps.d.ts +2 -2
  61. package/dist/hooks/useSearchActor.d.ts +2 -0
  62. package/dist/hooks/useSearchActor.js +10 -0
  63. package/dist/hooks/useUpdateEffect.js +3 -0
  64. package/dist/hooks/useXYFlow.d.ts +39 -37
  65. package/dist/hooks/useXYFlow.js +5 -1
  66. package/dist/index.js +1 -1
  67. package/dist/likec4diagram/DiagramUI.js +7 -2
  68. package/dist/likec4diagram/DiagramXYFlow.d.ts +4 -3
  69. package/dist/likec4diagram/DiagramXYFlow.js +107 -110
  70. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +7 -6
  71. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +8 -0
  72. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +7 -5
  73. package/dist/likec4diagram/custom/index.d.ts +7 -6
  74. package/dist/likec4diagram/custom/nodes/nodes.js +39 -26
  75. package/dist/likec4diagram/custom/nodes/toolbar/_shared.d.ts +7 -7
  76. package/dist/likec4diagram/types.d.ts +5 -5
  77. package/dist/likec4diagram/ui/controls/Controls.js +12 -4
  78. package/dist/likec4diagram/ui/notation/NotationPanel.js +1 -1
  79. package/dist/likec4diagram/useViewToNodesEdges.d.ts +5 -3
  80. package/dist/likec4diagram/useViewToNodesEdges.js +20 -30
  81. package/dist/likec4model/LikeC4ModelContext.d.ts +2 -2
  82. package/dist/likec4model/index.d.ts +1 -1
  83. package/dist/likec4model/useLikeC4ElementsTree.js +1 -1
  84. package/dist/likec4model/useLikeC4Model.d.ts +23 -10
  85. package/dist/likec4model/useLikeC4Model.js +22 -7
  86. package/dist/overlays/Overlays.js +4 -27
  87. package/dist/overlays/element-details/ElementDetailsCard.d.ts +1 -1
  88. package/dist/overlays/element-details/ElementDetailsCard.js +19 -12
  89. package/dist/overlays/element-details/TabPanelDeployments.d.ts +1 -1
  90. package/dist/overlays/element-details/TabPanelDeployments.js +1 -1
  91. package/dist/overlays/element-details/TabPanelRelationships.d.ts +1 -1
  92. package/dist/overlays/element-details/TabPanelStructure.d.ts +1 -1
  93. package/dist/overlays/element-details/actor.d.ts +288 -286
  94. package/dist/overlays/overlay/Overlay.css.d.ts +57 -2
  95. package/dist/overlays/overlay/Overlay.css.js +76 -39
  96. package/dist/overlays/overlay/Overlay.d.ts +12 -0
  97. package/dist/overlays/overlay/Overlay.js +58 -27
  98. package/dist/overlays/overlaysActor.d.ts +1 -1
  99. package/dist/overlays/relationship-details/RelationshipDetails.d.ts +3 -2
  100. package/dist/overlays/relationship-details/RelationshipDetails.js +3 -3
  101. package/dist/overlays/relationship-details/_types.d.ts +2 -2
  102. package/dist/overlays/relationship-details/actor.js +7 -7
  103. package/dist/overlays/relationship-details/custom/edgeTypes.d.ts +3 -2
  104. package/dist/overlays/relationship-details/custom/nodeTypes.js +4 -0
  105. package/dist/overlays/relationship-details/hooks.d.ts +10 -9
  106. package/dist/overlays/relationship-details/layout-to-xyflow.js +1 -0
  107. package/dist/overlays/relationship-details/layout.js +3 -1
  108. package/dist/overlays/relationships-browser/RelationshipsBrowser.d.ts +3 -2
  109. package/dist/overlays/relationships-browser/SelectElement.js +1 -1
  110. package/dist/overlays/relationships-browser/_types.d.ts +2 -2
  111. package/dist/overlays/relationships-browser/actor.d.ts +1 -1
  112. package/dist/overlays/relationships-browser/actor.js +7 -7
  113. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -2
  114. package/dist/overlays/relationships-browser/custom/nodeTypes.d.ts +8 -5
  115. package/dist/overlays/relationships-browser/custom/nodeTypes.js +4 -0
  116. package/dist/overlays/relationships-browser/layout.d.ts +2 -2
  117. package/dist/overlays/relationships-browser/layout.js +10 -6
  118. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +4 -3
  119. package/dist/search/Search.d.ts +4 -0
  120. package/dist/search/Search.js +173 -0
  121. package/dist/{likec4diagram/ui/search → search/components}/ElementsColumn.css.js +0 -1
  122. package/dist/search/components/ElementsColumn.d.ts +2 -0
  123. package/dist/{likec4diagram/ui/search → search/components}/ElementsColumn.js +26 -25
  124. package/dist/search/components/PickView.d.ts +4 -0
  125. package/dist/search/components/PickView.js +135 -0
  126. package/dist/search/components/SearchByTags.d.ts +1 -0
  127. package/dist/search/components/SearchByTags.js +119 -0
  128. package/dist/search/components/SearchInput.d.ts +2 -0
  129. package/dist/{likec4diagram/ui/search → search/components}/SearchInput.js +61 -40
  130. package/dist/{likec4diagram/ui/search → search/components}/ViewsColum.d.ts +3 -2
  131. package/dist/{likec4diagram/ui/search → search/components}/ViewsColum.js +25 -17
  132. package/dist/{likec4diagram/ui/search → search/components}/_shared.css.js +26 -35
  133. package/dist/{likec4diagram/ui/search/LikeC4Search.css.d.ts → search/components/styles.css.d.ts} +0 -2
  134. package/dist/search/components/styles.css.js +60 -0
  135. package/dist/search/components/utils.d.ts +5 -0
  136. package/dist/search/components/utils.js +58 -0
  137. package/dist/search/hooks.d.ts +10 -0
  138. package/dist/search/hooks.js +35 -0
  139. package/dist/search/searchActor.d.ts +90 -0
  140. package/dist/search/searchActor.js +75 -0
  141. package/dist/state/DiagramActorProvider.d.ts +9 -7
  142. package/dist/state/DiagramActorProvider.js +24 -18
  143. package/dist/state/assign.d.ts +0 -1
  144. package/dist/state/assign.js +9 -19
  145. package/dist/state/diagram-machine.d.ts +645 -183
  146. package/dist/state/diagram-machine.js +127 -60
  147. package/dist/state/syncManualLayoutActor.d.ts +2 -2
  148. package/dist/state/types.d.ts +2 -0
  149. package/dist/state/utils.d.ts +2 -0
  150. package/dist/state/utils.js +3 -0
  151. package/dist/styles-min.css +1 -1
  152. package/dist/styles-xyflow.css +1 -1
  153. package/dist/styles.css +1 -1
  154. package/dist/styles.css2.js +1 -1
  155. package/package.json +20 -19
  156. package/panda.buildinfo.json +171 -148
  157. package/src/LikeC4CustomColors.tsx +21 -6
  158. package/src/LikeC4Diagram.props.ts +48 -32
  159. package/src/LikeC4Diagram.tsx +19 -20
  160. package/src/LikeC4ModelProvider.tsx +6 -5
  161. package/src/StaticLikeC4Diagram.tsx +8 -6
  162. package/src/base/const.ts +1 -1
  163. package/src/base/index.ts +2 -1
  164. package/src/base/primitives/compound/CompoundActionButton.tsx +2 -1
  165. package/src/base/primitives/compound/CompoundDetailsButton.tsx +2 -1
  166. package/src/base/primitives/compound/CompoundTitle.tsx +3 -2
  167. package/src/base/primitives/edge/EdgeContainer.tsx +2 -1
  168. package/src/base/primitives/edge/EdgeLabel.css.ts +1 -0
  169. package/src/base/primitives/edge/EdgeLabel.tsx +20 -15
  170. package/src/base/primitives/edge/EdgeLabelContainer.tsx +1 -1
  171. package/src/base/primitives/edge/EdgeMarkers.tsx +2 -1
  172. package/src/base/primitives/edge/EdgePath.tsx +1 -1
  173. package/src/base/primitives/element/ElementActionButtons.tsx +11 -10
  174. package/src/base/primitives/element/ElementDetailsButton.tsx +2 -1
  175. package/src/base/primitives/element/ElementShape.tsx +2 -1
  176. package/src/base/primitives/element/ElementTags.tsx +182 -0
  177. package/src/base/primitives/element/ElementTitle.tsx +21 -15
  178. package/src/base/primitives/index.ts +1 -0
  179. package/src/base/types.Base.ts +104 -0
  180. package/src/base/types.ts +2 -99
  181. package/src/bundle/LikeC4View.props.ts +17 -4
  182. package/src/bundle/LikeC4View.tsx +27 -36
  183. package/src/bundle/ReactLikeC4.tsx +16 -36
  184. package/src/components/ErrorFallback.tsx +30 -0
  185. package/src/context/DiagramEventHandlers.tsx +5 -4
  186. package/src/context/DiagramFeatures.tsx +3 -22
  187. package/src/context/EnsureMantine.tsx +1 -1
  188. package/src/context/IconRenderer.tsx +1 -4
  189. package/src/context/RootContainer.tsx +9 -3
  190. package/src/context/TagStylesContext.tsx +96 -0
  191. package/src/custom/components.ts +2 -0
  192. package/src/custom/customNodes.tsx +29 -14
  193. package/src/custom/hooks.ts +2 -0
  194. package/src/hooks/index.ts +1 -1
  195. package/src/hooks/useCurrentViewId.ts +1 -4
  196. package/src/hooks/useDiagram.ts +104 -12
  197. package/src/hooks/useMantinePortalProps.ts +10 -1
  198. package/src/hooks/useSearchActor.ts +11 -0
  199. package/src/hooks/useUpdateEffect.ts +7 -4
  200. package/src/hooks/useXYFlow.ts +5 -0
  201. package/src/likec4diagram/DiagramUI.tsx +5 -0
  202. package/src/likec4diagram/DiagramXYFlow.tsx +118 -121
  203. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +9 -0
  204. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +10 -9
  205. package/src/likec4diagram/custom/nodes/nodes.tsx +45 -43
  206. package/src/likec4diagram/custom/nodes/toolbar/_shared.tsx +11 -3
  207. package/src/likec4diagram/types.ts +5 -3
  208. package/src/likec4diagram/ui/controls/Controls.tsx +11 -4
  209. package/src/likec4diagram/ui/notation/NotationPanel.tsx +2 -2
  210. package/src/likec4diagram/useViewToNodesEdges.ts +34 -36
  211. package/src/likec4model/LikeC4ModelContext.ts +2 -2
  212. package/src/likec4model/index.ts +2 -0
  213. package/src/likec4model/useLikeC4ElementsTree.ts +1 -1
  214. package/src/likec4model/useLikeC4Model.ts +32 -16
  215. package/src/overlays/Overlays.tsx +5 -32
  216. package/src/overlays/element-details/ElementDetailsCard.tsx +25 -18
  217. package/src/overlays/element-details/TabPanelDeployments.tsx +3 -2
  218. package/src/overlays/element-details/TabPanelRelationships.tsx +2 -1
  219. package/src/overlays/element-details/TabPanelStructure.tsx +1 -1
  220. package/src/overlays/element-details/actor.ts +2 -2
  221. package/src/overlays/overlay/Overlay.css.ts +75 -38
  222. package/src/overlays/overlay/Overlay.tsx +61 -29
  223. package/src/overlays/overlaysActor.ts +1 -1
  224. package/src/overlays/relationship-details/RelationshipDetails.tsx +4 -4
  225. package/src/overlays/relationship-details/_types.ts +2 -1
  226. package/src/overlays/relationship-details/custom/nodeTypes.tsx +4 -0
  227. package/src/overlays/relationship-details/layout-to-xyflow.ts +1 -0
  228. package/src/overlays/relationship-details/layout.ts +3 -1
  229. package/src/overlays/relationships-browser/SelectElement.tsx +1 -1
  230. package/src/overlays/relationships-browser/_types.ts +2 -1
  231. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +4 -0
  232. package/src/overlays/relationships-browser/layout.ts +12 -7
  233. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +4 -4
  234. package/src/search/Search.tsx +220 -0
  235. package/src/{likec4diagram/ui/search → search/components}/ElementsColumn.css.ts +0 -29
  236. package/src/{likec4diagram/ui/search → search/components}/ElementsColumn.tsx +28 -29
  237. package/src/search/components/PickView.tsx +134 -0
  238. package/src/search/components/SearchByTags.tsx +112 -0
  239. package/src/{likec4diagram/ui/search → search/components}/SearchInput.tsx +72 -42
  240. package/src/{likec4diagram/ui/search → search/components}/ViewsColum.tsx +29 -22
  241. package/src/{likec4diagram/ui/search → search/components}/_shared.css.ts +26 -37
  242. package/src/{likec4diagram/ui/search/LikeC4Search.css.ts → search/components/styles.css.ts} +9 -36
  243. package/src/search/components/utils.ts +64 -0
  244. package/src/search/hooks.ts +38 -0
  245. package/src/search/searchActor.ts +99 -0
  246. package/src/state/DiagramActorProvider.tsx +37 -25
  247. package/src/state/assign.ts +10 -10
  248. package/src/state/diagram-machine.ts +123 -58
  249. package/src/state/syncManualLayoutActor.ts +1 -1
  250. package/src/state/types.ts +2 -0
  251. package/src/state/utils.ts +4 -0
  252. package/dist/base/types.js +0 -54
  253. package/dist/likec4diagram/ui/search/ElementsColumn.d.ts +0 -1
  254. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +0 -87
  255. package/dist/likec4diagram/ui/search/LikeC4Search.d.ts +0 -2
  256. package/dist/likec4diagram/ui/search/LikeC4Search.js +0 -267
  257. package/dist/likec4diagram/ui/search/SearchInput.d.ts +0 -1
  258. package/dist/likec4diagram/ui/search/state.d.ts +0 -19
  259. package/dist/likec4diagram/ui/search/state.js +0 -63
  260. package/dist/likec4diagram/ui/search/utils.d.ts +0 -3
  261. package/dist/likec4diagram/ui/search/utils.js +0 -20
  262. package/src/likec4diagram/ui/search/LikeC4Search.tsx +0 -301
  263. package/src/likec4diagram/ui/search/state.ts +0 -78
  264. package/src/likec4diagram/ui/search/utils.ts +0 -20
  265. /package/dist/{likec4diagram/ui/search → search/components}/ElementsColumn.css.d.ts +0 -0
  266. /package/dist/{likec4diagram/ui/search → search/components}/ViewsColumn.css.d.ts +0 -0
  267. /package/dist/{likec4diagram/ui/search → search/components}/ViewsColumn.css.js +0 -0
  268. /package/dist/{likec4diagram/ui/search → search/components}/_shared.css.d.ts +0 -0
  269. /package/src/{likec4diagram/ui/search → search/components}/ViewsColumn.css.ts +0 -0
@@ -0,0 +1,112 @@
1
+ import { css } from '@likec4/styles/css'
2
+ import { HStack } from '@likec4/styles/jsx'
3
+ import { Button } from '@mantine/core'
4
+ import { IconX } from '@tabler/icons-react'
5
+ import { useRef } from 'react'
6
+ import { ElementTag } from '../../base/primitives'
7
+ import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
8
+ import { useNormalizedSearch, useUpdateSearch } from '../hooks'
9
+ import { focusToFirstFoundElement, moveFocusToSearchInput } from './utils'
10
+
11
+ export function SearchByTags() {
12
+ const ref = useRef<HTMLDivElement>(null)
13
+ let tags = useLikeC4Model().tagsSortedByUsage
14
+ let setSearch = useUpdateSearch()
15
+ let search = useNormalizedSearch()
16
+ let countBefore = tags.length
17
+ if (search.startsWith('#')) {
18
+ const searchTag = search.slice(1)
19
+ tags = tags.filter(({ tag }) => tag.toLocaleLowerCase().includes(searchTag))
20
+ }
21
+ if (tags.length === 0) {
22
+ return null
23
+ }
24
+
25
+ const isFiltered = tags.length !== countBefore
26
+
27
+ return (
28
+ <HStack
29
+ ref={ref}
30
+ css={{
31
+ gap: 'md',
32
+ paddingLeft: 48,
33
+ flexWrap: 'nowrap',
34
+ }}
35
+ >
36
+ <HStack
37
+ css={{
38
+ gap: 6,
39
+ flexWrap: 'wrap',
40
+ opacity: isFiltered ? 1 : .3,
41
+ grayscale: isFiltered ? 0 : .9,
42
+ filter: 'auto',
43
+ transition: 'fast',
44
+ _groupHover: {
45
+ opacity: 1,
46
+ grayscale: 0,
47
+ },
48
+ _groupFocusWithin: {
49
+ opacity: 1,
50
+ grayscale: 0,
51
+ },
52
+ }}
53
+ >
54
+ {tags.map(({ tag }) => (
55
+ // <Pill
56
+ // key={tag}
57
+ // data-likec4-tag={tag}
58
+ // className={cx(
59
+ // likec4tag(),
60
+ // css({
61
+ // minWidth: 'unset',
62
+ // cursor: 'pointer',
63
+ // }),
64
+ // )}
65
+ // onClick={(e) => {
66
+ // e.stopPropagation()
67
+ // setSearch(`#${tag}`)
68
+ // }}
69
+ // onRemove={() => {
70
+ // setSearch('#')
71
+ // moveFocusToSearchInput()
72
+ // }}
73
+ // withRemoveButton={tag === `${search.slice(1)}`}
74
+ // >
75
+ // <span style={{ opacity: 0.7, fontSize: '1.05em' }}>#</span>
76
+ // {tag}
77
+ // </Pill>
78
+ <ElementTag
79
+ key={tag}
80
+ tag={tag}
81
+ className={css({
82
+ userSelect: 'none',
83
+ cursor: 'pointer',
84
+ })}
85
+ onClick={(e) => {
86
+ e.stopPropagation()
87
+ setSearch(`#${tag}`)
88
+ // Let react to display filtered elements
89
+ setTimeout(() => {
90
+ focusToFirstFoundElement(ref.current)
91
+ }, 350)
92
+ }}
93
+ />
94
+ ))}
95
+ </HStack>
96
+ {isFiltered && (
97
+ <Button
98
+ size="compact-xs"
99
+ variant="light"
100
+ onClick={(e) => {
101
+ e.stopPropagation()
102
+ setSearch('')
103
+ moveFocusToSearchInput(ref.current)
104
+ }}
105
+ rightSection={<IconX size={14} />}
106
+ >
107
+ Clear
108
+ </Button>
109
+ )}
110
+ </HStack>
111
+ )
112
+ }
@@ -1,4 +1,3 @@
1
- import { compareNatural } from '@likec4/core'
2
1
  import {
3
2
  Combobox,
4
3
  ComboboxDropdown,
@@ -12,15 +11,14 @@ import {
12
11
  Text,
13
12
  useCombobox,
14
13
  } from '@mantine/core'
15
- import { useDebouncedValue, useFocusWithin, useIsFirstRender, useWindowEvent } from '@mantine/hooks'
16
- import { usePreviousDistinct } from '@react-hookz/web'
14
+ import { useFocusWithin, useMergedRef, useWindowEvent } from '@mantine/hooks'
17
15
  import { IconSearch } from '@tabler/icons-react'
18
- import { type ReactNode } from 'react'
19
- import { isString, keys, only } from 'remeda'
20
- import { useLikeC4Model } from '../../../likec4model/useLikeC4Model'
21
- import * as css from './LikeC4Search.css'
22
- import { setSearch, useIsPickViewActive, useSearch } from './state'
23
- import { moveFocusToSearchInput, stopAndPrevent } from './utils'
16
+ import { type ReactNode, memo, useRef } from 'react'
17
+ import { keys } from 'remeda'
18
+ import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
19
+ import { useSearch, useSearchActor } from '../hooks'
20
+ import * as css from './styles.css'
21
+ import { focusToFirstFoundElement, moveFocusToSearchInput, stopAndPrevent } from './utils'
24
22
 
25
23
  function startingWithKind(search: string) {
26
24
  return search.match(/^(k|ki|kin|kind|kind:)$/) != null
@@ -28,31 +26,41 @@ function startingWithKind(search: string) {
28
26
 
29
27
  const SEARCH_PREFIXES = ['#', 'kind:']
30
28
 
31
- export function LikeC4SearchInput() {
32
- const isPickViewActive = useIsPickViewActive()
33
- const likec4model = useLikeC4Model(true)
29
+ export const LikeC4SearchInput = memo(() => {
30
+ const searchActorRef = useSearchActor()
31
+ const likec4model = useLikeC4Model()
32
+ const inputRef = useRef<HTMLInputElement>(null)
33
+ const { ref, focused } = useFocusWithin<HTMLInputElement>()
34
+ const [search, setSearch] = useSearch()
35
+ // const previous = usePreviousDistinct(search)
36
+
37
+ // const [isEmptyForSomeTime, cancel] = useDebouncedValue(
38
+ // focused ? search : 'rstrs',
39
+ // 2000,
40
+ // )
41
+ // // useF
42
+ // // const isEmptyForSomeTime = debouncedSearch === ''
43
+ // useEffect(() => {
44
+ // // cancel()
45
+ // }, [])
46
+
34
47
  const combobox = useCombobox({
35
48
  scrollBehavior: 'smooth',
49
+ loop: false,
36
50
  })
37
- const { ref, focused } = useFocusWithin<HTMLInputElement>()
38
- const search = useSearch()
39
- const previous = usePreviousDistinct(search)
40
- const isFirstRender = useIsFirstRender()
41
-
42
- const [isEmptyForSomeTime] = useDebouncedValue(search === '' && !isFirstRender, isString(previous) ? 500 : 2000)
43
51
 
44
52
  useWindowEvent(
45
53
  'keydown',
46
54
  (event) => {
47
55
  try {
48
56
  if (
49
- !focused && !isPickViewActive && (
57
+ !focused && (
50
58
  event.key === 'Backspace' ||
51
59
  event.key.startsWith('Arrow') ||
52
60
  event.key.match(/^\p{L}$/u)
53
61
  )
54
62
  ) {
55
- moveFocusToSearchInput()
63
+ moveFocusToSearchInput(inputRef.current)
56
64
  }
57
65
  } catch (e) {
58
66
  console.warn(e)
@@ -64,28 +72,27 @@ export function LikeC4SearchInput() {
64
72
  let isExactMatch = false
65
73
 
66
74
  switch (true) {
67
- case search === '' && (isEmptyForSomeTime || SEARCH_PREFIXES.includes(previous ?? '')): {
68
- options = SEARCH_PREFIXES.map((prefix) => (
69
- <ComboboxOption value={prefix} key={prefix}>
70
- <Text component="span" opacity={.5} mr={4} fz={'sm'}>Search by</Text>
71
- {prefix}
72
- </ComboboxOption>
73
- ))
74
- break
75
- }
75
+ // case search === '' && isEmptyForSomeTime === '': {
76
+ // options = SEARCH_PREFIXES.map((prefix) => (
77
+ // <ComboboxOption value={prefix} key={prefix}>
78
+ // <Text component="span" opacity={.5} mr={4} fz={'sm'}>Search by</Text>
79
+ // {prefix}
80
+ // </ComboboxOption>
81
+ // ))
82
+ // break
83
+ // }
76
84
  case search.startsWith('#'): {
77
85
  const searchTag = search.toLocaleLowerCase().slice(1)
78
- const alloptions = likec4model.allTags().filter((tag) => tag.toLocaleLowerCase().includes(searchTag)).sort(
79
- compareNatural,
80
- )
81
- isExactMatch = only(alloptions)?.toLocaleLowerCase() === searchTag
86
+ const alloptions = likec4model.tags.filter((tag) => tag.toLocaleLowerCase().includes(searchTag))
82
87
  if (alloptions.length === 0) {
88
+ isExactMatch = false
83
89
  options = [
84
90
  <ComboboxEmpty key="empty-tags">
85
91
  No tags found
86
92
  </ComboboxEmpty>,
87
93
  ]
88
94
  } else {
95
+ isExactMatch = likec4model.tags.some((tag) => tag.toLocaleLowerCase() === searchTag)
89
96
  options = alloptions.map((tag) => (
90
97
  <ComboboxOption value={`#${tag}`} key={tag}>
91
98
  <Text component="span" opacity={.5} mr={1} fz={'sm'}>#</Text>
@@ -98,18 +105,19 @@ export function LikeC4SearchInput() {
98
105
  case search.startsWith('kind:'):
99
106
  case startingWithKind(search): {
100
107
  const term = search.length > 5 ? search.slice(5).toLocaleLowerCase() : ''
101
- let alloptions = keys(likec4model.$model.specification.elements)
108
+ let alloptions = keys(likec4model.specification.elements)
102
109
  if (term) {
103
110
  alloptions = alloptions.filter((kind) => kind.toLocaleLowerCase().includes(term))
104
- isExactMatch = only(alloptions)?.toLocaleLowerCase() === term
105
111
  }
106
112
  if (alloptions.length === 0) {
113
+ isExactMatch = false
107
114
  options = [
108
115
  <ComboboxEmpty key="empty-kinds">
109
116
  No kinds found
110
117
  </ComboboxEmpty>,
111
118
  ]
112
119
  } else {
120
+ isExactMatch = alloptions.some((kind) => kind.toLocaleLowerCase() === term)
113
121
  options = alloptions.map((kind) => (
114
122
  <ComboboxOption value={`kind:${kind}`} key={kind}>
115
123
  <Text component="span" opacity={.5} mr={1} fz={'sm'}>kind:</Text>
@@ -130,8 +138,8 @@ export function LikeC4SearchInput() {
130
138
  combobox.closeDropdown()
131
139
  // Let react to display filtered elements
132
140
  setTimeout(() => {
133
- document.querySelector<HTMLButtonElement>(`[data-likec4-search] .${css.focusable}`)?.focus()
134
- }, 50)
141
+ focusToFirstFoundElement(inputRef.current)
142
+ }, 350)
135
143
  }
136
144
  }}
137
145
  width={'max-content'}
@@ -146,16 +154,31 @@ export function LikeC4SearchInput() {
146
154
  >
147
155
  <ComboboxTarget>
148
156
  <Input
149
- ref={ref}
150
- id="likec4searchinput"
157
+ ref={useMergedRef(inputRef, ref)}
151
158
  placeholder="Search by title, description or start with # or kind:"
159
+ autoFocus
160
+ data-autofocus
161
+ data-likec4-search-input
152
162
  tabIndex={0}
153
163
  classNames={{
154
164
  input: css.input,
155
165
  }}
156
- size="xl"
166
+ size="lg"
157
167
  value={search}
158
168
  leftSection={<IconSearch style={{ width: rem(20) }} stroke={2} />}
169
+ rightSection={
170
+ <Input.ClearButton
171
+ onClick={(e) => {
172
+ e.stopPropagation()
173
+ const openedWithSearch = searchActorRef.getSnapshot().context.openedWithSearch
174
+ if (search === '' || search === openedWithSearch) {
175
+ searchActorRef.send({ type: 'close' })
176
+ } else {
177
+ setSearch('')
178
+ }
179
+ }} />
180
+ }
181
+ rightSectionPointerEvents="auto"
159
182
  onChange={(event) => {
160
183
  setSearch(event.currentTarget.value)
161
184
  combobox.openDropdown()
@@ -207,6 +230,13 @@ export function LikeC4SearchInput() {
207
230
  combobox.closeDropdown()
208
231
  return
209
232
  }
233
+ if (
234
+ e.key === 'ArrowUp' && combobox.dropdownOpened && search === '' && combobox.getSelectedOptionIndex() === 0
235
+ ) {
236
+ combobox.closeDropdown()
237
+ stopAndPrevent(e)
238
+ return
239
+ }
210
240
  if (
211
241
  e.key === 'ArrowDown' && (
212
242
  !combobox.dropdownOpened ||
@@ -217,7 +247,7 @@ export function LikeC4SearchInput() {
217
247
  ) {
218
248
  combobox.closeDropdown()
219
249
  stopAndPrevent(e)
220
- document.querySelector<HTMLButtonElement>(`[data-likec4-search] .${css.focusable}`)?.focus()
250
+ focusToFirstFoundElement(inputRef.current)
221
251
  return
222
252
  }
223
253
  }} />
@@ -232,4 +262,4 @@ export function LikeC4SearchInput() {
232
262
  </ComboboxDropdown>
233
263
  </Combobox>
234
264
  )
235
- }
265
+ })
@@ -1,8 +1,4 @@
1
- import {
2
- type LikeC4Model,
3
- type Tag,
4
- isDeploymentView,
5
- } from '@likec4/core'
1
+ import type { LikeC4Model } from '@likec4/core/model'
6
2
  import { cx } from '@likec4/styles/css'
7
3
  import { Box } from '@likec4/styles/jsx'
8
4
  import {
@@ -19,12 +15,14 @@ import {
19
15
  } from '@mantine/core'
20
16
  import { IconStack2, IconZoomScan } from '@tabler/icons-react'
21
17
  import * as m from 'motion/react-m'
18
+ import { memo, useRef } from 'react'
22
19
  import { first } from 'remeda'
23
- import { useCurrentViewId } from '../../../hooks/useCurrentViewId'
24
- import { useLikeC4Model } from '../../../likec4model/useLikeC4Model'
20
+ import { useCurrentViewId } from '../../hooks/useCurrentViewId'
21
+ import { useDiagram } from '../../hooks/useDiagram'
22
+ import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
23
+ import { useNormalizedSearch, useSearchActor } from '../hooks'
25
24
  import { buttonsva } from './_shared.css'
26
- import { useCloseSearchAndNavigateTo, useNormalizedSearch } from './state'
27
- import { centerY, moveFocusToSearchInput } from './utils'
25
+ import { centerY, moveFocusToSearchInput, queryAllFocusable } from './utils'
28
26
  import * as styles from './ViewsColumn.css'
29
27
 
30
28
  export const NothingFound = () => (
@@ -33,16 +31,17 @@ export const NothingFound = () => (
33
31
  </Box>
34
32
  )
35
33
 
36
- export function ViewsColumn() {
37
- const search = useNormalizedSearch()
38
- let views = [...useLikeC4Model(true).views()]
34
+ export const ViewsColumn = memo(() => {
35
+ const ref = useRef<HTMLDivElement>(null)
36
+ let views = [...useLikeC4Model().views()]
37
+ let search = useNormalizedSearch()
39
38
  if (search) {
40
39
  if (search.startsWith('kind:')) {
41
40
  views = []
42
41
  } else {
43
42
  views = views.filter(view => {
44
43
  if (search.startsWith('#')) {
45
- return view.tags.some((tag: Tag) => tag.toLocaleLowerCase().includes(search.slice(1)))
44
+ return view.tags.some((tag) => tag.toLocaleLowerCase().includes(search.slice(1)))
46
45
  }
47
46
  return (view.title ?? '' + view.$view.description ?? '').toLocaleLowerCase().includes(search)
48
47
  })
@@ -51,15 +50,14 @@ export function ViewsColumn() {
51
50
 
52
51
  return (
53
52
  <Stack
53
+ ref={ref}
54
54
  renderRoot={props => <m.div layout {...props} />}
55
55
  gap={8}
56
56
  data-likec4-search-views
57
57
  onKeyDown={(e) => {
58
58
  if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
59
59
  const maxY = (e.target as HTMLElement).getBoundingClientRect().y
60
- const elementButtons = [...document.querySelectorAll<HTMLButtonElement>(
61
- `[data-likec4-search-elements] .likec4-element-button`,
62
- )]
60
+ const elementButtons = queryAllFocusable(ref.current, 'elements', '.likec4-element-button')
63
61
  let elementButton = elementButtons.length > 1
64
62
  ? elementButtons.find((el, i, all) => centerY(el) > maxY || i === all.length - 1)
65
63
  : null
@@ -80,7 +78,7 @@ export function ViewsColumn() {
80
78
  tabIndex={-1}
81
79
  onFocus={e => {
82
80
  e.stopPropagation()
83
- moveFocusToSearchInput()
81
+ moveFocusToSearchInput(ref.current)
84
82
  }} />
85
83
  </VisuallyHidden>
86
84
  )}
@@ -95,7 +93,7 @@ export function ViewsColumn() {
95
93
  ))}
96
94
  </Stack>
97
95
  )
98
- }
96
+ })
99
97
 
100
98
  const btn = buttonsva()
101
99
 
@@ -109,9 +107,18 @@ export function ViewButton(
109
107
  & UnstyledButtonProps
110
108
  & ElementProps<'button'>,
111
109
  ) {
112
- const navigateTo = useCloseSearchAndNavigateTo()
110
+ const searchActorRef = useSearchActor()
111
+ const diagram = useDiagram()
113
112
  const currentViewId = useCurrentViewId()
114
113
  const isCurrentView = view.id === currentViewId
114
+
115
+ const navigate = () => {
116
+ searchActorRef.send({ type: 'close' })
117
+ setTimeout(() => {
118
+ diagram.navigateTo(view.id)
119
+ }, 100)
120
+ }
121
+
115
122
  return (
116
123
  <UnstyledButton
117
124
  {...props}
@@ -120,7 +127,7 @@ export function ViewButton(
120
127
  {...isCurrentView && { 'data-disabled': true }}
121
128
  onClick={(e) => {
122
129
  e.stopPropagation()
123
- navigateTo(view.id)
130
+ navigate()
124
131
  }}
125
132
  onKeyDown={createScopedKeydownHandler({
126
133
  siblingSelector: '[data-likec4-view]',
@@ -131,12 +138,12 @@ export function ViewButton(
131
138
  onKeyDown: (e) => {
132
139
  if (e.nativeEvent.code === 'Space') {
133
140
  e.stopPropagation()
134
- navigateTo(view.id)
141
+ navigate()
135
142
  }
136
143
  },
137
144
  })}>
138
145
  <ThemeIcon variant="transparent" className={btn.icon!}>
139
- {isDeploymentView(view)
146
+ {view.isDeploymentView()
140
147
  ? <IconStack2 stroke={1.8} />
141
148
  : <IconZoomScan stroke={1.8} />}
142
149
  </ThemeIcon>
@@ -1,5 +1,4 @@
1
1
  import { css, sva } from '@likec4/styles/css'
2
- import type { ColorToken } from '@likec4/styles/tokens'
3
2
 
4
3
  // export const titleColor = '--title-color'
5
4
  // export const descriptionColor = '---description-color'
@@ -13,21 +12,6 @@ const buttonFocused = css.raw({
13
12
 
14
13
  const _treenodefocus = '.mantine-Tree-node:focus > .mantine-Tree-label &'
15
14
 
16
- function colors(color: ColorToken, hover: ColorToken) {
17
- return {
18
- color,
19
- _groupHover: {
20
- color: hover,
21
- },
22
- _groupFocus: {
23
- color: hover,
24
- },
25
- [_treenodefocus]: {
26
- color: hover,
27
- },
28
- }
29
- }
30
-
31
15
  const button = css.raw({
32
16
  display: 'flex',
33
17
  width: '100%',
@@ -35,7 +19,7 @@ const button = css.raw({
35
19
  rounded: 'sm',
36
20
  padding: `12px 8px 12px 14px`,
37
21
  minHeight: '60px',
38
- gap: '8',
22
+ gap: 8,
39
23
  // alignItems: 'flex-start',
40
24
  // transition: `all 50ms ${easings.inOut}`,
41
25
  border: `1px solid`,
@@ -76,9 +60,14 @@ const iconSize = {
76
60
  }
77
61
 
78
62
  const icon = css.raw({
79
- ...colors('mantine.colors.dimmed', 'mantine.colors.primary[0]'),
80
- _light: {
81
- ...colors('mantine.colors.gray[6]', 'mantine.colors.primary[3]'),
63
+ color: {
64
+ base: 'mantine.colors.dimmed',
65
+ _light: 'mantine.colors.gray[5]',
66
+ _groupHover: 'mantine.colors.primary[0]',
67
+ _groupFocus: 'mantine.colors.primary[0]',
68
+ },
69
+ [_treenodefocus]: {
70
+ color: 'mantine.colors.primary[0]',
82
71
  },
83
72
  flex: `0 0 ${iconSize.ref}`,
84
73
  height: iconSize.ref,
@@ -116,23 +105,23 @@ const title = css.raw({
116
105
  opacity: 0.4,
117
106
  },
118
107
 
119
- // color: 'mantine.colors.dark[1]',
120
- // _groupHover: {
121
- // color: 'mantine.colors.primary[1]',
122
- // },
123
- // _groupFocus: {
124
- // color: 'mantine.colors.primary[1]',
125
- // },
126
- ...colors('mantine.colors.dark[1]', 'mantine.colors.primary[1]'),
127
- _light: {
128
- ...colors('mantine.colors.gray[6]', 'mantine.colors.primary[3]'),
129
- // color: `mantine.colors.gray[6]`,
130
- // _groupHover: {
131
- // color: 'mantine.colors.primary[3]',
132
- // },
133
- // _groupFocus: {
134
- // color: 'mantine.colors.primary[3]',
135
- // },
108
+ color: {
109
+ base: 'mantine.colors.dark[1]',
110
+ _light: 'mantine.colors.gray[7]',
111
+ _groupHover: {
112
+ base: 'mantine.colors.primary[1]',
113
+ _light: 'mantine.colors.white',
114
+ },
115
+ _groupFocus: {
116
+ base: 'mantine.colors.primary[1]',
117
+ _light: 'mantine.colors.white',
118
+ },
119
+ },
120
+ [_treenodefocus]: {
121
+ color: {
122
+ base: 'mantine.colors.primary[1]',
123
+ _light: 'mantine.colors.white',
124
+ },
136
125
  },
137
126
  })
138
127
  const descriptionColor = css.raw({
@@ -1,44 +1,17 @@
1
1
  import { css } from '@likec4/styles/css'
2
2
  export { focusable } from './_shared.css'
3
3
 
4
- export const backdrop = css({
5
- position: 'fixed',
6
- zIndex: 900,
7
- inset: '0px',
8
- backgroundColor: '[rgb(34 34 34 / 0.95)]',
9
- backdropFilter: 'auto',
10
- backdropBlur: '10px',
11
- _light: {
12
- backgroundColor: '[rgb(255 255 255 / 0.92 )]',
13
- },
14
- })
15
- export const root = css({
16
- containerName: 'likec4-search',
17
- containerType: 'size',
18
- position: 'fixed',
19
- zIndex: 901,
20
- top: 0,
21
- left: 0,
22
- width: '100%',
23
- height: '100%',
24
- maxHeight: '100vh',
25
- overflow: 'hidden',
26
- display: 'flex',
27
- flexDirection: 'column',
28
- justifyContent: 'stretch',
29
- gap: 'md',
30
- paddingTop: '32px',
31
- paddingLeft: '16px',
32
- paddingRight: '16px',
33
- paddingBottom: 0,
34
- })
35
4
  export const input = css({
36
5
  border: 'transparent',
37
- background: 'transparent',
38
- _focusWithin: {
39
- background: `mantine.colors.gray[3]/55`,
40
- _dark: {
41
- background: `mantine.colors.dark[4]/55`,
6
+ background: {
7
+ base: 'transparent',
8
+ _focusWithin: {
9
+ base: `mantine.colors.gray[4]/55 !important`,
10
+ _dark: `mantine.colors.dark[5]/55 !important`,
11
+ },
12
+ _groupHover: {
13
+ base: 'mantine.colors.gray[3]/35',
14
+ _dark: 'mantine.colors.dark[5]/35',
42
15
  },
43
16
  },
44
17
  })
@@ -0,0 +1,64 @@
1
+ import { isFunction } from 'remeda'
2
+ import { focusable } from './_shared.css'
3
+
4
+ export function stopAndPrevent(e: React.KeyboardEvent | KeyboardEvent) {
5
+ e.stopPropagation()
6
+ e.preventDefault()
7
+ return
8
+ }
9
+
10
+ export function centerY(element: HTMLElement) {
11
+ const rect = element.getBoundingClientRect()
12
+ const y = rect.y + Math.floor(rect.height / 2)
13
+ return y
14
+ }
15
+
16
+ export function moveFocusToSearchInput(from: HTMLElement | null | undefined) {
17
+ if (!from) {
18
+ console.error('moveFocusToSearchInput: from is null or undefined')
19
+ return
20
+ }
21
+ const root = from.getRootNode() as Document | ShadowRoot
22
+ if (!isFunction(root.querySelector)) {
23
+ console.error('moveFocusToSearchInput: root.querySelector is not a function')
24
+ return
25
+ }
26
+ let input = root.querySelector('[data-likec4-search-input]') as HTMLInputElement | null
27
+ if (input) {
28
+ const length = input.value.length
29
+ input.focus()
30
+ input.setSelectionRange(length, length)
31
+ }
32
+ }
33
+
34
+ export function focusToFirstFoundElement(from: HTMLElement | null | undefined) {
35
+ if (!from) {
36
+ console.error('focusToFirstFoundElement: from is null or undefined')
37
+ return
38
+ }
39
+ const root = from.getRootNode() as Document | ShadowRoot
40
+ if (!isFunction(root.querySelector)) {
41
+ console.error('focusToFirstFoundElement: root.querySelector is not a function')
42
+ return
43
+ }
44
+ let firstFoundElement = root.querySelector<HTMLButtonElement>(`[data-likec4-search] .${focusable}`)
45
+ firstFoundElement?.focus()
46
+ }
47
+
48
+ export function queryAllFocusable(
49
+ from: HTMLElement | null | undefined,
50
+ where: 'elements' | 'views',
51
+ selector: string = `.${focusable}`,
52
+ ): HTMLButtonElement[] {
53
+ if (!from) {
54
+ console.error('queryAllFocusable: from is null or undefined')
55
+ return []
56
+ }
57
+ const root = from.getRootNode() as Document | ShadowRoot
58
+ if (!isFunction(root.querySelectorAll)) {
59
+ console.error('queryAllFocusable: root.querySelectorAll is not a function')
60
+ return []
61
+ }
62
+ const elements = root.querySelectorAll<HTMLButtonElement>(`[data-likec4-search-${where}] ${selector}`)
63
+ return [...elements]
64
+ }