@zag-js/radio-group 1.35.3 → 1.36.0

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.
@@ -216,14 +216,18 @@ function connect(service, normalize) {
216
216
  },
217
217
  getIndicatorProps() {
218
218
  const rect = context.get("indicatorRect");
219
- const rectIsEmpty = rect == null || rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
219
+ const animateIndicator = context.get("animateIndicator");
220
220
  return normalize.element({
221
221
  id: dom.getIndicatorId(scope),
222
222
  ...import_radio_group.parts.indicator.attrs,
223
223
  dir: prop("dir"),
224
- hidden: context.get("value") == null || rectIsEmpty,
224
+ hidden: context.get("value") == null || isRectEmpty(rect),
225
225
  "data-disabled": (0, import_dom_query.dataAttr)(groupDisabled),
226
226
  "data-orientation": prop("orientation"),
227
+ onTransitionEnd(event) {
228
+ if ((0, import_dom_query.getEventTarget)(event) !== event.currentTarget) return;
229
+ send({ type: "INDICATOR_TRANSITION_END" });
230
+ },
227
231
  style: {
228
232
  "--transition-property": "left, top, width, height",
229
233
  "--left": (0, import_utils.toPx)(rect?.x),
@@ -231,9 +235,9 @@ function connect(service, normalize) {
231
235
  "--width": (0, import_utils.toPx)(rect?.width),
232
236
  "--height": (0, import_utils.toPx)(rect?.height),
233
237
  position: "absolute",
234
- willChange: "var(--transition-property)",
235
- transitionProperty: "var(--transition-property)",
236
- transitionDuration: "var(--transition-duration, 150ms)",
238
+ willChange: animateIndicator ? "var(--transition-property)" : "auto",
239
+ transitionProperty: animateIndicator ? "var(--transition-property)" : "none",
240
+ transitionDuration: animateIndicator ? "var(--transition-duration, 150ms)" : "0ms",
237
241
  transitionTimingFunction: "var(--transition-timing-function)",
238
242
  [prop("orientation") === "horizontal" ? "left" : "top"]: prop("orientation") === "horizontal" ? "var(--left)" : "var(--top)"
239
243
  }
@@ -241,6 +245,7 @@ function connect(service, normalize) {
241
245
  }
242
246
  };
243
247
  }
248
+ var isRectEmpty = (rect) => rect == null || rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
244
249
  // Annotate the CommonJS export names for ESM import in node:
245
250
  0 && (module.exports = {
246
251
  connect
@@ -1,5 +1,5 @@
1
1
  // src/radio-group.connect.ts
2
- import { dataAttr, isLeftClick, isSafari, visuallyHiddenStyle } from "@zag-js/dom-query";
2
+ import { dataAttr, getEventTarget, isLeftClick, isSafari, visuallyHiddenStyle } from "@zag-js/dom-query";
3
3
  import { isFocusVisible } from "@zag-js/focus-visible";
4
4
  import { toPx } from "@zag-js/utils";
5
5
  import { parts } from "./radio-group.anatomy.mjs";
@@ -182,14 +182,18 @@ function connect(service, normalize) {
182
182
  },
183
183
  getIndicatorProps() {
184
184
  const rect = context.get("indicatorRect");
185
- const rectIsEmpty = rect == null || rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
185
+ const animateIndicator = context.get("animateIndicator");
186
186
  return normalize.element({
187
187
  id: dom.getIndicatorId(scope),
188
188
  ...parts.indicator.attrs,
189
189
  dir: prop("dir"),
190
- hidden: context.get("value") == null || rectIsEmpty,
190
+ hidden: context.get("value") == null || isRectEmpty(rect),
191
191
  "data-disabled": dataAttr(groupDisabled),
192
192
  "data-orientation": prop("orientation"),
193
+ onTransitionEnd(event) {
194
+ if (getEventTarget(event) !== event.currentTarget) return;
195
+ send({ type: "INDICATOR_TRANSITION_END" });
196
+ },
193
197
  style: {
194
198
  "--transition-property": "left, top, width, height",
195
199
  "--left": toPx(rect?.x),
@@ -197,9 +201,9 @@ function connect(service, normalize) {
197
201
  "--width": toPx(rect?.width),
198
202
  "--height": toPx(rect?.height),
199
203
  position: "absolute",
200
- willChange: "var(--transition-property)",
201
- transitionProperty: "var(--transition-property)",
202
- transitionDuration: "var(--transition-duration, 150ms)",
204
+ willChange: animateIndicator ? "var(--transition-property)" : "auto",
205
+ transitionProperty: animateIndicator ? "var(--transition-property)" : "none",
206
+ transitionDuration: animateIndicator ? "var(--transition-duration, 150ms)" : "0ms",
203
207
  transitionTimingFunction: "var(--transition-timing-function)",
204
208
  [prop("orientation") === "horizontal" ? "left" : "top"]: prop("orientation") === "horizontal" ? "var(--left)" : "var(--top)"
205
209
  }
@@ -207,6 +211,7 @@ function connect(service, normalize) {
207
211
  }
208
212
  };
209
213
  }
214
+ var isRectEmpty = (rect) => rect == null || rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
210
215
  export {
211
216
  connect
212
217
  };
@@ -72,6 +72,9 @@ var machine = (0, import_core.createMachine)({
72
72
  indicatorRect: bindable(() => ({
73
73
  defaultValue: null
74
74
  })),
75
+ animateIndicator: bindable(() => ({
76
+ defaultValue: false
77
+ })),
75
78
  fieldsetDisabled: bindable(() => ({
76
79
  defaultValue: false
77
80
  })),
@@ -83,18 +86,19 @@ var machine = (0, import_core.createMachine)({
83
86
  refs() {
84
87
  return {
85
88
  indicatorCleanup: null,
86
- focusVisibleValue: null
89
+ focusVisibleValue: null,
90
+ prevValue: null
87
91
  };
88
92
  },
89
93
  computed: {
90
94
  isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled")
91
95
  },
92
- entry: ["syncIndicatorRect", "syncSsr"],
96
+ entry: ["syncPrevValue", "syncIndicatorRect", "syncSsr"],
93
97
  exit: ["cleanupObserver"],
94
98
  effects: ["trackFormControlState", "trackFocusVisible"],
95
99
  watch({ track, action, context }) {
96
100
  track([() => context.get("value")], () => {
97
- action(["syncIndicatorRect", "syncInputElements"]);
101
+ action(["syncIndicatorAnimation", "syncIndicatorRect", "syncInputElements"]);
98
102
  });
99
103
  },
100
104
  on: {
@@ -115,6 +119,9 @@ var machine = (0, import_core.createMachine)({
115
119
  },
116
120
  SET_FOCUSED: {
117
121
  actions: ["setFocused"]
122
+ },
123
+ INDICATOR_TRANSITION_END: {
124
+ actions: ["clearIndicatorAnimation"]
118
125
  }
119
126
  },
120
127
  states: {
@@ -154,6 +161,19 @@ var machine = (0, import_core.createMachine)({
154
161
  const focusVisibleValue = event.value != null && event.focusVisible ? event.value : null;
155
162
  context.set("focusVisibleValue", focusVisibleValue);
156
163
  },
164
+ syncPrevValue({ context, refs }) {
165
+ refs.set("prevValue", context.get("value"));
166
+ },
167
+ syncIndicatorAnimation({ context, refs }) {
168
+ const prevValue = refs.get("prevValue");
169
+ const nextValue = context.get("value");
170
+ const animate = prevValue != null && nextValue != null && prevValue !== nextValue;
171
+ context.set("animateIndicator", animate);
172
+ refs.set("prevValue", nextValue);
173
+ },
174
+ clearIndicatorAnimation({ context }) {
175
+ context.set("animateIndicator", false);
176
+ },
157
177
  syncInputElements({ context, scope }) {
158
178
  const inputs = dom.getInputEls(scope);
159
179
  inputs.forEach((input) => {
@@ -38,6 +38,9 @@ var machine = createMachine({
38
38
  indicatorRect: bindable(() => ({
39
39
  defaultValue: null
40
40
  })),
41
+ animateIndicator: bindable(() => ({
42
+ defaultValue: false
43
+ })),
41
44
  fieldsetDisabled: bindable(() => ({
42
45
  defaultValue: false
43
46
  })),
@@ -49,18 +52,19 @@ var machine = createMachine({
49
52
  refs() {
50
53
  return {
51
54
  indicatorCleanup: null,
52
- focusVisibleValue: null
55
+ focusVisibleValue: null,
56
+ prevValue: null
53
57
  };
54
58
  },
55
59
  computed: {
56
60
  isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled")
57
61
  },
58
- entry: ["syncIndicatorRect", "syncSsr"],
62
+ entry: ["syncPrevValue", "syncIndicatorRect", "syncSsr"],
59
63
  exit: ["cleanupObserver"],
60
64
  effects: ["trackFormControlState", "trackFocusVisible"],
61
65
  watch({ track, action, context }) {
62
66
  track([() => context.get("value")], () => {
63
- action(["syncIndicatorRect", "syncInputElements"]);
67
+ action(["syncIndicatorAnimation", "syncIndicatorRect", "syncInputElements"]);
64
68
  });
65
69
  },
66
70
  on: {
@@ -81,6 +85,9 @@ var machine = createMachine({
81
85
  },
82
86
  SET_FOCUSED: {
83
87
  actions: ["setFocused"]
88
+ },
89
+ INDICATOR_TRANSITION_END: {
90
+ actions: ["clearIndicatorAnimation"]
84
91
  }
85
92
  },
86
93
  states: {
@@ -120,6 +127,19 @@ var machine = createMachine({
120
127
  const focusVisibleValue = event.value != null && event.focusVisible ? event.value : null;
121
128
  context.set("focusVisibleValue", focusVisibleValue);
122
129
  },
130
+ syncPrevValue({ context, refs }) {
131
+ refs.set("prevValue", context.get("value"));
132
+ },
133
+ syncIndicatorAnimation({ context, refs }) {
134
+ const prevValue = refs.get("prevValue");
135
+ const nextValue = context.get("value");
136
+ const animate = prevValue != null && nextValue != null && prevValue !== nextValue;
137
+ context.set("animateIndicator", animate);
138
+ refs.set("prevValue", nextValue);
139
+ },
140
+ clearIndicatorAnimation({ context }) {
141
+ context.set("animateIndicator", false);
142
+ },
123
143
  syncInputElements({ context, scope }) {
124
144
  const inputs = dom.getInputEls(scope);
125
145
  inputs.forEach((input) => {
@@ -86,6 +86,10 @@ interface PrivateContext {
86
86
  * The active tab indicator's dom rect
87
87
  */
88
88
  indicatorRect: Rect | null;
89
+ /**
90
+ * Whether indicator transitions should be animated
91
+ */
92
+ animateIndicator: boolean;
89
93
  /**
90
94
  * Whether the radio group's fieldset is disabled
91
95
  */
@@ -107,6 +111,10 @@ interface Refs {
107
111
  * Function to clean up the observer for the active tab's rect
108
112
  */
109
113
  indicatorCleanup: VoidFunction | null;
114
+ /**
115
+ * Previous selected value, used to detect real value transitions
116
+ */
117
+ prevValue: string | null;
110
118
  }
111
119
  interface RadioGroupSchema {
112
120
  state: "idle";
@@ -86,6 +86,10 @@ interface PrivateContext {
86
86
  * The active tab indicator's dom rect
87
87
  */
88
88
  indicatorRect: Rect | null;
89
+ /**
90
+ * Whether indicator transitions should be animated
91
+ */
92
+ animateIndicator: boolean;
89
93
  /**
90
94
  * Whether the radio group's fieldset is disabled
91
95
  */
@@ -107,6 +111,10 @@ interface Refs {
107
111
  * Function to clean up the observer for the active tab's rect
108
112
  */
109
113
  indicatorCleanup: VoidFunction | null;
114
+ /**
115
+ * Previous selected value, used to detect real value transitions
116
+ */
117
+ prevValue: string | null;
110
118
  }
111
119
  interface RadioGroupSchema {
112
120
  state: "idle";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/radio-group",
3
- "version": "1.35.3",
3
+ "version": "1.36.0",
4
4
  "description": "Core logic for the radio group widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -27,12 +27,12 @@
27
27
  "url": "https://github.com/chakra-ui/zag/issues"
28
28
  },
29
29
  "dependencies": {
30
- "@zag-js/anatomy": "1.35.3",
31
- "@zag-js/dom-query": "1.35.3",
32
- "@zag-js/focus-visible": "1.35.3",
33
- "@zag-js/utils": "1.35.3",
34
- "@zag-js/core": "1.35.3",
35
- "@zag-js/types": "1.35.3"
30
+ "@zag-js/anatomy": "1.36.0",
31
+ "@zag-js/dom-query": "1.36.0",
32
+ "@zag-js/focus-visible": "1.36.0",
33
+ "@zag-js/utils": "1.36.0",
34
+ "@zag-js/core": "1.36.0",
35
+ "@zag-js/types": "1.36.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "clean-package": "2.2.0"