@seed-design/react-field-button 1.0.0 → 1.0.2
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/lib/{FieldButton-12s-nUvzy79W.js → FieldButton-12s-BzczO8x6.js} +22 -12
- package/lib/{FieldButton-12s-CF9DP1xP.cjs → FieldButton-12s-CG6rqNcy.cjs} +22 -12
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +8 -2
- package/lib/index.js +2 -2
- package/package.json +3 -2
- package/src/FieldButton.tsx +2 -2
- package/src/useFieldButton.test.tsx +46 -7
- package/src/useFieldButton.ts +30 -11
|
@@ -4,6 +4,7 @@ import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
|
4
4
|
import { elementProps, dataAttr, buttonProps, inputProps, ariaAttr, mergeProps } from '@seed-design/dom-utils';
|
|
5
5
|
import { Primitive } from '@seed-design/react-primitive';
|
|
6
6
|
import { useId, useCallback, useState, createContext, useContext, forwardRef } from 'react';
|
|
7
|
+
import { useSupports } from '@seed-design/react-supports';
|
|
7
8
|
|
|
8
9
|
const getDescriptionId = (id)=>`field-button:${id}:description`;
|
|
9
10
|
const getErrorMessageId = (id)=>`field-button:${id}:error-message`;
|
|
@@ -42,9 +43,9 @@ function useFieldButtonState({ values = [], onValuesChange = ()=>{} }) {
|
|
|
42
43
|
setIsFocusVisible
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
function useFieldButton(
|
|
46
|
+
function useFieldButton({ values: propValues, onValuesChange, disabled = false, invalid = false, readOnly = false, name }) {
|
|
46
47
|
const id = useId();
|
|
47
|
-
const
|
|
48
|
+
const isFocusVisibleSupported = useSupports("selector(:focus-visible)");
|
|
48
49
|
const { values: stateValues, isHovered, isActive, isFocused, isFocusVisible, refs, renderedElements, setValues, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldButtonState({
|
|
49
50
|
values: propValues,
|
|
50
51
|
onValuesChange
|
|
@@ -59,13 +60,16 @@ function useFieldButton(props) {
|
|
|
59
60
|
"data-focus": dataAttr(isFocused),
|
|
60
61
|
"data-focus-visible": dataAttr(isFocusVisible),
|
|
61
62
|
"data-disabled": dataAttr(disabled),
|
|
62
|
-
"data-invalid": dataAttr(invalid)
|
|
63
|
+
"data-invalid": dataAttr(invalid),
|
|
64
|
+
"data-readonly": dataAttr(readOnly)
|
|
63
65
|
});
|
|
64
66
|
return {
|
|
65
67
|
values: stateValues,
|
|
66
68
|
active: isActive,
|
|
67
69
|
focused: isFocused,
|
|
68
70
|
invalid,
|
|
71
|
+
disabled,
|
|
72
|
+
readOnly,
|
|
69
73
|
setIsFocused,
|
|
70
74
|
setIsFocusVisible,
|
|
71
75
|
refs,
|
|
@@ -83,8 +87,8 @@ function useFieldButton(props) {
|
|
|
83
87
|
buttonProps: buttonProps({
|
|
84
88
|
...stateProps,
|
|
85
89
|
type: "button",
|
|
86
|
-
disabled,
|
|
87
|
-
"aria-disabled": ariaAttr(disabled),
|
|
90
|
+
disabled: disabled || readOnly,
|
|
91
|
+
"aria-disabled": ariaAttr(disabled || readOnly),
|
|
88
92
|
"aria-describedby": ariaDescribedBy,
|
|
89
93
|
// note that pointerdown and pointerup are attached to the button, not the root
|
|
90
94
|
// this is for preventing setting isActive to true when the clear button is pressed
|
|
@@ -96,21 +100,24 @@ function useFieldButton(props) {
|
|
|
96
100
|
},
|
|
97
101
|
onBlur () {
|
|
98
102
|
setIsFocused(false);
|
|
99
|
-
|
|
103
|
+
if (isFocusVisibleSupported) {
|
|
104
|
+
setIsFocusVisible(false);
|
|
105
|
+
}
|
|
100
106
|
},
|
|
101
107
|
onFocus (event) {
|
|
102
108
|
setIsFocused(true);
|
|
103
|
-
|
|
109
|
+
if (isFocusVisibleSupported) {
|
|
110
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
111
|
+
}
|
|
104
112
|
}
|
|
105
113
|
}),
|
|
106
114
|
clearButtonProps: buttonProps({
|
|
107
|
-
...stateProps,
|
|
108
115
|
type: "button",
|
|
109
|
-
disabled,
|
|
116
|
+
disabled: disabled || readOnly,
|
|
110
117
|
onClick: useCallback(()=>setValues([]), [
|
|
111
118
|
setValues
|
|
112
119
|
]),
|
|
113
|
-
hidden: disabled
|
|
120
|
+
hidden: disabled || readOnly
|
|
114
121
|
}),
|
|
115
122
|
getHiddenInputProps: useCallback((index)=>{
|
|
116
123
|
const value = stateValues[index];
|
|
@@ -118,12 +125,14 @@ function useFieldButton(props) {
|
|
|
118
125
|
return inputProps({
|
|
119
126
|
type: "hidden",
|
|
120
127
|
value,
|
|
128
|
+
disabled,
|
|
121
129
|
name: name || id
|
|
122
130
|
});
|
|
123
131
|
}, [
|
|
124
132
|
stateValues,
|
|
125
133
|
name,
|
|
126
|
-
id
|
|
134
|
+
id,
|
|
135
|
+
disabled
|
|
127
136
|
]),
|
|
128
137
|
descriptionProps: elementProps({
|
|
129
138
|
...stateProps,
|
|
@@ -147,9 +156,10 @@ function useFieldButtonContext({ strict = true } = {}) {
|
|
|
147
156
|
return context;
|
|
148
157
|
}
|
|
149
158
|
|
|
150
|
-
const FieldButtonRoot = /*#__PURE__*/ forwardRef(({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
|
|
159
|
+
const FieldButtonRoot = /*#__PURE__*/ forwardRef(({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
|
|
151
160
|
const api = useFieldButton({
|
|
152
161
|
disabled,
|
|
162
|
+
readOnly,
|
|
153
163
|
invalid,
|
|
154
164
|
name,
|
|
155
165
|
values,
|
|
@@ -4,6 +4,7 @@ var reactComposeRefs = require('@radix-ui/react-compose-refs');
|
|
|
4
4
|
var domUtils = require('@seed-design/dom-utils');
|
|
5
5
|
var reactPrimitive = require('@seed-design/react-primitive');
|
|
6
6
|
var react = require('react');
|
|
7
|
+
var reactSupports = require('@seed-design/react-supports');
|
|
7
8
|
|
|
8
9
|
const getDescriptionId = (id)=>`field-button:${id}:description`;
|
|
9
10
|
const getErrorMessageId = (id)=>`field-button:${id}:error-message`;
|
|
@@ -42,9 +43,9 @@ function useFieldButtonState({ values = [], onValuesChange = ()=>{} }) {
|
|
|
42
43
|
setIsFocusVisible
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
function useFieldButton(
|
|
46
|
+
function useFieldButton({ values: propValues, onValuesChange, disabled = false, invalid = false, readOnly = false, name }) {
|
|
46
47
|
const id = react.useId();
|
|
47
|
-
const
|
|
48
|
+
const isFocusVisibleSupported = reactSupports.useSupports("selector(:focus-visible)");
|
|
48
49
|
const { values: stateValues, isHovered, isActive, isFocused, isFocusVisible, refs, renderedElements, setValues, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldButtonState({
|
|
49
50
|
values: propValues,
|
|
50
51
|
onValuesChange
|
|
@@ -59,13 +60,16 @@ function useFieldButton(props) {
|
|
|
59
60
|
"data-focus": domUtils.dataAttr(isFocused),
|
|
60
61
|
"data-focus-visible": domUtils.dataAttr(isFocusVisible),
|
|
61
62
|
"data-disabled": domUtils.dataAttr(disabled),
|
|
62
|
-
"data-invalid": domUtils.dataAttr(invalid)
|
|
63
|
+
"data-invalid": domUtils.dataAttr(invalid),
|
|
64
|
+
"data-readonly": domUtils.dataAttr(readOnly)
|
|
63
65
|
});
|
|
64
66
|
return {
|
|
65
67
|
values: stateValues,
|
|
66
68
|
active: isActive,
|
|
67
69
|
focused: isFocused,
|
|
68
70
|
invalid,
|
|
71
|
+
disabled,
|
|
72
|
+
readOnly,
|
|
69
73
|
setIsFocused,
|
|
70
74
|
setIsFocusVisible,
|
|
71
75
|
refs,
|
|
@@ -83,8 +87,8 @@ function useFieldButton(props) {
|
|
|
83
87
|
buttonProps: domUtils.buttonProps({
|
|
84
88
|
...stateProps,
|
|
85
89
|
type: "button",
|
|
86
|
-
disabled,
|
|
87
|
-
"aria-disabled": domUtils.ariaAttr(disabled),
|
|
90
|
+
disabled: disabled || readOnly,
|
|
91
|
+
"aria-disabled": domUtils.ariaAttr(disabled || readOnly),
|
|
88
92
|
"aria-describedby": ariaDescribedBy,
|
|
89
93
|
// note that pointerdown and pointerup are attached to the button, not the root
|
|
90
94
|
// this is for preventing setting isActive to true when the clear button is pressed
|
|
@@ -96,21 +100,24 @@ function useFieldButton(props) {
|
|
|
96
100
|
},
|
|
97
101
|
onBlur () {
|
|
98
102
|
setIsFocused(false);
|
|
99
|
-
|
|
103
|
+
if (isFocusVisibleSupported) {
|
|
104
|
+
setIsFocusVisible(false);
|
|
105
|
+
}
|
|
100
106
|
},
|
|
101
107
|
onFocus (event) {
|
|
102
108
|
setIsFocused(true);
|
|
103
|
-
|
|
109
|
+
if (isFocusVisibleSupported) {
|
|
110
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
111
|
+
}
|
|
104
112
|
}
|
|
105
113
|
}),
|
|
106
114
|
clearButtonProps: domUtils.buttonProps({
|
|
107
|
-
...stateProps,
|
|
108
115
|
type: "button",
|
|
109
|
-
disabled,
|
|
116
|
+
disabled: disabled || readOnly,
|
|
110
117
|
onClick: react.useCallback(()=>setValues([]), [
|
|
111
118
|
setValues
|
|
112
119
|
]),
|
|
113
|
-
hidden: disabled
|
|
120
|
+
hidden: disabled || readOnly
|
|
114
121
|
}),
|
|
115
122
|
getHiddenInputProps: react.useCallback((index)=>{
|
|
116
123
|
const value = stateValues[index];
|
|
@@ -118,12 +125,14 @@ function useFieldButton(props) {
|
|
|
118
125
|
return domUtils.inputProps({
|
|
119
126
|
type: "hidden",
|
|
120
127
|
value,
|
|
128
|
+
disabled,
|
|
121
129
|
name: name || id
|
|
122
130
|
});
|
|
123
131
|
}, [
|
|
124
132
|
stateValues,
|
|
125
133
|
name,
|
|
126
|
-
id
|
|
134
|
+
id,
|
|
135
|
+
disabled
|
|
127
136
|
]),
|
|
128
137
|
descriptionProps: domUtils.elementProps({
|
|
129
138
|
...stateProps,
|
|
@@ -147,9 +156,10 @@ function useFieldButtonContext({ strict = true } = {}) {
|
|
|
147
156
|
return context;
|
|
148
157
|
}
|
|
149
158
|
|
|
150
|
-
const FieldButtonRoot = /*#__PURE__*/ react.forwardRef(({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
|
|
159
|
+
const FieldButtonRoot = /*#__PURE__*/ react.forwardRef(({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref)=>{
|
|
151
160
|
const api = useFieldButton({
|
|
152
161
|
disabled,
|
|
162
|
+
readOnly,
|
|
153
163
|
invalid,
|
|
154
164
|
name,
|
|
155
165
|
values,
|
package/lib/index.cjs
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -15,14 +15,20 @@ interface UseFieldButtonProps extends UseFieldButtonStateProps {
|
|
|
15
15
|
* @default false
|
|
16
16
|
*/
|
|
17
17
|
invalid?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
readOnly?: boolean;
|
|
18
22
|
name?: string;
|
|
19
23
|
}
|
|
20
24
|
type UseFieldButtonReturn = ReturnType<typeof useFieldButton>;
|
|
21
|
-
declare function useFieldButton(
|
|
25
|
+
declare function useFieldButton({ values: propValues, onValuesChange, disabled, invalid, readOnly, name, }: UseFieldButtonProps): {
|
|
22
26
|
values: string[];
|
|
23
27
|
active: boolean;
|
|
24
28
|
focused: boolean;
|
|
25
29
|
invalid: boolean;
|
|
30
|
+
disabled: boolean;
|
|
31
|
+
readOnly: boolean;
|
|
26
32
|
setIsFocused: react.Dispatch<react.SetStateAction<boolean>>;
|
|
27
33
|
setIsFocusVisible: react.Dispatch<react.SetStateAction<boolean>>;
|
|
28
34
|
refs: {
|
|
@@ -1179,6 +1185,7 @@ declare function useFieldButton(props: UseFieldButtonProps): {
|
|
|
1179
1185
|
};
|
|
1180
1186
|
getHiddenInputProps: (index: number) => {
|
|
1181
1187
|
disabled?: boolean | undefined | undefined;
|
|
1188
|
+
readOnly?: boolean | undefined | undefined;
|
|
1182
1189
|
name?: string | undefined | undefined;
|
|
1183
1190
|
defaultChecked?: boolean | undefined | undefined;
|
|
1184
1191
|
defaultValue?: string | number | readonly string[] | undefined;
|
|
@@ -1482,7 +1489,6 @@ declare function useFieldButton(props: UseFieldButtonProps): {
|
|
|
1482
1489
|
multiple?: boolean | undefined | undefined;
|
|
1483
1490
|
pattern?: string | undefined | undefined;
|
|
1484
1491
|
placeholder?: string | undefined | undefined;
|
|
1485
|
-
readOnly?: boolean | undefined | undefined;
|
|
1486
1492
|
required?: boolean | undefined | undefined;
|
|
1487
1493
|
size?: number | undefined | undefined;
|
|
1488
1494
|
src?: string | undefined | undefined;
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FieldButtonButton, a as FieldButtonClearButton, b as FieldButtonDescription, c as FieldButtonErrorMessage, d as FieldButtonHiddenInput, e as FieldButtonRoot } from './FieldButton-12s-
|
|
2
|
-
export { u as useFieldButtonContext } from './FieldButton-12s-
|
|
1
|
+
import { F as FieldButtonButton, a as FieldButtonClearButton, b as FieldButtonDescription, c as FieldButtonErrorMessage, d as FieldButtonHiddenInput, e as FieldButtonRoot } from './FieldButton-12s-BzczO8x6.js';
|
|
2
|
+
export { u as useFieldButtonContext } from './FieldButton-12s-BzczO8x6.js';
|
|
3
3
|
|
|
4
4
|
var FieldButton_namespace = {
|
|
5
5
|
__proto__: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seed-design/react-field-button",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/daangn/seed-design.git",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"@radix-ui/react-compose-refs": "^1.1.2",
|
|
31
31
|
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
|
32
32
|
"@seed-design/dom-utils": "1.0.0",
|
|
33
|
-
"@seed-design/react-primitive": "1.0.0"
|
|
33
|
+
"@seed-design/react-primitive": "1.0.0",
|
|
34
|
+
"@seed-design/react-supports": "0.0.1"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/react": "^19.1.6",
|
package/src/FieldButton.tsx
CHANGED
|
@@ -13,8 +13,8 @@ export interface FieldButtonRootProps
|
|
|
13
13
|
HTMLAttributes<HTMLDivElement> {}
|
|
14
14
|
|
|
15
15
|
export const FieldButtonRoot = forwardRef<HTMLDivElement, FieldButtonRootProps>(
|
|
16
|
-
({ disabled, invalid, name, values, onValuesChange, ...otherProps }, ref) => {
|
|
17
|
-
const api = useFieldButton({ disabled, invalid, name, values, onValuesChange });
|
|
16
|
+
({ disabled, readOnly, invalid, name, values, onValuesChange, ...otherProps }, ref) => {
|
|
17
|
+
const api = useFieldButton({ disabled, readOnly, invalid, name, values, onValuesChange });
|
|
18
18
|
const mergedProps = mergeProps(api.rootProps, otherProps);
|
|
19
19
|
|
|
20
20
|
return (
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import "@testing-library/
|
|
2
|
-
import { cleanup, fireEvent, render } from "@testing-library/react";
|
|
1
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
3
2
|
import userEvent from "@testing-library/user-event";
|
|
4
|
-
import {
|
|
3
|
+
import { describe, expect, it, mock } from "bun:test";
|
|
5
4
|
|
|
6
5
|
import type { ReactElement } from "react";
|
|
7
6
|
|
|
@@ -15,8 +14,6 @@ import {
|
|
|
15
14
|
type FieldButtonRootProps,
|
|
16
15
|
} from "./FieldButton";
|
|
17
16
|
|
|
18
|
-
afterEach(cleanup);
|
|
19
|
-
|
|
20
17
|
function setUp(jsx: ReactElement) {
|
|
21
18
|
return {
|
|
22
19
|
user: userEvent.setup(),
|
|
@@ -103,6 +100,15 @@ describe("useFieldButton", () => {
|
|
|
103
100
|
expect(button).toHaveAttribute("aria-disabled", "true");
|
|
104
101
|
});
|
|
105
102
|
|
|
103
|
+
it("should have readOnly state when readOnly prop is true", () => {
|
|
104
|
+
const { getByRole } = setUp(<FieldButton readOnly />);
|
|
105
|
+
const button = getByRole("button", { name: "Click me" });
|
|
106
|
+
|
|
107
|
+
expect(button).toBeDisabled();
|
|
108
|
+
expect(button).toHaveAttribute("data-readonly");
|
|
109
|
+
expect(button).toHaveAttribute("aria-disabled", "true");
|
|
110
|
+
});
|
|
111
|
+
|
|
106
112
|
it("should have invalid state when invalid prop is true", () => {
|
|
107
113
|
const { getByRole } = setUp(<FieldButton invalid />);
|
|
108
114
|
const button = getByRole("button", { name: "Click me" });
|
|
@@ -158,6 +164,26 @@ describe("useFieldButton", () => {
|
|
|
158
164
|
expect(button).not.toHaveAttribute("data-active");
|
|
159
165
|
});
|
|
160
166
|
|
|
167
|
+
it("should not disable hidden inputs when readOnly (unlike disabled)", () => {
|
|
168
|
+
const { container } = setUp(<FieldButton readOnly values={["value1", "value2"]} />);
|
|
169
|
+
const hiddenInputs = container.querySelectorAll('input[type="hidden"]');
|
|
170
|
+
|
|
171
|
+
expect(hiddenInputs).toHaveLength(2);
|
|
172
|
+
expect(hiddenInputs[0]).not.toBeDisabled();
|
|
173
|
+
expect(hiddenInputs[1]).not.toBeDisabled();
|
|
174
|
+
expect(hiddenInputs[0]).toHaveValue("value1");
|
|
175
|
+
expect(hiddenInputs[1]).toHaveValue("value2");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should disable hidden inputs when disabled", () => {
|
|
179
|
+
const { container } = setUp(<FieldButton disabled values={["value1", "value2"]} />);
|
|
180
|
+
const hiddenInputs = container.querySelectorAll('input[type="hidden"]');
|
|
181
|
+
|
|
182
|
+
expect(hiddenInputs).toHaveLength(2);
|
|
183
|
+
expect(hiddenInputs[0]).toBeDisabled();
|
|
184
|
+
expect(hiddenInputs[1]).toBeDisabled();
|
|
185
|
+
});
|
|
186
|
+
|
|
161
187
|
it("should render without description and errorMessage", () => {
|
|
162
188
|
function MinimalFieldButton(props: FieldButtonRootProps) {
|
|
163
189
|
return (
|
|
@@ -199,7 +225,7 @@ describe("useFieldButton", () => {
|
|
|
199
225
|
|
|
200
226
|
describe("clear button", () => {
|
|
201
227
|
it("should call onValuesChange when clear button is clicked", async () => {
|
|
202
|
-
const handleValuesChange =
|
|
228
|
+
const handleValuesChange = mock(() => {});
|
|
203
229
|
const { getByRole, user } = setUp(
|
|
204
230
|
<FieldButton values={["value1"]} onValuesChange={handleValuesChange} />,
|
|
205
231
|
);
|
|
@@ -210,7 +236,7 @@ describe("useFieldButton", () => {
|
|
|
210
236
|
});
|
|
211
237
|
|
|
212
238
|
it("should hide clear button when disabled", async () => {
|
|
213
|
-
const handleValuesChange =
|
|
239
|
+
const handleValuesChange = mock(() => {});
|
|
214
240
|
const { getByText, user } = setUp(
|
|
215
241
|
<FieldButton disabled values={["value1"]} onValuesChange={handleValuesChange} />,
|
|
216
242
|
);
|
|
@@ -222,6 +248,19 @@ describe("useFieldButton", () => {
|
|
|
222
248
|
expect(handleValuesChange).not.toHaveBeenCalled();
|
|
223
249
|
});
|
|
224
250
|
|
|
251
|
+
it("should hide clear button when readOnly", async () => {
|
|
252
|
+
const handleValuesChange = mock(() => {});
|
|
253
|
+
const { getByText, user } = setUp(
|
|
254
|
+
<FieldButton readOnly values={["value1"]} onValuesChange={handleValuesChange} />,
|
|
255
|
+
);
|
|
256
|
+
const clearButton = getByText("Clear");
|
|
257
|
+
|
|
258
|
+
expect(clearButton).toHaveAttribute("hidden");
|
|
259
|
+
|
|
260
|
+
await user.click(clearButton);
|
|
261
|
+
expect(handleValuesChange).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
|
|
225
264
|
it("should not have active state when clear button is pressed", () => {
|
|
226
265
|
const { getByRole } = setUp(<FieldButton />);
|
|
227
266
|
const button = getByRole("button", { name: "Click me" });
|
package/src/useFieldButton.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ariaAttr, buttonProps, dataAttr, elementProps, inputProps } from "@seed-design/dom-utils";
|
|
2
2
|
import { useCallback, useId, useState } from "react";
|
|
3
3
|
import { getDescriptionId, getErrorMessageId } from "./dom";
|
|
4
|
+
import { useSupports } from "@seed-design/react-supports";
|
|
4
5
|
|
|
5
6
|
interface UseFieldButtonStateProps {
|
|
6
7
|
values?: string[];
|
|
@@ -57,14 +58,26 @@ export interface UseFieldButtonProps extends UseFieldButtonStateProps {
|
|
|
57
58
|
*/
|
|
58
59
|
invalid?: boolean;
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
readOnly?: boolean;
|
|
65
|
+
|
|
60
66
|
name?: string;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export type UseFieldButtonReturn = ReturnType<typeof useFieldButton>;
|
|
64
70
|
|
|
65
|
-
export function useFieldButton(
|
|
71
|
+
export function useFieldButton({
|
|
72
|
+
values: propValues,
|
|
73
|
+
onValuesChange,
|
|
74
|
+
disabled = false,
|
|
75
|
+
invalid = false,
|
|
76
|
+
readOnly = false,
|
|
77
|
+
name,
|
|
78
|
+
}: UseFieldButtonProps) {
|
|
66
79
|
const id = useId();
|
|
67
|
-
const
|
|
80
|
+
const isFocusVisibleSupported = useSupports("selector(:focus-visible)");
|
|
68
81
|
|
|
69
82
|
const {
|
|
70
83
|
values: stateValues,
|
|
@@ -96,6 +109,7 @@ export function useFieldButton(props: UseFieldButtonProps) {
|
|
|
96
109
|
"data-focus-visible": dataAttr(isFocusVisible),
|
|
97
110
|
"data-disabled": dataAttr(disabled),
|
|
98
111
|
"data-invalid": dataAttr(invalid),
|
|
112
|
+
"data-readonly": dataAttr(readOnly),
|
|
99
113
|
});
|
|
100
114
|
|
|
101
115
|
return {
|
|
@@ -103,6 +117,8 @@ export function useFieldButton(props: UseFieldButtonProps) {
|
|
|
103
117
|
active: isActive,
|
|
104
118
|
focused: isFocused,
|
|
105
119
|
invalid,
|
|
120
|
+
disabled,
|
|
121
|
+
readOnly,
|
|
106
122
|
|
|
107
123
|
setIsFocused,
|
|
108
124
|
setIsFocusVisible,
|
|
@@ -127,9 +143,9 @@ export function useFieldButton(props: UseFieldButtonProps) {
|
|
|
127
143
|
...stateProps,
|
|
128
144
|
|
|
129
145
|
type: "button",
|
|
130
|
-
disabled,
|
|
146
|
+
disabled: disabled || readOnly,
|
|
131
147
|
|
|
132
|
-
"aria-disabled": ariaAttr(disabled),
|
|
148
|
+
"aria-disabled": ariaAttr(disabled || readOnly),
|
|
133
149
|
"aria-describedby": ariaDescribedBy,
|
|
134
150
|
|
|
135
151
|
// note that pointerdown and pointerup are attached to the button, not the root
|
|
@@ -143,23 +159,25 @@ export function useFieldButton(props: UseFieldButtonProps) {
|
|
|
143
159
|
|
|
144
160
|
onBlur() {
|
|
145
161
|
setIsFocused(false);
|
|
146
|
-
|
|
162
|
+
if (isFocusVisibleSupported) {
|
|
163
|
+
setIsFocusVisible(false);
|
|
164
|
+
}
|
|
147
165
|
},
|
|
148
166
|
onFocus(event) {
|
|
149
167
|
setIsFocused(true);
|
|
150
|
-
|
|
168
|
+
if (isFocusVisibleSupported) {
|
|
169
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
170
|
+
}
|
|
151
171
|
},
|
|
152
172
|
}),
|
|
153
173
|
|
|
154
174
|
clearButtonProps: buttonProps({
|
|
155
|
-
...stateProps,
|
|
156
|
-
|
|
157
175
|
type: "button",
|
|
158
|
-
disabled,
|
|
176
|
+
disabled: disabled || readOnly,
|
|
159
177
|
|
|
160
178
|
onClick: useCallback(() => setValues([]), [setValues]),
|
|
161
179
|
|
|
162
|
-
hidden: disabled,
|
|
180
|
+
hidden: disabled || readOnly,
|
|
163
181
|
}),
|
|
164
182
|
|
|
165
183
|
getHiddenInputProps: useCallback(
|
|
@@ -171,10 +189,11 @@ export function useFieldButton(props: UseFieldButtonProps) {
|
|
|
171
189
|
return inputProps({
|
|
172
190
|
type: "hidden",
|
|
173
191
|
value,
|
|
192
|
+
disabled, // disabled field button should not submit values, while readonly field button should submit values
|
|
174
193
|
name: name || id,
|
|
175
194
|
});
|
|
176
195
|
},
|
|
177
|
-
[stateValues, name, id],
|
|
196
|
+
[stateValues, name, id, disabled],
|
|
178
197
|
),
|
|
179
198
|
|
|
180
199
|
descriptionProps: elementProps({
|