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