@noya-app/noya-designsystem 0.0.7 → 0.0.9

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.
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./src/index.tsx"
9
+ },
7
10
  "scripts": {
8
11
  "build": "tsup src/index.tsx --format cjs,esm --dts --sourcemap",
9
12
  "dev": "npm run build -- --watch"
@@ -12,9 +15,11 @@
12
15
  "@dnd-kit/core": "3.1.1",
13
16
  "@dnd-kit/modifiers": "3.0.0",
14
17
  "@dnd-kit/sortable": "4.0.0",
15
- "@noya-app/noya-colorpicker": "0.0.3",
16
- "@noya-app/noya-utils": "0.0.7",
17
- "@noya-app/noya-geometry": "0.0.7",
18
+ "@noya-app/noya-colorpicker": "workspace:*",
19
+ "@noya-app/noya-utils": "workspace:*",
20
+ "@noya-app/noya-geometry": "workspace:*",
21
+ "@noya-app/noya-icons": "workspace:*",
22
+ "@noya-app/noya-keymap": "workspace:*",
18
23
  "@radix-ui/primitive": "^1.0.0",
19
24
  "@radix-ui/react-avatar": "^1.0.1",
20
25
  "@radix-ui/react-compose-refs": "^1.0.0",
@@ -0,0 +1,168 @@
1
+ import { range } from "@noya-app/noya-utils";
2
+ import React, { ReactNode, memo, useCallback, useMemo } from "react";
3
+ import styled, { useTheme } from "styled-components";
4
+ import withSeparatorElements from "../utils/withSeparatorElements";
5
+ import { Button } from "./Button";
6
+ import { IconButton } from "./IconButton";
7
+ import * as InspectorPrimitives from "./InspectorPrimitives";
8
+ import { ListView } from "./ListView";
9
+ import { RelativeDropPosition, Sortable } from "./Sortable";
10
+ import { Spacer } from "./Spacer";
11
+ import { Stack } from "./Stack";
12
+
13
+ const ElementRow = styled.div({
14
+ flex: "0 0 auto",
15
+ display: "flex",
16
+ flexDirection: "row",
17
+ alignItems: "center",
18
+ });
19
+
20
+ const ItemContainer = styled.div({
21
+ position: "relative",
22
+ });
23
+
24
+ export interface ArrayControllerProps<Item> {
25
+ id: string;
26
+ items: Item[];
27
+ title: ReactNode;
28
+ sortable?: boolean;
29
+ reversed?: boolean;
30
+ expanded?: boolean;
31
+ getKey?: (item: Item) => string;
32
+ onMoveItem?: (sourceIndex: number, destinationIndex: number) => void;
33
+ onClickPlus?: () => void;
34
+ onClickTrash?: () => void;
35
+ onClickExpand?: () => void;
36
+ renderItem: (props: { item: Item; index: number }) => ReactNode;
37
+ renderExpandedContent?: () => ReactNode;
38
+ padding?: number;
39
+ }
40
+
41
+ export const ArrayController = memo(function ArrayController<Item>({
42
+ id,
43
+ items,
44
+ title,
45
+ sortable = false,
46
+ reversed = true,
47
+ expanded = false,
48
+ getKey,
49
+ onMoveItem,
50
+ onClickPlus,
51
+ onClickTrash,
52
+ onClickExpand,
53
+ renderItem,
54
+ renderExpandedContent,
55
+ padding = 10,
56
+ }: ArrayControllerProps<Item>) {
57
+ const iconColor = useTheme().colors.icon;
58
+ const primaryLightColor = useTheme().colors.primaryLight;
59
+
60
+ const keys = useMemo(
61
+ () => items.map((item, index) => getKey?.(item) ?? index.toString()),
62
+ [getKey, items]
63
+ );
64
+
65
+ const indexes = reversed
66
+ ? range(0, items.length).reverse()
67
+ : range(0, items.length);
68
+
69
+ const handleMoveItem = useCallback(
70
+ (
71
+ sourceIndex: number,
72
+ destinationIndex: number,
73
+ position: RelativeDropPosition
74
+ ) => {
75
+ if (reversed) {
76
+ if (position === "above") {
77
+ position = "below";
78
+ } else if (position === "below") {
79
+ position = "above";
80
+ }
81
+ }
82
+
83
+ onMoveItem?.(
84
+ sourceIndex,
85
+ position === "below" ? destinationIndex + 1 : destinationIndex
86
+ );
87
+ },
88
+ [onMoveItem, reversed]
89
+ );
90
+
91
+ const renderRow = (index: number) => {
92
+ return (
93
+ <ElementRow key={keys[index]}>
94
+ {renderItem({ item: items[index], index: index })}
95
+ </ElementRow>
96
+ );
97
+ };
98
+
99
+ return (
100
+ <InspectorPrimitives.Section id={id} padding={padding} gap="2px">
101
+ <InspectorPrimitives.SectionHeader>
102
+ <Button variant="none" onClick={onClickPlus}>
103
+ <InspectorPrimitives.Title>{title}</InspectorPrimitives.Title>
104
+ </Button>
105
+ <Spacer.Horizontal />
106
+ {withSeparatorElements(
107
+ [
108
+ onClickTrash && (
109
+ <IconButton
110
+ id={`${id}-trash`}
111
+ iconName="TrashIcon"
112
+ color={iconColor}
113
+ onClick={onClickTrash}
114
+ />
115
+ ),
116
+ onClickExpand && (
117
+ <IconButton
118
+ id={`${id}-gear`}
119
+ iconName="GearIcon"
120
+ color={expanded ? primaryLightColor : iconColor}
121
+ onClick={onClickExpand}
122
+ />
123
+ ),
124
+ onClickPlus && (
125
+ <IconButton
126
+ id={`${id}-add`}
127
+ iconName="PlusIcon"
128
+ color={iconColor}
129
+ onClick={onClickPlus}
130
+ />
131
+ ),
132
+ ],
133
+ <Spacer.Horizontal size={8} />
134
+ )}
135
+ </InspectorPrimitives.SectionHeader>
136
+ <Stack.V gap="4px">
137
+ {sortable ? (
138
+ <Sortable.Root
139
+ keys={keys}
140
+ renderOverlay={renderRow}
141
+ onMoveItem={handleMoveItem}
142
+ >
143
+ {indexes.map((index) => (
144
+ <Sortable.Item<HTMLDivElement> id={keys[index]} key={keys[index]}>
145
+ {({ relativeDropPosition, ...sortableProps }) => (
146
+ <ItemContainer {...sortableProps}>
147
+ {renderRow(index)}
148
+ {relativeDropPosition && (
149
+ <ListView.DragIndicator
150
+ gap={0}
151
+ colorScheme="primary"
152
+ relativeDropPosition={relativeDropPosition}
153
+ offsetLeft={0}
154
+ />
155
+ )}
156
+ </ItemContainer>
157
+ )}
158
+ </Sortable.Item>
159
+ ))}
160
+ </Sortable.Root>
161
+ ) : (
162
+ indexes.map(renderRow)
163
+ )}
164
+ </Stack.V>
165
+ {expanded && renderExpandedContent?.()}
166
+ </InspectorPrimitives.Section>
167
+ );
168
+ });
@@ -0,0 +1,66 @@
1
+ import { round } from "@noya-app/noya-utils";
2
+ import React, { memo, useCallback } from "react";
3
+ import { InputField } from "./InputField";
4
+
5
+ export type SetNumberMode = "replace" | "adjust";
6
+
7
+ export function getNewValue(
8
+ value: number,
9
+ mode: SetNumberMode,
10
+ delta?: number
11
+ ) {
12
+ return delta === undefined
13
+ ? value
14
+ : mode === "replace"
15
+ ? delta
16
+ : value + delta;
17
+ }
18
+
19
+ export type DimensionValue = number | undefined;
20
+
21
+ interface Props {
22
+ id?: string;
23
+ value: DimensionValue;
24
+ onSetValue: (value: number, mode: SetNumberMode) => void;
25
+ label?: string;
26
+ size?: number;
27
+ placeholder?: string;
28
+ disabled?: boolean;
29
+ trigger?: "change" | "submit";
30
+ }
31
+
32
+ export const DimensionInput = memo(function DimensionInput({
33
+ id,
34
+ value,
35
+ onSetValue,
36
+ label,
37
+ size,
38
+ placeholder = "multi",
39
+ disabled,
40
+ trigger = "submit",
41
+ }: Props) {
42
+ const handleNudgeValue = useCallback(
43
+ (value: number) => onSetValue(value, "adjust"),
44
+ [onSetValue]
45
+ );
46
+
47
+ const handleSetValue = useCallback(
48
+ (value: number) => onSetValue(value, "replace"),
49
+ [onSetValue]
50
+ );
51
+
52
+ return (
53
+ <InputField.Root id={id} width={size}>
54
+ <InputField.NumberInput
55
+ value={value === undefined ? value : round(value, 2)}
56
+ placeholder={value === undefined ? placeholder : undefined}
57
+ onNudge={handleNudgeValue}
58
+ disabled={disabled}
59
+ {...(trigger === "change"
60
+ ? { onChange: handleSetValue }
61
+ : { onSubmit: handleSetValue })}
62
+ />
63
+ {label && <InputField.Label>{label}</InputField.Label>}
64
+ </InputField.Root>
65
+ );
66
+ });
@@ -144,7 +144,7 @@ export const CompletionMenu = memo(
144
144
  })
145
145
  );
146
146
 
147
- interface Props {
147
+ type Props = {
148
148
  loading?: boolean;
149
149
  initialValue?: string;
150
150
  placeholder?: string;
@@ -160,7 +160,7 @@ interface Props {
160
160
  children?: React.ReactNode;
161
161
  hideChildrenWhenFocused?: boolean;
162
162
  hideMenuWhenEmptyValue?: boolean;
163
- }
163
+ };
164
164
 
165
165
  export const InputFieldWithCompletions = memo(
166
166
  forwardRef(function InputFieldWithCompletions(
@@ -0,0 +1,139 @@
1
+ import React, { CSSProperties, memo, ReactNode } from "react";
2
+ import styled, { useTheme } from "styled-components";
3
+ import { Spacer } from "./Spacer";
4
+
5
+ export const Section = styled.div<{
6
+ padding?: CSSProperties["padding"];
7
+ gap?: CSSProperties["gap"];
8
+ }>(({ theme, padding = "10px", gap }) => ({
9
+ flex: "0 0 auto",
10
+ display: "flex",
11
+ flexDirection: "column",
12
+ padding,
13
+ gap,
14
+ }));
15
+
16
+ export const SectionHeader = styled.div(({ theme }) => ({
17
+ display: "flex",
18
+ alignItems: "center",
19
+ }));
20
+
21
+ export const Title = styled.div<{
22
+ textStyle?: "small" | "heading5" | "heading4" | "heading3";
23
+ }>(({ theme, textStyle }) => ({
24
+ display: "flex",
25
+ flexDirection: "row",
26
+ userSelect: "none",
27
+ ...(textStyle
28
+ ? {
29
+ ...theme.textStyles[textStyle],
30
+ color: theme.colors.text,
31
+ }
32
+ : {
33
+ ...theme.textStyles.label,
34
+ color: theme.colors.textSubtle,
35
+ fontWeight: "bold",
36
+ }),
37
+ }));
38
+
39
+ export const Row = styled.div<{ gap?: CSSProperties["gap"] }>(
40
+ ({ theme, gap }) => ({
41
+ flex: "1",
42
+ display: "flex",
43
+ flexDirection: "row",
44
+ alignItems: "center",
45
+ gap,
46
+ })
47
+ );
48
+
49
+ export const Column = styled.div(({ theme }) => ({
50
+ flex: "1",
51
+ display: "flex",
52
+ flexDirection: "column",
53
+ gap: "2px",
54
+ }));
55
+
56
+ export const Checkbox = styled.input(({ theme }) => ({
57
+ margin: 0,
58
+ }));
59
+
60
+ export const Text = styled.span(({ theme }) => ({
61
+ ...theme.textStyles.small,
62
+ }));
63
+
64
+ export const VerticalSeparator = () => (
65
+ <Spacer.Vertical size={useTheme().sizes.inspector.verticalSeparator} />
66
+ );
67
+
68
+ export const HorizontalSeparator = () => (
69
+ <Spacer.Horizontal size={useTheme().sizes.inspector.horizontalSeparator} />
70
+ );
71
+
72
+ const SliderRowLabel = styled.span(({ theme }) => ({
73
+ ...theme.textStyles.small,
74
+ color: theme.colors.textMuted,
75
+ marginBottom: "-6px",
76
+ }));
77
+
78
+ export const RowLabel = styled.span<{
79
+ textStyle?: "small" | "heading5" | "heading4" | "heading3";
80
+ }>(({ theme, textStyle }) => ({
81
+ // marginBottom: '6px',
82
+ display: "flex",
83
+ ...(textStyle
84
+ ? {
85
+ ...theme.textStyles[textStyle],
86
+ }
87
+ : {
88
+ ...theme.textStyles.label,
89
+ color: theme.colors.textSubtle,
90
+ fontWeight: "bold",
91
+ }),
92
+ lineHeight: "19px", // Button height
93
+ }));
94
+
95
+ export const LabeledRow = memo(function LabeledRow({
96
+ id,
97
+ children,
98
+ label,
99
+ labelTextStyle,
100
+ gap,
101
+ right,
102
+ }: {
103
+ id?: string;
104
+ children: ReactNode;
105
+ label: string;
106
+ labelTextStyle?: "small" | "heading5" | "heading4" | "heading3";
107
+ gap?: CSSProperties["gap"];
108
+ right?: ReactNode;
109
+ }) {
110
+ return (
111
+ <Row id={id}>
112
+ <Column>
113
+ <RowLabel textStyle={labelTextStyle}>
114
+ {label}
115
+ {right && <Spacer.Horizontal />}
116
+ {right}
117
+ </RowLabel>
118
+ <Row gap={gap}>{children}</Row>
119
+ </Column>
120
+ </Row>
121
+ );
122
+ });
123
+
124
+ export const LabeledSliderRow = memo(function LabeledRow({
125
+ children,
126
+ label,
127
+ }: {
128
+ children: ReactNode;
129
+ label: string;
130
+ }) {
131
+ return (
132
+ <Row>
133
+ <Column>
134
+ <SliderRowLabel>{label}</SliderRowLabel>
135
+ <Row>{children}</Row>
136
+ </Column>
137
+ </Row>
138
+ );
139
+ });
@@ -1,143 +1,144 @@
1
- import { fireEvent, render } from '@testing-library/react';
2
- import React from 'react';
3
- import TextInput from '../TextInput';
4
-
5
- describe('submittable text input', () => {
6
- test('change and blur', () => {
7
- let text = '';
1
+ import { fireEvent, render } from "@testing-library/react";
2
+ import { describe, expect, mock, test } from "bun:test";
3
+ import React from "react";
4
+ import TextInput from "../TextInput";
5
+
6
+ describe("submittable text input", () => {
7
+ test("change and blur", () => {
8
+ let text = "";
8
9
  const handleSubmit = (value: string) => {
9
10
  text = value;
10
11
  };
11
12
 
12
13
  const { container, rerender } = render(
13
- <TextInput value={text} onSubmit={handleSubmit} />,
14
+ <TextInput value={text} onSubmit={handleSubmit} />
14
15
  );
15
16
 
16
- const input = container.querySelector('input')!;
17
- fireEvent.change(input, { target: { value: 'abc' } });
17
+ const input = container.querySelector("input")!;
18
+ fireEvent.change(input, { target: { value: "abc" } });
18
19
  fireEvent.blur(input);
19
20
 
20
- expect(text).toEqual('abc');
21
- expect(input.value).toEqual('');
21
+ expect(text).toEqual("abc");
22
+ expect(input.value).toEqual("");
22
23
 
23
24
  rerender(<TextInput value={text} onSubmit={handleSubmit} />);
24
25
 
25
- expect(input.value).toEqual('abc');
26
+ expect(input.value).toEqual("abc");
26
27
  });
27
28
 
28
- test('change and blur, reset', () => {
29
+ test("change and blur, reset", () => {
29
30
  const handleSubmit = (value: string) => {};
30
31
 
31
32
  const { container, rerender } = render(
32
- <TextInput value="" onSubmit={handleSubmit} />,
33
+ <TextInput value="" onSubmit={handleSubmit} />
33
34
  );
34
35
 
35
- const input = container.querySelector('input')!;
36
- fireEvent.change(input, { target: { value: 'abc' } });
36
+ const input = container.querySelector("input")!;
37
+ fireEvent.change(input, { target: { value: "abc" } });
37
38
  fireEvent.blur(input);
38
39
 
39
- expect(input.value).toEqual('');
40
+ expect(input.value).toEqual("");
40
41
 
41
42
  rerender(<TextInput value="def" onSubmit={handleSubmit} />);
42
43
 
43
- expect(input.value).toEqual('def');
44
+ expect(input.value).toEqual("def");
44
45
  });
45
46
 
46
- test('submit without changing', () => {
47
- const handleSubmit = jest.fn();
47
+ test("submit without changing", () => {
48
+ const handleSubmit = mock(() => {});
48
49
 
49
50
  const { container } = render(
50
- <TextInput value="" onSubmit={handleSubmit} />,
51
+ <TextInput value="" onSubmit={handleSubmit} />
51
52
  );
52
53
 
53
- const input = container.querySelector('input')!;
54
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
54
+ const input = container.querySelector("input")!;
55
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
55
56
 
56
57
  expect(handleSubmit).not.toBeCalled();
57
58
  });
58
59
 
59
- test('change and submit', () => {
60
- let text = '';
60
+ test("change and submit", () => {
61
+ let text = "";
61
62
  const handleSubmit = (value: string) => {
62
63
  text = value;
63
64
  };
64
65
 
65
66
  const { container, rerender } = render(
66
- <TextInput value={text} onSubmit={handleSubmit} />,
67
+ <TextInput value={text} onSubmit={handleSubmit} />
67
68
  );
68
69
 
69
- const input = container.querySelector('input')!;
70
- fireEvent.change(input, { target: { value: 'abc' } });
71
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
70
+ const input = container.querySelector("input")!;
71
+ fireEvent.change(input, { target: { value: "abc" } });
72
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
72
73
 
73
- expect(text).toEqual('abc');
74
- expect(input.value).toEqual('');
74
+ expect(text).toEqual("abc");
75
+ expect(input.value).toEqual("");
75
76
 
76
77
  rerender(<TextInput value={text} onSubmit={handleSubmit} />);
77
78
 
78
- expect(input.value).toEqual('abc');
79
+ expect(input.value).toEqual("abc");
79
80
 
80
- fireEvent.change(input, { target: { value: 'abcdef' } });
81
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
81
+ fireEvent.change(input, { target: { value: "abcdef" } });
82
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
82
83
 
83
- expect(text).toEqual('abcdef');
84
- expect(input.value).toEqual('abc');
84
+ expect(text).toEqual("abcdef");
85
+ expect(input.value).toEqual("abc");
85
86
 
86
87
  rerender(<TextInput value={text} onSubmit={handleSubmit} />);
87
88
 
88
- expect(input.value).toEqual('abcdef');
89
+ expect(input.value).toEqual("abcdef");
89
90
  });
90
91
 
91
- test('change and submit, reset', () => {
92
+ test("change and submit, reset", () => {
92
93
  const handleSubmit = (value: string) => {};
93
94
 
94
95
  const { container, rerender } = render(
95
- <TextInput value="" onSubmit={handleSubmit} />,
96
+ <TextInput value="" onSubmit={handleSubmit} />
96
97
  );
97
98
 
98
- const input = container.querySelector('input')!;
99
- fireEvent.change(input, { target: { value: 'abc' } });
100
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
99
+ const input = container.querySelector("input")!;
100
+ fireEvent.change(input, { target: { value: "abc" } });
101
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
101
102
 
102
- expect(input.value).toEqual('');
103
+ expect(input.value).toEqual("");
103
104
 
104
105
  rerender(<TextInput value="" onSubmit={handleSubmit} />);
105
106
 
106
- expect(input.value).toEqual('');
107
+ expect(input.value).toEqual("");
107
108
  });
108
109
 
109
- test('submit only once', () => {
110
- const handleSubmit = jest.fn();
110
+ test("submit only once", () => {
111
+ const handleSubmit = mock(() => {});
111
112
 
112
113
  const { container } = render(
113
- <TextInput value="" onSubmit={handleSubmit} />,
114
+ <TextInput value="" onSubmit={handleSubmit} />
114
115
  );
115
116
 
116
- const input = container.querySelector('input')!;
117
- fireEvent.change(input, { target: { value: 'abc' } });
118
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
119
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
120
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
117
+ const input = container.querySelector("input")!;
118
+ fireEvent.change(input, { target: { value: "abc" } });
119
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
120
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
121
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
121
122
 
122
123
  expect(handleSubmit).toBeCalledTimes(1);
123
124
  });
124
125
 
125
- test('submit multiple times', () => {
126
- const handleSubmit = jest.fn();
126
+ test("submit multiple times", () => {
127
+ const handleSubmit = mock(() => {});
127
128
 
128
129
  const { container } = render(
129
130
  <TextInput
130
131
  value=""
131
132
  onSubmit={handleSubmit}
132
133
  allowSubmittingWithSameValue
133
- />,
134
+ />
134
135
  );
135
136
 
136
- const input = container.querySelector('input')!;
137
- fireEvent.change(input, { target: { value: 'abc' } });
138
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
139
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
140
- fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
137
+ const input = container.querySelector("input")!;
138
+ fireEvent.change(input, { target: { value: "abc" } });
139
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
140
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
141
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
141
142
 
142
143
  expect(handleSubmit).toBeCalledTimes(3);
143
144
  });
@@ -1,9 +1,10 @@
1
- import { ReactEventHandlers } from 'react-use-gesture/dist/types';
2
- import { mergeEventHandlers } from '../..';
1
+ import { expect, test } from "bun:test";
2
+ import { ReactEventHandlers } from "react-use-gesture/dist/types";
3
+ import { mergeEventHandlers } from "../..";
3
4
 
4
5
  const event = { defaultPrevented: false };
5
6
 
6
- test('merges handlers', () => {
7
+ test("merges handlers", () => {
7
8
  const out: [number, any][] = [];
8
9
 
9
10
  const a: ReactEventHandlers = { onClick: (e) => out.push([1, e]) };
@@ -15,8 +16,8 @@ test('merges handlers', () => {
15
16
 
16
17
  result.onClick?.(event as any);
17
18
 
18
- expect(Object.keys(result)).toEqual(['onClick']);
19
- expect(typeof result.onClick).toEqual('function');
19
+ expect(Object.keys(result)).toEqual(["onClick"]);
20
+ expect(typeof result.onClick).toEqual("function");
20
21
  expect(out).toEqual([
21
22
  [1, event],
22
23
  [2, event],
@@ -26,7 +27,7 @@ test('merges handlers', () => {
26
27
 
27
28
  const noop = () => {};
28
29
 
29
- test('merges multiple handlers', () => {
30
+ test("merges multiple handlers", () => {
30
31
  const a: ReactEventHandlers = {
31
32
  onMouseDown: noop,
32
33
  onMouseMove: noop,
@@ -40,8 +41,8 @@ test('merges multiple handlers', () => {
40
41
  const result = mergeEventHandlers(a, b);
41
42
 
42
43
  expect(Object.keys(result)).toEqual([
43
- 'onMouseDown',
44
- 'onMouseMove',
45
- 'onMouseUp',
44
+ "onMouseDown",
45
+ "onMouseMove",
46
+ "onMouseUp",
46
47
  ]);
47
48
  });
package/src/index.tsx CHANGED
@@ -76,3 +76,8 @@ export * from "./utils/fuzzyScorer";
76
76
  export * from "./utils/sketchColor";
77
77
  export * from "./utils/sketchPattern";
78
78
  export { default as withSeparatorElements } from "./utils/withSeparatorElements";
79
+
80
+ // Avoid dependency cycles
81
+ export * as InspectorPrimitives from "./components/InspectorPrimitives";
82
+ export * from "./components/ArrayController";
83
+ export * from "./components/DimensionInput";