@zag-js/radio-group 0.7.0 → 0.8.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.
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  parts
3
- } from "./chunk-MGLN5L3R.mjs";
3
+ } from "./chunk-R5YJZPCP.mjs";
4
4
  import {
5
5
  dom
6
- } from "./chunk-ITVKBCBK.mjs";
6
+ } from "./chunk-DVMAGSYZ.mjs";
7
7
 
8
8
  // src/radio-group.connect.ts
9
9
  import { ariaAttr, dataAttr } from "@zag-js/dom-query";
@@ -192,7 +192,22 @@ function connect(state, send, normalize) {
192
192
  "aria-checked": ariaAttr(inputState.isChecked),
193
193
  style: visuallyHiddenStyle
194
194
  });
195
- }
195
+ },
196
+ indicatorProps: normalize.element({
197
+ id: dom.getIndicatorId(state.context),
198
+ ...parts.indicator.attrs,
199
+ "data-orientation": state.context.orientation,
200
+ style: {
201
+ "--transition-duration": "150ms",
202
+ "--transition-property": "left, top, width, height",
203
+ position: "absolute",
204
+ willChange: "var(--transition-property)",
205
+ transitionProperty: "var(--transition-property)",
206
+ transitionDuration: state.context.canIndicatorTransition ? "var(--transition-duration)" : "0ms",
207
+ transitionTimingFunction: "var(--transition-timing-function)",
208
+ ...state.context.indicatorRect
209
+ }
210
+ })
196
211
  };
197
212
  }
198
213
 
@@ -7,14 +7,41 @@ var dom = createScope({
7
7
  getRadioInputId: (ctx, value) => ctx.ids?.radioInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
8
8
  getRadioControlId: (ctx, value) => ctx.ids?.radioControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
9
9
  getRadioLabelId: (ctx, value) => ctx.ids?.radioLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
10
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
10
11
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
11
12
  getRadioInputEl: (ctx, value) => dom.getById(ctx, dom.getRadioInputId(ctx, value)),
13
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
12
14
  getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
13
15
  getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
14
16
  getInputEls: (ctx) => {
15
17
  const ownerId = CSS.escape(dom.getRootId(ctx));
16
18
  const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
17
19
  return queryAll(dom.getRootEl(ctx), selector);
20
+ },
21
+ getActiveRadioEl: (ctx) => {
22
+ if (!ctx.value)
23
+ return;
24
+ return dom.getById(ctx, dom.getRadioId(ctx, ctx.value));
25
+ },
26
+ getOffsetRect: (el) => {
27
+ return {
28
+ left: el?.offsetLeft ?? 0,
29
+ top: el?.offsetTop ?? 0,
30
+ width: el?.offsetWidth ?? 0,
31
+ height: el?.offsetHeight ?? 0
32
+ };
33
+ },
34
+ getRectById: (ctx, id) => {
35
+ const tab = dom.queryById(ctx, dom.getRadioId(ctx, id));
36
+ return dom.resolveRect(dom.getOffsetRect(tab));
37
+ },
38
+ resolveRect(rect) {
39
+ return {
40
+ width: `${rect.width}px`,
41
+ height: `${rect.height}px`,
42
+ left: `${rect.left}px`,
43
+ top: `${rect.top}px`
44
+ };
18
45
  }
19
46
  });
20
47
 
@@ -6,7 +6,8 @@ var anatomy = createAnatomy("radio-group").parts(
6
6
  "radio",
7
7
  "radioLabel",
8
8
  "radioControl",
9
- "radioInput"
9
+ "radioInput",
10
+ "indicator"
10
11
  );
11
12
  var parts = anatomy.build();
12
13
 
@@ -0,0 +1,133 @@
1
+ import {
2
+ dom
3
+ } from "./chunk-DVMAGSYZ.mjs";
4
+
5
+ // src/radio-group.machine.ts
6
+ import { createMachine } from "@zag-js/core";
7
+ import { nextTick } from "@zag-js/dom-query";
8
+ import { trackElementRect } from "@zag-js/element-rect";
9
+ import { trackFormControl } from "@zag-js/form-utils";
10
+ import { compact, isString } from "@zag-js/utils";
11
+ function machine(userContext) {
12
+ const ctx = compact(userContext);
13
+ return createMachine(
14
+ {
15
+ id: "radio",
16
+ initial: "idle",
17
+ context: {
18
+ previousValue: null,
19
+ value: null,
20
+ activeId: null,
21
+ focusedId: null,
22
+ hoveredId: null,
23
+ indicatorRect: {},
24
+ canIndicatorTransition: false,
25
+ ...ctx
26
+ },
27
+ entry: ["syncIndicatorRect"],
28
+ exit: ["cleanupObserver"],
29
+ activities: ["trackFormControlState"],
30
+ watch: {
31
+ value: [
32
+ "setIndicatorTransition",
33
+ // important to set this after `setIndicatorTransition`
34
+ "setPreviousValue",
35
+ "invokeOnChange",
36
+ "syncIndicatorRect",
37
+ "syncInputElements"
38
+ ]
39
+ },
40
+ on: {
41
+ SET_VALUE: {
42
+ actions: ["setValue"]
43
+ },
44
+ SET_HOVERED: {
45
+ actions: "setHovered"
46
+ },
47
+ SET_ACTIVE: {
48
+ actions: "setActive"
49
+ },
50
+ SET_FOCUSED: {
51
+ actions: "setFocused"
52
+ }
53
+ },
54
+ states: {
55
+ idle: {}
56
+ }
57
+ },
58
+ {
59
+ activities: {
60
+ trackFormControlState(ctx2, _evt, { send, initialContext }) {
61
+ return trackFormControl(dom.getRootEl(ctx2), {
62
+ onFieldsetDisabled() {
63
+ ctx2.disabled = true;
64
+ },
65
+ onFormReset() {
66
+ send({ type: "SET_VALUE", value: initialContext.value });
67
+ }
68
+ });
69
+ }
70
+ },
71
+ actions: {
72
+ setValue(ctx2, evt) {
73
+ ctx2.value = evt.value;
74
+ },
75
+ setHovered(ctx2, evt) {
76
+ ctx2.hoveredId = evt.value;
77
+ },
78
+ setActive(ctx2, evt) {
79
+ ctx2.activeId = evt.value;
80
+ },
81
+ setFocused(ctx2, evt) {
82
+ ctx2.focusedId = evt.value;
83
+ },
84
+ invokeOnChange(ctx2, evt) {
85
+ ctx2.onChange?.({ value: evt.value });
86
+ },
87
+ syncInputElements(ctx2) {
88
+ const inputs = dom.getInputEls(ctx2);
89
+ inputs.forEach((input) => {
90
+ input.checked = input.value === ctx2.value;
91
+ });
92
+ },
93
+ setPreviousValue(ctx2) {
94
+ ctx2.previousValue = ctx2.value;
95
+ },
96
+ setIndicatorTransition(ctx2) {
97
+ ctx2.canIndicatorTransition = isString(ctx2.previousValue) && isString(ctx2.value);
98
+ },
99
+ cleanupObserver(ctx2) {
100
+ ctx2.indicatorCleanup?.();
101
+ },
102
+ syncIndicatorRect(ctx2) {
103
+ ctx2.indicatorCleanup?.();
104
+ if (!dom.getIndicatorEl(ctx2))
105
+ return;
106
+ const value = ctx2.value;
107
+ if (value == null) {
108
+ ctx2.indicatorRect = {};
109
+ return;
110
+ }
111
+ const radioEl = dom.getActiveRadioEl(ctx2);
112
+ if (!radioEl)
113
+ return;
114
+ ctx2.indicatorCleanup = trackElementRect(radioEl, {
115
+ getRect(el) {
116
+ return dom.getOffsetRect(el);
117
+ },
118
+ onChange(rect) {
119
+ ctx2.indicatorRect = dom.resolveRect(rect);
120
+ nextTick(() => {
121
+ ctx2.canIndicatorTransition = false;
122
+ });
123
+ }
124
+ });
125
+ }
126
+ }
127
+ }
128
+ );
129
+ }
130
+
131
+ export {
132
+ machine
133
+ };
package/dist/index.js CHANGED
@@ -34,7 +34,8 @@ var anatomy = (0, import_anatomy.createAnatomy)("radio-group").parts(
34
34
  "radio",
35
35
  "radioLabel",
36
36
  "radioControl",
37
- "radioInput"
37
+ "radioInput",
38
+ "indicator"
38
39
  );
39
40
  var parts = anatomy.build();
40
41
 
@@ -51,14 +52,41 @@ var dom = (0, import_dom_query.createScope)({
51
52
  getRadioInputId: (ctx, value) => ctx.ids?.radioInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
52
53
  getRadioControlId: (ctx, value) => ctx.ids?.radioControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
53
54
  getRadioLabelId: (ctx, value) => ctx.ids?.radioLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
55
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
54
56
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
55
57
  getRadioInputEl: (ctx, value) => dom.getById(ctx, dom.getRadioInputId(ctx, value)),
58
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
56
59
  getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
57
60
  getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
58
61
  getInputEls: (ctx) => {
59
62
  const ownerId = CSS.escape(dom.getRootId(ctx));
60
63
  const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
61
64
  return (0, import_dom_query.queryAll)(dom.getRootEl(ctx), selector);
65
+ },
66
+ getActiveRadioEl: (ctx) => {
67
+ if (!ctx.value)
68
+ return;
69
+ return dom.getById(ctx, dom.getRadioId(ctx, ctx.value));
70
+ },
71
+ getOffsetRect: (el) => {
72
+ return {
73
+ left: el?.offsetLeft ?? 0,
74
+ top: el?.offsetTop ?? 0,
75
+ width: el?.offsetWidth ?? 0,
76
+ height: el?.offsetHeight ?? 0
77
+ };
78
+ },
79
+ getRectById: (ctx, id) => {
80
+ const tab = dom.queryById(ctx, dom.getRadioId(ctx, id));
81
+ return dom.resolveRect(dom.getOffsetRect(tab));
82
+ },
83
+ resolveRect(rect) {
84
+ return {
85
+ width: `${rect.width}px`,
86
+ height: `${rect.height}px`,
87
+ left: `${rect.left}px`,
88
+ top: `${rect.top}px`
89
+ };
62
90
  }
63
91
  });
64
92
 
@@ -247,12 +275,29 @@ function connect(state, send, normalize) {
247
275
  "aria-checked": (0, import_dom_query2.ariaAttr)(inputState.isChecked),
248
276
  style: import_visually_hidden.visuallyHiddenStyle
249
277
  });
250
- }
278
+ },
279
+ indicatorProps: normalize.element({
280
+ id: dom.getIndicatorId(state.context),
281
+ ...parts.indicator.attrs,
282
+ "data-orientation": state.context.orientation,
283
+ style: {
284
+ "--transition-duration": "150ms",
285
+ "--transition-property": "left, top, width, height",
286
+ position: "absolute",
287
+ willChange: "var(--transition-property)",
288
+ transitionProperty: "var(--transition-property)",
289
+ transitionDuration: state.context.canIndicatorTransition ? "var(--transition-duration)" : "0ms",
290
+ transitionTimingFunction: "var(--transition-timing-function)",
291
+ ...state.context.indicatorRect
292
+ }
293
+ })
251
294
  };
252
295
  }
253
296
 
254
297
  // src/radio-group.machine.ts
255
298
  var import_core = require("@zag-js/core");
299
+ var import_dom_query3 = require("@zag-js/dom-query");
300
+ var import_element_rect = require("@zag-js/element-rect");
256
301
  var import_form_utils = require("@zag-js/form-utils");
257
302
  var import_utils = require("@zag-js/utils");
258
303
  function machine(userContext) {
@@ -262,14 +307,28 @@ function machine(userContext) {
262
307
  id: "radio",
263
308
  initial: "idle",
264
309
  context: {
310
+ previousValue: null,
265
311
  value: null,
266
- initialValue: null,
267
312
  activeId: null,
268
313
  focusedId: null,
269
314
  hoveredId: null,
315
+ indicatorRect: {},
316
+ canIndicatorTransition: false,
270
317
  ...ctx
271
318
  },
319
+ entry: ["syncIndicatorRect"],
320
+ exit: ["cleanupObserver"],
272
321
  activities: ["trackFormControlState"],
322
+ watch: {
323
+ value: [
324
+ "setIndicatorTransition",
325
+ // important to set this after `setIndicatorTransition`
326
+ "setPreviousValue",
327
+ "invokeOnChange",
328
+ "syncIndicatorRect",
329
+ "syncInputElements"
330
+ ]
331
+ },
273
332
  on: {
274
333
  SET_VALUE: {
275
334
  actions: ["setValue"]
@@ -284,31 +343,24 @@ function machine(userContext) {
284
343
  actions: "setFocused"
285
344
  }
286
345
  },
287
- watch: {
288
- value: ["dispatchChangeEvent", "invokeOnChange", "syncInputElements"]
289
- },
290
- entry: ["checkValue"],
291
346
  states: {
292
347
  idle: {}
293
348
  }
294
349
  },
295
350
  {
296
351
  activities: {
297
- trackFormControlState(ctx2, _evt, { send }) {
352
+ trackFormControlState(ctx2, _evt, { send, initialContext }) {
298
353
  return (0, import_form_utils.trackFormControl)(dom.getRootEl(ctx2), {
299
354
  onFieldsetDisabled() {
300
355
  ctx2.disabled = true;
301
356
  },
302
357
  onFormReset() {
303
- send({ type: "SET_VALUE", value: ctx2.initialValue });
358
+ send({ type: "SET_VALUE", value: initialContext.value });
304
359
  }
305
360
  });
306
361
  }
307
362
  },
308
363
  actions: {
309
- checkValue(ctx2) {
310
- ctx2.initialValue = ctx2.value;
311
- },
312
364
  setValue(ctx2, evt) {
313
365
  ctx2.value = evt.value;
314
366
  },
@@ -324,17 +376,44 @@ function machine(userContext) {
324
376
  invokeOnChange(ctx2, evt) {
325
377
  ctx2.onChange?.({ value: evt.value });
326
378
  },
327
- dispatchChangeEvent(ctx2, evt) {
328
- if (!evt.manual)
329
- return;
330
- const el = dom.getRadioInputEl(ctx2, evt.value);
331
- (0, import_form_utils.dispatchInputCheckedEvent)(el, !!evt.value);
332
- },
333
379
  syncInputElements(ctx2) {
334
380
  const inputs = dom.getInputEls(ctx2);
335
381
  inputs.forEach((input) => {
336
382
  input.checked = input.value === ctx2.value;
337
383
  });
384
+ },
385
+ setPreviousValue(ctx2) {
386
+ ctx2.previousValue = ctx2.value;
387
+ },
388
+ setIndicatorTransition(ctx2) {
389
+ ctx2.canIndicatorTransition = (0, import_utils.isString)(ctx2.previousValue) && (0, import_utils.isString)(ctx2.value);
390
+ },
391
+ cleanupObserver(ctx2) {
392
+ ctx2.indicatorCleanup?.();
393
+ },
394
+ syncIndicatorRect(ctx2) {
395
+ ctx2.indicatorCleanup?.();
396
+ if (!dom.getIndicatorEl(ctx2))
397
+ return;
398
+ const value = ctx2.value;
399
+ if (value == null) {
400
+ ctx2.indicatorRect = {};
401
+ return;
402
+ }
403
+ const radioEl = dom.getActiveRadioEl(ctx2);
404
+ if (!radioEl)
405
+ return;
406
+ ctx2.indicatorCleanup = (0, import_element_rect.trackElementRect)(radioEl, {
407
+ getRect(el) {
408
+ return dom.getOffsetRect(el);
409
+ },
410
+ onChange(rect) {
411
+ ctx2.indicatorRect = dom.resolveRect(rect);
412
+ (0, import_dom_query3.nextTick)(() => {
413
+ ctx2.canIndicatorTransition = false;
414
+ });
415
+ }
416
+ });
338
417
  }
339
418
  }
340
419
  }
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-BVCYFZ6N.mjs";
3
+ } from "./chunk-DM5GVJFW.mjs";
4
4
  import {
5
5
  anatomy
6
- } from "./chunk-MGLN5L3R.mjs";
6
+ } from "./chunk-R5YJZPCP.mjs";
7
7
  import {
8
8
  machine
9
- } from "./chunk-DYVEYVKH.mjs";
10
- import "./chunk-ITVKBCBK.mjs";
9
+ } from "./chunk-UMCKI3BN.mjs";
10
+ import "./chunk-DVMAGSYZ.mjs";
11
11
  export {
12
12
  anatomy,
13
13
  connect,
@@ -1,6 +1,6 @@
1
1
  import * as _zag_js_anatomy from '@zag-js/anatomy';
2
2
 
3
- declare const anatomy: _zag_js_anatomy.AnatomyInstance<"root" | "label" | "radio" | "radioLabel" | "radioControl" | "radioInput">;
4
- declare const parts: Record<"root" | "label" | "radio" | "radioLabel" | "radioControl" | "radioInput", _zag_js_anatomy.AnatomyPart>;
3
+ declare const anatomy: _zag_js_anatomy.AnatomyInstance<"root" | "label" | "radio" | "radioLabel" | "radioControl" | "radioInput" | "indicator">;
4
+ declare const parts: Record<"root" | "label" | "radio" | "radioLabel" | "radioControl" | "radioInput" | "indicator", _zag_js_anatomy.AnatomyPart>;
5
5
 
6
6
  export { anatomy, parts };
@@ -31,7 +31,8 @@ var anatomy = (0, import_anatomy.createAnatomy)("radio-group").parts(
31
31
  "radio",
32
32
  "radioLabel",
33
33
  "radioControl",
34
- "radioInput"
34
+ "radioInput",
35
+ "indicator"
35
36
  );
36
37
  var parts = anatomy.build();
37
38
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  anatomy,
3
3
  parts
4
- } from "./chunk-MGLN5L3R.mjs";
4
+ } from "./chunk-R5YJZPCP.mjs";
5
5
  export {
6
6
  anatomy,
7
7
  parts
@@ -42,6 +42,7 @@ declare function connect<T extends PropTypes>(state: State, send: Send, normaliz
42
42
  getRadioLabelProps(props: RadioProps): T["element"];
43
43
  getRadioControlProps(props: RadioProps): T["element"];
44
44
  getRadioInputProps(props: InputProps): T["input"];
45
+ indicatorProps: T["element"];
45
46
  };
46
47
 
47
48
  export { connect };
@@ -34,7 +34,8 @@ var anatomy = (0, import_anatomy.createAnatomy)("radio-group").parts(
34
34
  "radio",
35
35
  "radioLabel",
36
36
  "radioControl",
37
- "radioInput"
37
+ "radioInput",
38
+ "indicator"
38
39
  );
39
40
  var parts = anatomy.build();
40
41
 
@@ -47,14 +48,41 @@ var dom = (0, import_dom_query.createScope)({
47
48
  getRadioInputId: (ctx, value) => ctx.ids?.radioInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
48
49
  getRadioControlId: (ctx, value) => ctx.ids?.radioControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
49
50
  getRadioLabelId: (ctx, value) => ctx.ids?.radioLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
51
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
50
52
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
51
53
  getRadioInputEl: (ctx, value) => dom.getById(ctx, dom.getRadioInputId(ctx, value)),
54
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
52
55
  getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
53
56
  getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
54
57
  getInputEls: (ctx) => {
55
58
  const ownerId = CSS.escape(dom.getRootId(ctx));
56
59
  const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
57
60
  return (0, import_dom_query.queryAll)(dom.getRootEl(ctx), selector);
61
+ },
62
+ getActiveRadioEl: (ctx) => {
63
+ if (!ctx.value)
64
+ return;
65
+ return dom.getById(ctx, dom.getRadioId(ctx, ctx.value));
66
+ },
67
+ getOffsetRect: (el) => {
68
+ return {
69
+ left: el?.offsetLeft ?? 0,
70
+ top: el?.offsetTop ?? 0,
71
+ width: el?.offsetWidth ?? 0,
72
+ height: el?.offsetHeight ?? 0
73
+ };
74
+ },
75
+ getRectById: (ctx, id) => {
76
+ const tab = dom.queryById(ctx, dom.getRadioId(ctx, id));
77
+ return dom.resolveRect(dom.getOffsetRect(tab));
78
+ },
79
+ resolveRect(rect) {
80
+ return {
81
+ width: `${rect.width}px`,
82
+ height: `${rect.height}px`,
83
+ left: `${rect.left}px`,
84
+ top: `${rect.top}px`
85
+ };
58
86
  }
59
87
  });
60
88
 
@@ -243,7 +271,22 @@ function connect(state, send, normalize) {
243
271
  "aria-checked": (0, import_dom_query2.ariaAttr)(inputState.isChecked),
244
272
  style: import_visually_hidden.visuallyHiddenStyle
245
273
  });
246
- }
274
+ },
275
+ indicatorProps: normalize.element({
276
+ id: dom.getIndicatorId(state.context),
277
+ ...parts.indicator.attrs,
278
+ "data-orientation": state.context.orientation,
279
+ style: {
280
+ "--transition-duration": "150ms",
281
+ "--transition-property": "left, top, width, height",
282
+ position: "absolute",
283
+ willChange: "var(--transition-property)",
284
+ transitionProperty: "var(--transition-property)",
285
+ transitionDuration: state.context.canIndicatorTransition ? "var(--transition-duration)" : "0ms",
286
+ transitionTimingFunction: "var(--transition-timing-function)",
287
+ ...state.context.indicatorRect
288
+ }
289
+ })
247
290
  };
248
291
  }
249
292
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-BVCYFZ6N.mjs";
4
- import "./chunk-MGLN5L3R.mjs";
5
- import "./chunk-ITVKBCBK.mjs";
3
+ } from "./chunk-DM5GVJFW.mjs";
4
+ import "./chunk-R5YJZPCP.mjs";
5
+ import "./chunk-DVMAGSYZ.mjs";
6
6
  export {
7
7
  connect
8
8
  };
@@ -28,11 +28,32 @@ declare const dom: {
28
28
  getRadioInputId: (ctx: MachineContext, value: string) => string;
29
29
  getRadioControlId: (ctx: MachineContext, value: string) => string;
30
30
  getRadioLabelId: (ctx: MachineContext, value: string) => string;
31
+ getIndicatorId: (ctx: MachineContext) => string;
31
32
  getRootEl: (ctx: MachineContext) => HTMLElement | null;
32
33
  getRadioInputEl: (ctx: MachineContext, value: string) => HTMLInputElement | null;
34
+ getIndicatorEl: (ctx: MachineContext) => HTMLElement | null;
33
35
  getFirstEnabledInputEl: (ctx: MachineContext) => HTMLInputElement | null | undefined;
34
36
  getFirstEnabledAndCheckedInputEl: (ctx: MachineContext) => HTMLInputElement | null | undefined;
35
37
  getInputEls: (ctx: MachineContext) => HTMLInputElement[];
38
+ getActiveRadioEl: (ctx: MachineContext) => HTMLElement | null | undefined;
39
+ getOffsetRect: (el: HTMLElement | undefined) => {
40
+ left: number;
41
+ top: number;
42
+ width: number;
43
+ height: number;
44
+ };
45
+ getRectById: (ctx: MachineContext, id: string) => {
46
+ width: string;
47
+ height: string;
48
+ left: string;
49
+ top: string;
50
+ };
51
+ resolveRect(rect: Record<"width" | "height" | "left" | "top", number>): {
52
+ width: string;
53
+ height: string;
54
+ left: string;
55
+ top: string;
56
+ };
36
57
  };
37
58
 
38
59
  export { dom };
@@ -31,14 +31,41 @@ var dom = (0, import_dom_query.createScope)({
31
31
  getRadioInputId: (ctx, value) => ctx.ids?.radioInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
32
32
  getRadioControlId: (ctx, value) => ctx.ids?.radioControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
33
33
  getRadioLabelId: (ctx, value) => ctx.ids?.radioLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
34
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
34
35
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
35
36
  getRadioInputEl: (ctx, value) => dom.getById(ctx, dom.getRadioInputId(ctx, value)),
37
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
36
38
  getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
37
39
  getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
38
40
  getInputEls: (ctx) => {
39
41
  const ownerId = CSS.escape(dom.getRootId(ctx));
40
42
  const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
41
43
  return (0, import_dom_query.queryAll)(dom.getRootEl(ctx), selector);
44
+ },
45
+ getActiveRadioEl: (ctx) => {
46
+ if (!ctx.value)
47
+ return;
48
+ return dom.getById(ctx, dom.getRadioId(ctx, ctx.value));
49
+ },
50
+ getOffsetRect: (el) => {
51
+ return {
52
+ left: el?.offsetLeft ?? 0,
53
+ top: el?.offsetTop ?? 0,
54
+ width: el?.offsetWidth ?? 0,
55
+ height: el?.offsetHeight ?? 0
56
+ };
57
+ },
58
+ getRectById: (ctx, id) => {
59
+ const tab = dom.queryById(ctx, dom.getRadioId(ctx, id));
60
+ return dom.resolveRect(dom.getOffsetRect(tab));
61
+ },
62
+ resolveRect(rect) {
63
+ return {
64
+ width: `${rect.width}px`,
65
+ height: `${rect.height}px`,
66
+ left: `${rect.left}px`,
67
+ top: `${rect.top}px`
68
+ };
42
69
  }
43
70
  });
44
71
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dom
3
- } from "./chunk-ITVKBCBK.mjs";
3
+ } from "./chunk-DVMAGSYZ.mjs";
4
4
  export {
5
5
  dom
6
6
  };
@@ -24,6 +24,8 @@ __export(radio_group_machine_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(radio_group_machine_exports);
26
26
  var import_core = require("@zag-js/core");
27
+ var import_dom_query2 = require("@zag-js/dom-query");
28
+ var import_element_rect = require("@zag-js/element-rect");
27
29
  var import_form_utils = require("@zag-js/form-utils");
28
30
  var import_utils = require("@zag-js/utils");
29
31
 
@@ -36,14 +38,41 @@ var dom = (0, import_dom_query.createScope)({
36
38
  getRadioInputId: (ctx, value) => ctx.ids?.radioInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
37
39
  getRadioControlId: (ctx, value) => ctx.ids?.radioControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
38
40
  getRadioLabelId: (ctx, value) => ctx.ids?.radioLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
41
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
39
42
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
40
43
  getRadioInputEl: (ctx, value) => dom.getById(ctx, dom.getRadioInputId(ctx, value)),
44
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
41
45
  getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
42
46
  getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
43
47
  getInputEls: (ctx) => {
44
48
  const ownerId = CSS.escape(dom.getRootId(ctx));
45
49
  const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
46
50
  return (0, import_dom_query.queryAll)(dom.getRootEl(ctx), selector);
51
+ },
52
+ getActiveRadioEl: (ctx) => {
53
+ if (!ctx.value)
54
+ return;
55
+ return dom.getById(ctx, dom.getRadioId(ctx, ctx.value));
56
+ },
57
+ getOffsetRect: (el) => {
58
+ return {
59
+ left: el?.offsetLeft ?? 0,
60
+ top: el?.offsetTop ?? 0,
61
+ width: el?.offsetWidth ?? 0,
62
+ height: el?.offsetHeight ?? 0
63
+ };
64
+ },
65
+ getRectById: (ctx, id) => {
66
+ const tab = dom.queryById(ctx, dom.getRadioId(ctx, id));
67
+ return dom.resolveRect(dom.getOffsetRect(tab));
68
+ },
69
+ resolveRect(rect) {
70
+ return {
71
+ width: `${rect.width}px`,
72
+ height: `${rect.height}px`,
73
+ left: `${rect.left}px`,
74
+ top: `${rect.top}px`
75
+ };
47
76
  }
48
77
  });
49
78
 
@@ -55,14 +84,28 @@ function machine(userContext) {
55
84
  id: "radio",
56
85
  initial: "idle",
57
86
  context: {
87
+ previousValue: null,
58
88
  value: null,
59
- initialValue: null,
60
89
  activeId: null,
61
90
  focusedId: null,
62
91
  hoveredId: null,
92
+ indicatorRect: {},
93
+ canIndicatorTransition: false,
63
94
  ...ctx
64
95
  },
96
+ entry: ["syncIndicatorRect"],
97
+ exit: ["cleanupObserver"],
65
98
  activities: ["trackFormControlState"],
99
+ watch: {
100
+ value: [
101
+ "setIndicatorTransition",
102
+ // important to set this after `setIndicatorTransition`
103
+ "setPreviousValue",
104
+ "invokeOnChange",
105
+ "syncIndicatorRect",
106
+ "syncInputElements"
107
+ ]
108
+ },
66
109
  on: {
67
110
  SET_VALUE: {
68
111
  actions: ["setValue"]
@@ -77,31 +120,24 @@ function machine(userContext) {
77
120
  actions: "setFocused"
78
121
  }
79
122
  },
80
- watch: {
81
- value: ["dispatchChangeEvent", "invokeOnChange", "syncInputElements"]
82
- },
83
- entry: ["checkValue"],
84
123
  states: {
85
124
  idle: {}
86
125
  }
87
126
  },
88
127
  {
89
128
  activities: {
90
- trackFormControlState(ctx2, _evt, { send }) {
129
+ trackFormControlState(ctx2, _evt, { send, initialContext }) {
91
130
  return (0, import_form_utils.trackFormControl)(dom.getRootEl(ctx2), {
92
131
  onFieldsetDisabled() {
93
132
  ctx2.disabled = true;
94
133
  },
95
134
  onFormReset() {
96
- send({ type: "SET_VALUE", value: ctx2.initialValue });
135
+ send({ type: "SET_VALUE", value: initialContext.value });
97
136
  }
98
137
  });
99
138
  }
100
139
  },
101
140
  actions: {
102
- checkValue(ctx2) {
103
- ctx2.initialValue = ctx2.value;
104
- },
105
141
  setValue(ctx2, evt) {
106
142
  ctx2.value = evt.value;
107
143
  },
@@ -117,17 +153,44 @@ function machine(userContext) {
117
153
  invokeOnChange(ctx2, evt) {
118
154
  ctx2.onChange?.({ value: evt.value });
119
155
  },
120
- dispatchChangeEvent(ctx2, evt) {
121
- if (!evt.manual)
122
- return;
123
- const el = dom.getRadioInputEl(ctx2, evt.value);
124
- (0, import_form_utils.dispatchInputCheckedEvent)(el, !!evt.value);
125
- },
126
156
  syncInputElements(ctx2) {
127
157
  const inputs = dom.getInputEls(ctx2);
128
158
  inputs.forEach((input) => {
129
159
  input.checked = input.value === ctx2.value;
130
160
  });
161
+ },
162
+ setPreviousValue(ctx2) {
163
+ ctx2.previousValue = ctx2.value;
164
+ },
165
+ setIndicatorTransition(ctx2) {
166
+ ctx2.canIndicatorTransition = (0, import_utils.isString)(ctx2.previousValue) && (0, import_utils.isString)(ctx2.value);
167
+ },
168
+ cleanupObserver(ctx2) {
169
+ ctx2.indicatorCleanup?.();
170
+ },
171
+ syncIndicatorRect(ctx2) {
172
+ ctx2.indicatorCleanup?.();
173
+ if (!dom.getIndicatorEl(ctx2))
174
+ return;
175
+ const value = ctx2.value;
176
+ if (value == null) {
177
+ ctx2.indicatorRect = {};
178
+ return;
179
+ }
180
+ const radioEl = dom.getActiveRadioEl(ctx2);
181
+ if (!radioEl)
182
+ return;
183
+ ctx2.indicatorCleanup = (0, import_element_rect.trackElementRect)(radioEl, {
184
+ getRect(el) {
185
+ return dom.getOffsetRect(el);
186
+ },
187
+ onChange(rect) {
188
+ ctx2.indicatorRect = dom.resolveRect(rect);
189
+ (0, import_dom_query2.nextTick)(() => {
190
+ ctx2.canIndicatorTransition = false;
191
+ });
192
+ }
193
+ });
131
194
  }
132
195
  }
133
196
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  machine
3
- } from "./chunk-DYVEYVKH.mjs";
4
- import "./chunk-ITVKBCBK.mjs";
3
+ } from "./chunk-UMCKI3BN.mjs";
4
+ import "./chunk-DVMAGSYZ.mjs";
5
5
  export {
6
6
  machine
7
7
  };
@@ -4,6 +4,7 @@ import { RequiredBy, DirectionProperty, CommonProperties, Context } from '@zag-j
4
4
  type ElementIds = Partial<{
5
5
  root: string;
6
6
  label: string;
7
+ indicator: string;
7
8
  radio(value: string): string;
8
9
  radioLabel(value: string): string;
9
10
  radioControl(value: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/radio-group",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core logic for the radio group widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -29,6 +29,7 @@
29
29
  "dependencies": {
30
30
  "@zag-js/anatomy": "0.1.4",
31
31
  "@zag-js/dom-query": "0.1.4",
32
+ "@zag-js/element-rect": "0.8.0",
32
33
  "@zag-js/form-utils": "0.2.5",
33
34
  "@zag-js/visually-hidden": "0.0.1",
34
35
  "@zag-js/utils": "0.3.4",
@@ -1,97 +0,0 @@
1
- import {
2
- dom
3
- } from "./chunk-ITVKBCBK.mjs";
4
-
5
- // src/radio-group.machine.ts
6
- import { createMachine } from "@zag-js/core";
7
- import { dispatchInputCheckedEvent, trackFormControl } from "@zag-js/form-utils";
8
- import { compact } from "@zag-js/utils";
9
- function machine(userContext) {
10
- const ctx = compact(userContext);
11
- return createMachine(
12
- {
13
- id: "radio",
14
- initial: "idle",
15
- context: {
16
- value: null,
17
- initialValue: null,
18
- activeId: null,
19
- focusedId: null,
20
- hoveredId: null,
21
- ...ctx
22
- },
23
- activities: ["trackFormControlState"],
24
- on: {
25
- SET_VALUE: {
26
- actions: ["setValue"]
27
- },
28
- SET_HOVERED: {
29
- actions: "setHovered"
30
- },
31
- SET_ACTIVE: {
32
- actions: "setActive"
33
- },
34
- SET_FOCUSED: {
35
- actions: "setFocused"
36
- }
37
- },
38
- watch: {
39
- value: ["dispatchChangeEvent", "invokeOnChange", "syncInputElements"]
40
- },
41
- entry: ["checkValue"],
42
- states: {
43
- idle: {}
44
- }
45
- },
46
- {
47
- activities: {
48
- trackFormControlState(ctx2, _evt, { send }) {
49
- return trackFormControl(dom.getRootEl(ctx2), {
50
- onFieldsetDisabled() {
51
- ctx2.disabled = true;
52
- },
53
- onFormReset() {
54
- send({ type: "SET_VALUE", value: ctx2.initialValue });
55
- }
56
- });
57
- }
58
- },
59
- actions: {
60
- checkValue(ctx2) {
61
- ctx2.initialValue = ctx2.value;
62
- },
63
- setValue(ctx2, evt) {
64
- ctx2.value = evt.value;
65
- },
66
- setHovered(ctx2, evt) {
67
- ctx2.hoveredId = evt.value;
68
- },
69
- setActive(ctx2, evt) {
70
- ctx2.activeId = evt.value;
71
- },
72
- setFocused(ctx2, evt) {
73
- ctx2.focusedId = evt.value;
74
- },
75
- invokeOnChange(ctx2, evt) {
76
- ctx2.onChange?.({ value: evt.value });
77
- },
78
- dispatchChangeEvent(ctx2, evt) {
79
- if (!evt.manual)
80
- return;
81
- const el = dom.getRadioInputEl(ctx2, evt.value);
82
- dispatchInputCheckedEvent(el, !!evt.value);
83
- },
84
- syncInputElements(ctx2) {
85
- const inputs = dom.getInputEls(ctx2);
86
- inputs.forEach((input) => {
87
- input.checked = input.value === ctx2.value;
88
- });
89
- }
90
- }
91
- }
92
- );
93
- }
94
-
95
- export {
96
- machine
97
- };