@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.
- package/dist/components/NavigationPanel.js +4 -2
- package/dist/components/SearchControl.css.js +79 -0
- package/dist/components/SearchControl.js +17 -0
- package/dist/custom/index.d.ts +1 -0
- package/dist/custom/index.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/search/Search.d.ts +8 -0
- package/dist/search/Search.js +40 -47
- package/dist/search/SearchContext.d.ts +21 -0
- package/dist/search/SearchContext.js +20 -0
- package/dist/search/XStateSearchAdapter.d.ts +5 -0
- package/dist/search/XStateSearchAdapter.js +64 -0
- package/dist/search/components/ElementsColumn.js +5 -8
- package/dist/search/components/PickView.d.ts +1 -3
- package/dist/search/components/PickView.js +4 -4
- package/dist/search/components/SearchInput.js +5 -6
- package/dist/search/components/ViewsColum.js +6 -10
- package/dist/search/hooks.d.ts +0 -2
- package/dist/search/hooks.js +7 -33
- package/package.json +8 -8
|
@@ -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 };
|
package/dist/custom/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/custom/index.js
CHANGED
|
@@ -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';
|
package/dist/search/Search.d.ts
CHANGED
|
@@ -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>;
|
package/dist/search/Search.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
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
|
|
9
|
-
import { memo, useRef, useMemo, useEffect
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
120
|
-
const SearchOverlayBody = memo(({ searchActorRef }) => {
|
|
108
|
+
const SearchPanelContent = memo(() => {
|
|
121
109
|
const ref = useRef(null);
|
|
122
|
-
const pickViewFor =
|
|
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:
|
|
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:
|
|
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, {
|
|
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,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
|
|
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
|
|
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
|
-
|
|
292
|
+
ctx.openPickView(elementFqn);
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
294
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
17
|
+
const searchCtx = useSearchContext();
|
|
18
18
|
const likec4model = useLikeC4Model();
|
|
19
19
|
const inputRef = useRef(null);
|
|
20
20
|
const { ref, focused } = useFocusWithin();
|
|
21
|
-
const
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
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 =
|
|
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
|
|
93
|
+
const ctx = useSearchContext();
|
|
94
94
|
const nextViewId = view.id;
|
|
95
95
|
const isCurrentView = nextViewId === currentViewId;
|
|
96
96
|
const navigate = () => {
|
|
97
|
-
|
|
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,
|
package/dist/search/hooks.d.ts
CHANGED
|
@@ -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;
|
package/dist/search/hooks.js
CHANGED
|
@@ -1,41 +1,15 @@
|
|
|
1
|
-
import { useSelector } from '@xstate/react';
|
|
2
1
|
import { useCallback, useDeferredValue } from 'react';
|
|
3
|
-
import {
|
|
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
|
|
32
|
-
return useDeferredValue(
|
|
5
|
+
const ctx = useSearchContext();
|
|
6
|
+
return useDeferredValue(ctx.normalizedSearch);
|
|
33
7
|
}
|
|
34
8
|
function useUpdateSearch() {
|
|
35
|
-
const
|
|
9
|
+
const { setSearchValue } = useSearchContext();
|
|
36
10
|
return useCallback((search) => {
|
|
37
|
-
|
|
38
|
-
}, [
|
|
11
|
+
setSearchValue(search);
|
|
12
|
+
}, [setSearchValue]);
|
|
39
13
|
}
|
|
40
14
|
|
|
41
|
-
export { useNormalizedSearch,
|
|
15
|
+
export { useNormalizedSearch, useUpdateSearch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/diagram",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
76
|
-
"@xyflow/system": "0.0.
|
|
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.
|
|
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/
|
|
93
|
-
"@likec4/
|
|
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.
|
|
125
|
+
"@likec4/tsconfig": "1.52.0",
|
|
126
126
|
"@likec4/devops": "1.42.0"
|
|
127
127
|
},
|
|
128
128
|
"scripts": {
|