@astryxdesign/core 0.7.3-canary.296edaf → 0.7.3-canary.2f47a4a

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.
@@ -9,7 +9,7 @@
9
9
  * comes from the parent DropdownMenu's useListFocus, which matches
10
10
  * menuitemradio rows.
11
11
  */
12
- import { type ReactNode } from 'react';
12
+ import { type ReactNode, type Ref } from 'react';
13
13
  import type { BaseProps } from '../BaseProps';
14
14
  export interface DropdownMenuRadioGroupProps extends Omit<BaseProps, 'onChange'> {
15
15
  /**
@@ -24,9 +24,9 @@ export interface DropdownMenuRadioGroupProps extends Omit<BaseProps, 'onChange'>
24
24
  /**
25
25
  * Accessible name for the group, announced by screen readers so the radios
26
26
  * read as a named set (e.g. "Sort by"). Applied as the group's `aria-label`.
27
- * Required -- an unnamed radio group is an accessibility defect. Pass
28
- * `aria-labelledby` (via base props) instead if the name already exists as a
29
- * visible element on the page.
27
+ * Required even when `aria-labelledby` (via base props) points to visible
28
+ * text; that visible reference becomes the computed name while `label`
29
+ * remains the fallback.
30
30
  */
31
31
  label: string;
32
32
  /**
@@ -36,6 +36,10 @@ export interface DropdownMenuRadioGroupProps extends Omit<BaseProps, 'onChange'>
36
36
  * @default true
37
37
  */
38
38
  hasCloseOnSelect?: boolean;
39
+ /**
40
+ * Ref forwarded to the role="group" root.
41
+ */
42
+ ref?: Ref<HTMLDivElement>;
39
43
  /**
40
44
  * The `DropdownMenuRadioItem`s that make up the group.
41
45
  */
@@ -58,7 +62,7 @@ export interface DropdownMenuRadioGroupProps extends Omit<BaseProps, 'onChange'>
58
62
  * </DropdownMenu>
59
63
  * ```
60
64
  */
61
- export declare function DropdownMenuRadioGroup({ value, onChange, label, hasCloseOnSelect, children, xstyle, className, style, ...rest }: DropdownMenuRadioGroupProps): import("react").JSX.Element;
65
+ export declare function DropdownMenuRadioGroup({ value, onChange, label, hasCloseOnSelect, children, ref, xstyle, className, style, ...rest }: DropdownMenuRadioGroupProps): import("react").JSX.Element;
62
66
  export declare namespace DropdownMenuRadioGroup {
63
67
  var displayName: string;
64
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuRadioGroup.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuRadioGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAU,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAI9C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAiB5C,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CACvD,SAAS,EACT,UAAU,CACX;IACC;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,KAAK,EACL,gBAAuB,EACvB,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,2BAA2B,+BAiB7B;yBA3Be,sBAAsB"}
1
+ {"version":3,"file":"DropdownMenuRadioGroup.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuRadioGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAU,KAAK,SAAS,EAAE,KAAK,GAAG,EAAC,MAAM,OAAO,CAAC;AAIxD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAiB5C,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CACvD,SAAS,EACT,UAAU,CACX;IACC;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,KAAK,EACL,gBAAuB,EACvB,QAAQ,EACR,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,2BAA2B,+BAkB7B;yBA7Be,sBAAsB"}
@@ -51,6 +51,7 @@ export function DropdownMenuRadioGroup({
51
51
  label,
52
52
  hasCloseOnSelect = true,
53
53
  children,
54
+ ref,
54
55
  xstyle,
55
56
  className,
56
57
  style,
@@ -63,6 +64,7 @@ export function DropdownMenuRadioGroup({
63
64
  }), [value, onChange, hasCloseOnSelect]);
64
65
  return /*#__PURE__*/_jsx("div", {
65
66
  ...rest,
67
+ ref: ref,
66
68
  role: "group",
67
69
  "aria-label": label,
68
70
  ...mergeProps(stylex.props(styles.group, xstyle), {
@@ -59,12 +59,12 @@ export interface TimeInputProps extends Omit<FieldInputBaseProps<HTMLInputElemen
59
59
  /**
60
60
  * Explains why the input is disabled. When set together with
61
61
  * `isDisabled`, the input shows a tooltip with this text on hover and
62
- * keyboard focus, and the field stays focusable (via `aria-disabled`)
63
- * so the reason is discoverable by keyboard and assistive technology.
64
- * Typing and arrow-key adjustment stay blocked.
62
+ * keyboard focus, and the input is described by it, so the reason is
63
+ * discoverable by keyboard and assistive technology. Typing and arrow-key
64
+ * adjustment stay blocked.
65
65
  *
66
- * Use this instead of wrapping a disabled input in `Tooltip` — disabled
67
- * controls don't emit the pointer events an external tooltip needs.
66
+ * Use this instead of wrapping a disabled input in `Tooltip`, so the
67
+ * reason is tied to the field.
68
68
  *
69
69
  * @example
70
70
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"TimeInput.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TimeInput.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAO3C,OAAO,EAGL,KAAK,WAAW,EAKhB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAIlB,OAAO,EACL,KAAK,aAAa,EAUnB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAgB9C,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AA6CpC,QAAA,MAAM,UAAU;;;;;;;;;;;;;EAad,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,UAAU,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,KAAK,CAAC;AAEhD,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjE,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAItD,YAAY,EACV,WAAW,IAAI,eAAe,EAC9B,eAAe,IAAI,mBAAmB,GACvC,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,UAAU,GAAG,cAAc,CAC5B;IACC,qEAAqE;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,CAAC;IAEtD;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1E;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;OAEG;IACH,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAEjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IAErB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IAEnC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2gBD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAyC9C;yBAzCe,SAAS"}
1
+ {"version":3,"file":"TimeInput.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TimeInput.tsx"],"names":[],"mappings":"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"}
@@ -46,6 +46,13 @@ import { effectiveInputPresentation, resolveInputPresentation } from "../utils/i
46
46
  import { useDevWarning } from "../hooks/useDevWarning.js";
47
47
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
48
48
  const TOUCH_POINTER_QUERY = '(pointer: coarse)';
49
+ const styles = {
50
+ disabledFocusRing: {
51
+ kVAM5u: "xvy26l8 x6q1khz",
52
+ kGVxlE: "x1gnnqk1 xuihb5h",
53
+ $$css: true
54
+ }
55
+ };
49
56
  const sizeStyles = {
50
57
  sm: {
51
58
  kZKoxP: "x6k0iem",
@@ -175,10 +182,10 @@ function TimeField({
175
182
  }
176
183
  }, [announce, inputGroup, status?.message, status?.type]);
177
184
 
178
- // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
179
- // tooltip listeners attach to the input container (which already exists) and
180
- // the input stays perceivable via aria-disabled instead of the disabled
181
- // attribute. Typing is blocked with readOnly and value mutation guards.
185
+ // A disabled field never renders native `disabled` (spec:AST-085 FR1): the
186
+ // input stays focusable via aria-disabled, and readOnly plus the value
187
+ // mutation guards block typing. The disabled-reason tooltip's listeners
188
+ // attach to the input container.
182
189
  const showsDisabledMessage = isDisabled && !!disabledMessage;
183
190
  const disabledMessageTooltip = useTooltip({
184
191
  placement: 'above',
@@ -258,8 +265,8 @@ function TimeField({
258
265
 
259
266
  // Handle input text change - update immediately if valid
260
267
  const handleInputChange = useCallback(e => {
261
- // With a disabledMessage the input drops `disabled` for focusability, so
262
- // guard value mutation explicitly (readOnly also blocks typing).
268
+ // The input is never natively disabled (spec:AST-085 FR1), so guard
269
+ // value mutation explicitly (readOnly also blocks typing).
263
270
  if (isDisabled) {
264
271
  return;
265
272
  }
@@ -290,6 +297,11 @@ function TimeField({
290
297
  if (pendingInput === null) {
291
298
  return;
292
299
  }
300
+ // Disabled: keep the value, drop the draft (spec:AST-085 FR2).
301
+ if (isDisabled) {
302
+ setPendingInput(null);
303
+ return;
304
+ }
293
305
  if (!pendingInput.trim()) {
294
306
  // Empty input clears the value
295
307
  if (value !== undefined) {
@@ -307,9 +319,15 @@ function TimeField({
307
319
  }
308
320
  // Clear pending input - display will revert to formatted value
309
321
  setPendingInput(null);
310
- }, [pendingInput, value, fireChange, hasSeconds, min, max]);
322
+ }, [pendingInput, isDisabled, value, fireChange, hasSeconds, min, max]);
311
323
 
312
324
  // Handle keyboard navigation on input
325
+ // Enter in a focused disabled field would submit a surrounding form (FR2).
326
+ const preventDisabledEnter = e => {
327
+ if (isDisabled && e.key === 'Enter') {
328
+ e.preventDefault();
329
+ }
330
+ };
313
331
  const handleInputKeyDown = useCallback(e => {
314
332
  // ArrowUp/ArrowDown step the time and preventDefault; an IME candidate
315
333
  // window uses those same arrows to navigate candidates, so guard the
@@ -318,8 +336,7 @@ function TimeField({
318
336
  if (isImeKeyEvent(e.nativeEvent)) {
319
337
  return;
320
338
  }
321
- // Arrow-key adjustment mutates the value; block it while showing a
322
- // disabled reason (the input keeps focusability via aria-disabled).
339
+ // Arrow-key adjustment mutates the value; block it while disabled.
323
340
  if (isDisabled) {
324
341
  return;
325
342
  }
@@ -393,7 +410,7 @@ function TimeField({
393
410
  size,
394
411
  status: status?.type ?? null,
395
412
  disabled: isDisabled ? 'disabled' : null
396
- }), stylex.props(inputWrapperStyles.base, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
413
+ }), stylex.props(inputWrapperStyles.base, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, isDisabled && styles.disabledFocusRing, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
397
414
  children: [inputGroup && /*#__PURE__*/_jsx(VisuallyHidden, {
398
415
  id: inputLabelID,
399
416
  children: label
@@ -420,7 +437,7 @@ function TimeField({
420
437
  max: max,
421
438
  hourFormat: hourFormat,
422
439
  isEffectivelyDisabled: isDisabled,
423
- isFocusableDisabled: showsDisabledMessage,
440
+ isFocusableDisabled: true,
424
441
  isEffectivelyRequired: isEffectivelyRequired,
425
442
  isBusy: isBusy,
426
443
  statusType: status?.type,
@@ -429,7 +446,8 @@ function TimeField({
429
446
  ...controlProps,
430
447
  onFocus: onFocusProp,
431
448
  onBlur: onBlurProp,
432
- onKeyDown: onKeyDownProp
449
+ onKeyDown: chainEventHandlers(preventDisabledEnter, onKeyDownProp),
450
+ onClick: isDisabled ? undefined : controlProps.onClick
433
451
  }
434
452
  }) : /*#__PURE__*/_jsxs(_Fragment, {
435
453
  children: [/*#__PURE__*/_jsx("div", {
@@ -450,15 +468,14 @@ function TimeField({
450
468
  onChange: handleInputChange,
451
469
  onFocus: chainEventHandlers(handleFocus, onFocusProp),
452
470
  onBlur: chainEventHandlers(handleBlur, onBlurProp),
453
- onKeyDown: chainEventHandlers(handleInputKeyDown, onKeyDownProp),
454
- placeholder: displayPlaceholder
455
- // With a disabledMessage the input keeps focusability via
456
- // aria-disabled so the reason is focus-discoverable; typing and
457
- // arrow-key adjustment are blocked with readOnly and the guards.
471
+ onKeyDown: chainEventHandlers(preventDisabledEnter, handleInputKeyDown, onKeyDownProp),
472
+ placeholder: displayPlaceholder,
473
+ "aria-disabled": isDisabled ? 'true' : undefined,
474
+ readOnly: isDisabled || undefined
475
+ // The browser still dispatches a click on an aria-disabled input,
476
+ // so a caller's onClick is skipped too (spec:AST-085 FR2).
458
477
  ,
459
- disabled: isDisabled && !showsDisabledMessage,
460
- "aria-disabled": showsDisabledMessage ? 'true' : undefined,
461
- readOnly: showsDisabledMessage || undefined,
478
+ onClick: isDisabled ? undefined : controlProps.onClick,
462
479
  autoFocus: hasAutoFocus,
463
480
  "data-autofocus": hasAutoFocus || undefined,
464
481
  "aria-describedby": ariaDescribedBy,
@@ -1 +1 @@
1
- {"version":3,"file":"TouchTimeField.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TouchTimeField.tsx"],"names":[],"mappings":"AAmEA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AA0GhD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAiB,EACjB,GAAG,EACH,GAAG,EACH,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAGlB,SAAS,EAAE,UAAU,EACrB,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,oBAAoB,EACjC,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,aAA0B,EAC1B,YAAY,EACZ,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,GAAG,SAAS,EACb,EAAE,cAAc,+BAuYhB;yBA1ae,cAAc"}
1
+ {"version":3,"file":"TouchTimeField.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TouchTimeField.tsx"],"names":[],"mappings":"AAoEA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAqHhD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAiB,EACjB,GAAG,EACH,GAAG,EACH,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAGlB,SAAS,EAAE,UAAU,EACrB,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,oBAAoB,EACjC,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,aAA0B,EAC1B,YAAY,EACZ,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,GAAG,SAAS,EACb,EAAE,cAAc,+BAiZhB;yBApbe,cAAc"}
@@ -30,7 +30,7 @@ import { useResolvedRequired } from "../hooks/useResolvedRequired.js";
30
30
  import { Icon } from "../Icon/index.js";
31
31
  import { useTranslator } from "../i18n/index.js";
32
32
  import "../theme/tokens.stylex.js";
33
- import { sizeVars, spacingVars } from "../theme/tokens.stylex.js";
33
+ import { colorVars, sizeVars, spacingVars } from "../theme/tokens.stylex.js";
34
34
  import { useSize } from "../SizeContext/SizeContext.js";
35
35
  import { Spinner } from "../Spinner/index.js";
36
36
  import { useTooltip } from "../Tooltip/index.js";
@@ -60,6 +60,11 @@ const sizeStyles = {
60
60
  }
61
61
  };
62
62
  const styles = {
63
+ disabledFocusRing: {
64
+ kVAM5u: "xvy26l8 x6q1khz",
65
+ kGVxlE: "x1gnnqk1 xuihb5h",
66
+ $$css: true
67
+ },
63
68
  iconButton: {
64
69
  k1xSpc: "x78zum5",
65
70
  kGNEyG: "x6s0dn4",
@@ -309,11 +314,11 @@ export function TouchTimeField({
309
314
  size,
310
315
  status: status?.type ?? null,
311
316
  disabled: isDisabled ? 'disabled' : null
312
- }), stylex.props(inputWrapperStyles.base, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
317
+ }), stylex.props(inputWrapperStyles.base, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, isDisabled && styles.disabledFocusRing, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
313
318
  children: [/*#__PURE__*/_jsx("button", {
314
319
  type: "button",
315
320
  onClick: openSheet,
316
- disabled: isEffectivelyDisabled,
321
+ "aria-disabled": isEffectivelyDisabled || undefined,
317
322
  "aria-label": t('@astryx.timeInput.openPicker', {
318
323
  label
319
324
  }),
@@ -334,10 +339,16 @@ export function TouchTimeField({
334
339
  readOnly: true,
335
340
  inputMode: "none",
336
341
  onChange: () => {},
337
- onClick: chainEventHandlers(openSheet, onClickProp),
342
+ onClick: isEffectivelyDisabled ? undefined : chainEventHandlers(openSheet, onClickProp)
343
+ // Enter would submit a surrounding form while disabled (FR2).
344
+ ,
345
+ onKeyDown: chainEventHandlers(e => {
346
+ if (isEffectivelyDisabled && e.key === 'Enter') {
347
+ e.preventDefault();
348
+ }
349
+ }, controlProps.onKeyDown),
338
350
  placeholder: placeholder,
339
- disabled: isEffectivelyDisabled && !showsDisabledMessage,
340
- "aria-disabled": showsDisabledMessage ? 'true' : undefined,
351
+ "aria-disabled": isEffectivelyDisabled ? 'true' : undefined,
341
352
  "aria-describedby": ariaDescribedBy,
342
353
  "aria-required": isEffectivelyRequired ? 'true' : undefined,
343
354
  "aria-invalid": status?.type === 'error' ? 'true' : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.3-canary.296edaf",
3
+ "version": "0.7.3-canary.2f47a4a",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -792,8 +792,8 @@
792
792
  "react-dom": ">=19.0.0"
793
793
  },
794
794
  "devDependencies": {
795
- "@astryxdesign/a11y-spec": "0.7.3-canary.296edaf",
796
- "@astryxdesign/cli": "0.7.3-canary.296edaf",
795
+ "@astryxdesign/a11y-spec": "0.7.3-canary.2f47a4a",
796
+ "@astryxdesign/cli": "0.7.3-canary.2f47a4a",
797
797
  "@babel/cli": "^7.29.7",
798
798
  "@babel/core": "^7.29.7",
799
799
  "@babel/preset-react": "^7.29.7",
@@ -8,7 +8,50 @@ export const docs = {
8
8
  displayName: 'Dropdown Menu Item',
9
9
  isHiddenFromOverview: true,
10
10
  description:
11
- 'Helper component for custom item rendering with consistent styling. Host attributes and DOM event handlers (aria-*, data-*, id, drag and drop) reach the row root, the element carrying role="menuitem".',
11
+ 'One action or destination row in the pointer presentation of DropdownMenu. The parent menu owns keyboard navigation; the row owns activation, disabled state, destination behavior, content layout, and its theme target. Supported host attributes, DOM handlers, className, style, xstyle, and ref reach or compose on the role="menuitem" root.',
12
+ usage: {
13
+ description:
14
+ 'Use DropdownMenuItem inside DropdownMenu compound mode for one action or destination. Keep label, description, icon, and endContent non-interactive; controls belong in separate rows or the menu header/footer.',
15
+ bestPractices: [
16
+ {
17
+ guidance: true,
18
+ description:
19
+ 'Use href for a destination so LinkProvider, modified clicks, middle clicks, and browser link affordances remain available.',
20
+ },
21
+ {
22
+ guidance: true,
23
+ description:
24
+ 'Use hasCloseOnSelect=false only when activation reports a result in place on the same row.',
25
+ },
26
+ {
27
+ guidance: false,
28
+ description:
29
+ 'Put buttons, links, or other focusable controls inside label, description, or endContent. Each menu row is one interactive target.',
30
+ },
31
+ ],
32
+ anatomy: [
33
+ {
34
+ name: 'Row',
35
+ required: true,
36
+ description:
37
+ 'The role="menuitem" root that owns activation, disabled state, destination behavior, and theme state.',
38
+ },
39
+ {
40
+ name: 'Content',
41
+ required: true,
42
+ description:
43
+ 'The non-interactive label plus optional icon, description, and end content.',
44
+ },
45
+ ],
46
+ },
47
+ theming: {
48
+ targets: [
49
+ {
50
+ className: 'astryx-dropdown-menu-item',
51
+ visualProps: ['size', 'variant'],
52
+ },
53
+ ],
54
+ },
12
55
  playground: {
13
56
  // Standalone DropdownMenuItem has no required props, so the properties-tab
14
57
  // preview renders an empty row without seeded content. Seed a label and
@@ -26,6 +69,7 @@ export const docs = {
26
69
  name: 'label',
27
70
  type: 'ReactNode',
28
71
  description: 'Primary label text.',
72
+ required: true,
29
73
  },
30
74
  {
31
75
  name: 'description',
@@ -55,11 +99,18 @@ export const docs = {
55
99
  description:
56
100
  'Link relationship. noopener noreferrer is added for target="_blank". Only used with href.',
57
101
  },
102
+ {
103
+ name: 'isDisabled',
104
+ type: 'boolean',
105
+ description:
106
+ 'Whether the row is unavailable. Disabled rows expose aria-disabled, remove link destinations, and suppress activation.',
107
+ default: 'false',
108
+ },
58
109
  {
59
110
  name: 'endContent',
60
111
  type: 'ReactNode',
61
112
  description:
62
- 'Additional content rendered after the label and description.',
113
+ 'Non-interactive content rendered after the label and description.',
63
114
  },
64
115
  {
65
116
  name: 'hasCloseOnSelect',
@@ -95,7 +146,7 @@ export const docsZh = {
95
146
  isHiddenFromOverview: true,
96
147
  displayName: 'Dropdown Menu Item',
97
148
  description:
98
- '用于自定义项渲染的辅助组件,提供一致的样式。宿主属性和 DOM 事件处理器(aria-*、data-*、id、拖放)会落在行根元素上,即带 role="menuitem" 的元素。',
149
+ 'DropdownMenu 指针呈现中的一个操作或目标行。父菜单负责键盘导航;该行负责激活、禁用状态、目标行为、内容布局和主题目标。支持的宿主属性、DOM 处理程序、className、style、xstyle 和 ref 会落在或组合到 role="menuitem" 根元素上。',
99
150
  props: [
100
151
  {
101
152
  name: 'icon',
@@ -106,6 +157,7 @@ export const docsZh = {
106
157
  name: 'label',
107
158
  type: 'ReactNode',
108
159
  description: '主标签文本。',
160
+ required: true,
109
161
  },
110
162
  {
111
163
  name: 'description',
@@ -134,10 +186,16 @@ export const docsZh = {
134
186
  type: 'string',
135
187
  description: '链接关系。target="_blank" 时自动加入 noopener noreferrer。',
136
188
  },
189
+ {
190
+ name: 'isDisabled',
191
+ type: 'boolean',
192
+ description: '该行是否不可用;不可用的行不会激活或导航。',
193
+ default: 'false',
194
+ },
137
195
  {
138
196
  name: 'endContent',
139
197
  type: 'ReactNode',
140
- description: '在标签和描述之后渲染的附加内容。',
198
+ description: '在标签和描述之后渲染的非交互式附加内容。',
141
199
  },
142
200
  {
143
201
  name: 'hasCloseOnSelect',
@@ -168,9 +226,9 @@ export const docsZh = {
168
226
 
169
227
  export const docsDense = {
170
228
  name: 'DropdownMenuItem',
171
- isHiddenFromOverview: true,
172
229
  displayName: 'Dropdown Menu Item',
173
- description: 'helper for custom item rendering w/ consistent styling',
230
+ description:
231
+ 'one pointer-menu action/destination row; parent owns keyboard, row owns activation, state, destination, content, and root inputs',
174
232
  propDescriptions: {
175
233
  icon: 'icon before label',
176
234
  label: 'primary label text',
@@ -180,7 +238,8 @@ export const docsDense = {
180
238
  href: 'row is a real anchor w/ role=menuitem; modified/middle click keeps browser meaning',
181
239
  target: "link target ('_blank' | '_self'), with href",
182
240
  rel: 'link rel; noopener noreferrer added for _blank',
183
- endContent: 'additional content after label+description',
241
+ isDisabled: 'unavailable; removes destinations and suppresses activation',
242
+ endContent: 'non-interactive content after label+description',
184
243
  hasCloseOnSelect:
185
244
  'false keeps the menu open on activation (in-place result on the item)',
186
245
  variant: