likec4 0.45.0 → 0.46.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,7 +1,16 @@
1
- import { compareFqnHierarchically, isAncestor } from "./fqn.js";
1
+ import { commonAncestor, compareFqnHierarchically, isAncestor } from "./fqn.js";
2
2
  import { either } from "rambdax";
3
3
  export const compareRelations = (a, b) => {
4
- return compareFqnHierarchically(a.source, b.source) || compareFqnHierarchically(a.target, b.target);
4
+ const parentA = commonAncestor(a.source, a.target);
5
+ const parentB = commonAncestor(b.source, b.target);
6
+ if (parentA && !parentB) {
7
+ return 1;
8
+ }
9
+ if (!parentA && parentB) {
10
+ return -1;
11
+ }
12
+ const compareParents = parentA && parentB ? compareFqnHierarchically(parentA, parentB) : 0;
13
+ return compareParents || compareFqnHierarchically(a.source, b.source) || compareFqnHierarchically(a.target, b.target);
5
14
  };
6
15
  const isInside = (parent) => {
7
16
  const prefix = parent + ".";
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { nonNullable, defaultTheme as theme } from "@likec4/core";
3
3
  import { useHookableRef, useUpdateEffect } from "@react-hookz/web/esm";
4
- import { easings, useSpring } from "@react-spring/konva";
4
+ import { useSpring } from "@react-spring/konva";
5
5
  import { clamp } from "rambdax";
6
6
  import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
7
7
  import { AnimatedStage, Layer } from "../konva.js";
@@ -30,7 +30,7 @@ function diagramNodeId(konvaNode) {
30
30
  export const Diagram = /* @__PURE__ */ forwardRef(
31
31
  ({
32
32
  diagram,
33
- padding: _padding = NoPadding,
33
+ padding = NoPadding,
34
34
  pannable = true,
35
35
  zoomable = true,
36
36
  animate = true,
@@ -56,11 +56,11 @@ export const Diagram = /* @__PURE__ */ forwardRef(
56
56
  }
57
57
  return value;
58
58
  });
59
- const width = _width ?? diagram.width;
60
- const height = _height ?? diagram.height;
59
+ const [paddingTop, paddingRight, paddingBottom, paddingLeft] = Array.isArray(padding) ? padding : [padding, padding, padding, padding];
60
+ const width = _width ?? diagram.width + paddingLeft + paddingRight;
61
+ const height = _height ?? diagram.height + paddingTop + paddingBottom;
61
62
  const toCenterOnRect = (centerTo) => {
62
- const [paddingTop, paddingRight, paddingBottom, paddingLeft] = Array.isArray(_padding) ? _padding : [_padding, _padding, _padding, _padding];
63
- const container = stageRef.current?.container();
63
+ const container = containerRef.current;
64
64
  const viewRect = {
65
65
  width: Math.min(container?.clientWidth ?? width, width) - paddingLeft - paddingRight,
66
66
  height: Math.min(container?.clientHeight ?? height, height) - paddingTop - paddingBottom
@@ -80,11 +80,13 @@ export const Diagram = /* @__PURE__ */ forwardRef(
80
80
  const [stageProps, stageSpringApi] = useSpring(
81
81
  () => initialPosition ? {
82
82
  from: initialPosition,
83
- to: toFitDiagram()
83
+ to: toFitDiagram(),
84
+ immediate
84
85
  } : {
85
- from: toFitDiagram(),
86
+ to: toFitDiagram(),
86
87
  immediate
87
- }
88
+ },
89
+ []
88
90
  );
89
91
  const centerOnRect = (centerTo) => {
90
92
  stageSpringApi.start({
@@ -93,14 +95,10 @@ export const Diagram = /* @__PURE__ */ forwardRef(
93
95
  });
94
96
  return;
95
97
  };
96
- const centerAndFit = (delay = 70, durationMs) => {
98
+ const centerAndFit = (delay = 0) => {
97
99
  stageSpringApi.start({
98
100
  to: toFitDiagram(),
99
101
  delay,
100
- config: durationMs ? {
101
- duration: durationMs,
102
- easing: easings.easeInOutCubic
103
- } : {},
104
102
  immediate
105
103
  });
106
104
  return;
@@ -128,13 +126,13 @@ export const Diagram = /* @__PURE__ */ forwardRef(
128
126
  ref,
129
127
  () => ({
130
128
  get stage() {
131
- return nonNullable(stageRef.current, "not mounted");
129
+ return stageRef.current;
132
130
  },
133
131
  get diagramView() {
134
132
  return refs.current.diagram;
135
133
  },
136
134
  get container() {
137
- return nonNullable(stageRef.current?.container(), "not mounted");
135
+ return stageRef.current?.container() ?? null;
138
136
  },
139
137
  resetStageZoom: (_immediate) => {
140
138
  refs.current.resetStageZoom(_immediate);
@@ -147,15 +145,18 @@ export const Diagram = /* @__PURE__ */ forwardRef(
147
145
  }),
148
146
  centerAndFit: () => refs.current.centerAndFit()
149
147
  }),
150
- [refs, id, stageRef]
148
+ [refs, stageRef]
151
149
  );
152
150
  const resetHoveredStates = useResetHoveredStates();
153
151
  useUpdateEffect(() => {
154
152
  resetHoveredStates();
155
153
  }, [id]);
156
154
  useUpdateEffect(() => {
157
- refs.current.centerAndFit(80, 650);
158
- }, [id, height, width]);
155
+ refs.current.centerAndFit(200);
156
+ }, [id]);
157
+ useUpdateEffect(() => {
158
+ refs.current.centerAndFit(50);
159
+ }, [height, width]);
159
160
  useEffect(() => {
160
161
  if (!zoomable) {
161
162
  return;
@@ -170,39 +171,41 @@ export const Diagram = /* @__PURE__ */ forwardRef(
170
171
  }, [zoomable]);
171
172
  useGesture(
172
173
  {
173
- onDragEnd: () => {
174
- DiagramGesture.isDragging = false;
175
- },
176
174
  onDrag: (state) => {
177
175
  const {
178
- pinching,
176
+ first,
177
+ last,
179
178
  down,
180
- cancel,
181
179
  intentional,
182
180
  offset: [x, y]
183
181
  } = state;
184
- if (pinching) {
185
- return cancel();
182
+ if (!intentional) {
183
+ return;
186
184
  }
187
- if (intentional) {
188
- DiagramGesture.isDragging = true;
189
- stageSpringApi.start({
190
- to: {
191
- x,
192
- y
193
- },
194
- immediate: immediate || down
195
- });
185
+ if (first || last) {
186
+ DiagramGesture.isDragging = first && !last;
196
187
  }
188
+ stageSpringApi.start({
189
+ to: {
190
+ x,
191
+ y
192
+ },
193
+ delay: 0,
194
+ immediate: immediate || down && !last
195
+ });
197
196
  },
198
197
  onPinch: ({ memo, first, last, origin: [ox, oy], movement: [ms], offset: [scale] }) => {
199
198
  if (first) {
199
+ DiagramGesture.isDragging = true;
200
200
  const stage = nonNullable(stageRef.current);
201
201
  const { x: x2, y: y2 } = stage.getAbsolutePosition();
202
202
  const tx = Math.round(ox - x2);
203
203
  const ty = Math.round(oy - y2);
204
204
  memo = [stage.x(), stage.y(), tx, ty];
205
205
  }
206
+ if (last) {
207
+ DiagramGesture.isDragging = false;
208
+ }
206
209
  const x = Math.round(memo[0] - (ms - 1) * memo[2]);
207
210
  const y = Math.round(memo[1] - (ms - 1) * memo[3]);
208
211
  stageSpringApi.start({
@@ -211,7 +214,7 @@ export const Diagram = /* @__PURE__ */ forwardRef(
211
214
  y,
212
215
  scale
213
216
  },
214
- immediate: immediate || !last || !first
217
+ delay: 0
215
218
  });
216
219
  return memo;
217
220
  }
@@ -222,23 +225,27 @@ export const Diagram = /* @__PURE__ */ forwardRef(
222
225
  target: containerRef,
223
226
  enabled: pannable,
224
227
  threshold: 4,
225
- from: () => [stageProps.x.get(), stageProps.y.get()],
226
- pointer: {
227
- buttons: -1,
228
- keys: false,
229
- touch: true,
230
- capture: true
231
- }
228
+ from: () => [stageRef.current?.x() ?? 0, stageRef.current?.y() ?? 0],
229
+ pointer: Object.assign(
230
+ {
231
+ keys: false,
232
+ mouse: true,
233
+ capture: true
234
+ },
235
+ !onNodeContextMenu && !onStageContextMenu && {
236
+ buttons: -1
237
+ }
238
+ )
232
239
  },
233
240
  pinch: {
234
241
  target: containerRef,
235
242
  pointer: {
236
243
  touch: true
237
244
  },
245
+ from: () => [stageRef.current?.scaleX() ?? 1, 0],
238
246
  enabled: zoomable,
239
- // eventOptions
240
247
  scaleBounds: { min: minZoom, max: maxZoom + 0.4 },
241
- rubberband: 0.04,
248
+ rubberband: 0.045,
242
249
  pinchOnWheel: true
243
250
  }
244
251
  }
@@ -133,7 +133,7 @@ const NodeShape = memo(
133
133
  ({ animate, node, ctrl, theme, isHovered, expired, onNodeClick }) => {
134
134
  const setHoveredNode = useSetHoveredNode();
135
135
  const _isCompound = isCompound(node);
136
- const isNavigatable = !!node.navigateTo && !!onNodeClick;
136
+ const isNavigatable = animate && !!node.navigateTo && !!onNodeClick;
137
137
  const Shape = nodeShape(node);
138
138
  const springs = ctrl.springs;
139
139
  let zoomInIconY;
@@ -150,18 +150,16 @@ const NodeShape = memo(
150
150
  {
151
151
  name: node.id,
152
152
  visible: expired !== true,
153
- ...animate && {
154
- onPointerEnter: (e) => {
155
- setHoveredNode(node);
156
- if (isNavigatable) {
157
- mousePointer(e);
158
- }
159
- },
160
- onPointerLeave: (e) => {
161
- setHoveredNode(null);
162
- mouseDefault(e);
153
+ onPointerEnter: (e) => {
154
+ setHoveredNode(node);
155
+ if (isNavigatable) {
156
+ mousePointer(e);
163
157
  }
164
158
  },
159
+ onPointerLeave: (e) => {
160
+ setHoveredNode(null);
161
+ mouseDefault(e);
162
+ },
165
163
  ...onNodeClick && {
166
164
  onPointerClick: (e) => {
167
165
  if (DiagramGesture.isDragging || e.evt.button !== 0) {
@@ -20,11 +20,11 @@ function EdgeArrow({
20
20
  closed: true,
21
21
  fill: isOutline ? void 0 : springs.lineColor,
22
22
  stroke: springs.lineColor,
23
- strokeWidth: 1.6,
24
- hitStrokeWidth: 5,
23
+ strokeWidth: 1,
25
24
  lineCap: "round",
26
25
  lineJoin: "miter",
27
26
  perfectDrawEnabled: false,
27
+ listening: false,
28
28
  globalCompositeOperation
29
29
  }
30
30
  );
@@ -35,7 +35,7 @@ function EdgeLabelBg({
35
35
  isHovered,
36
36
  springs
37
37
  }) {
38
- const padding = 4;
38
+ const padding = 2;
39
39
  const props = useSpring({
40
40
  to: {
41
41
  x: labelBBox.x - padding,
@@ -1,27 +1,24 @@
1
- import { useRef, useMemo } from "react";
2
1
  import { nonNullable } from "@likec4/core";
2
+ import { useRef, useState } from "react";
3
3
  export function useDiagramApi() {
4
4
  const ref = useRef(null);
5
- return useMemo(
6
- () => [
7
- ref,
8
- {
9
- get stage() {
10
- return nonNullable(ref.current, "not mounted, use ref").stage;
11
- },
12
- get diagramView() {
13
- return nonNullable(ref.current, "not mounted, use ref").diagramView;
14
- },
15
- get container() {
16
- return nonNullable(ref.current, "not mounted, use ref").container;
17
- },
18
- resetStageZoom: (_immediate) => {
19
- nonNullable(ref.current, "not mounted, use ref").resetStageZoom(_immediate);
20
- },
21
- centerOnNode: (node) => nonNullable(ref.current, "not mounted, use ref").centerOnNode(node),
22
- centerAndFit: () => nonNullable(ref.current, "not mounted, use ref").centerAndFit()
23
- }
24
- ],
25
- [ref]
5
+ const [api] = useState(
6
+ () => ({
7
+ get stage() {
8
+ return ref.current?.stage ?? null;
9
+ },
10
+ get diagramView() {
11
+ return nonNullable(ref.current, "not mounted, use ref").diagramView;
12
+ },
13
+ get container() {
14
+ return ref.current?.container ?? null;
15
+ },
16
+ resetStageZoom: (_immediate) => {
17
+ nonNullable(ref.current, "not mounted, use ref").resetStageZoom(_immediate);
18
+ },
19
+ centerOnNode: (node) => nonNullable(ref.current, "not mounted, use ref").centerOnNode(node),
20
+ centerAndFit: () => nonNullable(ref.current, "not mounted, use ref").centerAndFit()
21
+ })
26
22
  );
23
+ return [ref, api];
27
24
  }
@@ -8,7 +8,7 @@
8
8
  <link rel="stylesheet" type="text/css" href="/likec4.css" />
9
9
  </head>
10
10
  <body>
11
- <div id="like4-root" class="w-screen h-screen m-0 p-0"></div>
11
+ <div id="like4-root"></div>
12
12
  <script type="module" src="/src/main"></script>
13
13
  </body>
14
14
  </html>
@@ -8,9 +8,28 @@
8
8
  --font-weight-bold: 600;
9
9
  }
10
10
 
11
+ *,
12
+ :before,
13
+ :after {
14
+ box-sizing: border-box;
15
+ outline: none;
16
+ border-width: 0;
17
+ border-style: solid;
18
+ border-color: transparent;
19
+ }
20
+
11
21
  html, body {
12
22
  margin: 0;
13
23
  padding: 0;
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+
28
+ #like4-root {
29
+ margin: 0;
30
+ padding: 0;
31
+ width: 100vw;
32
+ height: 100vh;
14
33
  }
15
34
 
16
35
  .transparent-bg {
@@ -5,8 +5,10 @@ import { ExportPage, IndexPage, EmbedPage, ViewPage } from './pages';
5
5
  import { useRoute } from './router';
6
6
  import { Theme } from '@radix-ui/themes';
7
7
  import { nonexhaustive } from '@likec4/core';
8
+ import { isNil } from 'remeda';
8
9
  const Routes = () => {
9
10
  const r = useRoute();
11
+ const theme = r.params?.theme;
10
12
  let page = null;
11
13
  switch (r.route) {
12
14
  case 'view': {
@@ -18,7 +20,7 @@ const Routes = () => {
18
20
  break;
19
21
  }
20
22
  case 'embed': {
21
- page = <EmbedPage key='embed' viewId={r.params.viewId} padding={r.params.padding}/>;
23
+ page = (<EmbedPage key='embed' viewId={r.params.viewId} padding={r.params.padding} transparentBg={isNil(r.params.theme)}/>);
22
24
  break;
23
25
  }
24
26
  case 'index': {
@@ -28,7 +30,7 @@ const Routes = () => {
28
30
  default:
29
31
  nonexhaustive(r);
30
32
  }
31
- return (<Theme hasBackground={r.route !== 'export'} accentColor='indigo' radius='small' appearance={r.route !== 'export' ? r.params?.theme ?? 'inherit' : undefined}>
33
+ return (<Theme hasBackground={!!theme} accentColor='indigo' radius='small' appearance={theme}>
32
34
  {page}
33
35
  {r.showUI && (<Fragment key='ui'>
34
36
  <Sidebar />
@@ -2,7 +2,7 @@ import { Box, Button, Flex, IconButton, ScrollArea, Separator } from '@radix-ui/
2
2
  import { useClickOutside, useToggle } from '@react-hookz/web/esm';
3
3
  import { HamburgerMenuIcon, ArrowLeftIcon } from '@radix-ui/react-icons';
4
4
  import { useRef } from 'react';
5
- import { cn } from '~/utils';
5
+ import { cn } from '../../utils';
6
6
  import { DiagramsTree } from './DiagramsTree';
7
7
  import styles from './styles.module.css';
8
8
  import { $pages } from '../../router';
@@ -25,7 +25,7 @@ export const DisplayModeSelector = () => {
25
25
  };
26
26
  return (<Flex display={{
27
27
  initial: 'none',
28
- md: 'flex'
28
+ sm: 'flex'
29
29
  }} gap='3' align='center'>
30
30
  <Button variant={current === first ? 'solid' : 'ghost'} size='1' onClick={changeMode(first)}>
31
31
  {Mode[first]}
@@ -1,7 +1,7 @@
1
1
  import { Diagram, useDiagramApi } from '@likec4/diagrams';
2
2
  import { Box, Portal } from '@radix-ui/themes';
3
3
  import { useDebouncedEffect } from '@react-hookz/web/esm';
4
- import { memo } from 'react';
4
+ import { memo, useRef } from 'react';
5
5
  function downloadBlob(blob, name) {
6
6
  // Convert your blob into a Blob URL (a special url that points to an object in the browser's memory)
7
7
  const blobUrl = URL.createObjectURL(blob);
@@ -32,13 +32,19 @@ function downloadBlob(blob, name) {
32
32
  }
33
33
  const ExportDiagram = memo(({ diagram, onCompleted }) => {
34
34
  const id = diagram.id;
35
- const [ref, diagramApi] = useDiagramApi();
35
+ const [ref, api] = useDiagramApi();
36
36
  const padding = 20;
37
37
  const width = diagram.width + padding * 2;
38
38
  const height = diagram.height + padding * 2;
39
+ const onCompletedRef = useRef(onCompleted);
40
+ onCompletedRef.current = onCompleted;
39
41
  // To avoid flickering and double rendering
40
42
  useDebouncedEffect(() => {
41
- void diagramApi.stage
43
+ const stage = api.stage;
44
+ if (!stage) {
45
+ return;
46
+ }
47
+ void stage
42
48
  .toBlob({
43
49
  pixelRatio: 2,
44
50
  mimeType: 'image/png',
@@ -46,17 +52,17 @@ const ExportDiagram = memo(({ diagram, onCompleted }) => {
46
52
  if (blob) {
47
53
  downloadBlob(blob, `${diagram.id}.png`);
48
54
  }
49
- onCompleted();
55
+ onCompletedRef.current();
50
56
  }
51
57
  })
52
58
  .catch(err => {
53
- onCompleted();
59
+ onCompletedRef.current();
54
60
  // Show error after 100ms to avoid blocking the UI
55
61
  setTimeout(() => {
56
62
  window.alert(err);
57
63
  }, 100);
58
64
  });
59
- }, [id], 400);
65
+ }, [id, api], 400);
60
66
  return (<Portal>
61
67
  <Box position={'fixed'} style={{
62
68
  top: 0,
@@ -65,7 +71,7 @@ const ExportDiagram = memo(({ diagram, onCompleted }) => {
65
71
  height,
66
72
  transform: `translateY(${-height}px)`
67
73
  }}>
68
- <Diagram ref={ref} animate={false} pannable={false} zoomable={false} diagram={diagram} padding={padding} width={width} height={height}/>
74
+ <Diagram ref={ref} animate={false} pannable={false} zoomable={false} minZoom={1} maxZoom={1} diagram={diagram} padding={padding} width={width} height={height}/>
69
75
  </Box>
70
76
  </Portal>);
71
77
  });
@@ -34,11 +34,17 @@ const ExportMenu = ({ onExport, children }) => (<DropdownMenu.Root>
34
34
  </DropdownMenu.Root>);
35
35
  export const ViewActionsToolbar = ({ diagram }) => {
36
36
  const [exportTo, setExportTo] = useState(null);
37
- return (<Flex position='fixed' top='0' right='0' p='2' gap={'3'} justify='end' align='center'>
37
+ return (<Flex position='fixed' top='0' right='0' p={{
38
+ initial: '3',
39
+ md: '2'
40
+ }} gap={'3'} justify='end' align='center'>
38
41
  <DisplayModeSelector />
39
42
  <Dialog.Root>
40
43
  <Dialog.Trigger>
41
- <Button variant='solid'>
44
+ <Button variant='solid' size={{
45
+ initial: '1',
46
+ md: '2'
47
+ }}>
42
48
  <ShareIcon />
43
49
  <Text>Share</Text>
44
50
  </Button>
@@ -46,7 +52,10 @@ export const ViewActionsToolbar = ({ diagram }) => {
46
52
  <ShareDialog diagram={diagram}/>
47
53
  </Dialog.Root>
48
54
  <ExportMenu onExport={setExportTo}>
49
- <Button variant='soft' color='gray'>
55
+ <Button variant='soft' color='gray' size={{
56
+ initial: '1',
57
+ md: '2'
58
+ }}>
50
59
  <Text>Export</Text>
51
60
  <CaretDownIcon />
52
61
  </Button>
@@ -58,26 +58,15 @@ export const selectLikeC4ViewAtom = (viewId) => {
58
58
  if (import.meta.hot) {
59
59
  let $updateViews;
60
60
  viewsAtom.onMount = set => {
61
- console.log('mount viewsAtom');
62
61
  $updateViews = set;
63
62
  return () => {
64
- console.log('unmount viewsAtom');
65
63
  $updateViews = undefined;
66
64
  };
67
65
  };
68
66
  import.meta.hot.accept('/@vite-plugin-likec4/likec4-generated', md => {
69
67
  const update = md?.LikeC4Views;
70
- console.debug('accept ./data update');
71
- console.dir(update, {
72
- colors: true,
73
- compact: false,
74
- depth: 10
75
- });
76
68
  if (update) {
77
69
  $updateViews?.(update);
78
70
  }
79
- else {
80
- console.warn('no update', md);
81
- }
82
71
  });
83
72
  }
@@ -2,11 +2,13 @@ import { Diagram } from '@likec4/diagrams';
2
2
  import { useWindowSize } from '@react-hookz/web/esm';
3
3
  import { DiagramNotFound } from '../components';
4
4
  import { useLikeC4View } from '../data';
5
- export function EmbedPage({ viewId, padding }) {
5
+ import { useTransparentBackground } from './useTransparentBackground';
6
+ export function EmbedPage({ viewId, padding, transparentBg = true }) {
6
7
  const { width, height } = useWindowSize();
7
8
  const diagram = useLikeC4View(viewId);
9
+ useTransparentBackground(transparentBg && !!diagram);
8
10
  if (!diagram) {
9
11
  return <DiagramNotFound viewId={viewId}/>;
10
12
  }
11
- return <Diagram diagram={diagram} padding={padding} width={width} height={height}/>;
13
+ return (<Diagram animate={false} pannable={false} zoomable={false} diagram={diagram} padding={padding} width={width} height={height}/>);
12
14
  }
@@ -1,24 +1,12 @@
1
1
  import { Diagram } from '@likec4/diagrams';
2
2
  import { useWindowSize } from '@react-hookz/web/esm';
3
- import { useEffect } from 'react';
4
3
  import { DiagramNotFound } from '../components';
5
4
  import { useLikeC4View } from '../data';
5
+ import { useTransparentBackground } from './useTransparentBackground';
6
6
  export function ExportPage({ viewId, padding }) {
7
7
  const { width, height } = useWindowSize();
8
8
  const diagram = useLikeC4View(viewId);
9
- // To get the transparent background
10
- // We need to add a class to the HTML element
11
- useEffect(() => {
12
- const htmlEl = document.body.parentElement;
13
- if (!htmlEl)
14
- return;
15
- // see ../../likec4.css
16
- const classname = 'transparent-bg';
17
- htmlEl.classList.add(classname);
18
- return () => {
19
- htmlEl.classList.remove(classname);
20
- };
21
- }, []);
9
+ useTransparentBackground(!!diagram);
22
10
  if (!diagram) {
23
11
  return <DiagramNotFound viewId={viewId}/>;
24
12
  }
@@ -0,0 +1,16 @@
1
+ import { useEffect } from 'react';
2
+ // To get the transparent background
3
+ // We need to add a class to the HTML element
4
+ export function useTransparentBackground(enabled = true) {
5
+ useEffect(() => {
6
+ const htmlEl = document.body.parentElement;
7
+ if (!htmlEl || !enabled)
8
+ return;
9
+ // see ../../likec4.css
10
+ const classname = 'transparent-bg';
11
+ htmlEl.classList.add(classname);
12
+ return () => {
13
+ htmlEl.classList.remove(classname);
14
+ };
15
+ }, [enabled]);
16
+ }
@@ -0,0 +1,30 @@
1
+ .diagramBg {
2
+ overscroll-behavior: none;
3
+ overflow: hidden;
4
+ --diagram-bg-size: 24px;
5
+ --diagram-bg-position-x: 0;
6
+ --diagram-bg-position-y: 0;
7
+
8
+ :global(.konvajs-content) {
9
+
10
+ &::before {
11
+ content: '';
12
+ position: absolute;
13
+ padding: 0;
14
+ margin: 0;
15
+ top: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ pointer-events: none;
20
+ touch-action: none;
21
+ user-select: none;
22
+ background-origin: border-box;
23
+ background-attachment: fixed;
24
+ background-image: radial-gradient(var(--gray-a3) 12%, transparent 12%);
25
+ background-position: var(--diagram-bg-position-x) var(--diagram-bg-position-y);
26
+ background-size: var(--diagram-bg-size) var(--diagram-bg-size);
27
+ z-index: -1;
28
+ }
29
+ }
30
+ }