@likec4/diagram 1.21.1 → 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 (109) hide show
  1. package/dist/LikeC4Diagram.js +9 -13
  2. package/dist/base/index.d.ts +0 -1
  3. package/dist/base/primitives/element/styles.css.js +1 -1
  4. package/dist/{base → components}/Link.css.js +3 -3
  5. package/dist/components/SearchControl.css.d.ts +2 -0
  6. package/dist/components/SearchControl.css.js +7 -0
  7. package/dist/components/SearchControl.d.ts +5 -0
  8. package/dist/components/SearchControl.js +17 -0
  9. package/dist/context/IconRenderer.d.ts +10 -0
  10. package/dist/context/IconRenderer.js +34 -0
  11. package/dist/hooks/useCurrentViewId.js +8 -0
  12. package/dist/hooks/useDiagram.d.ts +4 -4
  13. package/dist/likec4diagram/DiagramUI.js +0 -2
  14. package/dist/likec4diagram/XYFlow.d.ts +6 -1
  15. package/dist/likec4diagram/XYFlow.js +28 -29
  16. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -1
  17. package/dist/likec4diagram/state/actorContext.d.ts +16 -16
  18. package/dist/likec4diagram/state/assign.js +1 -1
  19. package/dist/likec4diagram/state/machine.d.ts +7 -7
  20. package/dist/likec4diagram/state/machine.js +10 -7
  21. package/dist/likec4diagram/ui/controls/Controls.js +63 -48
  22. package/dist/likec4diagram/ui/controls/NavigationButtons.js +44 -69
  23. package/dist/likec4diagram/ui/controls/_shared.js +1 -1
  24. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +1 -1
  25. package/dist/likec4diagram/ui/search/ElementsColumn.css.d.ts +15 -0
  26. package/dist/likec4diagram/ui/search/ElementsColumn.css.js +30 -0
  27. package/dist/likec4diagram/ui/search/ElementsColumn.d.ts +1 -0
  28. package/dist/likec4diagram/ui/search/ElementsColumn.js +243 -0
  29. package/dist/likec4diagram/ui/search/LikeC4Search.css.d.ts +8 -0
  30. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +20 -0
  31. package/dist/likec4diagram/ui/search/LikeC4Search.d.ts +1 -0
  32. package/dist/likec4diagram/ui/search/LikeC4Search.js +265 -0
  33. package/dist/likec4diagram/ui/search/SearchInput.d.ts +1 -0
  34. package/dist/likec4diagram/ui/search/SearchInput.js +196 -0
  35. package/dist/likec4diagram/ui/search/ViewsColum.d.ts +9 -0
  36. package/dist/likec4diagram/ui/search/ViewsColum.js +130 -0
  37. package/dist/likec4diagram/ui/search/ViewsColumn.css.d.ts +5 -0
  38. package/dist/likec4diagram/ui/search/ViewsColumn.css.js +12 -0
  39. package/dist/likec4diagram/ui/search/_shared.css.d.ts +16 -0
  40. package/dist/likec4diagram/ui/search/_shared.css.js +5 -0
  41. package/dist/likec4diagram/ui/search/state.d.ts +19 -0
  42. package/dist/likec4diagram/ui/search/state.js +72 -0
  43. package/dist/likec4diagram/ui/search/utils.d.ts +3 -0
  44. package/dist/likec4diagram/ui/search/utils.js +23 -0
  45. package/dist/likec4diagram/useLayoutConstraints.js +4 -5
  46. package/dist/likec4model/useLikeC4ElementsTree.d.ts +1 -0
  47. package/dist/likec4model/useLikeC4ElementsTree.js +1 -0
  48. package/dist/overlays/Overlays.js +9 -9
  49. package/dist/overlays/element-details/ElementDetailsCard.js +20 -13
  50. package/dist/overlays/overlay/Overlay.d.ts +6 -2
  51. package/dist/overlays/overlay/Overlay.js +18 -29
  52. package/dist/overlays/relationship-details/SelectEdge.js +1 -3
  53. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +6 -5
  54. package/dist/overlays/relationships-browser/SelectElement.js +10 -6
  55. package/dist/overlays/relationships-browser/actor.d.ts +2 -2
  56. package/dist/overlays/relationships-browser/actor.js +8 -8
  57. package/dist/overlays/relationships-browser/custom/nodeTypes.js +2 -2
  58. package/dist/style.css +1 -1
  59. package/package.json +13 -12
  60. package/src/LikeC4Diagram.tsx +0 -1
  61. package/src/base/index.ts +0 -2
  62. package/src/base/primitives/element/styles.css.ts +2 -2
  63. package/src/{base → components}/Link.css.ts +3 -3
  64. package/src/components/SearchControl.css.ts +69 -0
  65. package/src/components/SearchControl.tsx +33 -0
  66. package/src/context/IconRenderer.tsx +54 -1
  67. package/src/globals.css.ts +0 -2
  68. package/src/likec4diagram/DiagramUI.tsx +0 -4
  69. package/src/likec4diagram/XYFlow.tsx +30 -30
  70. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -1
  71. package/src/likec4diagram/state/assign.ts +1 -1
  72. package/src/likec4diagram/state/machine.ts +12 -10
  73. package/src/likec4diagram/ui/controls/Controls.tsx +35 -25
  74. package/src/likec4diagram/ui/controls/NavigationButtons.tsx +36 -58
  75. package/src/likec4diagram/ui/controls/styles.css.ts +3 -1
  76. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +1 -1
  77. package/src/likec4diagram/ui/search/ElementsColumn.css.ts +119 -0
  78. package/src/likec4diagram/ui/search/ElementsColumn.tsx +291 -0
  79. package/src/likec4diagram/ui/search/LikeC4Search.css.ts +117 -0
  80. package/src/likec4diagram/ui/search/LikeC4Search.tsx +312 -0
  81. package/src/likec4diagram/ui/search/SearchInput.tsx +235 -0
  82. package/src/likec4diagram/ui/search/ViewsColum.tsx +158 -0
  83. package/src/likec4diagram/ui/search/ViewsColumn.css.ts +37 -0
  84. package/src/likec4diagram/ui/search/_shared.css.ts +104 -0
  85. package/src/likec4diagram/ui/search/state.ts +76 -0
  86. package/src/likec4diagram/ui/search/utils.ts +20 -0
  87. package/src/likec4diagram/useLayoutConstraints.ts +4 -5
  88. package/src/likec4model/useLikeC4ElementsTree.ts +4 -4
  89. package/src/overlays/Overlays.tsx +31 -25
  90. package/src/overlays/element-details/ElementDetailsCard.css.ts +4 -4
  91. package/src/overlays/element-details/ElementDetailsCard.tsx +245 -240
  92. package/src/overlays/overlay/Overlay.css.ts +10 -9
  93. package/src/overlays/overlay/Overlay.tsx +38 -26
  94. package/src/overlays/relationship-details/SelectEdge.css.ts +1 -0
  95. package/src/overlays/relationship-details/SelectEdge.tsx +2 -4
  96. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +10 -5
  97. package/src/overlays/relationships-browser/SelectElement.tsx +88 -82
  98. package/src/overlays/relationships-browser/actor.ts +10 -10
  99. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +2 -2
  100. package/dist/LikeC4Search.css.d.ts +0 -1
  101. package/dist/LikeC4Search.css.js +0 -5
  102. package/dist/LikeC4Search.d.ts +0 -2
  103. package/dist/LikeC4Search.js +0 -88
  104. package/src/LikeC4Search.css.ts +0 -12
  105. package/src/LikeC4Search.tsx +0 -91
  106. /package/dist/{base → components}/Link.css.d.ts +0 -0
  107. /package/dist/{base → components}/Link.d.ts +0 -0
  108. /package/dist/{base → components}/Link.js +0 -0
  109. /package/src/{base → components}/Link.tsx +0 -0
@@ -0,0 +1,265 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { Portal, RemoveScroll, Group, Box, ActionIcon, Grid, GridCol, Title, ScrollArea, ScrollAreaAutosize, Stack } from "@mantine/core";
3
+ import { useDisclosure, useHotkeys, useCallbackRef, useFocusTrap, useFocusWithin, useMergedRef, useWindowEvent, useFocusReturn } from "@mantine/hooks";
4
+ import { IconX } from "@tabler/icons-react";
5
+ import { AnimatePresence, m, LayoutGroup } from "framer-motion";
6
+ import { useRef } from "react";
7
+ import { SearchControl } from "../../../components/SearchControl.js";
8
+ import { ElementsColumn } from "./ElementsColumn.js";
9
+ import { backdrop, root, scrollArea, pickviewBackdrop, pickview, pickviewGroup } from "./LikeC4Search.css.js";
10
+ import { LikeC4SearchInput } from "./SearchInput.js";
11
+ import { LikeC4SearchContext, useCloseSearch, wasResetPickView, usePickView, setPickView } from "./state.js";
12
+ import { stopAndPrevent } from "./utils.js";
13
+ import { ViewsColumn, ViewButton } from "./ViewsColum.js";
14
+ import { useMantinePortalProps } from "../../../hooks/useMantinePortalProps.js";
15
+ function LikeC4Search() {
16
+ const [searchOpened, searchOps] = useDisclosure(false);
17
+ useHotkeys([
18
+ ["mod+k", () => searchOps.toggle(), {
19
+ preventDefault: true
20
+ }],
21
+ ["mod+f", () => searchOps.toggle(), {
22
+ preventDefault: true
23
+ }]
24
+ ]);
25
+ const afterCloseCbRef = useRef(null);
26
+ const close = useCallbackRef((cb) => {
27
+ afterCloseCbRef.current = cb ?? null;
28
+ searchOps.close();
29
+ });
30
+ const onExitComplete = useCallbackRef(() => {
31
+ if (afterCloseCbRef.current) {
32
+ afterCloseCbRef.current();
33
+ afterCloseCbRef.current = null;
34
+ }
35
+ });
36
+ const { portalProps } = useMantinePortalProps();
37
+ return /* @__PURE__ */ jsxs(LikeC4SearchContext, { value: close, children: [
38
+ /* @__PURE__ */ jsx(
39
+ SearchControl,
40
+ {
41
+ onClick: (e) => {
42
+ e.stopPropagation();
43
+ searchOps.toggle();
44
+ }
45
+ }
46
+ ),
47
+ /* @__PURE__ */ jsx(Portal, { ...portalProps, children: /* @__PURE__ */ jsx(RemoveScroll, { enabled: searchOpened, children: /* @__PURE__ */ jsx(AnimatePresence, { onExitComplete, children: searchOpened && /* @__PURE__ */ jsxs(Fragment, { children: [
48
+ /* @__PURE__ */ jsx(
49
+ m.div,
50
+ {
51
+ className: backdrop,
52
+ initial: { opacity: 0.7 },
53
+ animate: {
54
+ opacity: 1,
55
+ transition: {
56
+ duration: 0.1
57
+ }
58
+ },
59
+ exit: {
60
+ opacity: 0,
61
+ transition: {
62
+ duration: 0.075
63
+ }
64
+ }
65
+ },
66
+ "backdrop"
67
+ ),
68
+ /* @__PURE__ */ jsx(LikeC4SearchOverlay, {}, "overlay")
69
+ ] }) }) }) })
70
+ ] });
71
+ }
72
+ function LikeC4SearchOverlay() {
73
+ const close = useCloseSearch();
74
+ const focusTrapRef = useFocusTrap();
75
+ const { ref: forcusRef, focused } = useFocusWithin();
76
+ const ref = useMergedRef(forcusRef, focusTrapRef);
77
+ useWindowEvent(
78
+ "keydown",
79
+ (event) => {
80
+ if (event.key === "Escape") {
81
+ stopAndPrevent(event);
82
+ if (!wasResetPickView()) {
83
+ close();
84
+ }
85
+ return;
86
+ }
87
+ }
88
+ );
89
+ return /* @__PURE__ */ jsxs(
90
+ m.div,
91
+ {
92
+ ref,
93
+ className: root,
94
+ animate: {
95
+ opacity: 1,
96
+ scale: 1,
97
+ translateY: 0
98
+ },
99
+ exit: {
100
+ opacity: 0,
101
+ scale: 0.9,
102
+ transition: {
103
+ duration: 0.1
104
+ }
105
+ },
106
+ children: [
107
+ /* @__PURE__ */ jsxs(Group, { wrap: "nowrap", children: [
108
+ /* @__PURE__ */ jsx(Box, { flex: 1, px: "sm", children: /* @__PURE__ */ jsx(LikeC4SearchInput, {}) }),
109
+ /* @__PURE__ */ jsx(Box, { flex: "0 0 auto", children: /* @__PURE__ */ jsx(
110
+ ActionIcon,
111
+ {
112
+ size: "lg",
113
+ variant: "default",
114
+ onClick: (e) => {
115
+ e.stopPropagation();
116
+ close();
117
+ },
118
+ children: /* @__PURE__ */ jsx(IconX, {})
119
+ }
120
+ ) })
121
+ ] }),
122
+ /* @__PURE__ */ jsxs(Grid, { children: [
123
+ /* @__PURE__ */ jsx(GridCol, { span: 6, children: /* @__PURE__ */ jsx(Title, { component: "div", order: 6, c: "dimmed", pl: "sm", children: "Elements" }) }),
124
+ /* @__PURE__ */ jsx(GridCol, { span: 6, children: /* @__PURE__ */ jsx(Title, { component: "div", order: 6, c: "dimmed", pl: "sm", children: "Views" }) })
125
+ ] }),
126
+ /* @__PURE__ */ jsxs(
127
+ Grid,
128
+ {
129
+ style: {
130
+ containerName: "likec4-search-elements",
131
+ containerType: "size",
132
+ overflow: "hidden",
133
+ flexGrow: 1
134
+ },
135
+ children: [
136
+ /* @__PURE__ */ jsx(GridCol, { span: 6, children: /* @__PURE__ */ jsx(
137
+ ScrollArea,
138
+ {
139
+ type: "hover",
140
+ className: scrollArea,
141
+ pr: "xs",
142
+ scrollbars: "y",
143
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsx(LayoutGroup, { id: "likec4-search-elements", children: /* @__PURE__ */ jsx(ElementsColumn, {}) }) })
144
+ }
145
+ ) }),
146
+ /* @__PURE__ */ jsx(GridCol, { span: 6, children: /* @__PURE__ */ jsx(
147
+ ScrollArea,
148
+ {
149
+ type: "hover",
150
+ className: scrollArea,
151
+ pr: "xs",
152
+ scrollbars: "y",
153
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsx(LayoutGroup, { id: "likec4-search-views", children: /* @__PURE__ */ jsx(ViewsColumn, {}) }) })
154
+ }
155
+ ) })
156
+ ]
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsx(Box, {}),
160
+ /* @__PURE__ */ jsx(PickView, {})
161
+ ]
162
+ }
163
+ );
164
+ }
165
+ function PickView() {
166
+ const pickview$1 = usePickView();
167
+ const focusTrapRef = useFocusTrap(!!pickview$1);
168
+ useFocusReturn({
169
+ opened: !!pickview$1
170
+ });
171
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsx(LayoutGroup, { id: "likec4-search-pickview", children: pickview$1 && /* @__PURE__ */ jsxs(Fragment, { children: [
172
+ /* @__PURE__ */ jsx(
173
+ m.div,
174
+ {
175
+ className: pickviewBackdrop,
176
+ onClick: (e) => {
177
+ e.stopPropagation();
178
+ setPickView(null);
179
+ }
180
+ },
181
+ "backdrop"
182
+ ),
183
+ /* @__PURE__ */ jsxs(
184
+ m.div,
185
+ {
186
+ initial: {
187
+ opacity: 0,
188
+ scale: 0.95,
189
+ originY: 0,
190
+ translateX: "-50%",
191
+ translateY: -20
192
+ },
193
+ animate: {
194
+ opacity: 1,
195
+ scale: 1,
196
+ translateY: 0
197
+ },
198
+ exit: {
199
+ opacity: 0,
200
+ scale: 0.98,
201
+ translateY: -20,
202
+ transition: {
203
+ duration: 0.1
204
+ }
205
+ },
206
+ className: pickview,
207
+ "data-likec4-search-views": true,
208
+ ref: focusTrapRef,
209
+ children: [
210
+ /* @__PURE__ */ jsxs(Group, { px: "sm", py: "md", justify: "space-between", children: [
211
+ /* @__PURE__ */ jsx(Title, { order: 2, lh: 1, children: "Select view" }),
212
+ /* @__PURE__ */ jsx(
213
+ ActionIcon,
214
+ {
215
+ size: "md",
216
+ variant: "default",
217
+ onClick: (e) => {
218
+ e.stopPropagation();
219
+ setPickView(null);
220
+ },
221
+ children: /* @__PURE__ */ jsx(IconX, {})
222
+ }
223
+ )
224
+ ] }),
225
+ /* @__PURE__ */ jsxs(ScrollAreaAutosize, { mah: "calc(100vh - 8rem - 50px)", type: "never", children: [
226
+ pickview$1.scoped.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: "sm", px: "sm", className: pickviewGroup, children: [
227
+ /* @__PURE__ */ jsx(Title, { order: 6, c: "dimmed", children: "scoped views of the element" }),
228
+ pickview$1.scoped.map((view, i) => /* @__PURE__ */ jsx(
229
+ ViewButton,
230
+ {
231
+ view,
232
+ search: "",
233
+ loop: true,
234
+ mod: {
235
+ autofocus: i === 0
236
+ }
237
+ },
238
+ view.id
239
+ ))
240
+ ] }),
241
+ pickview$1.others.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: "sm", px: "sm", className: pickviewGroup, children: [
242
+ /* @__PURE__ */ jsx(Title, { order: 6, c: "dimmed", children: "views including this element" }),
243
+ pickview$1.others.map((view, i) => /* @__PURE__ */ jsx(
244
+ ViewButton,
245
+ {
246
+ view,
247
+ search: "",
248
+ loop: true,
249
+ mod: {
250
+ autofocus: i === 0 && pickview$1.scoped.length === 0
251
+ }
252
+ },
253
+ view.id
254
+ ))
255
+ ] })
256
+ ] })
257
+ ]
258
+ },
259
+ "pickview"
260
+ )
261
+ ] }) }) });
262
+ }
263
+ export {
264
+ LikeC4Search
265
+ };
@@ -0,0 +1 @@
1
+ export declare function LikeC4SearchInput(): import("react/jsx-runtime").JSX.Element;
@@ -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 {};