@likec4/diagram 1.51.0 → 1.52.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.
@@ -36,13 +36,15 @@ const Logo = withContext(LogoButton, "logo");
36
36
  const Label = withContext(m.div, "label", {
37
37
  shouldForwardProp
38
38
  });
39
- withContext(m.div, "dropdown", {
39
+ const Dropdown = withContext(m.div, "dropdown", {
40
40
  shouldForwardProp
41
41
  });
42
42
  const NavigationPanel = {
43
43
  Root,
44
44
  Body,
45
45
  Logo,
46
- Label};
46
+ Label,
47
+ Dropdown
48
+ };
47
49
 
48
50
  export { NavigationPanel };
@@ -0,0 +1,79 @@
1
+ import { css } from '@likec4/styles/css';
2
+
3
+ const root = css({
4
+ height: "30px",
5
+ paddingLeft: "sm",
6
+ paddingRight: "1",
7
+ borderRadius: "sm",
8
+ // TODO
9
+ // color: fallbackVar('var(--search-color)', 'mantine.colors.placeholder)',
10
+ border: "1px solid",
11
+ borderColor: {
12
+ base: "default.border",
13
+ _light: "mantine.colors.gray[4]",
14
+ _dark: "mantine.colors.dark[4]",
15
+ _hover: "default.border"
16
+ },
17
+ cursor: "pointer",
18
+ background: {
19
+ base: "default",
20
+ _light: "white",
21
+ _dark: "mantine.colors.dark[6]",
22
+ _hover: "default.hover"
23
+ },
24
+ width: "100%",
25
+ "& .tabler-icon": {
26
+ color: "text"
27
+ },
28
+ transition: {
29
+ base: "fast",
30
+ _whenPanning: "none !important"
31
+ },
32
+ boxShadow: {
33
+ base: "xs",
34
+ _hover: "sm",
35
+ _whenPanning: "none !important"
36
+ }
37
+ });
38
+ const placeholder = css({
39
+ fontSize: "sm",
40
+ // mantine.fontSizes.sm,
41
+ fontWeight: "medium",
42
+ paddingRight: "2.5",
43
+ // 10px
44
+ color: "text.placeholder",
45
+ flex: "1",
46
+ sm: {
47
+ paddingRight: "[30px]"
48
+ },
49
+ md: {
50
+ paddingRight: "[50px]"
51
+ }
52
+ });
53
+ const shortcut = css({
54
+ fontSize: "11px",
55
+ fontWeight: "bold",
56
+ lineHeight: 1,
57
+ padding: "[4px 7px]",
58
+ borderRadius: "sm",
59
+ border: "1px solid",
60
+ transition: "fast",
61
+ _light: {
62
+ color: "mantine.colors.gray[7]",
63
+ borderColor: "mantine.colors.gray[2]"
64
+ },
65
+ _dark: {
66
+ color: "mantine.colors.dark[0]",
67
+ borderColor: "mantine.colors.dark[7]"
68
+ },
69
+ backgroundColor: {
70
+ _light: "mantine.colors.gray[2]/70",
71
+ _dark: "mantine.colors.dark[8]/70",
72
+ _groupHover: {
73
+ _light: "mantine.colors.gray[2]",
74
+ _dark: "mantine.colors.dark[8]"
75
+ }
76
+ }
77
+ });
78
+
79
+ export { placeholder, root, shortcut };
@@ -0,0 +1,17 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { cx } from '@likec4/styles/css';
3
+ import { UnstyledButton, Group, Text } from '@mantine/core';
4
+ import { isMacOs } from '@xyflow/system';
5
+ import { placeholder, shortcut, root } from './SearchControl.css.js';
6
+ import IconSearch from '../node_modules/.pnpm/@tabler_icons-react@3.36.1_react@19.2.4/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.js';
7
+
8
+ function SearchControl({ className, ...others }) {
9
+ const isMac = isMacOs();
10
+ return /* @__PURE__ */ jsx(UnstyledButton, { ...others, className: cx("group", root, className), children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
11
+ /* @__PURE__ */ jsx(IconSearch, { style: { width: "15px", height: "15px" }, stroke: 2 }),
12
+ /* @__PURE__ */ jsx(Text, { component: "div", className: placeholder, children: "Search" }),
13
+ /* @__PURE__ */ jsx(Text, { component: "div", className: shortcut, children: isMac ? "⌘ + K" : "Ctrl + K" })
14
+ ] }) });
15
+ }
16
+
17
+ export { SearchControl };
@@ -12,3 +12,4 @@ export type { OverlayProps } from '../overlays/overlay/Overlay';
12
12
  export { PortalToContainer } from '../components/PortalToContainer';
13
13
  export { ShadowRoot } from '../shadowroot/ShadowRoot';
14
14
  export { FramerMotionConfig } from '../context/FramerMotionConfig';
15
+ export { NavigationPanel } from '../components/NavigationPanel';
@@ -5,6 +5,7 @@ export { Overlay } from '../overlays/overlay/Overlay.js';
5
5
  export { PortalToContainer } from '../components/PortalToContainer.js';
6
6
  export { ShadowRoot } from '../shadowroot/ShadowRoot.js';
7
7
  export { FramerMotionConfig } from '../context/FramerMotionConfig.js';
8
+ export { NavigationPanel } from '../components/NavigationPanel.js';
8
9
  export { CompoundActionButton } from '../base-primitives/compound/CompoundActionButton.js';
9
10
  export { CompoundActions } from '../likec4diagram/custom/nodes/CompoundActions.js';
10
11
  export { CompoundDeploymentNode, CompoundDetailsButtonWithHandler, CompoundElementNode, DeploymentNode, ElementDetailsButtonWithHandler, ElementNode, ViewGroupNode } from '../likec4diagram/custom/nodes/nodes.js';
package/dist/index.d.ts CHANGED
@@ -19,3 +19,6 @@ export { ReactLikeC4, type ReactLikeC4Props } from './ReactLikeC4';
19
19
  export { useCurrentView, useCurrentViewId, } from './hooks/useCurrentView';
20
20
  export { pickViewBounds } from './utils/view-bounds';
21
21
  export { LikeC4ProjectsOverview, type LikeC4ProjectsOverviewProps, } from './LikeC4ProjectsOverview';
22
+ export { normalizeSearch, SearchContext, type SearchContextValue, useSearchContext, } from './search/SearchContext';
23
+ export { bodyCss as searchBodyCss, dialogCss as searchDialogCss, SearchPanelContent, } from './search/Search';
24
+ export { SearchControl } from './components/SearchControl';
package/dist/index.js CHANGED
@@ -16,5 +16,8 @@ export { ReactLikeC4 } from './ReactLikeC4.js';
16
16
  export { useCurrentView, useCurrentViewId } from './hooks/useCurrentView.js';
17
17
  export { pickViewBounds } from './utils/view-bounds.js';
18
18
  export { LikeC4ProjectsOverview } from './LikeC4ProjectsOverview.js';
19
+ export { SearchContext, normalizeSearch, useSearchContext } from './search/SearchContext.js';
20
+ export { SearchPanelContent, bodyCss as searchBodyCss, dialogCss as searchDialogCss } from './search/Search.js';
21
+ export { SearchControl } from './components/SearchControl.js';
19
22
  export { useDiagram } from './hooks/safeContext.js';
20
23
  export { useOptionalCurrentViewModel } from './context/LikeC4ModelContext.js';
@@ -1,2 +1,10 @@
1
1
  import { MemoExoticComponent } from 'react';
2
+ export declare const dialogCss: string;
3
+ export declare const bodyCss: string;
4
+ export declare const scrollAreaCss: string;
2
5
  export declare const Search: MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
6
+ /**
7
+ * The shared search panel content used by both diagram and overview search.
8
+ * Must be rendered inside a SearchContext.Provider.
9
+ */
10
+ export declare const SearchPanelContent: MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -1,12 +1,12 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { css } from '@likec4/styles/css';
3
3
  import { Box, VStack } from '@likec4/styles/jsx';
4
4
  import { Group, Grid, GridCol, Title, ScrollArea } from '@mantine/core';
5
5
  import { useHotkeys } from '@mantine/hooks';
6
6
  import { useTimeoutEffect } from '@react-hookz/web';
7
7
  import { useSelector } from '@xstate/react';
8
- import { AnimatePresence, usePresence, LayoutGroup } from 'motion/react';
9
- import { memo, useRef, useMemo, useEffect, Suspense } from 'react';
8
+ import { LayoutGroup, AnimatePresence, usePresence } from 'motion/react';
9
+ import { memo, useRef, Suspense, useMemo, useEffect } from 'react';
10
10
  import { isTruthy } from 'remeda';
11
11
  import { createEmptyActor } from 'xstate';
12
12
  import { useCallbackRef } from '../hooks/useCallbackRef.js';
@@ -16,27 +16,20 @@ import { ElementsColumn } from './components/ElementsColumn.js';
16
16
  import { PickView } from './components/PickView.js';
17
17
  import { SearchByTags } from './components/SearchByTags.js';
18
18
  import { LikeC4SearchInput } from './components/SearchInput.js';
19
- import { focusToFirstFoundElement, moveFocusToSearchInput } from './components/utils.js';
19
+ import { moveFocusToSearchInput, focusToFirstFoundElement } from './components/utils.js';
20
20
  import { ViewsColumn } from './components/ViewsColum.js';
21
+ import { useSearchContext } from './SearchContext.js';
22
+ import { XStateSearchAdapter } from './XStateSearchAdapter.js';
21
23
 
22
- const dialog = css({
24
+ const dialogCss = css({
23
25
  backgroundColor: `[rgb(34 34 34 / var(--_opacity, 95%))]`,
24
26
  _light: {
25
27
  backgroundColor: `[rgb(250 250 250 / var(--_opacity, 95%))]`
26
28
  },
27
29
  backdropFilter: "auto",
28
30
  backdropBlur: "var(--_blur, 10px)"
29
- // base: `[rgb(34 34 34 / var(${backdropOpacity}))]`,
30
- // _light: `[rgb(255 255 255/ var(${backdropOpacity}))]`,
31
- // },
32
31
  });
33
- const body = css({
34
- // containerName: 'likec4-search',
35
- // containerType: 'size',
36
- // position: 'fixed',
37
- // zIndex: 901,
38
- // top: '0',
39
- // left: '0',
32
+ const bodyCss = css({
40
33
  width: "100%",
41
34
  height: "100%",
42
35
  maxHeight: "100vh",
@@ -50,12 +43,8 @@ const body = css({
50
43
  paddingRight: "md",
51
44
  paddingBottom: "sm",
52
45
  background: "transparent"
53
- // backgroundColor: {
54
- // _dark: `[rgb(34 34 34 / 0.95)]`,
55
- // _light: `[rgb(255 255 255/ 0.95)]`,
56
- // },
57
46
  });
58
- const scrollArea = css({
47
+ const scrollAreaCss = css({
59
48
  height: [
60
49
  "100%",
61
50
  "100cqh"
@@ -105,38 +94,20 @@ const Search = memo(() => {
105
94
  opacity: 0.9
106
95
  },
107
96
  classes: {
108
- dialog,
109
- body
97
+ dialog: dialogCss,
98
+ body: bodyCss
110
99
  },
111
100
  openDelay: 0,
112
101
  onClose: close,
113
102
  "data-likec4-search": "true",
114
- children: /* @__PURE__ */ jsx(SearchOverlayBody, { searchActorRef })
103
+ children: /* @__PURE__ */ jsx(XStateSearchAdapter, { searchActorRef, children: /* @__PURE__ */ jsx(SearchOverlayBody, { searchActorRef }) })
115
104
  }
116
105
  ) });
117
106
  });
118
107
  Search.displayName = "Search";
119
- const selectPickViewFor = (s) => s.context.pickViewFor;
120
- const SearchOverlayBody = memo(({ searchActorRef }) => {
108
+ const SearchPanelContent = memo(() => {
121
109
  const ref = useRef(null);
122
- const pickViewFor = useSelector(searchActorRef, selectPickViewFor);
123
- useTimeoutEffect(() => {
124
- if (isTruthy(searchActorRef.getSnapshot().context.openedWithSearch)) {
125
- focusToFirstFoundElement(ref.current);
126
- }
127
- }, 150);
128
- const [isPresent, safeToRemove] = usePresence();
129
- useEffect(() => {
130
- if (isPresent) {
131
- return;
132
- }
133
- safeToRemove();
134
- try {
135
- searchActorRef.send({ type: "animation.presence.end" });
136
- } catch (e) {
137
- console.debug("SearchOverlayBody: animation.presence.end failed", e);
138
- }
139
- }, [isPresent, searchActorRef, safeToRemove]);
110
+ const { pickViewFor } = useSearchContext();
140
111
  return /* @__PURE__ */ jsxs(Box, { ref, display: "contents", children: [
141
112
  /* @__PURE__ */ jsx(
142
113
  Group,
@@ -171,7 +142,7 @@ const SearchOverlayBody = memo(({ searchActorRef }) => {
171
142
  ScrollArea,
172
143
  {
173
144
  type: "scroll",
174
- className: scrollArea,
145
+ className: scrollAreaCss,
175
146
  pr: "xs",
176
147
  scrollbars: "y",
177
148
  children: /* @__PURE__ */ jsx(LayoutGroup, { id: "likec4-search-elements", children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(ElementsColumn, {}) }) })
@@ -181,7 +152,7 @@ const SearchOverlayBody = memo(({ searchActorRef }) => {
181
152
  ScrollArea,
182
153
  {
183
154
  type: "scroll",
184
- className: scrollArea,
155
+ className: scrollAreaCss,
185
156
  pr: "xs",
186
157
  scrollbars: "y",
187
158
  children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(LayoutGroup, { id: "likec4-search-views", children: /* @__PURE__ */ jsx(ViewsColumn, {}) }) })
@@ -190,8 +161,30 @@ const SearchOverlayBody = memo(({ searchActorRef }) => {
190
161
  ]
191
162
  }
192
163
  ),
193
- pickViewFor && /* @__PURE__ */ jsx(PickView, { searchActorRef, elementFqn: pickViewFor })
164
+ pickViewFor && /* @__PURE__ */ jsx(PickView, { elementFqn: pickViewFor })
194
165
  ] });
195
166
  });
167
+ SearchPanelContent.displayName = "SearchPanelContent";
168
+ const SearchOverlayBody = memo(function SearchOverlayBody2({ searchActorRef }) {
169
+ const ref = useRef(null);
170
+ useTimeoutEffect(() => {
171
+ if (isTruthy(searchActorRef.getSnapshot().context.openedWithSearch)) {
172
+ focusToFirstFoundElement(ref.current);
173
+ }
174
+ }, 150);
175
+ const [isPresent, safeToRemove] = usePresence();
176
+ useEffect(() => {
177
+ if (isPresent) {
178
+ return;
179
+ }
180
+ safeToRemove();
181
+ try {
182
+ searchActorRef.send({ type: "animation.presence.end" });
183
+ } catch (e) {
184
+ console.debug("SearchOverlayBody: animation.presence.end failed", e);
185
+ }
186
+ }, [isPresent, searchActorRef, safeToRemove]);
187
+ return /* @__PURE__ */ jsx(Box, { ref, display: "contents", children: /* @__PURE__ */ jsx(SearchPanelContent, {}) });
188
+ });
196
189
 
197
- export { Search };
190
+ export { Search, SearchPanelContent, bodyCss, dialogCss, scrollAreaCss };
@@ -0,0 +1,21 @@
1
+ import { Fqn, ViewId } from '@likec4/core/types';
2
+ import { Context } from 'react';
3
+ export interface SearchContextValue {
4
+ searchValue: string;
5
+ setSearchValue: (value: string) => void;
6
+ normalizedSearch: string;
7
+ navigateTo: (viewId: ViewId, focusOnElement?: Fqn) => void;
8
+ openPickView: (elementFqn: Fqn) => void;
9
+ closePickView: () => void;
10
+ pickViewFor: Fqn | null;
11
+ close: () => void;
12
+ currentViewId: string | null;
13
+ openedWithSearch: string | null;
14
+ }
15
+ export declare const SearchContext: Context<SearchContextValue | null>;
16
+ export declare function useSearchContext(): SearchContextValue;
17
+ /**
18
+ * Normalize a raw search string for comparison.
19
+ * Extracted as a pure function so both XState and standalone adapters can reuse it.
20
+ */
21
+ export declare function normalizeSearch(value: string): string;
@@ -0,0 +1,20 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ const SearchContext = createContext(null);
4
+ function useSearchContext() {
5
+ const ctx = useContext(SearchContext);
6
+ if (!ctx) {
7
+ throw new Error("useSearchContext must be used within a SearchContext.Provider");
8
+ }
9
+ return ctx;
10
+ }
11
+ function normalizeSearch(value) {
12
+ if (value === "") return value;
13
+ const v = value.trim().toLowerCase();
14
+ if (v.startsWith("#") && v.length <= 2) {
15
+ return "";
16
+ }
17
+ return v.length > 1 ? v : "";
18
+ }
19
+
20
+ export { SearchContext, normalizeSearch, useSearchContext };
@@ -0,0 +1,5 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import { SearchActorRef } from './searchActor';
3
+ export declare function XStateSearchAdapter({ searchActorRef, children, }: PropsWithChildren<{
4
+ searchActorRef: SearchActorRef;
5
+ }>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useSelector } from '@xstate/react';
3
+ import { useMemo } from 'react';
4
+ import { useCallbackRef } from '../hooks/useCallbackRef.js';
5
+ import { useCurrentViewId } from '../hooks/useCurrentView.js';
6
+ import { normalizeSearch, SearchContext } from './SearchContext.js';
7
+
8
+ const selectSearchValue = (s) => s.context.searchValue;
9
+ const selectPickViewFor = (s) => s.context.pickViewFor;
10
+ const selectOpenedWithSearch = (s) => s.context.openedWithSearch;
11
+ function XStateSearchAdapter({
12
+ searchActorRef,
13
+ children
14
+ }) {
15
+ const searchValue = useSelector(searchActorRef, selectSearchValue);
16
+ const pickViewFor = useSelector(searchActorRef, selectPickViewFor);
17
+ const openedWithSearch = useSelector(searchActorRef, selectOpenedWithSearch);
18
+ const currentViewId = useCurrentViewId();
19
+ const setSearchValue = useCallbackRef((search) => {
20
+ searchActorRef.send({ type: "change.search", search });
21
+ });
22
+ const navigateTo = useCallbackRef((viewId, focusOnElement) => {
23
+ searchActorRef.send({
24
+ type: "navigate.to",
25
+ viewId,
26
+ focusOnElement
27
+ });
28
+ });
29
+ const openPickView = useCallbackRef((elementFqn) => {
30
+ searchActorRef.send({ type: "pickview.open", elementFqn });
31
+ });
32
+ const closePickView = useCallbackRef(() => {
33
+ searchActorRef.send({ type: "pickview.close" });
34
+ });
35
+ const close = useCallbackRef(() => {
36
+ searchActorRef.send({ type: "close" });
37
+ });
38
+ const value = useMemo(() => ({
39
+ searchValue,
40
+ setSearchValue,
41
+ normalizedSearch: normalizeSearch(searchValue),
42
+ navigateTo,
43
+ openPickView,
44
+ closePickView,
45
+ pickViewFor,
46
+ close,
47
+ currentViewId,
48
+ openedWithSearch
49
+ }), [
50
+ searchValue,
51
+ pickViewFor,
52
+ currentViewId,
53
+ openedWithSearch,
54
+ // stable refs from useCallbackRef — listed for exhaustive-deps lint rule
55
+ setSearchValue,
56
+ navigateTo,
57
+ openPickView,
58
+ closePickView,
59
+ close
60
+ ]);
61
+ return /* @__PURE__ */ jsx(SearchContext.Provider, { value, children });
62
+ }
63
+
64
+ export { XStateSearchAdapter };
@@ -9,7 +9,8 @@ import { pipe, filter, hasAtLeast, reduce, only, first, last } from 'remeda';
9
9
  import { IconOrShapeRenderer } from '../../context/IconRenderer.js';
10
10
  import { useCallbackRef } from '../../hooks/useCallbackRef.js';
11
11
  import { useLikeC4Model } from '../../hooks/useLikeC4Model.js';
12
- import { useNormalizedSearch, useSearchActor } from '../hooks.js';
12
+ import { useNormalizedSearch } from '../hooks.js';
13
+ import { useSearchContext } from '../SearchContext.js';
13
14
  import { focusable, buttonsva } from './_shared.css.js';
14
15
  import { treeSubtree, treeLabel, treeRoot, treeNode, elementIcon, elementExpandIcon, elementTitleAndId, elementId, elementViewsCount } from './ElementsColumn.css.js';
15
16
  import { stopAndPrevent, moveFocusToSearchInput, queryAllFocusable, centerY } from './utils.js';
@@ -279,7 +280,7 @@ function ElementTreeNode({ node, elementProps, hasChildren, expanded, handleClic
279
280
  ] });
280
281
  }
281
282
  function useHandleElementSelection() {
282
- const searchActorRef = useSearchActor();
283
+ const ctx = useSearchContext();
283
284
  return useCallbackRef((element) => {
284
285
  const views = [...element.views()];
285
286
  if (views.length === 0) {
@@ -288,14 +289,10 @@ function useHandleElementSelection() {
288
289
  const elementFqn = element.id;
289
290
  const onlyOneViewId = only(views)?.id;
290
291
  if (!onlyOneViewId) {
291
- searchActorRef.send({ type: "pickview.open", elementFqn });
292
+ ctx.openPickView(elementFqn);
292
293
  return;
293
294
  }
294
- searchActorRef.send({
295
- type: "navigate.to",
296
- viewId: onlyOneViewId,
297
- focusOnElement: elementFqn
298
- });
295
+ ctx.navigateTo(onlyOneViewId, elementFqn);
299
296
  });
300
297
  }
301
298
 
@@ -1,6 +1,4 @@
1
1
  import { Fqn } from '@likec4/core/types';
2
- import { SearchActorRef } from '../searchActor';
3
- export declare function PickView({ searchActorRef, elementFqn, }: {
4
- searchActorRef: SearchActorRef;
2
+ export declare function PickView({ elementFqn, }: {
5
3
  elementFqn: Fqn;
6
4
  }): import("react/jsx-runtime").JSX.Element;
@@ -2,17 +2,17 @@ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { FocusTrap, Group, Title, ActionIcon, ScrollAreaAutosize, Stack } from '@mantine/core';
3
3
  import { useWindowEvent, useCallbackRef } from '@mantine/hooks';
4
4
  import * as m from 'motion/react-m';
5
- import { useCurrentViewId } from '../../hooks/useCurrentView.js';
6
5
  import { useLikeC4Model } from '../../hooks/useLikeC4Model.js';
6
+ import { useSearchContext } from '../SearchContext.js';
7
7
  import { pickviewBackdrop, pickview, pickviewGroup } from './styles.css.js';
8
8
  import { ViewButton } from './ViewsColum.js';
9
9
  import IconX from '../../node_modules/.pnpm/@tabler_icons-react@3.36.1_react@19.2.4/node_modules/@tabler/icons-react/dist/esm/icons/IconX.js';
10
10
 
11
11
  function PickView({
12
- searchActorRef,
13
12
  elementFqn
14
13
  }) {
15
- const currentViewId = useCurrentViewId();
14
+ const ctx = useSearchContext();
15
+ const currentViewId = ctx.currentViewId ?? "";
16
16
  const element = useLikeC4Model().element(elementFqn);
17
17
  const scoped = [];
18
18
  const others = [];
@@ -24,7 +24,7 @@ function PickView({
24
24
  }
25
25
  }
26
26
  const closePickView = () => {
27
- searchActorRef.send({ type: "pickview.close" });
27
+ ctx.closePickView();
28
28
  };
29
29
  useWindowEvent(
30
30
  "keydown",
@@ -4,7 +4,7 @@ import { useFocusWithin, useWindowEvent, useMergedRef } from '@mantine/hooks';
4
4
  import { memo, useRef } from 'react';
5
5
  import { keys } from 'remeda';
6
6
  import { useLikeC4Model } from '../../hooks/useLikeC4Model.js';
7
- import { useSearchActor, useSearch } from '../hooks.js';
7
+ import { useSearchContext } from '../SearchContext.js';
8
8
  import { input } from './styles.css.js';
9
9
  import { moveFocusToSearchInput, stopAndPrevent, focusToFirstFoundElement } from './utils.js';
10
10
  import IconSearch from '../../node_modules/.pnpm/@tabler_icons-react@3.36.1_react@19.2.4/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.js';
@@ -14,11 +14,11 @@ function startingWithKind(search) {
14
14
  }
15
15
  const SEARCH_PREFIXES = ["#", "kind:"];
16
16
  const LikeC4SearchInput = memo(() => {
17
- const searchActorRef = useSearchActor();
17
+ const searchCtx = useSearchContext();
18
18
  const likec4model = useLikeC4Model();
19
19
  const inputRef = useRef(null);
20
20
  const { ref, focused } = useFocusWithin();
21
- const [search, setSearch] = useSearch();
21
+ const { searchValue: search, setSearchValue: setSearch } = searchCtx;
22
22
  const combobox = useCombobox({
23
23
  scrollBehavior: "smooth",
24
24
  loop: false
@@ -129,9 +129,8 @@ const LikeC4SearchInput = memo(() => {
129
129
  {
130
130
  onClick: (e) => {
131
131
  e.stopPropagation();
132
- const openedWithSearch = searchActorRef.getSnapshot().context.openedWithSearch;
133
- if (search === "" || search === openedWithSearch) {
134
- searchActorRef.send({ type: "close" });
132
+ if (search === "" || search === searchCtx.openedWithSearch) {
133
+ searchCtx.close();
135
134
  } else {
136
135
  setSearch("");
137
136
  }
@@ -6,9 +6,9 @@ import { Stack, VisuallyHidden, UnstyledButton, ThemeIcon, Group, Highlight, Bad
6
6
  import * as m from 'motion/react-m';
7
7
  import { memo, useRef, useMemo } from 'react';
8
8
  import { first } from 'remeda';
9
- import { useCurrentViewId } from '../../hooks/useCurrentView.js';
10
9
  import { useLikeC4Model } from '../../hooks/useLikeC4Model.js';
11
- import { useNormalizedSearch, useSearchActor } from '../hooks.js';
10
+ import { useNormalizedSearch } from '../hooks.js';
11
+ import { useSearchContext } from '../SearchContext.js';
12
12
  import { emptyBoX, focusable, buttonsva } from './_shared.css.js';
13
13
  import { moveFocusToSearchInput, queryAllFocusable, centerY } from './utils.js';
14
14
  import { viewButton } from './ViewsColumn.css.js';
@@ -17,7 +17,7 @@ import IconZoomScan from '../../node_modules/.pnpm/@tabler_icons-react@3.36.1_re
17
17
 
18
18
  const NothingFound = () => /* @__PURE__ */ jsx(Box, { className: emptyBoX, children: "Nothing found" });
19
19
  const useFoundViews = () => {
20
- const currentViewId = useCurrentViewId();
20
+ const { currentViewId } = useSearchContext();
21
21
  const likec4model = useLikeC4Model();
22
22
  const search = useNormalizedSearch();
23
23
  return useMemo(() => {
@@ -79,7 +79,7 @@ const ViewsColumn = memo(() => {
79
79
  ViewButton,
80
80
  {
81
81
  view,
82
- currentViewId,
82
+ currentViewId: currentViewId ?? "",
83
83
  search,
84
84
  tabIndex: i === 0 ? 0 : -1
85
85
  }
@@ -90,15 +90,11 @@ const ViewsColumn = memo(() => {
90
90
  });
91
91
  const btn = buttonsva();
92
92
  function ViewButton({ className, view, loop = false, search, focusOnElement, currentViewId, ...props }) {
93
- const searchActorRef = useSearchActor();
93
+ const ctx = useSearchContext();
94
94
  const nextViewId = view.id;
95
95
  const isCurrentView = nextViewId === currentViewId;
96
96
  const navigate = () => {
97
- searchActorRef.send({
98
- type: "navigate.to",
99
- viewId: nextViewId,
100
- focusOnElement
101
- });
97
+ ctx.navigateTo(nextViewId, focusOnElement);
102
98
  };
103
99
  return /* @__PURE__ */ jsxs(
104
100
  UnstyledButton,
@@ -1,5 +1,3 @@
1
- import { SearchActorRef } from './searchActor';
2
- export declare function useSearchActor(): SearchActorRef;
3
1
  export declare function useSearch(): [string, (search: string) => void];
4
2
  export declare function useNormalizedSearch(): string;
5
3
  export declare function useUpdateSearch(): (search: string) => void;
@@ -1,41 +1,15 @@
1
- import { useSelector } from '@xstate/react';
2
1
  import { useCallback, useDeferredValue } from 'react';
3
- import { useSearchActorRef } from '../hooks/useSearchActor.js';
2
+ import { useSearchContext } from './SearchContext.js';
4
3
 
5
- function useSearchActor() {
6
- const searchActorRef = useSearchActorRef();
7
- if (!searchActorRef) {
8
- throw new Error("Search actor not found");
9
- }
10
- return searchActorRef;
11
- }
12
- const selectSearchValue = (s) => s.context.searchValue;
13
- function useSearch() {
14
- const searchActorRef = useSearchActor();
15
- const searchValue = useSelector(searchActorRef, selectSearchValue);
16
- const updateSearch = useCallback((search) => {
17
- searchActorRef.send({ type: "change.search", search });
18
- }, [searchActorRef]);
19
- return [searchValue, updateSearch];
20
- }
21
- const selectNormalizedSearchValue = (s) => {
22
- let v = selectSearchValue(s);
23
- if (v === "") return v;
24
- v = v.trim().toLowerCase();
25
- if (v.startsWith("#") && v.length <= 2) {
26
- return "";
27
- }
28
- return v.length > 1 ? v : "";
29
- };
30
4
  function useNormalizedSearch() {
31
- const searchActorRef = useSearchActor();
32
- return useDeferredValue(useSelector(searchActorRef, selectNormalizedSearchValue));
5
+ const ctx = useSearchContext();
6
+ return useDeferredValue(ctx.normalizedSearch);
33
7
  }
34
8
  function useUpdateSearch() {
35
- const searchActorRef = useSearchActor();
9
+ const { setSearchValue } = useSearchContext();
36
10
  return useCallback((search) => {
37
- searchActorRef.send({ type: "change.search", search });
38
- }, [searchActorRef]);
11
+ setSearchValue(search);
12
+ }, [setSearchValue]);
39
13
  }
40
14
 
41
- export { useNormalizedSearch, useSearch, useSearchActor, useUpdateSearch };
15
+ export { useNormalizedSearch, useUpdateSearch };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.51.0",
3
+ "version": "1.52.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "@dagrejs/dagre": "1.1.8",
66
- "@floating-ui/dom": "^1.7.5",
66
+ "@floating-ui/dom": "^1.7.6",
67
67
  "@lume/kiwi": "^0.4.4",
68
68
  "@mantine/core": "8.3.15",
69
69
  "@mantine/hooks": "8.3.15",
@@ -72,8 +72,8 @@
72
72
  "@react-hookz/web": "^25.2.0",
73
73
  "@xstate/react": "6.0.0",
74
74
  "@xstate/store": "3.14.0",
75
- "@xyflow/react": "12.10.0",
76
- "@xyflow/system": "0.0.74",
75
+ "@xyflow/react": "12.10.1",
76
+ "@xyflow/system": "0.0.75",
77
77
  "@zag-js/collection": "^1.33.1",
78
78
  "@zag-js/react": "^1.33.1",
79
79
  "@zag-js/tree-view": "^1.33.1",
@@ -82,15 +82,15 @@
82
82
  "defu": "^6.1.4",
83
83
  "fast-equals": "^6.0.0",
84
84
  "immer": "^11.1.4",
85
- "motion": "^12.34.2",
85
+ "motion": "^12.34.5",
86
86
  "nanostores": "1.1.0",
87
87
  "react-error-boundary": "^6.1.1",
88
88
  "react-shadow": "^20.6.0",
89
89
  "remeda": "^2.33.6",
90
90
  "type-fest": "^4.41.0",
91
91
  "xstate": "5.24.0",
92
- "@likec4/core": "1.51.0",
93
- "@likec4/styles": "1.51.0"
92
+ "@likec4/styles": "1.52.0",
93
+ "@likec4/core": "1.52.0"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "@pandacss/dev": "^1.8.2",
@@ -122,7 +122,7 @@
122
122
  "vitest": "4.0.18",
123
123
  "wireit": "0.14.12",
124
124
  "zx": "8.8.5",
125
- "@likec4/tsconfig": "1.51.0",
125
+ "@likec4/tsconfig": "1.52.0",
126
126
  "@likec4/devops": "1.42.0"
127
127
  },
128
128
  "scripts": {