Skip to content

Commit 7e3e1c8

Browse files
committed
refactor: radio group
1 parent 46fd1e9 commit 7e3e1c8

File tree

7 files changed

+476
-286
lines changed

7 files changed

+476
-286
lines changed

.xstate/rating-group.js

Lines changed: 172 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,82 @@ const {
1010
choose
1111
} = actions;
1212
const fetchMachine = createMachine({
13-
id: "rating",
14-
initial: "idle",
15-
context: {
16-
"isValueEmpty": false,
17-
"isRadioFocused": false
13+
props({
14+
props
15+
}) {
16+
return {
17+
name: "rating",
18+
count: 5,
19+
dir: "ltr",
20+
value: -1,
21+
...compact(props),
22+
translations: {
23+
ratingValueText: index => `${index} stars`,
24+
...props.translations
25+
}
26+
};
27+
},
28+
initialState() {
29+
return "idle";
30+
},
31+
context({
32+
prop,
33+
bindable
34+
}) {
35+
return {
36+
value: bindable(() => ({
37+
defaultValue: prop("defaultValue"),
38+
value: prop("value"),
39+
onChange(value) {
40+
prop("onValueChange")?.({
41+
value
42+
});
43+
}
44+
})),
45+
hoveredValue: bindable(() => ({
46+
defaultValue: -1,
47+
onChange(value) {
48+
prop("onHoverChange")?.({
49+
hoveredValue: value
50+
});
51+
}
52+
})),
53+
fieldsetDisabled: bindable(() => ({
54+
defaultValue: false
55+
}))
56+
};
1857
},
19-
activities: ["trackFormControlState"],
58+
watch({
59+
track,
60+
action,
61+
prop
62+
}) {
63+
track([() => prop("allowHalf")], () => {
64+
action(["roundValueIfNeeded"]);
65+
});
66+
},
67+
effects: ["trackFormControlState"],
2068
on: {
2169
SET_VALUE: {
2270
actions: ["setValue"]
2371
},
2472
CLEAR_VALUE: {
2573
actions: ["clearValue"]
26-
}
27-
},
28-
on: {
74+
},
2975
UPDATE_CONTEXT: {
3076
actions: "updateContext"
3177
}
3278
},
3379
states: {
3480
idle: {
35-
entry: "clearHoveredValue",
81+
entry: ["clearHoveredValue"],
3682
on: {
37-
GROUP_POINTER_OVER: "hover",
38-
FOCUS: "focus",
83+
GROUP_POINTER_OVER: {
84+
target: "hover"
85+
},
86+
FOCUS: {
87+
target: "focus"
88+
},
3989
CLICK: {
4090
actions: ["setValue", "focusActiveRadio"]
4191
}
@@ -44,12 +94,14 @@ const fetchMachine = createMachine({
4494
focus: {
4595
on: {
4696
POINTER_OVER: {
47-
actions: "setHoveredValue"
97+
actions: ["setHoveredValue"]
4898
},
4999
GROUP_POINTER_LEAVE: {
50-
actions: "clearHoveredValue"
100+
actions: ["clearHoveredValue"]
101+
},
102+
BLUR: {
103+
target: "idle"
51104
},
52-
BLUR: "idle",
53105
SPACE: {
54106
cond: "isValueEmpty",
55107
actions: ["setValue"]
@@ -74,21 +126,123 @@ const fetchMachine = createMachine({
74126
hover: {
75127
on: {
76128
POINTER_OVER: {
77-
actions: "setHoveredValue"
129+
actions: ["setHoveredValue"]
78130
},
79131
GROUP_POINTER_LEAVE: [{
80132
cond: "isRadioFocused",
81133
target: "focus",
82-
actions: "clearHoveredValue"
134+
actions: ["clearHoveredValue"]
83135
}, {
84136
target: "idle",
85-
actions: "clearHoveredValue"
137+
actions: ["clearHoveredValue"]
86138
}],
87139
CLICK: {
88140
actions: ["setValue", "focusActiveRadio"]
89141
}
90142
}
91143
}
144+
},
145+
implementations: {
146+
guards: {
147+
isInteractive: ({
148+
prop
149+
}) => !(prop("disabled") || prop("readOnly")),
150+
isHoveredValueEmpty: ({
151+
context: {
152+
"isValueEmpty": false,
153+
"isRadioFocused": false
154+
}
155+
}) => context.get("hoveredValue") === -1,
156+
isValueEmpty: ({
157+
context
158+
}) => context.get("value") <= 0,
159+
isRadioFocused: ({
160+
scope
161+
}) => !!dom.getControlEl(scope)?.contains(scope.getActiveElement())
162+
},
163+
effects: {
164+
trackFormControlState({
165+
context,
166+
scope
167+
}) {
168+
return trackFormControl(dom.getHiddenInputEl(scope), {
169+
onFieldsetDisabledChange(disabled) {
170+
context.set("fieldsetDisabled", disabled);
171+
},
172+
onFormReset() {
173+
context.set("value", context.initial("value"));
174+
}
175+
});
176+
}
177+
},
178+
actions: {
179+
clearHoveredValue({
180+
context
181+
}) {
182+
context.set("hoveredValue", -1);
183+
},
184+
focusActiveRadio({
185+
scope,
186+
context
187+
}) {
188+
raf(() => dom.getRadioEl(scope, context.get("value"))?.focus());
189+
},
190+
setPrevValue({
191+
context,
192+
prop
193+
}) {
194+
const factor = prop("allowHalf") ? 0.5 : 1;
195+
context.set("value", Math.max(0, context.get("value") - factor));
196+
},
197+
setNextValue({
198+
context,
199+
prop
200+
}) {
201+
const factor = prop("allowHalf") ? 0.5 : 1;
202+
const value = context.get("value") === -1 ? 0 : context.get("value");
203+
context.set("value", Math.min(prop("count"), value + factor));
204+
},
205+
setValueToMin({
206+
context
207+
}) {
208+
context.set("value", 1);
209+
},
210+
setValueToMax({
211+
context,
212+
prop
213+
}) {
214+
context.set("value", prop("count"));
215+
},
216+
setValue({
217+
context,
218+
event
219+
}) {
220+
const hoveredValue = context.get("hoveredValue");
221+
const value = hoveredValue === -1 ? event.value : hoveredValue;
222+
context.set("value", value);
223+
},
224+
clearValue({
225+
context
226+
}) {
227+
context.set("value", -1);
228+
},
229+
setHoveredValue({
230+
context,
231+
prop,
232+
event
233+
}) {
234+
const half = prop("allowHalf") && event.isMidway;
235+
const factor = half ? 0.5 : 0;
236+
context.set("hoveredValue", event.index - factor);
237+
},
238+
roundValueIfNeeded({
239+
context,
240+
prop
241+
}) {
242+
if (prop("allowHalf")) return;
243+
context.set("value", Math.round(context.get("value")));
244+
}
245+
}
92246
}
93247
}, {
94248
actions: {

packages/machines/rating-group/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ export { connect } from "./rating-group.connect"
33
export { machine } from "./rating-group.machine"
44
export * from "./rating-group.props"
55
export type {
6-
MachineApi as Api,
7-
UserDefinedContext as Context,
6+
RatingGroupApi as Api,
7+
RatingGroupProps as Props,
88
HoverChangeDetails,
99
IntlTranslations,
1010
ItemProps,
1111
ItemState,
12-
Service,
12+
RatingGroupService as Service,
1313
ValueChangeDetails,
1414
} from "./rating-group.types"

0 commit comments

Comments
 (0)