@destyler/radio 0.0.5 → 0.0.7-beta.1

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/dist/index.mjs CHANGED
@@ -1,8 +1,428 @@
1
- export { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot, radioGroupIndicatorProps, radioGroupItemProps, radioGroupRootProps } from './component.mjs';
2
- import 'vue';
3
- import '@destyler/primitive';
4
- import '@destyler/shared';
5
- import '@destyler/composition';
6
- import '@destyler/roving-focus';
7
- import '@vueuse/core';
8
- import '@destyler/presence';
1
+ import { createAnatomy } from '@zag-js/anatomy';
2
+ import { createScope, queryAll, dataAttr, visuallyHiddenStyle, nextTick } from '@zag-js/dom-query';
3
+ import { isFocusVisible, trackFocusVisible } from '@zag-js/focus-visible';
4
+ import { createMachine, guards } from '@zag-js/core';
5
+ import { trackElementRect } from '@zag-js/element-rect';
6
+ import { trackFormControl, dispatchInputCheckedEvent } from '@zag-js/form-utils';
7
+ import { compact, isString, isEqual, createSplitProps } from '@zag-js/utils';
8
+ import { createProps } from '@zag-js/types';
9
+
10
+ const anatomy = createAnatomy("radio-group").parts(
11
+ "root",
12
+ "label",
13
+ "item",
14
+ "itemText",
15
+ "itemControl",
16
+ "indicator"
17
+ );
18
+ const parts = anatomy.build();
19
+
20
+ const dom = createScope({
21
+ getRootId: (ctx) => ctx.ids?.root ?? `radio-group:${ctx.id}`,
22
+ getLabelId: (ctx) => ctx.ids?.label ?? `radio-group:${ctx.id}:label`,
23
+ getItemId: (ctx, value) => ctx.ids?.item?.(value) ?? `radio-group:${ctx.id}:radio:${value}`,
24
+ getItemHiddenInputId: (ctx, value) => ctx.ids?.itemHiddenInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`,
25
+ getItemControlId: (ctx, value) => ctx.ids?.itemControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`,
26
+ getItemLabelId: (ctx, value) => ctx.ids?.itemLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`,
27
+ getIndicatorId: (ctx) => ctx.ids?.indicator ?? `radio-group:${ctx.id}:indicator`,
28
+ getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
29
+ getItemHiddenInputEl: (ctx, value) => dom.getById(ctx, dom.getItemHiddenInputId(ctx, value)),
30
+ getIndicatorEl: (ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
31
+ getFirstEnabledInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled)"),
32
+ getFirstEnabledAndCheckedInputEl: (ctx) => dom.getRootEl(ctx)?.querySelector("input:not(:disabled):checked"),
33
+ getInputEls: (ctx) => {
34
+ const ownerId = CSS.escape(dom.getRootId(ctx));
35
+ const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
36
+ return queryAll(dom.getRootEl(ctx), selector);
37
+ },
38
+ getActiveRadioEl: (ctx) => {
39
+ if (!ctx.value)
40
+ return;
41
+ return dom.getById(ctx, dom.getItemId(ctx, ctx.value));
42
+ },
43
+ getOffsetRect: (el) => ({
44
+ left: el?.offsetLeft ?? 0,
45
+ top: el?.offsetTop ?? 0,
46
+ width: el?.offsetWidth ?? 0,
47
+ height: el?.offsetHeight ?? 0
48
+ }),
49
+ getRectById: (ctx, id) => {
50
+ const radioEl = dom.getById(ctx, dom.getItemId(ctx, id));
51
+ if (!radioEl)
52
+ return;
53
+ return dom.resolveRect(dom.getOffsetRect(radioEl));
54
+ },
55
+ resolveRect: (rect) => ({
56
+ width: `${rect.width}px`,
57
+ height: `${rect.height}px`,
58
+ left: `${rect.left}px`,
59
+ top: `${rect.top}px`
60
+ })
61
+ });
62
+
63
+ function connect(state, send, normalize) {
64
+ const groupDisabled = state.context.isDisabled;
65
+ const readOnly = state.context.readOnly;
66
+ function getItemState(props) {
67
+ return {
68
+ invalid: !!props.invalid,
69
+ disabled: !!props.disabled || groupDisabled,
70
+ checked: state.context.value === props.value,
71
+ focused: state.context.focusedValue === props.value,
72
+ hovered: state.context.hoveredValue === props.value,
73
+ active: state.context.activeValue === props.value
74
+ };
75
+ }
76
+ function getItemDataAttrs(props) {
77
+ const radioState = getItemState(props);
78
+ return {
79
+ "data-focus": dataAttr(radioState.focused),
80
+ "data-focus-visible": dataAttr(radioState.focused && state.context.focusVisible),
81
+ "data-disabled": dataAttr(radioState.disabled),
82
+ "data-readonly": dataAttr(readOnly),
83
+ "data-state": radioState.checked ? "checked" : "unchecked",
84
+ "data-hover": dataAttr(radioState.hovered),
85
+ "data-invalid": dataAttr(radioState.invalid),
86
+ "data-orientation": state.context.orientation,
87
+ "data-ssr": dataAttr(state.context.ssr)
88
+ };
89
+ }
90
+ const focus = () => {
91
+ const firstEnabledAndCheckedInput = dom.getFirstEnabledAndCheckedInputEl(state.context);
92
+ if (firstEnabledAndCheckedInput) {
93
+ firstEnabledAndCheckedInput.focus();
94
+ return;
95
+ }
96
+ const firstEnabledInput = dom.getFirstEnabledInputEl(state.context);
97
+ firstEnabledInput?.focus();
98
+ };
99
+ return {
100
+ focus,
101
+ value: state.context.value,
102
+ setValue(value) {
103
+ send({ type: "SET_VALUE", value, isTrusted: false });
104
+ },
105
+ clearValue() {
106
+ send({ type: "SET_VALUE", value: null, isTrusted: false });
107
+ },
108
+ getRootProps() {
109
+ return normalize.element({
110
+ ...parts.root.attrs,
111
+ "role": "radiogroup",
112
+ "id": dom.getRootId(state.context),
113
+ "aria-labelledby": dom.getLabelId(state.context),
114
+ "data-orientation": state.context.orientation,
115
+ "data-disabled": dataAttr(groupDisabled),
116
+ "aria-orientation": state.context.orientation,
117
+ "dir": state.context.dir,
118
+ "style": {
119
+ position: "relative"
120
+ }
121
+ });
122
+ },
123
+ getLabelProps() {
124
+ return normalize.element({
125
+ ...parts.label.attrs,
126
+ "dir": state.context.dir,
127
+ "data-orientation": state.context.orientation,
128
+ "data-disabled": dataAttr(groupDisabled),
129
+ "id": dom.getLabelId(state.context),
130
+ "onClick": focus
131
+ });
132
+ },
133
+ getItemState,
134
+ getItemProps(props) {
135
+ const itemState = getItemState(props);
136
+ return normalize.label({
137
+ ...parts.item.attrs,
138
+ dir: state.context.dir,
139
+ id: dom.getItemId(state.context, props.value),
140
+ htmlFor: dom.getItemHiddenInputId(state.context, props.value),
141
+ ...getItemDataAttrs(props),
142
+ onPointerMove() {
143
+ if (itemState.disabled)
144
+ return;
145
+ if (itemState.hovered)
146
+ return;
147
+ send({ type: "SET_HOVERED", value: props.value, hovered: true });
148
+ },
149
+ onPointerLeave() {
150
+ if (itemState.disabled)
151
+ return;
152
+ send({ type: "SET_HOVERED", value: null });
153
+ },
154
+ onPointerDown(event) {
155
+ if (itemState.disabled)
156
+ return;
157
+ if (itemState.focused && event.pointerType === "mouse") {
158
+ event.preventDefault();
159
+ }
160
+ send({ type: "SET_ACTIVE", value: props.value, active: true });
161
+ },
162
+ onPointerUp() {
163
+ if (itemState.disabled)
164
+ return;
165
+ send({ type: "SET_ACTIVE", value: null });
166
+ }
167
+ });
168
+ },
169
+ getItemTextProps(props) {
170
+ return normalize.element({
171
+ ...parts.itemText.attrs,
172
+ dir: state.context.dir,
173
+ id: dom.getItemLabelId(state.context, props.value),
174
+ ...getItemDataAttrs(props)
175
+ });
176
+ },
177
+ getItemControlProps(props) {
178
+ const controlState = getItemState(props);
179
+ return normalize.element({
180
+ ...parts.itemControl.attrs,
181
+ "dir": state.context.dir,
182
+ "id": dom.getItemControlId(state.context, props.value),
183
+ "data-active": dataAttr(controlState.active),
184
+ "aria-hidden": true,
185
+ ...getItemDataAttrs(props)
186
+ });
187
+ },
188
+ getItemHiddenInputProps(props) {
189
+ const inputState = getItemState(props);
190
+ return normalize.input({
191
+ "data-ownedby": dom.getRootId(state.context),
192
+ "id": dom.getItemHiddenInputId(state.context, props.value),
193
+ "type": "radio",
194
+ "name": state.context.name || state.context.id,
195
+ "form": state.context.form,
196
+ "value": props.value,
197
+ onClick(event) {
198
+ if (readOnly) {
199
+ event.preventDefault();
200
+ return;
201
+ }
202
+ if (event.currentTarget.checked) {
203
+ send({ type: "SET_VALUE", value: props.value, isTrusted: true });
204
+ }
205
+ },
206
+ onBlur() {
207
+ send({ type: "SET_FOCUSED", value: null, focused: false, focusVisible: false });
208
+ },
209
+ onFocus() {
210
+ const focusVisible = isFocusVisible();
211
+ send({ type: "SET_FOCUSED", value: props.value, focused: true, focusVisible });
212
+ },
213
+ onKeyDown(event) {
214
+ if (event.defaultPrevented)
215
+ return;
216
+ if (event.key === " ") {
217
+ send({ type: "SET_ACTIVE", value: props.value, active: true });
218
+ }
219
+ },
220
+ onKeyUp(event) {
221
+ if (event.defaultPrevented)
222
+ return;
223
+ if (event.key === " ") {
224
+ send({ type: "SET_ACTIVE", value: null });
225
+ }
226
+ },
227
+ "disabled": inputState.disabled,
228
+ "defaultChecked": inputState.checked,
229
+ "style": visuallyHiddenStyle
230
+ });
231
+ },
232
+ getIndicatorProps() {
233
+ return normalize.element({
234
+ "id": dom.getIndicatorId(state.context),
235
+ ...parts.indicator.attrs,
236
+ "dir": state.context.dir,
237
+ "hidden": state.context.value == null,
238
+ "data-disabled": dataAttr(groupDisabled),
239
+ "data-orientation": state.context.orientation,
240
+ "style": {
241
+ "--transition-property": "left, top, width, height",
242
+ "--left": state.context.indicatorRect?.left,
243
+ "--top": state.context.indicatorRect?.top,
244
+ "--width": state.context.indicatorRect?.width,
245
+ "--height": state.context.indicatorRect?.height,
246
+ "position": "absolute",
247
+ "willChange": "var(--transition-property)",
248
+ "transitionProperty": "var(--transition-property)",
249
+ "transitionDuration": state.context.canIndicatorTransition ? "var(--transition-duration, 150ms)" : "0ms",
250
+ "transitionTimingFunction": "var(--transition-timing-function)",
251
+ [state.context.orientation === "horizontal" ? "left" : "top"]: state.context.orientation === "horizontal" ? "var(--left)" : "var(--top)"
252
+ }
253
+ });
254
+ }
255
+ };
256
+ }
257
+
258
+ const { not } = guards;
259
+ const invoke = {
260
+ change: (ctx) => {
261
+ if (ctx.value == null)
262
+ return;
263
+ ctx.onValueChange?.({ value: ctx.value });
264
+ }
265
+ };
266
+ const set = {
267
+ value: (ctx, value) => {
268
+ if (isEqual(ctx.value, value))
269
+ return;
270
+ ctx.value = value;
271
+ invoke.change(ctx);
272
+ }
273
+ };
274
+ function machine(userContext) {
275
+ const ctx = compact(userContext);
276
+ return createMachine(
277
+ {
278
+ id: "radio",
279
+ initial: "idle",
280
+ context: {
281
+ value: null,
282
+ activeValue: null,
283
+ focusedValue: null,
284
+ hoveredValue: null,
285
+ disabled: false,
286
+ orientation: "vertical",
287
+ ...ctx,
288
+ indicatorRect: {},
289
+ canIndicatorTransition: false,
290
+ fieldsetDisabled: false,
291
+ focusVisible: false,
292
+ ssr: true
293
+ },
294
+ computed: {
295
+ isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled
296
+ },
297
+ entry: ["syncIndicatorRect", "syncSsr"],
298
+ exit: ["cleanupObserver"],
299
+ activities: ["trackFormControlState", "trackFocusVisible"],
300
+ watch: {
301
+ value: ["setIndicatorTransition", "syncIndicatorRect", "syncInputElements"]
302
+ },
303
+ on: {
304
+ SET_VALUE: [
305
+ {
306
+ guard: not("isTrusted"),
307
+ actions: ["setValue", "dispatchChangeEvent"]
308
+ },
309
+ {
310
+ actions: ["setValue"]
311
+ }
312
+ ],
313
+ SET_HOVERED: {
314
+ actions: "setHovered"
315
+ },
316
+ SET_ACTIVE: {
317
+ actions: "setActive"
318
+ },
319
+ SET_FOCUSED: {
320
+ actions: "setFocused"
321
+ }
322
+ },
323
+ states: {
324
+ idle: {}
325
+ }
326
+ },
327
+ {
328
+ guards: {
329
+ isTrusted: (_ctx, evt) => !!evt.isTrusted
330
+ },
331
+ activities: {
332
+ trackFormControlState(ctx2, _evt, { send, initialContext }) {
333
+ return trackFormControl(dom.getRootEl(ctx2), {
334
+ onFieldsetDisabledChange(disabled) {
335
+ ctx2.fieldsetDisabled = disabled;
336
+ },
337
+ onFormReset() {
338
+ send({ type: "SET_VALUE", value: initialContext.value });
339
+ }
340
+ });
341
+ },
342
+ trackFocusVisible(ctx2) {
343
+ return trackFocusVisible({ root: dom.getRootNode(ctx2) });
344
+ }
345
+ },
346
+ actions: {
347
+ setValue(ctx2, evt) {
348
+ set.value(ctx2, evt.value);
349
+ },
350
+ setHovered(ctx2, evt) {
351
+ ctx2.hoveredValue = evt.value;
352
+ },
353
+ setActive(ctx2, evt) {
354
+ ctx2.activeValue = evt.value;
355
+ },
356
+ setFocused(ctx2, evt) {
357
+ ctx2.focusedValue = evt.value;
358
+ ctx2.focusVisible = evt.focusVisible;
359
+ },
360
+ syncInputElements(ctx2) {
361
+ const inputs = dom.getInputEls(ctx2);
362
+ inputs.forEach((input) => {
363
+ input.checked = input.value === ctx2.value;
364
+ });
365
+ },
366
+ setIndicatorTransition(ctx2) {
367
+ ctx2.canIndicatorTransition = isString(ctx2.value);
368
+ },
369
+ cleanupObserver(ctx2) {
370
+ ctx2.indicatorCleanup?.();
371
+ },
372
+ syncSsr(ctx2) {
373
+ ctx2.ssr = false;
374
+ },
375
+ syncIndicatorRect(ctx2) {
376
+ ctx2.indicatorCleanup?.();
377
+ if (!dom.getIndicatorEl(ctx2))
378
+ return;
379
+ const value = ctx2.value;
380
+ const radioEl = dom.getActiveRadioEl(ctx2);
381
+ if (value == null || !radioEl) {
382
+ ctx2.indicatorRect = {};
383
+ return;
384
+ }
385
+ ctx2.indicatorCleanup = trackElementRect(radioEl, {
386
+ getRect(el) {
387
+ return dom.getOffsetRect(el);
388
+ },
389
+ onChange(rect) {
390
+ ctx2.indicatorRect = dom.resolveRect(rect);
391
+ nextTick(() => {
392
+ ctx2.canIndicatorTransition = false;
393
+ });
394
+ }
395
+ });
396
+ },
397
+ dispatchChangeEvent(ctx2) {
398
+ const inputEls = dom.getInputEls(ctx2);
399
+ inputEls.forEach((inputEl) => {
400
+ const checked = inputEl.value === ctx2.value;
401
+ if (checked === inputEl.checked)
402
+ return;
403
+ dispatchInputCheckedEvent(inputEl, { checked });
404
+ });
405
+ }
406
+ }
407
+ }
408
+ );
409
+ }
410
+
411
+ const props = createProps()([
412
+ "dir",
413
+ "disabled",
414
+ "form",
415
+ "getRootNode",
416
+ "id",
417
+ "ids",
418
+ "name",
419
+ "onValueChange",
420
+ "orientation",
421
+ "readOnly",
422
+ "value"
423
+ ]);
424
+ const splitProps = createSplitProps(props);
425
+ const itemProps = createProps()(["value", "disabled", "invalid"]);
426
+ const splitItemProps = createSplitProps(itemProps);
427
+
428
+ export { anatomy, connect, itemProps, machine, props, splitItemProps, splitProps };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@destyler/radio",
3
3
  "type": "module",
4
- "version": "0.0.5",
5
- "description": "a set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.",
4
+ "version": "0.0.7-beta.1",
5
+ "description": "A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.",
6
6
  "author": "Elone Hoo <elonehoo@gmail.com>",
7
7
  "license": "MIT",
8
8
  "exports": {
@@ -10,14 +10,6 @@
10
10
  "types": "./dist/index.d.mts",
11
11
  "default": "./dist/index.mjs"
12
12
  },
13
- "./resolver": {
14
- "types": "./dist/resolver.d.mts",
15
- "default": "./dist/resolver.mjs"
16
- },
17
- "./component": {
18
- "types": "./dist/component.d.mts",
19
- "default": "./dist/component.mjs"
20
- },
21
13
  "./*": "./*"
22
14
  },
23
15
  "main": "dist/index.cjs",
@@ -27,20 +19,19 @@
27
19
  "*.d.ts",
28
20
  "dist"
29
21
  ],
30
- "peerDependencies": {
31
- "vue": ">=3.4.21"
32
- },
33
22
  "dependencies": {
34
- "@vueuse/core": "^10.9.0",
35
- "@destyler/composition": "0.0.5",
36
- "@destyler/presence": "0.0.5",
37
- "@destyler/primitive": "0.0.5",
38
- "@destyler/shared": "0.0.5",
39
- "@destyler/roving-focus": "0.0.5",
40
- "@destyler/directives": "0.0.5"
23
+ "@zag-js/anatomy": "^0.80.0",
24
+ "@zag-js/core": "^0.80.0",
25
+ "@zag-js/dom-query": "^0.80.0",
26
+ "@zag-js/element-rect": "^0.80.0",
27
+ "@zag-js/focus-visible": "^0.80.0",
28
+ "@zag-js/form-utils": "^0.80.0",
29
+ "@zag-js/types": "^0.80.0",
30
+ "@zag-js/utils": "^0.80.0"
41
31
  },
42
32
  "scripts": {
43
33
  "clear": "rimraf ./dist",
34
+ "clear:node_modules": "rimraf ./node_modules",
44
35
  "build": "pnpm run clear && unbuild",
45
36
  "stub": "unbuild --stub"
46
37
  }
package/README.md DELETED
@@ -1,7 +0,0 @@
1
- # @destyler/radio
2
-
3
- [![NPM version](https://img.shields.io/npm/v/@destyler/radio?color=a1b858&radio=)](https://www.npmjs.com/package/@destyler/radio)
4
-
5
- > a set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.
6
-
7
- [GitHub](https://github.com/destyler/destyler) | [Documentation](https://destyler-dev.zeabur.app/)