@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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +127 -43
- package/dist/index.d.ts +127 -43
- package/dist/index.js +715 -10794
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +339 -10422
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -4
- package/src/components/ArrayController.tsx +168 -0
- package/src/components/DimensionInput.tsx +66 -0
- package/src/components/InputFieldWithCompletions.tsx +2 -2
- package/src/components/InspectorPrimitives.tsx +139 -0
- package/src/components/internal/__tests__/TextInput.test.tsx +62 -61
- package/src/hooks/__tests__/mergeEventHandlers.test.ts +10 -9
- package/src/index.tsx +5 -0
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.0.
|
|
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": "
|
|
16
|
-
"@noya-app/noya-utils": "
|
|
17
|
-
"@noya-app/noya-geometry": "
|
|
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
|
-
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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(
|
|
17
|
-
fireEvent.change(input, { target: { value:
|
|
17
|
+
const input = container.querySelector("input")!;
|
|
18
|
+
fireEvent.change(input, { target: { value: "abc" } });
|
|
18
19
|
fireEvent.blur(input);
|
|
19
20
|
|
|
20
|
-
expect(text).toEqual(
|
|
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(
|
|
26
|
+
expect(input.value).toEqual("abc");
|
|
26
27
|
});
|
|
27
28
|
|
|
28
|
-
test(
|
|
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(
|
|
36
|
-
fireEvent.change(input, { target: { value:
|
|
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(
|
|
44
|
+
expect(input.value).toEqual("def");
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
test(
|
|
47
|
-
const handleSubmit =
|
|
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(
|
|
54
|
-
fireEvent.keyDown(input, { key:
|
|
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(
|
|
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(
|
|
70
|
-
fireEvent.change(input, { target: { value:
|
|
71
|
-
fireEvent.keyDown(input, { key:
|
|
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(
|
|
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(
|
|
79
|
+
expect(input.value).toEqual("abc");
|
|
79
80
|
|
|
80
|
-
fireEvent.change(input, { target: { value:
|
|
81
|
-
fireEvent.keyDown(input, { key:
|
|
81
|
+
fireEvent.change(input, { target: { value: "abcdef" } });
|
|
82
|
+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
82
83
|
|
|
83
|
-
expect(text).toEqual(
|
|
84
|
-
expect(input.value).toEqual(
|
|
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(
|
|
89
|
+
expect(input.value).toEqual("abcdef");
|
|
89
90
|
});
|
|
90
91
|
|
|
91
|
-
test(
|
|
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(
|
|
99
|
-
fireEvent.change(input, { target: { value:
|
|
100
|
-
fireEvent.keyDown(input, { key:
|
|
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(
|
|
110
|
-
const handleSubmit =
|
|
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(
|
|
117
|
-
fireEvent.change(input, { target: { value:
|
|
118
|
-
fireEvent.keyDown(input, { key:
|
|
119
|
-
fireEvent.keyDown(input, { key:
|
|
120
|
-
fireEvent.keyDown(input, { key:
|
|
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(
|
|
126
|
-
const handleSubmit =
|
|
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(
|
|
137
|
-
fireEvent.change(input, { target: { value:
|
|
138
|
-
fireEvent.keyDown(input, { key:
|
|
139
|
-
fireEvent.keyDown(input, { key:
|
|
140
|
-
fireEvent.keyDown(input, { key:
|
|
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 {
|
|
2
|
-
import {
|
|
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(
|
|
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([
|
|
19
|
-
expect(typeof result.onClick).toEqual(
|
|
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(
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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";
|