@likec4/diagram 1.21.0 → 1.22.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.
Files changed (129) hide show
  1. package/dist/LikeC4Diagram.js +9 -13
  2. package/dist/base/BaseXYFlow.js +0 -1
  3. package/dist/base/index.d.ts +0 -1
  4. package/dist/base/primitives/compound/CompoundNodeContainer.js +2 -2
  5. package/dist/base/primitives/edge/EdgeLabel.js +2 -2
  6. package/dist/base/primitives/element/ElementNodeContainer.d.ts +6 -1
  7. package/dist/base/primitives/element/ElementNodeContainer.js +31 -13
  8. package/dist/base/primitives/element/ElementTitle.js +7 -3
  9. package/dist/base/primitives/element/styles.css.js +1 -1
  10. package/dist/{base → components}/Link.css.js +3 -3
  11. package/dist/components/SearchControl.css.d.ts +2 -0
  12. package/dist/components/SearchControl.css.js +7 -0
  13. package/dist/components/SearchControl.d.ts +5 -0
  14. package/dist/components/SearchControl.js +17 -0
  15. package/dist/context/IconRenderer.d.ts +10 -0
  16. package/dist/context/IconRenderer.js +34 -0
  17. package/dist/hooks/index.d.ts +0 -1
  18. package/dist/hooks/useDiagram.d.ts +4 -4
  19. package/dist/index.d.ts +1 -1
  20. package/dist/likec4diagram/DiagramUI.js +1 -4
  21. package/dist/likec4diagram/XYFlow.d.ts +6 -1
  22. package/dist/likec4diagram/XYFlow.js +28 -29
  23. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +3 -1
  24. package/dist/likec4diagram/state/actorContext.d.ts +16 -16
  25. package/dist/likec4diagram/state/assign.js +1 -1
  26. package/dist/likec4diagram/state/machine.d.ts +7 -7
  27. package/dist/likec4diagram/state/machine.js +10 -7
  28. package/dist/likec4diagram/ui/controls/Controls.js +63 -48
  29. package/dist/likec4diagram/ui/controls/NavigationButtons.js +44 -69
  30. package/dist/likec4diagram/ui/controls/_shared.js +1 -1
  31. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +5 -4
  32. package/dist/likec4diagram/ui/index.d.ts +2 -0
  33. package/dist/likec4diagram/ui/search/ElementsColumn.css.d.ts +15 -0
  34. package/dist/likec4diagram/ui/search/ElementsColumn.css.js +30 -0
  35. package/dist/likec4diagram/ui/search/ElementsColumn.d.ts +1 -0
  36. package/dist/likec4diagram/ui/search/ElementsColumn.js +243 -0
  37. package/dist/likec4diagram/ui/search/LikeC4Search.css.d.ts +8 -0
  38. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +20 -0
  39. package/dist/likec4diagram/ui/search/LikeC4Search.d.ts +1 -0
  40. package/dist/likec4diagram/ui/search/LikeC4Search.js +265 -0
  41. package/dist/likec4diagram/ui/search/SearchInput.d.ts +1 -0
  42. package/dist/likec4diagram/ui/search/SearchInput.js +196 -0
  43. package/dist/likec4diagram/ui/search/ViewsColum.d.ts +9 -0
  44. package/dist/likec4diagram/ui/search/ViewsColum.js +130 -0
  45. package/dist/likec4diagram/ui/search/ViewsColumn.css.d.ts +5 -0
  46. package/dist/likec4diagram/ui/search/ViewsColumn.css.js +12 -0
  47. package/dist/likec4diagram/ui/search/_shared.css.d.ts +16 -0
  48. package/dist/likec4diagram/ui/search/_shared.css.js +5 -0
  49. package/dist/likec4diagram/ui/search/state.d.ts +19 -0
  50. package/dist/likec4diagram/ui/search/state.js +72 -0
  51. package/dist/likec4diagram/ui/search/utils.d.ts +3 -0
  52. package/dist/likec4diagram/ui/search/utils.js +23 -0
  53. package/dist/likec4diagram/useLayoutConstraints.js +4 -5
  54. package/dist/likec4model/useLikeC4ElementsTree.d.ts +1 -0
  55. package/dist/likec4model/useLikeC4ElementsTree.js +1 -0
  56. package/dist/overlays/Overlays.js +10 -10
  57. package/dist/overlays/element-details/ElementDetailsCard.js +20 -13
  58. package/dist/overlays/element-details/TabPanelDeployments.js +4 -2
  59. package/dist/overlays/overlay/Overlay.d.ts +6 -2
  60. package/dist/overlays/overlay/Overlay.js +18 -29
  61. package/dist/overlays/relationship-details/SelectEdge.js +1 -3
  62. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +6 -5
  63. package/dist/overlays/relationships-browser/SelectElement.js +10 -6
  64. package/dist/overlays/relationships-browser/actor.d.ts +2 -2
  65. package/dist/overlays/relationships-browser/actor.js +8 -8
  66. package/dist/overlays/relationships-browser/custom/nodeTypes.js +2 -2
  67. package/dist/style.css +1 -1
  68. package/package.json +15 -14
  69. package/src/LikeC4Diagram.tsx +0 -1
  70. package/src/base/BaseXYFlow.tsx +0 -1
  71. package/src/base/index.ts +0 -2
  72. package/src/base/primitives/element/ElementNodeContainer.css.ts +4 -7
  73. package/src/base/primitives/element/ElementNodeContainer.tsx +36 -16
  74. package/src/base/primitives/element/ElementTitle.css.ts +12 -9
  75. package/src/base/primitives/element/ElementTitle.tsx +6 -2
  76. package/src/base/primitives/element/styles.css.ts +18 -40
  77. package/src/{base → components}/Link.css.ts +3 -3
  78. package/src/components/SearchControl.css.ts +69 -0
  79. package/src/components/SearchControl.tsx +33 -0
  80. package/src/context/IconRenderer.tsx +54 -1
  81. package/src/hooks/index.ts +0 -1
  82. package/src/index.ts +15 -1
  83. package/src/likec4diagram/DiagramUI.tsx +1 -8
  84. package/src/likec4diagram/XYFlow.tsx +30 -30
  85. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
  86. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.css.ts +1 -0
  87. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +3 -1
  88. package/src/likec4diagram/state/assign.ts +1 -1
  89. package/src/likec4diagram/state/machine.ts +12 -10
  90. package/src/likec4diagram/ui/controls/Controls.tsx +35 -25
  91. package/src/likec4diagram/ui/controls/NavigationButtons.tsx +36 -58
  92. package/src/likec4diagram/ui/controls/styles.css.ts +3 -1
  93. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +111 -108
  94. package/src/likec4diagram/ui/index.ts +2 -0
  95. package/src/likec4diagram/ui/notation/NotationPanel.tsx +2 -3
  96. package/src/likec4diagram/ui/search/ElementsColumn.css.ts +119 -0
  97. package/src/likec4diagram/ui/search/ElementsColumn.tsx +291 -0
  98. package/src/likec4diagram/ui/search/LikeC4Search.css.ts +117 -0
  99. package/src/likec4diagram/ui/search/LikeC4Search.tsx +312 -0
  100. package/src/likec4diagram/ui/search/SearchInput.tsx +235 -0
  101. package/src/likec4diagram/ui/search/ViewsColum.tsx +158 -0
  102. package/src/likec4diagram/ui/search/ViewsColumn.css.ts +37 -0
  103. package/src/likec4diagram/ui/search/_shared.css.ts +104 -0
  104. package/src/likec4diagram/ui/search/state.ts +76 -0
  105. package/src/likec4diagram/ui/search/utils.ts +20 -0
  106. package/src/likec4diagram/useLayoutConstraints.ts +4 -5
  107. package/src/likec4model/useLikeC4ElementsTree.ts +4 -4
  108. package/src/overlays/Overlays.tsx +32 -26
  109. package/src/overlays/element-details/ElementDetailsCard.css.ts +4 -4
  110. package/src/overlays/element-details/ElementDetailsCard.tsx +246 -244
  111. package/src/overlays/element-details/TabPanelDeployments.tsx +4 -4
  112. package/src/overlays/overlay/Overlay.css.ts +10 -9
  113. package/src/overlays/overlay/Overlay.tsx +38 -26
  114. package/src/overlays/relationship-details/SelectEdge.css.ts +1 -0
  115. package/src/overlays/relationship-details/SelectEdge.tsx +2 -4
  116. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +10 -5
  117. package/src/overlays/relationships-browser/SelectElement.tsx +88 -82
  118. package/src/overlays/relationships-browser/actor.ts +10 -10
  119. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +2 -2
  120. package/dist/LikeC4Search.css.d.ts +0 -1
  121. package/dist/LikeC4Search.css.js +0 -5
  122. package/dist/LikeC4Search.d.ts +0 -2
  123. package/dist/LikeC4Search.js +0 -88
  124. package/src/LikeC4Search.css.ts +0 -12
  125. package/src/LikeC4Search.tsx +0 -91
  126. /package/dist/{base → components}/Link.css.d.ts +0 -0
  127. /package/dist/{base → components}/Link.d.ts +0 -0
  128. /package/dist/{base → components}/Link.js +0 -0
  129. /package/src/{base → components}/Link.tsx +0 -0
@@ -0,0 +1,196 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { compareNatural } from "@likec4/core";
3
+ import { useCombobox, ComboboxOption, Text, Combobox, ComboboxTarget, Input, rem, ComboboxDropdown, ComboboxOptions, ScrollAreaAutosize, ComboboxEmpty } from "@mantine/core";
4
+ import { useFocusWithin, useIsFirstRender, useDebouncedValue, useWindowEvent } from "@mantine/hooks";
5
+ import { usePreviousDistinct } from "@react-hookz/web";
6
+ import { IconSearch } from "@tabler/icons-react";
7
+ import "react";
8
+ import { isString, keys, only } from "remeda";
9
+ import { useLikeC4Model } from "../../../likec4model/useLikeC4Model.js";
10
+ import { input, root, focusable } from "./LikeC4Search.css.js";
11
+ import { useIsPickViewActive, useSearch, setSearch } from "./state.js";
12
+ import { moveFocusToSearchInput, stopAndPrevent } from "./utils.js";
13
+ function startingWithKind(search) {
14
+ return search.match(/^(k|ki|kin|kind|kind:)$/) != null;
15
+ }
16
+ const SEARCH_PREFIXES = ["#", "kind:"];
17
+ function LikeC4SearchInput() {
18
+ var _a, _b;
19
+ const isPickViewActive = useIsPickViewActive();
20
+ const likec4model = useLikeC4Model(true);
21
+ const combobox = useCombobox({
22
+ scrollBehavior: "smooth"
23
+ });
24
+ const { ref, focused } = useFocusWithin();
25
+ const search = useSearch();
26
+ const previous = usePreviousDistinct(search);
27
+ const isFirstRender = useIsFirstRender();
28
+ const [isEmptyForSomeTime] = useDebouncedValue(search === "" && !isFirstRender, isString(previous) ? 400 : 1e3);
29
+ useWindowEvent(
30
+ "keydown",
31
+ (event) => {
32
+ try {
33
+ if (!focused && !isPickViewActive && (event.key === "Backspace" || event.key.startsWith("Arrow") || event.key.match(new RegExp("^\\p{L}$", "u")))) {
34
+ moveFocusToSearchInput();
35
+ }
36
+ } catch (e) {
37
+ console.warn(e);
38
+ }
39
+ }
40
+ );
41
+ let options = [];
42
+ let isExactMatch = false;
43
+ switch (true) {
44
+ case (search === "" && (isEmptyForSomeTime || SEARCH_PREFIXES.includes(previous ?? ""))): {
45
+ options = SEARCH_PREFIXES.map((prefix) => /* @__PURE__ */ jsxs(ComboboxOption, { value: prefix, children: [
46
+ /* @__PURE__ */ jsx(Text, { component: "span", opacity: 0.5, mr: 4, fz: "sm", children: "Search by" }),
47
+ prefix
48
+ ] }, prefix));
49
+ break;
50
+ }
51
+ case search.startsWith("#"): {
52
+ const searchTag = search.toLocaleLowerCase().slice(1);
53
+ const alloptions = likec4model.allTags().filter((tag) => tag.toLocaleLowerCase().includes(searchTag)).sort(
54
+ compareNatural
55
+ );
56
+ isExactMatch = ((_a = only(alloptions)) == null ? void 0 : _a.toLocaleLowerCase()) === searchTag;
57
+ if (alloptions.length === 0) {
58
+ options = [
59
+ /* @__PURE__ */ jsx(ComboboxEmpty, { children: "No tags found" }, "empty-tags")
60
+ ];
61
+ } else {
62
+ options = alloptions.map((tag) => /* @__PURE__ */ jsxs(ComboboxOption, { value: `#${tag}`, children: [
63
+ /* @__PURE__ */ jsx(Text, { component: "span", opacity: 0.5, mr: 1, fz: "sm", children: "#" }),
64
+ tag
65
+ ] }, tag));
66
+ }
67
+ break;
68
+ }
69
+ case search.startsWith("kind:"):
70
+ case startingWithKind(search): {
71
+ const term = search.length > 5 ? search.slice(5).toLocaleLowerCase() : "";
72
+ let alloptions = keys(likec4model.$model.specification.elements);
73
+ if (term) {
74
+ alloptions = alloptions.filter((kind) => kind.toLocaleLowerCase().includes(term));
75
+ isExactMatch = ((_b = only(alloptions)) == null ? void 0 : _b.toLocaleLowerCase()) === term;
76
+ }
77
+ if (alloptions.length === 0) {
78
+ options = [
79
+ /* @__PURE__ */ jsx(ComboboxEmpty, { children: "No kinds found" }, "empty-kinds")
80
+ ];
81
+ } else {
82
+ options = alloptions.map((kind) => /* @__PURE__ */ jsxs(ComboboxOption, { value: `kind:${kind}`, children: [
83
+ /* @__PURE__ */ jsx(Text, { component: "span", opacity: 0.5, mr: 1, fz: "sm", children: "kind:" }),
84
+ kind
85
+ ] }, kind));
86
+ }
87
+ break;
88
+ }
89
+ }
90
+ return /* @__PURE__ */ jsxs(
91
+ Combobox,
92
+ {
93
+ onOptionSubmit: (optionValue) => {
94
+ setSearch(optionValue);
95
+ combobox.resetSelectedOption();
96
+ if (!SEARCH_PREFIXES.includes(optionValue)) {
97
+ combobox.closeDropdown();
98
+ setTimeout(() => {
99
+ var _a2;
100
+ (_a2 = document.querySelector(`.${root} .${focusable}`)) == null ? void 0 : _a2.focus();
101
+ }, 50);
102
+ }
103
+ },
104
+ width: "max-content",
105
+ position: "bottom-start",
106
+ shadow: "md",
107
+ offset: {
108
+ mainAxis: 4,
109
+ crossAxis: 50
110
+ },
111
+ store: combobox,
112
+ withinPortal: false,
113
+ children: [
114
+ /* @__PURE__ */ jsx(ComboboxTarget, { children: /* @__PURE__ */ jsx(
115
+ Input,
116
+ {
117
+ ref,
118
+ id: "likec4searchinput",
119
+ placeholder: "Search by title, description or start with # or kind:",
120
+ tabIndex: 0,
121
+ classNames: {
122
+ input
123
+ },
124
+ size: "xl",
125
+ value: search,
126
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { style: { width: rem(20) }, stroke: 2 }),
127
+ onChange: (event) => {
128
+ setSearch(event.currentTarget.value);
129
+ combobox.openDropdown();
130
+ combobox.updateSelectedOptionIndex();
131
+ },
132
+ onClick: () => combobox.openDropdown(),
133
+ onFocus: () => combobox.openDropdown(),
134
+ onBlur: () => combobox.closeDropdown(),
135
+ onKeyDownCapture: (e) => {
136
+ var _a2;
137
+ if (e.key === "Tab") {
138
+ switch (true) {
139
+ case combobox.getSelectedOptionIndex() >= 0: {
140
+ combobox.clickSelectedOption();
141
+ return stopAndPrevent(e);
142
+ }
143
+ case options.length === 1: {
144
+ const firstOption = combobox.selectFirstOption();
145
+ if (firstOption) {
146
+ combobox.clickSelectedOption();
147
+ }
148
+ return stopAndPrevent(e);
149
+ }
150
+ case startingWithKind(search): {
151
+ setSearch("kind:");
152
+ return stopAndPrevent(e);
153
+ }
154
+ }
155
+ return;
156
+ }
157
+ if (e.key === "Backspace" && combobox.dropdownOpened) {
158
+ if (search === "kind:") {
159
+ setSearch("");
160
+ combobox.resetSelectedOption();
161
+ return stopAndPrevent(e);
162
+ }
163
+ if (search.startsWith("kind:") && isExactMatch) {
164
+ setSearch("kind:");
165
+ combobox.resetSelectedOption();
166
+ return stopAndPrevent(e);
167
+ }
168
+ if (search.startsWith("#") && isExactMatch) {
169
+ setSearch("#");
170
+ combobox.resetSelectedOption();
171
+ return stopAndPrevent(e);
172
+ }
173
+ }
174
+ if (e.key === "Escape" && combobox.dropdownOpened && options.length > 0) {
175
+ stopAndPrevent(e);
176
+ combobox.closeDropdown();
177
+ return;
178
+ }
179
+ if (e.key === "ArrowDown" && (!combobox.dropdownOpened || options.length === 0 || isExactMatch || // reached the last option and the search is empty
180
+ search === "" && combobox.getSelectedOptionIndex() === options.length - 1)) {
181
+ combobox.closeDropdown();
182
+ stopAndPrevent(e);
183
+ (_a2 = document.querySelector(`.${root} .${focusable}`)) == null ? void 0 : _a2.focus();
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ ) }),
189
+ /* @__PURE__ */ jsx(ComboboxDropdown, { hidden: options.length === 0, style: { minWidth: 300 }, children: /* @__PURE__ */ jsx(ComboboxOptions, { children: /* @__PURE__ */ jsx(ScrollAreaAutosize, { mah: "min(322px, calc(100cqh - 50px))", type: "scroll", children: options }) }) })
190
+ ]
191
+ }
192
+ );
193
+ }
194
+ export {
195
+ LikeC4SearchInput
196
+ };
@@ -0,0 +1,9 @@
1
+ import { LikeC4Model } from '@likec4/core';
2
+ import { ElementProps, UnstyledButtonProps } from '@mantine/core';
3
+ export declare const NothingFound: () => import("react/jsx-runtime").JSX.Element;
4
+ export declare function ViewsColumn(): import("react/jsx-runtime").JSX.Element;
5
+ export declare function ViewButton({ className, view, loop, search, ...props }: {
6
+ view: LikeC4Model.View;
7
+ search: string;
8
+ loop?: boolean;
9
+ } & UnstyledButtonProps & ElementProps<'button'>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,130 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { isDeploymentView } from "@likec4/core";
3
+ import { Stack, VisuallyHidden, UnstyledButton, Box, createScopedKeydownHandler, ThemeIcon, Group, Highlight, Badge } from "@mantine/core";
4
+ import { IconStack2, IconZoomScan } from "@tabler/icons-react";
5
+ import clsx from "clsx";
6
+ import { m } from "framer-motion";
7
+ import { first } from "remeda";
8
+ import { useLikeC4Model } from "../../../likec4model/useLikeC4Model.js";
9
+ import { emptyBoX } from "./_shared.css.js";
10
+ import { useNormalizedSearch, useCloseSearchAndNavigateTo } from "./state.js";
11
+ import { moveFocusToSearchInput, centerY } from "./utils.js";
12
+ import { viewTitle, viewDescription, focusable, viewButton } from "./ViewsColumn.css.js";
13
+ import { useCurrentViewId } from "../../../hooks/useCurrentViewId.js";
14
+ const NothingFound = () => /* @__PURE__ */ jsx(Box, { className: emptyBoX, children: "Nothing found" });
15
+ function ViewsColumn() {
16
+ const search = useNormalizedSearch();
17
+ let views = [...useLikeC4Model(true).views()];
18
+ if (search) {
19
+ if (search.startsWith("kind:")) {
20
+ views = [];
21
+ } else {
22
+ views = views.filter((view) => {
23
+ if (search.startsWith("#")) {
24
+ return view.tags.some((tag) => tag.toLocaleLowerCase().includes(search.slice(1)));
25
+ }
26
+ return (view.title ?? "" + view.$view.description ?? "").toLocaleLowerCase().includes(search);
27
+ });
28
+ }
29
+ }
30
+ return /* @__PURE__ */ jsxs(
31
+ Stack,
32
+ {
33
+ renderRoot: (props) => /* @__PURE__ */ jsx(m.div, { layout: true, ...props }),
34
+ gap: 8,
35
+ "data-likec4-search-views": true,
36
+ onKeyDown: (e) => {
37
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
38
+ const maxY = e.target.getBoundingClientRect().y;
39
+ const elementButtons = [...document.querySelectorAll(
40
+ `[data-likec4-search-elements] .likec4-element-button`
41
+ )];
42
+ let elementButton = elementButtons.length > 1 ? elementButtons.find((el, i, all) => centerY(el) > maxY || i === all.length - 1) : null;
43
+ elementButton ?? (elementButton = first(elementButtons));
44
+ if (elementButton) {
45
+ e.stopPropagation();
46
+ elementButton.focus();
47
+ }
48
+ return;
49
+ }
50
+ },
51
+ children: [
52
+ views.length === 0 && /* @__PURE__ */ jsx(NothingFound, {}),
53
+ views.length > 0 && /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(
54
+ UnstyledButton,
55
+ {
56
+ "data-likec4-view": true,
57
+ tabIndex: -1,
58
+ onFocus: (e) => {
59
+ e.stopPropagation();
60
+ moveFocusToSearchInput();
61
+ }
62
+ }
63
+ ) }),
64
+ views.map((view, i) => /* @__PURE__ */ jsx(m.div, { layoutId: `@view${view.id}`, children: /* @__PURE__ */ jsx(
65
+ ViewButton,
66
+ {
67
+ view,
68
+ search,
69
+ tabIndex: i === 0 ? 0 : -1
70
+ }
71
+ ) }, view.id))
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ function ViewButton({ className, view, loop = false, search, ...props }) {
77
+ const navigateTo = useCloseSearchAndNavigateTo();
78
+ const currentViewId = useCurrentViewId();
79
+ const isCurrentView = view.id === currentViewId;
80
+ return /* @__PURE__ */ jsxs(
81
+ UnstyledButton,
82
+ {
83
+ ...props,
84
+ className: clsx(focusable, viewButton, className),
85
+ "data-likec4-view": view.id,
86
+ ...isCurrentView && { "data-disabled": true },
87
+ onClick: (e) => {
88
+ e.stopPropagation();
89
+ navigateTo(view.id);
90
+ },
91
+ onKeyDown: createScopedKeydownHandler({
92
+ siblingSelector: "[data-likec4-view]",
93
+ parentSelector: "[data-likec4-search-views]",
94
+ activateOnFocus: false,
95
+ loop,
96
+ orientation: "vertical",
97
+ onKeyDown: (e) => {
98
+ if (e.nativeEvent.code === "Space") {
99
+ e.stopPropagation();
100
+ navigateTo(view.id);
101
+ }
102
+ }
103
+ }),
104
+ children: [
105
+ /* @__PURE__ */ jsx(ThemeIcon, { variant: "transparent", children: isDeploymentView(view) ? /* @__PURE__ */ jsx(IconStack2, { stroke: 1.8 }) : /* @__PURE__ */ jsx(IconZoomScan, { stroke: 1.8 }) }),
106
+ /* @__PURE__ */ jsxs(Box, { style: { flexGrow: 1 }, children: [
107
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", align: "center", children: [
108
+ /* @__PURE__ */ jsx(Highlight, { component: "div", highlight: search, className: viewTitle, children: view.title || "untitled" }),
109
+ isCurrentView && /* @__PURE__ */ jsx(Badge, { size: "xs", fz: 9, radius: "sm", children: "current" })
110
+ ] }),
111
+ /* @__PURE__ */ jsx(
112
+ Highlight,
113
+ {
114
+ highlight: view.$view.description ? search : "",
115
+ component: "div",
116
+ className: viewDescription,
117
+ lineClamp: 1,
118
+ children: view.$view.description || "No description"
119
+ }
120
+ )
121
+ ] })
122
+ ]
123
+ }
124
+ );
125
+ }
126
+ export {
127
+ NothingFound,
128
+ ViewButton,
129
+ ViewsColumn
130
+ };
@@ -0,0 +1,5 @@
1
+ export { focusable } from './_shared.css';
2
+ export declare const viewButton: string;
3
+ export declare const viewTitle: string;
4
+ export declare const viewDescription: string;
5
+ export declare const viewButtonDescription: string;
@@ -0,0 +1,12 @@
1
+ /* empty css */
2
+ /* empty css */
3
+ var focusable = "_1cfw8gr4";
4
+ var viewButton = "ia79t50 _1cfw8gr3";
5
+ var viewTitle = "ia79t51 _1cfw8gr5";
6
+ var viewDescription = "ia79t52 _1cfw8gr6";
7
+ export {
8
+ focusable,
9
+ viewButton,
10
+ viewDescription,
11
+ viewTitle
12
+ };
@@ -0,0 +1,16 @@
1
+ export declare const titleColor: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
2
+ export declare const descriptionColor: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
3
+ export declare const iconColor: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
4
+ export declare const buttonFocused: {
5
+ outline: "none";
6
+ backgroundColor: string;
7
+ borderColor: string;
8
+ vars: {
9
+ [x: string]: string;
10
+ };
11
+ };
12
+ export declare const button: string;
13
+ export declare const focusable: string;
14
+ export declare const title: string;
15
+ export declare const description: string;
16
+ export declare const emptyBoX: string;
@@ -0,0 +1,5 @@
1
+ /* empty css */
2
+ var emptyBoX = "_1cfw8gr7";
3
+ export {
4
+ emptyBoX
5
+ };
@@ -0,0 +1,19 @@
1
+ import { Fqn, LikeC4Model, ViewId } from '@likec4/core';
2
+ export declare function setSearch(term: string): void;
3
+ export declare function useSearch(): string;
4
+ export declare function useNormalizedSearch(): string;
5
+ export declare const LikeC4SearchContext: ({ children, value }: {
6
+ value: (cb?: () => void) => void;
7
+ children: React.ReactNode;
8
+ }) => import("react/jsx-runtime").JSX.Element, useCloseSearch: () => (cb?: () => void) => void;
9
+ type PickView = {
10
+ elementFqn: Fqn;
11
+ scoped: LikeC4Model.View[];
12
+ others: LikeC4Model.View[];
13
+ };
14
+ export declare function usePickView(): PickView | null;
15
+ export declare function setPickView(data: PickView | null): void;
16
+ export declare function wasResetPickView(): boolean;
17
+ export declare function useIsPickViewActive(): boolean;
18
+ export declare function useCloseSearchAndNavigateTo(): (viewId: ViewId, fromElementFqn?: Fqn) => void;
19
+ export {};
@@ -0,0 +1,72 @@
1
+ import { DiagramNode } from "@likec4/core";
2
+ import { createSafeContext } from "@mantine/core";
3
+ import { useCallbackRef } from "@mantine/hooks";
4
+ import { useStore } from "@nanostores/react";
5
+ import { atom, computed, onMount } from "nanostores";
6
+ import { useDeferredValue } from "react";
7
+ import { useDiagram } from "../../../hooks/useDiagram.js";
8
+ const $search = atom("");
9
+ function setSearch(term) {
10
+ $search.set(term);
11
+ }
12
+ function useSearch() {
13
+ return useStore($search);
14
+ }
15
+ const $normalizedSearch = computed($search, (search) => {
16
+ const v = search.trim().toLowerCase();
17
+ return v.length > 1 ? v : "";
18
+ });
19
+ function useNormalizedSearch() {
20
+ return useDeferredValue(useStore($normalizedSearch));
21
+ }
22
+ const [LikeC4SearchContext, useCloseSearch] = createSafeContext("LikeC4Search");
23
+ const $pickView = atom(null);
24
+ onMount($search, () => {
25
+ $pickView.set(null);
26
+ });
27
+ function usePickView() {
28
+ return useStore($pickView);
29
+ }
30
+ function setPickView(data) {
31
+ $pickView.set(data);
32
+ }
33
+ function wasResetPickView() {
34
+ if ($pickView.get() !== null) {
35
+ $pickView.set(null);
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ const $pickViewActive = computed($pickView, (pickView) => pickView !== null);
41
+ function useIsPickViewActive() {
42
+ return useStore($pickViewActive);
43
+ }
44
+ function useCloseSearchAndNavigateTo() {
45
+ const diagram = useDiagram();
46
+ const close = useCloseSearch();
47
+ return useCallbackRef((viewId, fromElementFqn) => {
48
+ close(() => {
49
+ var _a, _b;
50
+ fromElementFqn ?? (fromElementFqn = (_a = $pickView.get()) == null ? void 0 : _a.elementFqn);
51
+ setPickView(null);
52
+ const fromNode = fromElementFqn ? (_b = diagram.getContext().view.nodes.find((n) => DiagramNode.modelRef(n) === fromElementFqn)) == null ? void 0 : _b.id : void 0;
53
+ if (diagram.currentView().id === viewId && fromNode) {
54
+ diagram.focusNode(fromNode);
55
+ return;
56
+ }
57
+ diagram.navigateTo(viewId, fromNode);
58
+ });
59
+ });
60
+ }
61
+ export {
62
+ LikeC4SearchContext,
63
+ setPickView,
64
+ setSearch,
65
+ useCloseSearch,
66
+ useCloseSearchAndNavigateTo,
67
+ useIsPickViewActive,
68
+ useNormalizedSearch,
69
+ usePickView,
70
+ useSearch,
71
+ wasResetPickView
72
+ };
@@ -0,0 +1,3 @@
1
+ export declare function stopAndPrevent(e: React.KeyboardEvent | KeyboardEvent): void;
2
+ export declare function centerY(element: HTMLElement): number;
3
+ export declare function moveFocusToSearchInput(): void;
@@ -0,0 +1,23 @@
1
+ function stopAndPrevent(e) {
2
+ e.stopPropagation();
3
+ e.preventDefault();
4
+ return;
5
+ }
6
+ function centerY(element) {
7
+ const rect = element.getBoundingClientRect();
8
+ const y = rect.y + Math.floor(rect.height / 2);
9
+ return y;
10
+ }
11
+ function moveFocusToSearchInput() {
12
+ const input = document.getElementById("likec4searchinput");
13
+ if (input) {
14
+ const length = input.value.length;
15
+ input.focus();
16
+ input.setSelectionRange(length, length);
17
+ }
18
+ }
19
+ export {
20
+ centerY,
21
+ moveFocusToSearchInput,
22
+ stopAndPrevent
23
+ };
@@ -6,7 +6,6 @@ import "@xyflow/react";
6
6
  import { getNodeDimensions } from "@xyflow/system";
7
7
  import { useRef, useMemo } from "react";
8
8
  import { pipe, filter, map, hasAtLeast, isNullish } from "remeda";
9
- import { useSyncedRef } from "@react-hookz/web";
10
9
  import { useDiagram } from "../hooks/useDiagram.js";
11
10
  import { useXYStoreApi } from "../hooks/useXYFlow.js";
12
11
  class Rect {
@@ -192,11 +191,11 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
192
191
  }
193
192
  function useLayoutConstraints() {
194
193
  const xystore = useXYStoreApi();
195
- const diagram = useSyncedRef(useDiagram());
194
+ const diagram = useDiagram();
196
195
  const solverRef = useRef(void 0);
197
196
  return useMemo(() => ({
198
197
  onNodeDragStart: (_event, xynode) => {
199
- diagram.current.cancelSaveManualLayout();
198
+ diagram.cancelSaveManualLayout();
200
199
  const { nodeLookup } = xystore.getState();
201
200
  const draggingNodes = pipe(
202
201
  Array.from(nodeLookup.values()),
@@ -219,10 +218,10 @@ function useLayoutConstraints() {
219
218
  onNodeDragStop: () => {
220
219
  var _a;
221
220
  (_a = solverRef.current) == null ? void 0 : _a.updateXYFlowNodes();
222
- diagram.current.scheduleSaveManualLayout();
221
+ diagram.scheduleSaveManualLayout();
223
222
  solverRef.current = void 0;
224
223
  }
225
- }), [xystore]);
224
+ }), [xystore, diagram]);
226
225
  }
227
226
  export {
228
227
  createLayoutConstraints,
@@ -4,6 +4,7 @@ interface LikeC4ModelTreeNodeData {
4
4
  value: Fqn;
5
5
  children: LikeC4ModelTreeNodeData[];
6
6
  }
7
+ export declare const sortByLabel: (a: LikeC4ModelTreeNodeData, b: LikeC4ModelTreeNodeData) => 0 | 1 | -1;
7
8
  /**
8
9
  * Returns a tree of elements in the model.
9
10
  * If `viewId` is provided, returns the tree of elements in the view.
@@ -21,5 +21,6 @@ function useLikeC4ElementsTree(viewId) {
21
21
  }, [model, viewId ?? null]);
22
22
  }
23
23
  export {
24
+ sortByLabel,
24
25
  useLikeC4ElementsTree
25
26
  };
@@ -3,7 +3,7 @@ import { Box, Notification, Code, Group, Button } from "@mantine/core";
3
3
  import { useCallbackRef } from "@mantine/hooks";
4
4
  import { IconX } from "@tabler/icons-react";
5
5
  import { deepEqual } from "fast-equals";
6
- import { useReducedMotion } from "framer-motion";
6
+ import { useReducedMotion, AnimatePresence } from "framer-motion";
7
7
  import { animate } from "framer-motion/dom";
8
8
  import { memo, useMemo, useEffect } from "react";
9
9
  import { ErrorBoundary } from "react-error-boundary";
@@ -64,7 +64,7 @@ const Overlays = memo(() => {
64
64
  rectFromNode
65
65
  } = useDiagramActorState(selector, equals);
66
66
  const isMotionReduced = useReducedMotion() ?? false;
67
- const isActiveOverlay = !!activeElementDetailsOf;
67
+ const isActiveOverlay = !!activeElementDetailsOf || !!relationshipsBrowserActor || !!relationshipDetailsActor;
68
68
  useEffect(() => {
69
69
  if (!xyflowRendererDom || isMotionReduced) return;
70
70
  animate(xyflowRendererDom, {
@@ -79,23 +79,23 @@ const Overlays = memo(() => {
79
79
  diagram.closeOverlay();
80
80
  });
81
81
  return /* @__PURE__ */ jsx(DiagramFeatures.Overlays, { children: /* @__PURE__ */ jsxs(ErrorBoundary, { FallbackComponent: Fallback, onReset: onClose, children: [
82
- relationshipsBrowserActor && /* @__PURE__ */ jsx(
82
+ /* @__PURE__ */ jsx(AnimatePresence, { onExitComplete: onClose, children: relationshipsBrowserActor && /* @__PURE__ */ jsx(
83
83
  Overlay,
84
84
  {
85
- onClose,
85
+ onClose: () => relationshipsBrowserActor.send({ type: "close" }),
86
86
  children: /* @__PURE__ */ jsx(RelationshipsBrowser, { actorRef: relationshipsBrowserActor })
87
87
  },
88
88
  "relationships-browser"
89
- ),
90
- relationshipDetailsActor && /* @__PURE__ */ jsx(
89
+ ) }),
90
+ /* @__PURE__ */ jsx(AnimatePresence, { onExitComplete: onClose, children: relationshipDetailsActor && /* @__PURE__ */ jsx(
91
91
  Overlay,
92
92
  {
93
- onClose,
93
+ onClose: () => relationshipDetailsActor.send({ type: "close" }),
94
94
  children: /* @__PURE__ */ jsx(RelationshipDetails, { actorRef: relationshipDetailsActor })
95
95
  },
96
96
  "relationship-details"
97
- ),
98
- !!activeElementDetailsOf && /* @__PURE__ */ jsx(
97
+ ) }),
98
+ /* @__PURE__ */ jsx(AnimatePresence, { children: !!activeElementDetailsOf && /* @__PURE__ */ jsx(
99
99
  ElementDetailsCard,
100
100
  {
101
101
  fqn: activeElementDetailsOf,
@@ -105,7 +105,7 @@ const Overlays = memo(() => {
105
105
  onClose
106
106
  },
107
107
  "element-details"
108
- )
108
+ ) })
109
109
  ] }) });
110
110
  });
111
111
  Overlays.displayName = "Overlays";