likec4 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.
@@ -1,11 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { d8 as useRouter, d9 as useParams, da as isNotFound, db as Container, br as Alert, U as Text, dc as Code, W as Button, dd as e, de as e$1, as as e$2, bt as Group, bh as rem, df as Link, bZ as Title, d1 as createFileRoute, Q as m, d0 as Outlet, d as deepEqual, s as shallowEqual, dg as useMatches, dh as useIsomorphicLayoutEffect, d3 as useDocumentTitle, di as SimpleGrid, dj as useInViewport, dk as e$3, ci as Card, aQ as Box, d2 as redirect, k as useMantineColorScheme, dl as useComputedColorScheme, cY as useNavigate, aA as ActionIcon, aF as MotionDiv, cN as isValidMotionProp, aD as Menu, bc as MenuTarget, bd as MenuDropdown, be as MenuItem, dm as MenuDivider, bp as Stack, b5 as CopyButton$1, dn as Select, dp as ModalRoot, dq as ModalOverlay, dr as ModalContent, ds as ModalBody, bL as Tabs, bM as TabsList, bN as TabsTab, bO as TabsPanel, dt as useMantineTheme, du as useMediaQuery, cq as useDisclosure, aG as Divider, dv as MenuLabel, dw as useSearch, dx as LoadingOverlay, dy as toBlob, c$ as stripSearchParams, cZ as z, bS as useCallbackRef, aL as useIsMounted, T as Tooltip, dz as useAsync, dA as Tt, dB as _t, bP as ScrollArea, dC as Ht, dD as notFound } from "../vendors.js";
2
+ import { d8 as useRouter, d9 as useParams, da as isNotFound, db as Container, br as Alert, U as Text, dc as Code, W as Button, dd as e, de as e$1, as as e$2, bt as Group, bh as rem, df as Link, bZ as Title, d1 as createFileRoute, Q as m, d0 as Outlet, k as useMantineColorScheme, dg as useComputedColorScheme, cY as useNavigate, aA as ActionIcon, c9 as useHotkeys, aI as AnimatePresence, d as deepEqual, s as shallowEqual, dh as useMatches, di as useIsomorphicLayoutEffect, a9 as r, dj as onMount, v as useStore$1, r as atom, bq as useTree, bs as Tree, b6 as HoverCard, b7 as HoverCardTarget, b8 as HoverCardDropdown, R as ThemeIcon, ch as useLocalStorage, dk as Drawer, bP as ScrollArea, S as SegmentedControl, d3 as useDocumentTitle, dl as Burger, dm as SimpleGrid, dn as useInViewport, dp as e$3, ci as Card, aQ as Box$1, d2 as redirect, aF as MotionDiv, cN as isValidMotionProp, aD as Menu, bc as MenuTarget, bd as MenuDropdown, be as MenuItem, dq as MenuDivider, bp as Stack, b5 as CopyButton$1, dr as Select, ds as ModalRoot, dt as ModalOverlay, du as ModalContent, dv as ModalBody, bL as Tabs, bM as TabsList, bN as TabsTab, bO as TabsPanel, dw as useMantineTheme, dx as useMediaQuery, cq as useDisclosure, aG as Divider, dy as MenuLabel, dz as useSearch, dA as LoadingOverlay, dB as toBlob, c$ as stripSearchParams, cZ as z, bS as useCallbackRef, aL as useIsMounted, T as Tooltip, dC as useAsync, dD as Tt, dE as _t, dF as Ht, dG as notFound } from "../vendors.js";
3
3
  import { loadModel } from "likec4:model";
4
- import { c as css, s as styled, I as IconRendererProvider, a as LikeC4ModelProvider, u as useUpdateEffect, S as StaticLikeC4Diagram, M as Markdown, b as LikeC4AdHocViewEditor, B as Box$1, d as IconMoonStars, e as IconSun, f as createStyleContext, n as navigationPanel, i as isCssProperty, g as useLikeC4Projects$1, h as IconChevronDown, j as IconAlertTriangle, k as IconCheck, l as IconCopy, m as IconExternalLink, o as IconShare, p as pickViewBounds, q as LikeC4Diagram, r as useLikeC4Model, t as useDiagramContext, v as useDiagram, w as useOnDiagramEvent, x as LikeC4EditorProvider } from "../likec4.js";
4
+ import { c as css, s as styled, I as IconRendererProvider, a as LikeC4ModelProvider, b as IconMoonStars, d as IconSun, n as normalizeSearch, S as SearchContext, e as SearchControl, F as FramerMotionConfig, O as Overlay, f as bodyCss, g as dialogCss, h as SearchPanelContent, u as useUpdateEffect, i as useLikeC4Model, j as StaticLikeC4Diagram, B as Box, k as IconStarFilled, l as IconStack2, m as IconLayoutDashboard, o as IconFileCode, p as IconFolderOpen, q as IconFolderFilled, r as IconArrowLeft, N as NavigationPanel$1, M as Markdown, t as LikeC4AdHocViewEditor, v as createStyleContext, w as navigationPanel, x as isCssProperty, y as useLikeC4Projects$1, z as IconChevronDown, A as IconAlertTriangle, C as IconCheck, D as IconCopy, E as IconExternalLink, G as IconShare, H as pickViewBounds, J as LikeC4Diagram, K as useDiagramContext, P as useDiagram, Q as useOnDiagramEvent, R as LikeC4EditorProvider } from "../likec4.js";
5
5
  import { getProjectIcons } from "likec4:icons";
6
- import { useMemo, createContext, useContext, useState, useEffect, useRef, memo, useCallback } from "react";
6
+ import { useMemo, createContext, useContext, useRef, useEffect, useState, useCallback, memo } from "react";
7
7
  import { useStore } from "likec4/vite-plugin/internal";
8
8
  import { RichText } from "@likec4/core/types";
9
+ import { nonexhaustive, compareNatural } from "@likec4/core/utils";
9
10
  import { pageTitle, ComponentName, useHashHistory, isDevelopment, krokiPumlSvgUrl, krokiD2SvgUrl } from "../const.js";
10
11
  import { likec4rpc, isRpcAvailable } from "likec4:rpc";
11
12
  import { R as Route$c, r as resolveForceColorScheme } from "./index.js";
@@ -198,6 +199,95 @@ function RouteComponent$1() {
198
199
  const { $likec4model, projectId } = Route$b.useLoaderData();
199
200
  return /* @__PURE__ */ jsx(ViewOutlet, { children: /* @__PURE__ */ jsx(m, { FallbackComponent: Fallback, children: /* @__PURE__ */ jsx(LikeC4IconRendererContext, { projectId, children: /* @__PURE__ */ jsx(LikeC4ModelContext, { likec4model: $likec4model, children: /* @__PURE__ */ jsx(Outlet, {}) }) }) }) });
200
201
  }
202
+ function ColorSchemeToggle() {
203
+ const { setColorScheme } = useMantineColorScheme({
204
+ keepTransitions: !0
205
+ }), computedColorScheme = useComputedColorScheme("light"), navigate = useNavigate(), { theme: urlTheme } = Route$c.useSearch(), isForced = resolveForceColorScheme(urlTheme) != null, pendingScheme = useRef(null);
206
+ return useEffect(() => {
207
+ pendingScheme.current != null && !isForced && (setColorScheme(pendingScheme.current), pendingScheme.current = null);
208
+ }, [isForced, setColorScheme]), /* @__PURE__ */ jsxs(
209
+ ActionIcon,
210
+ {
211
+ size: "md",
212
+ variant: "subtle",
213
+ color: "gray",
214
+ onClick: () => {
215
+ if (pendingScheme.current != null) return;
216
+ const next = computedColorScheme === "light" ? "dark" : "light";
217
+ isForced ? (pendingScheme.current = next, navigate({
218
+ from: Route$c.fullPath,
219
+ search: (prev) => ({ ...prev, theme: void 0 }),
220
+ replace: !0
221
+ }).catch(() => {
222
+ pendingScheme.current = null;
223
+ })) : setColorScheme(next);
224
+ },
225
+ "aria-label": "Toggle color scheme",
226
+ children: [
227
+ /* @__PURE__ */ jsx(IconMoonStars, { stroke: 1.5, display: computedColorScheme === "light" ? "block" : "none" }),
228
+ /* @__PURE__ */ jsx(IconSun, { stroke: 1.5, display: computedColorScheme === "dark" ? "block" : "none" })
229
+ ]
230
+ }
231
+ );
232
+ }
233
+ function OverviewSearchAdapter({
234
+ onClose,
235
+ children
236
+ }) {
237
+ const [searchValue, setSearchValue] = useState(""), [pickViewFor, setPickViewFor] = useState(null), navigate = useNavigate(), navigateTo = useCallback((viewId, focusOnElement) => {
238
+ onClose(), navigate({
239
+ to: "/view/$viewId/",
240
+ params: { viewId },
241
+ search: (prev) => ({
242
+ ...prev,
243
+ ...focusOnElement && { focusOnElement }
244
+ })
245
+ });
246
+ }, [navigate, onClose]), openPickView = useCallback((elementFqn) => {
247
+ setPickViewFor(elementFqn);
248
+ }, []), closePickView = useCallback(() => {
249
+ setPickViewFor(null);
250
+ }, []), value = useMemo(() => ({
251
+ searchValue,
252
+ setSearchValue,
253
+ normalizedSearch: normalizeSearch(searchValue),
254
+ navigateTo,
255
+ openPickView,
256
+ closePickView,
257
+ pickViewFor,
258
+ close: onClose,
259
+ currentViewId: null,
260
+ openedWithSearch: null
261
+ }), [searchValue, pickViewFor, navigateTo, openPickView, closePickView, onClose]);
262
+ return /* @__PURE__ */ jsx(SearchContext.Provider, { value, children });
263
+ }
264
+ const OverviewSearch = memo(() => {
265
+ const [isOpened, setIsOpened] = useState(!1), open = useCallback(() => setIsOpened(!0), []), close = useCallback(() => setIsOpened(!1), []);
266
+ return useHotkeys([
267
+ ["mod+k", open, { preventDefault: !0 }]
268
+ ]), /* @__PURE__ */ jsxs(Fragment, { children: [
269
+ /* @__PURE__ */ jsx(SearchControl, { onClick: open }),
270
+ /* @__PURE__ */ jsx(FramerMotionConfig, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: isOpened && /* @__PURE__ */ jsx(
271
+ Overlay,
272
+ {
273
+ fullscreen: !0,
274
+ withBackdrop: !1,
275
+ backdrop: {
276
+ opacity: 0.9
277
+ },
278
+ classes: {
279
+ dialog: dialogCss,
280
+ body: bodyCss
281
+ },
282
+ openDelay: 0,
283
+ onClose: close,
284
+ "data-likec4-search": "true",
285
+ children: /* @__PURE__ */ jsx(OverviewSearchAdapter, { onClose: close, children: /* @__PURE__ */ jsx(SearchPanelContent, {}) })
286
+ }
287
+ ) }) })
288
+ ] });
289
+ });
290
+ OverviewSearch.displayName = "OverviewSearch";
201
291
  function useTransparentBackground(enabled = !0) {
202
292
  useIsomorphicLayoutEffect(() => {
203
293
  const htmlEl = document.body.parentElement;
@@ -248,6 +338,251 @@ function useCurrentProject() {
248
338
  });
249
339
  return projects.find((p) => p.id === projectId) ?? projects[0];
250
340
  }
341
+ const isTreeNodeData = (node) => "type" in node && ["file", "folder", "view", "deployment-view"].includes(node.type);
342
+ function dropFilename(relativePath) {
343
+ return relativePath === "" ? "" : relativePath.split("/").slice(0, -1).join("/");
344
+ }
345
+ function compareTreeNodes(a, b) {
346
+ return a.children.length === 0 && b.children.length > 0 ? 1 : a.children.length > 0 && b.children.length === 0 ? -1 : compareNatural(a.label, b.label);
347
+ }
348
+ function buildDiagramTreeData(views, groupBy) {
349
+ const root2 = {
350
+ value: "",
351
+ label: "Diagrams",
352
+ type: "folder",
353
+ children: []
354
+ }, findParent = (path) => {
355
+ let parent = root2;
356
+ if (path === "")
357
+ return parent;
358
+ const segments = path.split("/"), traversed = ["@fs"];
359
+ for (; segments.length; ) {
360
+ const label = segments.shift();
361
+ traversed.push(label);
362
+ const value = traversed.join("/");
363
+ let node = r(parent.children, (n) => n.value === value);
364
+ node || (node = { label, value, type: "folder", children: [] }, parent.children.push(node)), parent = node;
365
+ }
366
+ return parent;
367
+ };
368
+ for (const view of views) {
369
+ let relativePath;
370
+ switch (groupBy) {
371
+ case "by-files":
372
+ relativePath = view.$view.sourcePath ?? "";
373
+ break;
374
+ case "by-folders":
375
+ relativePath = dropFilename(view.$view.sourcePath ?? "");
376
+ break;
377
+ case "none":
378
+ relativePath = "";
379
+ break;
380
+ default:
381
+ nonexhaustive(groupBy);
382
+ }
383
+ const parent = findParent(relativePath);
384
+ parent.children.push({
385
+ value: view.id,
386
+ label: view.title ?? view.id,
387
+ type: view.isDeploymentView() ? "deployment-view" : "view",
388
+ children: []
389
+ }), parent !== root2 && (parent.children.sort(compareTreeNodes), groupBy === "by-files" && parent.type !== "file" && (parent.type = "file"));
390
+ }
391
+ return root2.children.sort(compareTreeNodes);
392
+ }
393
+ function useDiagramsTreeData(groupBy = "by-files") {
394
+ const model = useLikeC4Model();
395
+ return useMemo(() => buildDiagramTreeData([...model.views()], groupBy), [model, groupBy]);
396
+ }
397
+ const drawerOpenedAtom = atom(!1);
398
+ onMount(drawerOpenedAtom, () => {
399
+ drawerOpenedAtom.set(!1);
400
+ });
401
+ const useDrawerOpened = () => useStore$1(drawerOpenedAtom), SidebarDrawerOps = {
402
+ open: () => drawerOpenedAtom.set(!0),
403
+ close: () => drawerOpenedAtom.set(!1)
404
+ }, isFile = (node) => isTreeNodeData(node) && node.type === "file", FolderIcon = ({ node, expanded }) => isFile(node) ? /* @__PURE__ */ jsx(ThemeIcon, { size: "sm", variant: "transparent", color: "indigo", children: /* @__PURE__ */ jsx(IconFileCode, { size: 16 }) }) : /* @__PURE__ */ jsx(ThemeIcon, { size: "sm", variant: "transparent", color: "violet", children: expanded ? /* @__PURE__ */ jsx(IconFolderOpen, { size: 16 }) : /* @__PURE__ */ jsx(IconFolderFilled, { size: 16 }) }), setHoveredNode = () => {
405
+ }, DiagramsTree = /* @__PURE__ */ memo(({ groupBy }) => {
406
+ const views = useLikeC4Views(), data = useDiagramsTreeData(groupBy), navigate = useNavigate(), navigateTo = (viewId2) => {
407
+ SidebarDrawerOps.close(), navigate({
408
+ to: "/view/$viewId/",
409
+ viewTransition: !1,
410
+ params: { viewId: viewId2 }
411
+ });
412
+ }, [diagram] = useCurrentView(), viewId = diagram?.id ?? null, tree = useTree({
413
+ multiple: !1
414
+ });
415
+ tree.setHoveredNode = setHoveredNode;
416
+ const sourcePath = diagram?.sourcePath ?? null;
417
+ useUpdateEffect(() => {
418
+ tree.collapseAllNodes();
419
+ }, [groupBy]), useEffect(() => {
420
+ if (sourcePath) {
421
+ const segments = sourcePath.split("/");
422
+ let path = "@fs";
423
+ for (const segment of segments)
424
+ path += `/${segment}`, tree.expand(path);
425
+ }
426
+ }, [sourcePath, groupBy]), useEffect(() => {
427
+ viewId && tree.select(viewId);
428
+ }, [viewId]);
429
+ const theme = useComputedColorScheme();
430
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
431
+ Tree,
432
+ {
433
+ allowRangeSelection: !1,
434
+ tree,
435
+ data,
436
+ styles: {
437
+ node: {
438
+ marginTop: 2,
439
+ marginBottom: 2
440
+ }
441
+ },
442
+ levelOffset: "md",
443
+ renderNode: ({ node, selected, expanded, elementProps, hasChildren }) => /* @__PURE__ */ jsx(DiagramPreviewHoverCard, { diagram: hasChildren ? void 0 : views.find((v) => v.id === node.value), children: /* @__PURE__ */ jsx(
444
+ Button,
445
+ {
446
+ fullWidth: !0,
447
+ color: theme === "light" ? "dark" : "gray",
448
+ variant: selected ? "transparent" : "subtle",
449
+ size: "sm",
450
+ fz: "sm",
451
+ fw: hasChildren ? "600" : "500",
452
+ justify: "flex-start",
453
+ styles: {
454
+ section: {
455
+ opacity: 0.5
456
+ }
457
+ },
458
+ leftSection: /* @__PURE__ */ jsxs(Fragment, { children: [
459
+ !hasChildren && node.value === "index" && /* @__PURE__ */ jsx(IconStarFilled, { size: 14, opacity: 0.7 }),
460
+ !hasChildren && node.value !== "index" && isTreeNodeData(node) && /* @__PURE__ */ jsxs(Fragment, { children: [
461
+ node.type === "deployment-view" && /* @__PURE__ */ jsx(IconStack2, { size: 14 }),
462
+ node.type === "view" && /* @__PURE__ */ jsx(IconLayoutDashboard, { size: 14 })
463
+ ] }),
464
+ hasChildren && /* @__PURE__ */ jsx(FolderIcon, { node, expanded })
465
+ ] }),
466
+ ...elementProps,
467
+ ...!hasChildren && {
468
+ onClick: (e2) => {
469
+ e2.stopPropagation(), navigateTo(node.value);
470
+ }
471
+ },
472
+ children: node.label
473
+ }
474
+ ) })
475
+ }
476
+ ) });
477
+ }, (prev, next) => prev.groupBy === next.groupBy);
478
+ function DiagramPreviewHoverCard({ diagram, children }) {
479
+ const ratio = diagram ? Math.max(diagram.bounds.width / 400, diagram.bounds.height / 300) : 1, width = diagram ? Math.round(diagram.bounds.width / ratio) : 0, height = diagram ? Math.round(diagram.bounds.height / ratio) : 0;
480
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
481
+ diagram && /* @__PURE__ */ jsxs(HoverCard, { position: "right-start", openDelay: 400, closeDelay: 100, keepMounted: !1, shadow: "lg", children: [
482
+ /* @__PURE__ */ jsx(HoverCardTarget, { children }),
483
+ /* @__PURE__ */ jsx(HoverCardDropdown, { style: { width, height }, p: "xs", children: /* @__PURE__ */ jsx(DiagramPreview, { diagram }) })
484
+ ] }),
485
+ !diagram && children
486
+ ] });
487
+ }
488
+ const DiagramPreview = memo(({ diagram }) => {
489
+ const ratio = Math.max(diagram.bounds.width / 400, diagram.bounds.height / 300), width = Math.round(diagram.bounds.width / ratio), height = Math.round(diagram.bounds.height / ratio);
490
+ return /* @__PURE__ */ jsx(
491
+ StaticLikeC4Diagram,
492
+ {
493
+ view: diagram,
494
+ fitView: !0,
495
+ fitViewPadding: "4px",
496
+ enableElementDetails: !1,
497
+ reduceGraphics: !0,
498
+ initialWidth: width,
499
+ initialHeight: height
500
+ }
501
+ );
502
+ }, (prev, next) => prev.diagram.id === next.diagram.id), SidebarDrawer = memo(() => {
503
+ const opened = useDrawerOpened(), [grouping, setGrouping] = useLocalStorage({
504
+ key: "sidebar-drawer-grouping",
505
+ defaultValue: "by-files"
506
+ }), isSingleProject = useMatches({
507
+ select: (matches) => matches.some((match) => match.routeId === "/_single")
508
+ });
509
+ return /* @__PURE__ */ jsxs(
510
+ Drawer.Root,
511
+ {
512
+ keepMounted: isSingleProject,
513
+ opened,
514
+ scrollAreaComponent: ScrollArea.Autosize,
515
+ onClose: SidebarDrawerOps.close,
516
+ children: [
517
+ /* @__PURE__ */ jsx(Drawer.Overlay, { blur: 2 }),
518
+ /* @__PURE__ */ jsxs(Drawer.Content, { children: [
519
+ /* @__PURE__ */ jsxs(Drawer.Header, { children: [
520
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
521
+ /* @__PURE__ */ jsx(
522
+ Button,
523
+ {
524
+ component: Link,
525
+ to: "/",
526
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 14 }),
527
+ color: "dimmed",
528
+ variant: "subtle",
529
+ px: rem(5),
530
+ styles: {
531
+ section: {
532
+ marginInlineEnd: 4
533
+ }
534
+ },
535
+ size: "xs",
536
+ children: "Overview"
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsx(
540
+ SegmentedControl,
541
+ {
542
+ size: "xs",
543
+ withItemsBorders: !1,
544
+ value: grouping,
545
+ onChange: setGrouping,
546
+ data: [
547
+ { label: "By files", value: "by-files" },
548
+ { label: "By folders", value: "by-folders" },
549
+ { label: "List", value: "none" }
550
+ ]
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ leftSection: /* @__PURE__ */ jsx(IconStarFilled, { size: 12, stroke: 2 }),
557
+ color: "dimmed",
558
+ variant: "subtle",
559
+ px: rem(5),
560
+ styles: {
561
+ section: {
562
+ marginInlineEnd: 4
563
+ }
564
+ },
565
+ size: "xs",
566
+ renderRoot: (props) => /* @__PURE__ */ jsx(
567
+ Link,
568
+ {
569
+ to: "/view/$viewId",
570
+ params: { viewId: "index" },
571
+ ...props
572
+ }
573
+ ),
574
+ children: "Open index"
575
+ }
576
+ )
577
+ ] }),
578
+ /* @__PURE__ */ jsx(Drawer.CloseButton, {})
579
+ ] }),
580
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsx(DiagramsTree, { groupBy: grouping }) })
581
+ ] })
582
+ ]
583
+ }
584
+ );
585
+ });
251
586
  css({
252
587
  color: "text.dimmed"
253
588
  });
@@ -282,16 +617,54 @@ const previewBg = css({
282
617
  function RouteComponent() {
283
618
  useDocumentTitle(pageTitle);
284
619
  const views = useLikeC4Views();
285
- return /* @__PURE__ */ jsx(Container, { size: "xl", children: /* @__PURE__ */ jsx(
286
- SimpleGrid,
287
- {
288
- p: { base: "md", sm: "xl" },
289
- cols: { base: 1, sm: 2, md: 3, xl: 4 },
290
- spacing: { base: 10, sm: "xl" },
291
- verticalSpacing: { base: "md", sm: "xl" },
292
- children: views.map((v) => /* @__PURE__ */ jsx(ViewCard, { view: v }, v.id))
293
- }
294
- ) });
620
+ return /* @__PURE__ */ jsxs(Container, { size: "xl", children: [
621
+ /* @__PURE__ */ jsx(SidebarDrawer, {}),
622
+ /* @__PURE__ */ jsxs(
623
+ "div",
624
+ {
625
+ className: css({
626
+ containerName: "likec4-root",
627
+ containerType: "inline-size",
628
+ display: "flex",
629
+ alignItems: "flex-start",
630
+ justifyContent: "space-between",
631
+ padding: "xs",
632
+ gap: "xs",
633
+ position: "sticky",
634
+ top: "0",
635
+ zIndex: "10",
636
+ backgroundColor: "likec4.panel.bg/85",
637
+ backdropFilter: "blur(8px)"
638
+ }),
639
+ children: [
640
+ /* @__PURE__ */ jsx(NavigationPanel$1.Root, { css: { position: "relative", width: "max-content", margin: "0" }, children: /* @__PURE__ */ jsxs(NavigationPanel$1.Body, { children: [
641
+ /* @__PURE__ */ jsx("div", { style: { width: 0, height: 36 }, "aria-hidden": !0 }),
642
+ /* @__PURE__ */ jsx(Burger, { size: "sm", onClick: SidebarDrawerOps.open, "aria-label": "Toggle navigation" }),
643
+ /* @__PURE__ */ jsx(
644
+ NavigationPanel$1.Logo,
645
+ {
646
+ css: { flexShrink: 0 },
647
+ onClick: () => window.scrollTo({ top: 0, behavior: "smooth" })
648
+ }
649
+ ),
650
+ /* @__PURE__ */ jsx(OverviewSearch, {})
651
+ ] }) }),
652
+ /* @__PURE__ */ jsx(NavigationPanel$1.Root, { panelPosition: "right", css: { position: "relative", margin: "0" }, children: /* @__PURE__ */ jsx(NavigationPanel$1.Body, { children: /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", minHeight: 36 }, children: /* @__PURE__ */ jsx(ColorSchemeToggle, {}) }) }) })
653
+ ]
654
+ }
655
+ ),
656
+ /* @__PURE__ */ jsx(
657
+ SimpleGrid,
658
+ {
659
+ p: { base: "md", sm: "md" },
660
+ pt: { base: "sm", sm: "sm" },
661
+ cols: { base: 1, sm: 2, md: 3, xl: 4 },
662
+ spacing: { base: 10, sm: "xl" },
663
+ verticalSpacing: { base: "md", sm: "xl" },
664
+ children: views.map((v) => /* @__PURE__ */ jsx(ViewCard, { view: v }, v.id))
665
+ }
666
+ )
667
+ ] });
295
668
  }
296
669
  function ViewCard({ view }) {
297
670
  const [visible, setVisible] = useState(!1), { ref, inViewport } = useInViewport();
@@ -309,7 +682,7 @@ function ViewCard({ view }) {
309
682
  className: "group",
310
683
  withBorder: !0,
311
684
  children: [
312
- /* @__PURE__ */ jsx(Card.Section, { children: /* @__PURE__ */ jsx(Box, { className: previewBg, style: { height: 200 }, children: visible && /* @__PURE__ */ jsx(
685
+ /* @__PURE__ */ jsx(Card.Section, { children: /* @__PURE__ */ jsx(Box$1, { className: previewBg, style: { height: 200 }, children: visible && /* @__PURE__ */ jsx(
313
686
  StaticLikeC4Diagram,
314
687
  {
315
688
  background: "transparent",
@@ -410,7 +783,7 @@ function WebcomponentPage() {
410
783
  </html>
411
784
  `;
412
785
  return /* @__PURE__ */ jsx(
413
- Box$1,
786
+ Box,
414
787
  {
415
788
  css: {
416
789
  position: "fixed",
@@ -436,38 +809,6 @@ function WebcomponentPage() {
436
809
  }
437
810
  );
438
811
  }
439
- function ColorSchemeToggle() {
440
- const { setColorScheme } = useMantineColorScheme({
441
- keepTransitions: !0
442
- }), computedColorScheme = useComputedColorScheme("light"), navigate = useNavigate(), { theme: urlTheme } = Route$c.useSearch(), isForced = resolveForceColorScheme(urlTheme) != null, pendingScheme = useRef(null);
443
- return useEffect(() => {
444
- pendingScheme.current != null && !isForced && (setColorScheme(pendingScheme.current), pendingScheme.current = null);
445
- }, [isForced, setColorScheme]), /* @__PURE__ */ jsxs(
446
- ActionIcon,
447
- {
448
- visibleFrom: "sm",
449
- size: "md",
450
- variant: "subtle",
451
- color: "gray",
452
- onClick: () => {
453
- if (pendingScheme.current != null) return;
454
- const next = computedColorScheme === "light" ? "dark" : "light";
455
- isForced ? (pendingScheme.current = next, navigate({
456
- from: Route$c.fullPath,
457
- search: (prev) => ({ ...prev, theme: void 0 }),
458
- replace: !0
459
- }).catch(() => {
460
- pendingScheme.current = null;
461
- })) : setColorScheme(next);
462
- },
463
- "aria-label": "Toggle color scheme",
464
- children: [
465
- /* @__PURE__ */ jsx(IconMoonStars, { stroke: 1.5, display: computedColorScheme === "light" ? "block" : "none" }),
466
- /* @__PURE__ */ jsx(IconSun, { stroke: 1.5, display: computedColorScheme === "dark" ? "block" : "none" })
467
- ]
468
- }
469
- );
470
- }
471
812
  const { withProvider, withContext } = createStyleContext(navigationPanel), shouldForwardProp = (prop, variantKeys) => !variantKeys.includes(prop) && (isValidMotionProp(prop) || !isCssProperty(prop)), Root = withProvider(MotionDiv, "root", {
472
813
  shouldForwardProp
473
814
  }), Body = withContext(MotionDiv, "body", {
@@ -573,10 +914,10 @@ const AlertLocalhost = () => /* @__PURE__ */ jsx(
573
914
  `.trim();
574
915
  return /* @__PURE__ */ jsxs(Stack, { children: [
575
916
  code.includes("http://localhost") && /* @__PURE__ */ jsx(AlertLocalhost, {}),
576
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: "Embedded view is an iframe with a static diagram" }) }),
917
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: "Embedded view is an iframe with a static diagram" }) }),
577
918
  /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
578
919
  /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
579
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "HTML" }) }),
920
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "HTML" }) }),
580
921
  /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
581
922
  /* @__PURE__ */ jsx(ActionIcon, { component: "a", href, target: "_blank", variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconExternalLink, {}) }),
582
923
  /* @__PURE__ */ jsx(CopyButton$1, { value: code, timeout: 1500, children: CopyButtonChild })
@@ -584,7 +925,7 @@ const AlertLocalhost = () => /* @__PURE__ */ jsx(
584
925
  ] }),
585
926
  /* @__PURE__ */ jsx(Code, { block: !0, children: code }),
586
927
  /* @__PURE__ */ jsx(
587
- Box,
928
+ Box$1,
588
929
  {
589
930
  style: {
590
931
  alignSelf: "flex-start"
@@ -632,10 +973,10 @@ function WebcomponentsPanel({ diagram }) {
632
973
  );
633
974
  return /* @__PURE__ */ jsxs(Stack, { children: [
634
975
  jscode.includes("http://localhost") && /* @__PURE__ */ jsx(AlertLocalhost, {}),
635
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: "Add this script to your page:" }) }),
976
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: "Add this script to your page:" }) }),
636
977
  /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
637
978
  /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
638
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "JavaScript" }) }),
979
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "JavaScript" }) }),
639
980
  /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
640
981
  /* @__PURE__ */ jsx(ActionIcon, { component: "a", href: webcomponentPreview.href, target: "_blank", variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconExternalLink, {}) }),
641
982
  /* @__PURE__ */ jsx(
@@ -649,7 +990,7 @@ function WebcomponentsPanel({ diagram }) {
649
990
  ] })
650
991
  ] }),
651
992
  /* @__PURE__ */ jsx(Code, { block: !0, children: jscode }),
652
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
993
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
653
994
  "This script defines a custom element (webcomponent) that renders your diagrams.",
654
995
  /* @__PURE__ */ jsx("br", {}),
655
996
  "Script must be inserted once in the ",
@@ -662,8 +1003,8 @@ function WebcomponentsPanel({ diagram }) {
662
1003
  ] }),
663
1004
  /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
664
1005
  /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
665
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "HTML" }) }),
666
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
1006
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(Text, { fw: "500", size: "sm", children: "HTML" }) }),
1007
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(
667
1008
  CopyButton$1,
668
1009
  {
669
1010
  value: htmlCode,
@@ -673,7 +1014,7 @@ function WebcomponentsPanel({ diagram }) {
673
1014
  ) })
674
1015
  ] }),
675
1016
  /* @__PURE__ */ jsx(Code, { block: !0, children: htmlCode }),
676
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
1017
+ /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
677
1018
  "Insert this code to your page. Page may have multiple ",
678
1019
  /* @__PURE__ */ jsx("code", { children: "<likec4-view>" }),
679
1020
  "."
@@ -903,7 +1244,7 @@ function GuardedExportPage({ diagram }) {
903
1244
  });
904
1245
  }, extraPadding = 16, width = bounds.width + padding * 2 + extraPadding, height = bounds.height + padding * 2 + extraPadding;
905
1246
  return /* @__PURE__ */ jsxs(
906
- Box$1,
1247
+ Box,
907
1248
  {
908
1249
  ref: viewportRef,
909
1250
  "data-testid": "export-page",
@@ -1078,7 +1419,8 @@ function ViewReact() {
1078
1419
  },
1079
1420
  children: [
1080
1421
  /* @__PURE__ */ jsx(ListenForDynamicVariantChange, {}),
1081
- /* @__PURE__ */ jsx(OpenRelationshipBrowserFromUrl, {})
1422
+ /* @__PURE__ */ jsx(OpenRelationshipBrowserFromUrl, {}),
1423
+ /* @__PURE__ */ jsx(FocusElementFromUrl, {})
1082
1424
  ]
1083
1425
  }
1084
1426
  );
@@ -1115,6 +1457,30 @@ function OpenRelationshipBrowserFromUrl() {
1115
1457
  process();
1116
1458
  }, [relationships]), null;
1117
1459
  }
1460
+ function FocusElementFromUrl() {
1461
+ const router = useRouter(), diagram = useDiagram(), { focusOnElement } = useSearch({
1462
+ from: "__root__"
1463
+ }), processedRef = useRef(null), focusAndClear = useCallbackRef((fqn) => {
1464
+ try {
1465
+ if (processedRef.current === fqn) return;
1466
+ processedRef.current = fqn, diagram.focusOnElement(fqn), router.buildAndCommitLocation({
1467
+ search: (s) => {
1468
+ const { focusOnElement: _, ...rest } = s;
1469
+ return rest;
1470
+ },
1471
+ replace: !0,
1472
+ viewTransition: !1
1473
+ });
1474
+ } catch (error) {
1475
+ console.error("focusOnElement failed:", error);
1476
+ }
1477
+ });
1478
+ return useOnDiagramEvent("initialized", () => {
1479
+ focusOnElement && processedRef.current !== focusOnElement && focusAndClear(focusOnElement);
1480
+ }), useUpdateEffect(() => {
1481
+ focusOnElement || (processedRef.current = null);
1482
+ }, [focusOnElement]), null;
1483
+ }
1118
1484
  function ListenForDynamicVariantChange() {
1119
1485
  const router = useRouter(), dynamicViewVariant = useDiagramContext((c) => c.dynamicViewVariant);
1120
1486
  return useUpdateEffect(() => {
@@ -1192,7 +1558,8 @@ function ViewEditor() {
1192
1558
  },
1193
1559
  children: [
1194
1560
  /* @__PURE__ */ jsx(ListenForDynamicVariantChange, {}),
1195
- /* @__PURE__ */ jsx(OpenRelationshipBrowserFromUrl, {})
1561
+ /* @__PURE__ */ jsx(OpenRelationshipBrowserFromUrl, {}),
1562
+ /* @__PURE__ */ jsx(FocusElementFromUrl, {})
1196
1563
  ]
1197
1564
  }
1198
1565
  )
@@ -1206,7 +1573,7 @@ function CopyButton({ text }) {
1206
1573
  return /* @__PURE__ */ jsx(CopyButton$1, { value: text, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied" : "Copy", withArrow: !0, position: "right", children: /* @__PURE__ */ jsx(ActionIcon, { color: copied ? "teal" : "gray", variant: copied ? "light" : "subtle", onClick: copy, children: copied ? /* @__PURE__ */ jsx(IconCheck, { style: { width: rem(16) } }) : /* @__PURE__ */ jsx(IconCopy, { style: { width: rem(16) } }) }) }) });
1207
1574
  }
1208
1575
  function CopyToClipboard({ text }) {
1209
- return /* @__PURE__ */ jsx(Box, { pos: "absolute", top: "0", right: "0", p: "4", children: /* @__PURE__ */ jsx(CopyButton, { text }) });
1576
+ return /* @__PURE__ */ jsx(Box$1, { pos: "absolute", top: "0", right: "0", p: "4", children: /* @__PURE__ */ jsx(CopyButton, { text }) });
1210
1577
  }
1211
1578
  const svgContainer = css({
1212
1579
  minWidth: 300,
@@ -1285,9 +1652,9 @@ function ViewAsPuml({ pumlSource }) {
1285
1652
  children: krokiSvg.status === "loading" ? "Loading..." : "Render with Kroki"
1286
1653
  }
1287
1654
  ),
1288
- krokiSvg.status === "error" && /* @__PURE__ */ jsx(Box$1, { children: krokiSvg.error?.message })
1655
+ krokiSvg.status === "error" && /* @__PURE__ */ jsx(Box, { children: krokiSvg.error?.message })
1289
1656
  ] }),
1290
- krokiSvg.status === "success" && /* @__PURE__ */ jsx(Box$1, { className: svgContainer, children: krokiSvg.result ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: krokiSvg.result } }) : /* @__PURE__ */ jsx(Box$1, { children: "Empty result" }) })
1657
+ krokiSvg.status === "success" && /* @__PURE__ */ jsx(Box, { className: svgContainer, children: krokiSvg.result ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: krokiSvg.result } }) : /* @__PURE__ */ jsx(Box, { children: "Empty result" }) })
1291
1658
  ] }) })
1292
1659
  ]
1293
1660
  }
@@ -1354,7 +1721,7 @@ function ViewAsMmd({ viewId, mmdSource }) {
1354
1721
  }
1355
1722
  }
1356
1723
  ),
1357
- /* @__PURE__ */ jsx(_t, { children: /* @__PURE__ */ jsx(ScrollArea, { h: "100%", children: mmdSvg.result && /* @__PURE__ */ jsx(Box$1, { className: svgContainer, dangerouslySetInnerHTML: { __html: mmdSvg.result } }) }) })
1724
+ /* @__PURE__ */ jsx(_t, { children: /* @__PURE__ */ jsx(ScrollArea, { h: "100%", children: mmdSvg.result && /* @__PURE__ */ jsx(Box, { className: svgContainer, dangerouslySetInnerHTML: { __html: mmdSvg.result } }) }) })
1358
1725
  ]
1359
1726
  }
1360
1727
  ) });
@@ -1487,9 +1854,9 @@ function ViewAsD2({ d2Source }) {
1487
1854
  children: krokiSvg.status === "loading" ? "Loading..." : "Render with Kroki"
1488
1855
  }
1489
1856
  ),
1490
- krokiSvg.status === "error" && /* @__PURE__ */ jsx(Box$1, { children: krokiSvg.error?.message })
1857
+ krokiSvg.status === "error" && /* @__PURE__ */ jsx(Box, { children: krokiSvg.error?.message })
1491
1858
  ] }),
1492
- krokiSvg.status === "success" && /* @__PURE__ */ jsx(Box$1, { className: svgContainer, children: krokiSvg.result ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: krokiSvg.result } }) : /* @__PURE__ */ jsx(Box$1, { children: "Empty result" }) })
1859
+ krokiSvg.status === "success" && /* @__PURE__ */ jsx(Box, { className: svgContainer, children: krokiSvg.result ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: krokiSvg.result } }) : /* @__PURE__ */ jsx(Box, { children: "Empty result" }) })
1493
1860
  ] }) })
1494
1861
  ]
1495
1862
  }