@likec4/diagram 1.50.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/navigationpanel/comparepanel/DriftsSummary.js +13 -11
- package/dist/navigationpanel/walkthrough/DynamicViewControls.js +5 -5
- 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/dist/styles.css2.js +1 -1
- package/package.json +9 -17
- package/styles-min.css +1 -1
- package/styles.css +1 -1
|
@@ -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';
|
|
@@ -101,7 +101,7 @@ function DriftsSummary() {
|
|
|
101
101
|
if (view.length === 0 && nodes.length === 0 && edges.length === 0) {
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
|
-
return /* @__PURE__ */ jsx(AnimatePresence, {
|
|
104
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsxs(
|
|
105
105
|
m.div,
|
|
106
106
|
{
|
|
107
107
|
layout: "size",
|
|
@@ -120,16 +120,16 @@ function DriftsSummary() {
|
|
|
120
120
|
}),
|
|
121
121
|
children: [
|
|
122
122
|
hasAtLeast(view, 1) && /* @__PURE__ */ jsx(ViewDrifts, { drifts: view }),
|
|
123
|
-
nodes.length > 0 && /* @__PURE__ */ jsxs(m.div, { layout:
|
|
123
|
+
nodes.length > 0 && /* @__PURE__ */ jsxs(m.div, { layout: true, children: [
|
|
124
124
|
/* @__PURE__ */ jsx(SectionHeader, { children: "Elements:" }),
|
|
125
|
-
/* @__PURE__ */ jsx(
|
|
125
|
+
/* @__PURE__ */ jsx("div", { className: vstack({ mt: "2", gap: "2" }), children: map(
|
|
126
126
|
nodes,
|
|
127
127
|
(node) => /* @__PURE__ */ jsx(NodeDrifts, { ...node, ...handlers }, node.id)
|
|
128
128
|
) })
|
|
129
129
|
] }, `nodes-drifts`),
|
|
130
|
-
edges.length > 0 && /* @__PURE__ */ jsxs(m.div, { layout:
|
|
130
|
+
edges.length > 0 && /* @__PURE__ */ jsxs(m.div, { layout: true, children: [
|
|
131
131
|
/* @__PURE__ */ jsx(SectionHeader, { children: "Relationships:" }),
|
|
132
|
-
/* @__PURE__ */ jsx(
|
|
132
|
+
/* @__PURE__ */ jsx("div", { className: vstack({ mt: "2", gap: "2" }), children: map(edges, (edge) => /* @__PURE__ */ jsx(EdgeDrifts, { ...edge, ...handlers }, edge.edgeId)) })
|
|
133
133
|
] }, `edges-drifts`)
|
|
134
134
|
]
|
|
135
135
|
},
|
|
@@ -153,6 +153,7 @@ function ViewDrifts({ drifts }) {
|
|
|
153
153
|
) });
|
|
154
154
|
}
|
|
155
155
|
function NodeDrifts({ id, name, drifts, ...handlers }) {
|
|
156
|
+
const key = `node-drifts-${id}`;
|
|
156
157
|
return /* @__PURE__ */ jsxs(
|
|
157
158
|
DriftsGroup,
|
|
158
159
|
{
|
|
@@ -178,28 +179,29 @@ function NodeDrifts({ id, name, drifts, ...handlers }) {
|
|
|
178
179
|
children: name
|
|
179
180
|
}
|
|
180
181
|
),
|
|
181
|
-
map(drifts, (drift) => /* @__PURE__ */ jsx(DriftLabel, { children: drift },
|
|
182
|
+
map(drifts, (drift, i) => /* @__PURE__ */ jsx(DriftLabel, { children: drift }, key + drift + i))
|
|
182
183
|
]
|
|
183
184
|
},
|
|
184
|
-
|
|
185
|
+
key
|
|
185
186
|
);
|
|
186
187
|
}
|
|
187
188
|
function EdgeDrifts({ edgeId, drifts, ...handlers }) {
|
|
189
|
+
const key = `edge-drifts-${edgeId}`;
|
|
188
190
|
return /* @__PURE__ */ jsx(
|
|
189
191
|
DriftsGroup,
|
|
190
192
|
{
|
|
191
193
|
"data-drift-type": "edge",
|
|
192
194
|
"data-drift-id": edgeId,
|
|
193
195
|
...handlers,
|
|
194
|
-
children: map(drifts, (drift) => /* @__PURE__ */ jsx(
|
|
196
|
+
children: map(drifts, (drift, i) => /* @__PURE__ */ jsx(
|
|
195
197
|
DriftLabel,
|
|
196
198
|
{
|
|
197
199
|
children: drift
|
|
198
200
|
},
|
|
199
|
-
|
|
201
|
+
key + drift + i
|
|
200
202
|
))
|
|
201
203
|
},
|
|
202
|
-
|
|
204
|
+
key
|
|
203
205
|
);
|
|
204
206
|
}
|
|
205
207
|
const DriftsGroup = styled(m.div, {
|
|
@@ -220,7 +222,7 @@ const DriftsGroup = styled(m.div, {
|
|
|
220
222
|
defaultProps: {
|
|
221
223
|
className: "group",
|
|
222
224
|
variants,
|
|
223
|
-
layout:
|
|
225
|
+
layout: true,
|
|
224
226
|
initial: "initial",
|
|
225
227
|
animate: "animate",
|
|
226
228
|
exit: "exit"
|
|
@@ -32,14 +32,14 @@ const TriggerWalkthroughButton = forwardRef((props, ref) => /* @__PURE__ */ jsx(
|
|
|
32
32
|
}
|
|
33
33
|
));
|
|
34
34
|
function StartWalkthroughButton() {
|
|
35
|
-
const {
|
|
35
|
+
const { enableCompareWithLatest } = useEnabledFeatures();
|
|
36
36
|
const diagram = useDiagram();
|
|
37
37
|
const actor = useNavigationActor();
|
|
38
38
|
let tooltipLabel = "Start Dynamic View Walkthrough";
|
|
39
39
|
switch (true) {
|
|
40
|
-
case !enableReadOnly:
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// case !enableReadOnly:
|
|
41
|
+
// tooltipLabel = 'Walkthrough not available in Edit mode'
|
|
42
|
+
// break
|
|
43
43
|
case enableCompareWithLatest:
|
|
44
44
|
tooltipLabel = "Walkthrough not available when Compare is active";
|
|
45
45
|
break;
|
|
@@ -57,7 +57,7 @@ function StartWalkthroughButton() {
|
|
|
57
57
|
exit: { opacity: 0, translateX: -20 },
|
|
58
58
|
size: "compact-xs",
|
|
59
59
|
h: 26,
|
|
60
|
-
disabled:
|
|
60
|
+
disabled: enableCompareWithLatest,
|
|
61
61
|
classNames: {
|
|
62
62
|
label: css({
|
|
63
63
|
display: {
|
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
|
}
|