@dxos/react-ui-canvas 0.7.5-main.499c70c

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 (67) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +3 -0
  3. package/dist/lib/browser/index.mjs +525 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node/index.cjs +559 -0
  7. package/dist/lib/node/index.cjs.map +7 -0
  8. package/dist/lib/node/meta.json +1 -0
  9. package/dist/lib/node-esm/index.mjs +527 -0
  10. package/dist/lib/node-esm/index.mjs.map +7 -0
  11. package/dist/lib/node-esm/meta.json +1 -0
  12. package/dist/types/src/components/Canvas/Canvas.d.ts +15 -0
  13. package/dist/types/src/components/Canvas/Canvas.d.ts.map +1 -0
  14. package/dist/types/src/components/Canvas/Canvas.stories.d.ts +8 -0
  15. package/dist/types/src/components/Canvas/Canvas.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/Canvas/index.d.ts +2 -0
  17. package/dist/types/src/components/Canvas/index.d.ts.map +1 -0
  18. package/dist/types/src/components/FPS.d.ts +9 -0
  19. package/dist/types/src/components/FPS.d.ts.map +1 -0
  20. package/dist/types/src/components/Grid/Grid.d.ts +18 -0
  21. package/dist/types/src/components/Grid/Grid.d.ts.map +1 -0
  22. package/dist/types/src/components/Grid/Grid.stories.d.ts +8 -0
  23. package/dist/types/src/components/Grid/Grid.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/Grid/index.d.ts +2 -0
  25. package/dist/types/src/components/Grid/index.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +4 -0
  27. package/dist/types/src/components/index.d.ts.map +1 -0
  28. package/dist/types/src/hooks/index.d.ts +4 -0
  29. package/dist/types/src/hooks/index.d.ts.map +1 -0
  30. package/dist/types/src/hooks/projection.d.ts +58 -0
  31. package/dist/types/src/hooks/projection.d.ts.map +1 -0
  32. package/dist/types/src/hooks/useProjection.d.ts +12 -0
  33. package/dist/types/src/hooks/useProjection.d.ts.map +1 -0
  34. package/dist/types/src/hooks/useWheel.d.ts +7 -0
  35. package/dist/types/src/hooks/useWheel.d.ts.map +1 -0
  36. package/dist/types/src/index.d.ts +5 -0
  37. package/dist/types/src/index.d.ts.map +1 -0
  38. package/dist/types/src/types.d.ts +20 -0
  39. package/dist/types/src/types.d.ts.map +1 -0
  40. package/dist/types/src/util/index.d.ts +3 -0
  41. package/dist/types/src/util/index.d.ts.map +1 -0
  42. package/dist/types/src/util/svg.d.ts +33 -0
  43. package/dist/types/src/util/svg.d.ts.map +1 -0
  44. package/dist/types/src/util/svg.stories.d.ts +6 -0
  45. package/dist/types/src/util/svg.stories.d.ts.map +1 -0
  46. package/dist/types/src/util/util.d.ts +16 -0
  47. package/dist/types/src/util/util.d.ts.map +1 -0
  48. package/dist/types/tsconfig.tsbuildinfo +1 -0
  49. package/package.json +61 -0
  50. package/src/components/Canvas/Canvas.stories.tsx +82 -0
  51. package/src/components/Canvas/Canvas.tsx +83 -0
  52. package/src/components/Canvas/index.ts +5 -0
  53. package/src/components/FPS.tsx +98 -0
  54. package/src/components/Grid/Grid.stories.tsx +43 -0
  55. package/src/components/Grid/Grid.tsx +61 -0
  56. package/src/components/Grid/index.ts +5 -0
  57. package/src/components/index.ts +7 -0
  58. package/src/hooks/index.ts +7 -0
  59. package/src/hooks/projection.tsx +149 -0
  60. package/src/hooks/useProjection.tsx +28 -0
  61. package/src/hooks/useWheel.tsx +55 -0
  62. package/src/index.ts +8 -0
  63. package/src/types.ts +13 -0
  64. package/src/util/index.ts +6 -0
  65. package/src/util/svg.stories.tsx +45 -0
  66. package/src/util/svg.tsx +131 -0
  67. package/src/util/util.ts +39 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Canvas/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { type ThemedClassName } from '@dxos/react-ui';
3
+ export type FPSProps = ThemedClassName<{
4
+ width?: number;
5
+ height?: number;
6
+ bar?: string;
7
+ }>;
8
+ export declare const FPS: ({ classNames, width, height, bar }: FPSProps) => React.JSX.Element;
9
+ //# sourceMappingURL=FPS.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FPS.d.ts","sourceRoot":"","sources":["../../../../src/components/FPS.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAE7D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC,CAAC;AAYH,eAAO,MAAM,GAAG,uCAAkE,QAAQ,sBAuEzF,CAAC"}
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { type ThemedClassName } from '@dxos/react-ui';
3
+ import { type Point } from '../../types';
4
+ export type GridProps = ThemedClassName<{
5
+ id: string;
6
+ size: number;
7
+ scale?: number;
8
+ offset?: Point;
9
+ }>;
10
+ export declare const Grid: React.ForwardRefExoticComponent<Omit<{
11
+ id: string;
12
+ size: number;
13
+ scale?: number;
14
+ offset?: Point;
15
+ }, "className"> & {
16
+ classNames?: import("@dxos/react-ui").ClassNameValue;
17
+ } & React.RefAttributes<SVGSVGElement>>;
18
+ //# sourceMappingURL=Grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Grid.d.ts","sourceRoot":"","sources":["../../../../../src/components/Grid/Grid.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AASzC,MAAM,MAAM,SAAS,GAAG,eAAe,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC;AAEtG,eAAO,MAAM,IAAI;QAF6B,MAAM;UAAQ,MAAM;YAAU,MAAM;aAAW,KAAK;;;uCA0CjG,CAAC"}
@@ -0,0 +1,8 @@
1
+ import '@dxos-theme';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { type GridProps } from './Grid';
4
+ declare const meta: Meta<GridProps>;
5
+ export default meta;
6
+ type Story = StoryObj<GridProps>;
7
+ export declare const Default: Story;
8
+ //# sourceMappingURL=Grid.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Grid.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/Grid/Grid.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAKvD,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAgB9C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,CAKzB,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAEjC,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './Grid';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Grid/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './Canvas';
2
+ export * from './FPS';
3
+ export * from './Grid';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './projection';
2
+ export * from './useProjection';
3
+ export * from './useWheel';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { type Point, type Dimension } from '../types';
2
+ export declare const defaultOffset: Point;
3
+ export type ProjectionState = {
4
+ scale: number;
5
+ offset: Point;
6
+ };
7
+ /**
8
+ * Maps between screen and model coordinates.
9
+ */
10
+ export interface Projection {
11
+ get bounds(): Dimension;
12
+ get scale(): number;
13
+ get offset(): Point;
14
+ /**
15
+ * Maps the model space to the screen offset (from the top-left of the element).
16
+ */
17
+ toScreen(points: Point[]): Point[];
18
+ /**
19
+ * Maps the pointer coordinate (from the top-left of the element) to the model space.
20
+ */
21
+ toModel(points: Point[]): Point[];
22
+ }
23
+ export declare class ProjectionMapper implements Projection {
24
+ private _bounds;
25
+ private _scale;
26
+ private _offset;
27
+ private _toScreen;
28
+ private _toModel;
29
+ constructor(bounds?: Dimension, scale?: number, offset?: Point);
30
+ update(bounds: Dimension, scale: number, offset: Point): this;
31
+ get bounds(): {
32
+ readonly width: number;
33
+ readonly height: number;
34
+ };
35
+ get scale(): number;
36
+ get offset(): {
37
+ readonly x: number;
38
+ readonly y: number;
39
+ };
40
+ toScreen(points: Point[]): Point[];
41
+ toModel(points: Point[]): Point[];
42
+ }
43
+ /**
44
+ * Maintain position while zooming.
45
+ */
46
+ export declare const getZoomTransform: ({ scale, offset, pos, newScale, }: ProjectionState & {
47
+ pos: Point;
48
+ newScale: number;
49
+ }) => ProjectionState;
50
+ /**
51
+ * Zoom while keeping the specified position in place.
52
+ */
53
+ export declare const zoomInPlace: (setTransform: (state: ProjectionState) => void, pos: Point, offset: Point, current: number, next: number, delay?: number) => void;
54
+ /**
55
+ * Zoom to new scale and position.
56
+ */
57
+ export declare const zoomTo: (setTransform: (state: ProjectionState) => void, current: ProjectionState, next: ProjectionState, delay?: number) => void;
58
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../../../src/hooks/projection.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAEtD,eAAO,MAAM,aAAa,EAAE,KAAsB,CAAC;AAGnD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;CACf,CAAC;AAKF;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,MAAM,IAAI,SAAS,CAAC;IACxB,IAAI,KAAK,IAAI,MAAM,CAAC;IACpB,IAAI,MAAM,IAAI,KAAK,CAAC;IAEpB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC;IAEnC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC;CACnC;AAED,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAsB;gBAE1B,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK;IAM9D,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK;IAStD,IAAI,MAAM;;;MAET;IAED,IAAI,KAAK,WAER;IAED,IAAI,MAAM;;;MAET;IAED,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE;IAIlC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE;CAGlC;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,sCAK1B,eAAe,GAAG;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAG,eAQvD,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,WAAW,iBACR,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,OACzC,KAAK,UACF,KAAK,WACJ,MAAM,QACT,MAAM,yBAWb,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,MAAM,iBACH,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,WACrC,eAAe,QAClB,eAAe,yBAWtB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type CSSProperties, type Dispatch, type SetStateAction } from 'react';
2
+ import { type Projection, type ProjectionState } from './projection';
3
+ export type CanvasContext = ProjectionState & {
4
+ root: HTMLDivElement;
5
+ width: number;
6
+ height: number;
7
+ styles: CSSProperties;
8
+ projection: Projection;
9
+ setProjection: Dispatch<SetStateAction<ProjectionState>>;
10
+ };
11
+ export declare const useProjection: () => CanvasContext;
12
+ //# sourceMappingURL=useProjection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProjection.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useProjection.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAA6B,MAAM,OAAO,CAAC;AAI1G,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,CAAC;CAC1D,CAAC;AAQF,eAAO,MAAM,aAAa,QAAO,aAEhC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type Dispatch, type SetStateAction } from 'react';
2
+ import { type ProjectionState } from './projection';
3
+ /**
4
+ * Handle wheel events to update the transform state (zoom and offset).
5
+ */
6
+ export declare const useWheel: (el: HTMLDivElement | null, setProjection: Dispatch<SetStateAction<ProjectionState>>) => void;
7
+ //# sourceMappingURL=useWheel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWheel.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useWheel.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAa,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAGtE;;GAEG;AACH,eAAO,MAAM,QAAQ,OAAQ,cAAc,GAAG,IAAI,iBAAiB,QAAQ,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,SAyC3G,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './components';
2
+ export * from './hooks';
3
+ export * from './types';
4
+ export * from './util';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { S } from '@dxos/effect';
2
+ export declare const Point: S.Struct<{
3
+ x: typeof S.Number;
4
+ y: typeof S.Number;
5
+ }>;
6
+ export declare const Dimension: S.Struct<{
7
+ width: typeof S.Number;
8
+ height: typeof S.Number;
9
+ }>;
10
+ export declare const Rect: S.extend<S.Struct<{
11
+ x: typeof S.Number;
12
+ y: typeof S.Number;
13
+ }>, S.Struct<{
14
+ width: typeof S.Number;
15
+ height: typeof S.Number;
16
+ }>>;
17
+ export type Point = S.Schema.Type<typeof Point>;
18
+ export type Dimension = S.Schema.Type<typeof Dimension>;
19
+ export type Rect = S.Schema.Type<typeof Rect>;
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,eAAO,MAAM,KAAK;;;EAAyC,CAAC;AAC5D,eAAO,MAAM,SAAS;;;EAAkD,CAAC;AACzE,eAAO,MAAM,IAAI;;;;;;GAA6B,CAAC;AAE/C,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC;AAChD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './svg';
2
+ export * from './util';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import React, { type PropsWithChildren, type SVGProps } from 'react';
2
+ import { type ThemedClassName } from '@dxos/react-ui';
3
+ import { type Dimension, type Point } from '../types';
4
+ export declare const createPath: (points: Point[], join?: boolean) => string;
5
+ /**
6
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
7
+ * NOTE: Leave space around shape for line width.
8
+ */
9
+ export declare const Markers: ({ id, classNames }: ThemedClassName<{
10
+ id?: string;
11
+ }>) => React.JSX.Element;
12
+ export type MarkerProps = SVGProps<SVGMarkerElement> & PropsWithChildren<ThemedClassName<{
13
+ id: string;
14
+ pos: Point;
15
+ size: Dimension;
16
+ fill?: boolean;
17
+ }>>;
18
+ /**
19
+ * https://www.w3.org/TR/SVG2/painting.html#Markers
20
+ */
21
+ export declare const Marker: ({ id, className, children, pos: { x: refX, y: refY }, size: { width: markerWidth, height: markerHeight }, fill, ...rest }: MarkerProps) => React.JSX.Element;
22
+ export declare const Arrow: ({ classNames, id, size, dir, closed, }: ThemedClassName<{
23
+ id: string;
24
+ size?: number;
25
+ dir?: "start" | "end";
26
+ closed?: boolean;
27
+ }>) => React.JSX.Element;
28
+ export declare const GridPattern: ({ classNames, id, size, offset, }: ThemedClassName<{
29
+ id: string;
30
+ size: number;
31
+ offset: Point;
32
+ }>) => React.JSX.Element;
33
+ //# sourceMappingURL=svg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../../src/util/svg.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC;AAErE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAMtD,eAAO,MAAM,UAAU,WAAY,KAAK,EAAE,2BAEzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,uBAAsC,eAAe,CAAC;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,sBAYzF,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC,GAClD,iBAAiB,CACf,eAAe,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,KAAK,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC,CACH,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,MAAM,8HAQhB,WAAW,sBAgBb,CAAC;AAEF,eAAO,MAAM,KAAK,2CAMf,eAAe,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,sBA0BzF,CAAC;AAEF,eAAO,MAAM,WAAW,sCAKrB,eAAe,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,CAAA;CAAE,CAAC,sBAe9D,CAAC"}
@@ -0,0 +1,6 @@
1
+ import '@dxos-theme';
2
+ import type { Meta } from '@storybook/react';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ export declare const Default: {};
6
+ //# sourceMappingURL=svg.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg.stories.d.ts","sourceRoot":"","sources":["../../../../src/util/svg.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AA2B7C,QAAA,MAAM,IAAI,EAAE,IAOX,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,eAAO,MAAM,OAAO,IAAK,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Get the relative point of the cursor.
3
+ * NOTE: ev.offset returns the position relative to the target.
4
+ */
5
+ export declare const getRelativePoint: (el: HTMLElement, ev: MouseEvent) => {
6
+ x: number;
7
+ y: number;
8
+ };
9
+ /**
10
+ *
11
+ */
12
+ export declare const testId: <ID = string>(id: ID, inspect?: boolean) => {
13
+ "data-test-id": ID;
14
+ };
15
+ export declare const DATA_TEST_ID = "data-test-id";
16
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/util/util.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,OAAQ,WAAW,MAAM,UAAU;;;CAG/D,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,MAAM,GAAI,EAAE,eAAe,EAAE;;CAiBzC,CAAC;AAEF,eAAO,MAAM,YAAY,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":"5.7.2"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@dxos/react-ui-canvas",
3
+ "version": "0.7.5-main.499c70c",
4
+ "description": "A canvas component.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/src/index.d.ts",
12
+ "browser": "./dist/lib/browser/index.mjs",
13
+ "node": "./dist/lib/node-esm/index.mjs"
14
+ }
15
+ },
16
+ "types": "dist/types/src/index.d.ts",
17
+ "typesVersions": {
18
+ "*": {}
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "dependencies": {
25
+ "@preact/signals-core": "^1.6.0",
26
+ "@radix-ui/react-context": "^1.0.0",
27
+ "bind-event-listener": "^3.0.0",
28
+ "d3": "^7.9.0",
29
+ "effect": "^3.9.2",
30
+ "react-resize-detector": "^11.0.1",
31
+ "transformation-matrix": "^2.16.1",
32
+ "@dxos/debug": "0.7.5-main.499c70c",
33
+ "@dxos/effect": "0.7.5-main.499c70c",
34
+ "@dxos/invariant": "0.7.5-main.499c70c",
35
+ "@dxos/log": "0.7.5-main.499c70c",
36
+ "@dxos/util": "0.7.5-main.499c70c"
37
+ },
38
+ "devDependencies": {
39
+ "@effect/schema": "^0.75.5",
40
+ "@types/d3": "^7.4.3",
41
+ "@types/react": "~18.2.0",
42
+ "@types/react-dom": "~18.2.0",
43
+ "react": "~18.2.0",
44
+ "react-dom": "~18.2.0",
45
+ "vite": "5.4.7",
46
+ "@dxos/random": "0.7.5-main.499c70c",
47
+ "@dxos/react-ui-theme": "0.7.5-main.499c70c",
48
+ "@dxos/storybook-utils": "0.7.5-main.499c70c",
49
+ "@dxos/react-ui": "0.7.5-main.499c70c"
50
+ },
51
+ "peerDependencies": {
52
+ "@effect/schema": "^0.75.5",
53
+ "react": "~18.2.0",
54
+ "react-dom": "~18.2.0",
55
+ "@dxos/react-ui": "0.7.5-main.499c70c",
56
+ "@dxos/react-ui-theme": "0.7.5-main.499c70c"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }
@@ -0,0 +1,82 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import type { Meta, StoryObj } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
11
+
12
+ import { Canvas } from './Canvas';
13
+ import { useProjection, useWheel } from '../../hooks';
14
+ import { type Point } from '../../types';
15
+ import { testId } from '../../util';
16
+ import { Grid, type GridProps } from '../Grid';
17
+
18
+ const Render = (props: GridProps) => {
19
+ return (
20
+ <Canvas>
21
+ <Content {...props} />
22
+ </Canvas>
23
+ );
24
+ };
25
+
26
+ const Content = (props: GridProps) => {
27
+ const { root, scale, offset, styles, setProjection } = useProjection();
28
+ useWheel(root, setProjection);
29
+
30
+ return (
31
+ <>
32
+ <Grid scale={scale} offset={offset} {...props} />
33
+ <div className='absolute' style={styles}>
34
+ <Item x={0} y={-128} />
35
+ <Item x={-128} y={128} />
36
+ <Item x={128} y={128} />
37
+ </div>
38
+ </>
39
+ );
40
+ };
41
+
42
+ const Item = ({ x, y }: Point) => {
43
+ const size = 128;
44
+ const pos = {
45
+ left: x - size / 2,
46
+ top: y - size / 2,
47
+ width: size,
48
+ height: size,
49
+ };
50
+
51
+ return (
52
+ <div {...testId('dx-test', true)}>
53
+ <div className='absolute flex border border-red-500 justify-center items-center' style={pos}>
54
+ <div className='font-mono'>
55
+ ({x},{y})
56
+ </div>
57
+ </div>
58
+ {/* NOTE: Width and height are not important since overflow-visible. */}
59
+ <svg className='absolute overflow-visible' style={pos}>
60
+ <circle cx={64} cy={64} r={64} className='stroke-red-500 storke-width-2 fill-none' />
61
+ </svg>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ const meta: Meta<GridProps> = {
67
+ title: 'ui/react-ui-canvas/Canvas',
68
+ component: Grid,
69
+ render: Render,
70
+ decorators: [withTheme, withLayout({ fullscreen: true })],
71
+ };
72
+
73
+ export default meta;
74
+
75
+ type Story = StoryObj<GridProps>;
76
+
77
+ export const Default: Story = {
78
+ args: {
79
+ id: 'test',
80
+ size: 16,
81
+ },
82
+ };
@@ -0,0 +1,83 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, {
6
+ type CSSProperties,
7
+ type PropsWithChildren,
8
+ forwardRef,
9
+ useEffect,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useState,
13
+ type HTMLAttributes,
14
+ } from 'react';
15
+ import { useResizeDetector } from 'react-resize-detector';
16
+
17
+ import { type ThemedClassName } from '@dxos/react-ui';
18
+ import { mx } from '@dxos/react-ui-theme';
19
+
20
+ import { defaultOffset, CanvasContext, ProjectionMapper, type ProjectionState } from '../../hooks';
21
+
22
+ export interface CanvasController {
23
+ setProjection(projection: ProjectionState): Promise<void>;
24
+ }
25
+
26
+ export type CanvasProps = ThemedClassName<PropsWithChildren<Partial<ProjectionState> & HTMLAttributes<HTMLDivElement>>>;
27
+
28
+ /**
29
+ * Root canvas component.
30
+ * Manages CSS projection.
31
+ */
32
+ export const Canvas = forwardRef<CanvasController, CanvasProps>(
33
+ ({ children, classNames, scale: _scale = 1, offset: _offset = defaultOffset, ...props }, forwardedRef) => {
34
+ // Size.
35
+ const { ref, width = 0, height = 0 } = useResizeDetector();
36
+
37
+ // Projection.
38
+ const [{ scale, offset }, setProjection] = useState<ProjectionState>({ scale: _scale, offset: _offset });
39
+ useEffect(() => {
40
+ if (width && height && offset === defaultOffset) {
41
+ setProjection({ scale, offset: { x: width / 2, y: height / 2 } });
42
+ }
43
+ }, [width, height, scale, offset]);
44
+
45
+ // Projection mapper.
46
+ const projection = useMemo(() => new ProjectionMapper(), []);
47
+ useEffect(() => {
48
+ projection.update({ width, height }, scale, offset);
49
+ }, [projection, scale, offset, width, height]);
50
+
51
+ // CSS transforms.
52
+ const styles = useMemo<CSSProperties>(() => {
53
+ return {
54
+ // NOTE: Order is important.
55
+ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`,
56
+ visibility: width && height ? 'visible' : 'hidden',
57
+ };
58
+ }, [scale, offset]);
59
+
60
+ // Controller.
61
+ useImperativeHandle(
62
+ forwardedRef,
63
+ () => {
64
+ return {
65
+ setProjection: async (projection: ProjectionState) => {
66
+ setProjection(projection);
67
+ },
68
+ };
69
+ },
70
+ [ref],
71
+ );
72
+
73
+ return (
74
+ <CanvasContext.Provider
75
+ value={{ root: ref.current, width, height, scale, offset, styles, projection, setProjection }}
76
+ >
77
+ <div role='none' {...props} className={mx('absolute inset-0 overflow-hidden', classNames)} ref={ref}>
78
+ {children}
79
+ </div>
80
+ </CanvasContext.Provider>
81
+ );
82
+ },
83
+ );
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './Canvas';
@@ -0,0 +1,98 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ // Adapted from: https://github.com/smplrspace/react-fps-stats
4
+ //
5
+
6
+ import React, { useEffect, useReducer, useRef } from 'react';
7
+
8
+ import { type ThemedClassName } from '@dxos/react-ui';
9
+ import { mx } from '@dxos/react-ui-theme';
10
+
11
+ export type FPSProps = ThemedClassName<{
12
+ width?: number;
13
+ height?: number;
14
+ bar?: string;
15
+ }>;
16
+
17
+ type State = {
18
+ max: number;
19
+ len: number;
20
+ fps: number[];
21
+ frames: number;
22
+ prevTime: number;
23
+ };
24
+
25
+ const SEC = 1_000;
26
+
27
+ export const FPS = ({ classNames, width = 60, height = 30, bar = 'bg-cyan-500' }: FPSProps) => {
28
+ const [{ fps, max, len }, dispatch] = useReducer(
29
+ (state: State) => {
30
+ const currentTime = Date.now();
31
+ if (currentTime > state.prevTime + SEC) {
32
+ const nextFPS = [
33
+ ...new Array(Math.floor((currentTime - state.prevTime - SEC) / SEC)).fill(0),
34
+ Math.max(1, Math.round((state.frames * SEC) / (currentTime - state.prevTime))),
35
+ ];
36
+ return {
37
+ max: Math.max(state.max, ...nextFPS),
38
+ len: Math.min(state.len + nextFPS.length, width),
39
+ fps: [...state.fps, ...nextFPS].slice(-width),
40
+ frames: 1,
41
+ prevTime: currentTime,
42
+ };
43
+ } else {
44
+ return { ...state, frames: state.frames + 1 };
45
+ }
46
+ },
47
+ {
48
+ max: 0,
49
+ len: 0,
50
+ fps: [],
51
+ frames: 0,
52
+ prevTime: Date.now(),
53
+ },
54
+ );
55
+
56
+ const requestRef = useRef<number>();
57
+ const tick = () => {
58
+ dispatch();
59
+ requestRef.current = requestAnimationFrame(tick);
60
+ };
61
+
62
+ useEffect(() => {
63
+ requestRef.current = requestAnimationFrame(tick);
64
+ return () => {
65
+ if (requestRef.current) {
66
+ cancelAnimationFrame(requestRef.current);
67
+ }
68
+ };
69
+ }, []);
70
+
71
+ return (
72
+ <div
73
+ style={{ width: width + 6 }}
74
+ className={mx(
75
+ 'relative flex flex-col p-0.5',
76
+ 'bg-base text-xs text-subdued font-thin pointer-events-none border border-separator',
77
+ classNames,
78
+ )}
79
+ >
80
+ <div>{fps[len - 1]} FPS</div>
81
+ <div className='w-full relative' style={{ height }}>
82
+ {fps.map((frame, i) => (
83
+ <div
84
+ key={`fps-${i}`}
85
+ className={bar}
86
+ style={{
87
+ position: 'absolute',
88
+ bottom: 0,
89
+ right: `${len - 1 - i}px`,
90
+ height: `${(height * frame) / max}px`,
91
+ width: 1,
92
+ }}
93
+ />
94
+ ))}
95
+ </div>
96
+ </div>
97
+ );
98
+ };