@funstack/ui 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
Files changed (131) hide show
  1. package/dist/accordion/accordion-item.mjs +2 -2
  2. package/dist/accordion/accordion-panel.mjs +2 -2
  3. package/dist/accordion/accordion-root.mjs +3 -3
  4. package/dist/accordion/accordion-trigger.mjs +2 -2
  5. package/dist/button/button.d.mts +78 -0
  6. package/dist/button/button.d.mts.map +1 -0
  7. package/dist/button/button.mjs +77 -0
  8. package/dist/button/button.mjs.map +1 -0
  9. package/dist/button/index.d.mts +3 -0
  10. package/dist/button/index.mjs +1 -0
  11. package/dist/checkbox/checkbox.mjs +2 -2
  12. package/dist/checkbox-group/checkbox-group-item.mjs +1 -1
  13. package/dist/checkbox-group/checkbox-group-parent.mjs +1 -1
  14. package/dist/checkbox-group/checkbox-group-root.mjs +1 -1
  15. package/dist/combobox/combobox-clear.d.mts +6 -3
  16. package/dist/combobox/combobox-clear.d.mts.map +1 -1
  17. package/dist/combobox/combobox-clear.mjs +10 -5
  18. package/dist/combobox/combobox-clear.mjs.map +1 -1
  19. package/dist/combobox/combobox-group.mjs +1 -1
  20. package/dist/combobox/combobox-list.mjs +1 -1
  21. package/dist/combobox/combobox-option.mjs +1 -1
  22. package/dist/combobox/combobox-popup.mjs +2 -2
  23. package/dist/combobox/combobox-root.mjs +3 -3
  24. package/dist/combobox/combobox-trigger.d.mts +6 -2
  25. package/dist/combobox/combobox-trigger.d.mts.map +1 -1
  26. package/dist/combobox/combobox-trigger.mjs +10 -4
  27. package/dist/combobox/combobox-trigger.mjs.map +1 -1
  28. package/dist/dialog/dialog-close.d.mts +8 -3
  29. package/dist/dialog/dialog-close.d.mts.map +1 -1
  30. package/dist/dialog/dialog-close.mjs +12 -8
  31. package/dist/dialog/dialog-close.mjs.map +1 -1
  32. package/dist/dialog/dialog-form.mjs +1 -1
  33. package/dist/dialog/dialog-popup.mjs +3 -3
  34. package/dist/dialog/dialog-root.mjs +2 -2
  35. package/dist/dialog/dialog-trigger.d.mts +8 -3
  36. package/dist/dialog/dialog-trigger.d.mts.map +1 -1
  37. package/dist/dialog/dialog-trigger.mjs +15 -8
  38. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  39. package/dist/field/field-root.mjs +3 -3
  40. package/dist/form/form-root.mjs +1 -1
  41. package/dist/form/form-submit.d.mts +4 -1
  42. package/dist/form/form-submit.d.mts.map +1 -1
  43. package/dist/form/form-submit.mjs.map +1 -1
  44. package/dist/index.d.mts +3 -2
  45. package/dist/index.mjs +2 -1
  46. package/dist/internal/render-element.mjs +18 -5
  47. package/dist/internal/render-element.mjs.map +1 -1
  48. package/dist/internal/use-action-trigger.mjs +17 -6
  49. package/dist/internal/use-action-trigger.mjs.map +1 -1
  50. package/dist/internal/use-field-control.mjs +1 -1
  51. package/dist/internal/use-popover-root.mjs +1 -1
  52. package/dist/internal/use-popover-trigger.mjs +6 -5
  53. package/dist/internal/use-popover-trigger.mjs.map +1 -1
  54. package/dist/internal/use-render.mjs +2 -2
  55. package/dist/internal/use-render.mjs.map +1 -1
  56. package/dist/menu/menu-checkbox-item.mjs +2 -2
  57. package/dist/menu/menu-dom.mjs +7 -1
  58. package/dist/menu/menu-dom.mjs.map +1 -1
  59. package/dist/menu/menu-group.mjs +1 -1
  60. package/dist/menu/menu-item.d.mts +6 -2
  61. package/dist/menu/menu-item.d.mts.map +1 -1
  62. package/dist/menu/menu-item.mjs +6 -4
  63. package/dist/menu/menu-item.mjs.map +1 -1
  64. package/dist/menu/menu-popup.mjs +1 -1
  65. package/dist/menu/menu-radio-group.mjs +1 -1
  66. package/dist/menu/menu-root.mjs +1 -1
  67. package/dist/menu/menu-submenu-trigger.mjs +1 -1
  68. package/dist/menu/menu-trigger.d.mts +6 -3
  69. package/dist/menu/menu-trigger.d.mts.map +1 -1
  70. package/dist/menu/menu-trigger.mjs +3 -3
  71. package/dist/menu/menu-trigger.mjs.map +1 -1
  72. package/dist/number-field/number-field-decrement.d.mts +4 -3
  73. package/dist/number-field/number-field-decrement.d.mts.map +1 -1
  74. package/dist/number-field/number-field-decrement.mjs +4 -3
  75. package/dist/number-field/number-field-decrement.mjs.map +1 -1
  76. package/dist/number-field/number-field-increment.d.mts +8 -3
  77. package/dist/number-field/number-field-increment.d.mts.map +1 -1
  78. package/dist/number-field/number-field-increment.mjs +9 -6
  79. package/dist/number-field/number-field-increment.mjs.map +1 -1
  80. package/dist/number-field/number-field-input.mjs +1 -1
  81. package/dist/number-field/number-field-root.mjs +5 -5
  82. package/dist/number-field/number-field-root.mjs.map +1 -1
  83. package/dist/number-field/use-step-button.mjs +6 -1
  84. package/dist/number-field/use-step-button.mjs.map +1 -1
  85. package/dist/popover/popover-close.d.mts +9 -3
  86. package/dist/popover/popover-close.d.mts.map +1 -1
  87. package/dist/popover/popover-close.mjs +13 -8
  88. package/dist/popover/popover-close.mjs.map +1 -1
  89. package/dist/popover/popover-popup.mjs +2 -2
  90. package/dist/popover/popover-trigger.d.mts +8 -4
  91. package/dist/popover/popover-trigger.d.mts.map +1 -1
  92. package/dist/popover/popover-trigger.mjs +3 -3
  93. package/dist/popover/popover-trigger.mjs.map +1 -1
  94. package/dist/radio-group/radio-group-item.mjs +1 -1
  95. package/dist/radio-group/radio-group-root.mjs +1 -1
  96. package/dist/select/select-root.mjs +1 -1
  97. package/dist/slider/slider-root.mjs +6 -6
  98. package/dist/slider/slider-root.mjs.map +1 -1
  99. package/dist/slider/slider-thumb.mjs +1 -1
  100. package/dist/slider/slider-value.mjs +1 -1
  101. package/dist/switch/switch.mjs +2 -2
  102. package/dist/tabs/tabs-list.mjs +1 -1
  103. package/dist/tabs/tabs-panel.mjs +1 -1
  104. package/dist/tabs/tabs-root.mjs +2 -2
  105. package/dist/tabs/tabs-tab.mjs +1 -1
  106. package/dist/toast/toast-action.d.mts +4 -3
  107. package/dist/toast/toast-action.d.mts.map +1 -1
  108. package/dist/toast/toast-action.mjs +8 -6
  109. package/dist/toast/toast-action.mjs.map +1 -1
  110. package/dist/toast/toast-root.mjs +1 -1
  111. package/dist/toast/toast-viewport.mjs +3 -3
  112. package/dist/toggle/toggle.d.mts +4 -1
  113. package/dist/toggle/toggle.d.mts.map +1 -1
  114. package/dist/toggle/toggle.mjs +16 -10
  115. package/dist/toggle/toggle.mjs.map +1 -1
  116. package/dist/toggle-group/toggle-group-item.d.mts +6 -2
  117. package/dist/toggle-group/toggle-group-item.d.mts.map +1 -1
  118. package/dist/toggle-group/toggle-group-item.mjs +10 -5
  119. package/dist/toggle-group/toggle-group-item.mjs.map +1 -1
  120. package/dist/toggle-group/toggle-group-root.mjs +3 -3
  121. package/dist/toolbar/index.d.mts +4 -5
  122. package/dist/toolbar/index.d.mts.map +1 -1
  123. package/dist/toolbar/index.mjs +3 -4
  124. package/dist/toolbar/index.mjs.map +1 -1
  125. package/dist/toolbar/toolbar-root.mjs +1 -1
  126. package/dist/toolbar/toolbar-separator.mjs +1 -1
  127. package/package.json +2 -1
  128. package/dist/toolbar/toolbar-button.d.mts +0 -42
  129. package/dist/toolbar/toolbar-button.d.mts.map +0 -1
  130. package/dist/toolbar/toolbar-button.mjs +0 -50
  131. package/dist/toolbar/toolbar-button.mjs.map +0 -1
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { rendersButton } from "../internal/render-element.mjs";
5
- import { useRender } from "../internal/use-render.mjs";
6
4
  import { useActionTrigger } from "../internal/use-action-trigger.mjs";
5
+ import { isHostButton, rendersButton } from "../internal/render-element.mjs";
6
+ import { useRender } from "../internal/use-render.mjs";
7
7
  import { useDialogContext } from "./dialog-context.mjs";
8
8
  import { useCallback } from "react";
9
9
  //#region src/dialog/dialog-close.tsx
@@ -14,18 +14,21 @@ const isDev = process.env.NODE_ENV !== "production";
14
14
  * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes
15
15
  * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside
16
16
  * the view transition if the Popup's `transition` is set). With `action` the action runs first in
17
- * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.
17
+ * this part's own transition (`data-pending` and `aria-disabled` on the button; a second click
18
+ * meanwhile is ignored); a rejection leaves the dialog open.
18
19
  *
19
20
  * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
20
- * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,
21
+ * `command`, `value`, `onClick` (library mode, or with `action`), `aria-disabled`, `data-pending`,
21
22
  * `data-disabled`.
22
23
  */
23
24
  function DialogClose({ action, value, disabled = false, className, style, children, render, ...rest }) {
24
25
  const ctx = useDialogContext("Dialog.Close");
25
- const { pending, run } = useActionTrigger(ctx.openGeneration);
26
+ const { pending, run } = useActionTrigger({ openGeneration: ctx.openGeneration });
26
27
  const { ids, dialogRef, recordCloseIntent } = ctx;
27
28
  const isLibrary = ctx.mode === "library";
28
29
  const installClick = isLibrary || action !== void 0;
30
+ const focusable = installClick && !isHostButton(render);
31
+ const noInvoker = focusable && disabled;
29
32
  const state = {
30
33
  pending,
31
34
  disabled
@@ -61,11 +64,12 @@ function DialogClose({ action, value, disabled = false, className, style, childr
61
64
  };
62
65
  const libraryProps = {
63
66
  ...rendersButton(render) && { type: "button" },
64
- disabled,
65
- commandfor: ids.dialog,
66
- command: "request-close",
67
+ disabled: focusable ? void 0 : disabled,
68
+ commandfor: noInvoker ? void 0 : ids.dialog,
69
+ command: noInvoker ? void 0 : "request-close",
67
70
  value,
68
71
  ...installClick && { onClick },
72
+ "aria-disabled": pending || focusable && disabled ? true : void 0,
69
73
  "data-pending": dataAttr(pending),
70
74
  "data-disabled": dataAttr(disabled)
71
75
  };
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition (`data-pending` on the button), then closes the\n * dialog with `value` as `returnValue`: in library mode through the Root\n * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with\n * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and\n * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,\n * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the\n * button is a native `command=\"request-close\"` invoker.\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */\n value?: string;\n /** Native `disabled`; owned, so a `render` element cannot re-enable the button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. In JS-free mode\n * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with\n * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes\n * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside\n * the view transition if the Popup's `transition` is set). With `action` the action runs first in\n * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,\n * `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const hasAction = action !== undefined;\n const installClick = isLibrary || hasAction;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !installClick && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without ` +\n '`action`. Only a <button> runs `command=\"request-close\"`; this element will not ' +\n \"close the dialog.\",\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [installClick],\n );\n\n const close = (event: Event, returnValue: string): void => {\n if (isLibrary) {\n // A library-owned close (design 2.3): awaited, then `close(returnValue)` in the commit. A\n // no-op when `false` is already intended (a repeated click, or a dismissal meanwhile).\n ctx.requestClose(event, \"activate\", returnValue);\n return;\n }\n // JS-free mode with `action`: a browser-owned close; there is no Root state to go through.\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event });\n dialog.close(returnValue);\n };\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n if (action === undefined) {\n close(native, returnValue);\n return;\n }\n const details = createActionDetails(native, { returnValue });\n // Repeated clicks re-run the action; the first close wins (design 3.4). A close whose open\n // was superseded by a newer one (dismissed and re-opened meanwhile) is dropped by the hook.\n run(\n () => action(details),\n () => close(native, returnValue),\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(installClick && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;AAcvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,IAAI,SAAS;CAE/B,MAAM,eAAe,aADH,WAAW,KAAA;CAG7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,gBAAgB,QAAQ,YAAY,UAChD,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gIAG7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,YAAY,CACf;CAEA,MAAM,SAAS,OAAc,gBAA8B;EACzD,IAAI,WAAW;GAGb,IAAI,aAAa,OAAO,YAAY,WAAW;GAC/C;EACF;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY;EAAM,CAAC;EAC/C,OAAO,MAAM,WAAW;CAC1B;CAEA,MAAM,WAAW,UAAmC;EAElD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,QAAQ,WAAW;GACzB;EACF;EACA,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAG3D,UACQ,OAAO,OAAO,SACd,MAAM,QAAQ,WAAW,CACjC;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
1
+ {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { isHostButton, rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition (`data-pending` on the button), then closes the\n * dialog with `value` as `returnValue`: in library mode through the Root\n * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with\n * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and\n * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,\n * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the\n * button is a native `command=\"request-close\"` invoker.\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */\n value?: string;\n /**\n * Never closes. Native `disabled` on a `<button>`; owned, so a `render` element cannot\n * re-enable it. With a link or component `render` whose click this part handles (library mode,\n * or with `action`), `aria-disabled=\"true\"` instead: the element stays focusable.\n */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. In JS-free mode\n * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with\n * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes\n * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside\n * the view transition if the Popup's `transition` is set). With `action` the action runs first in\n * this part's own transition (`data-pending` and `aria-disabled` on the button; a second click\n * meanwhile is ignored); a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (library mode, or with `action`), `aria-disabled`, `data-pending`,\n * `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger({ openGeneration: ctx.openGeneration });\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const hasAction = action !== undefined;\n const installClick = isLibrary || hasAction;\n // A link or component render whose click this part handles: `aria-disabled`, enforced by the\n // handler (#83). JS-free mode without `action` is the browser's `request-close` command,\n // which only native `disabled` stops.\n const focusable = installClick && !isHostButton(render);\n // Disabled in the `aria-disabled` branch: no `commandfor` / `command` either. Without native\n // `disabled` the element would still be a live invoker before hydration (a component render\n // spreading onto a real <button>), and the command would run (wave 9 review M1).\n const noInvoker = focusable && disabled;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !installClick && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without ` +\n '`action`. Only a <button> runs `command=\"request-close\"`; this element will not ' +\n \"close the dialog.\",\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [installClick],\n );\n\n const close = (event: Event, returnValue: string): void => {\n if (isLibrary) {\n // A library-owned close (design 2.3): awaited, then `close(returnValue)` in the commit. A\n // no-op when `false` is already intended (a repeated click, or a dismissal meanwhile).\n ctx.requestClose(event, \"activate\", returnValue);\n return;\n }\n // JS-free mode with `action`: a browser-owned close; there is no Root state to go through.\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event });\n dialog.close(returnValue);\n };\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n if (action === undefined) {\n close(native, returnValue);\n return;\n }\n const details = createActionDetails(native, { returnValue });\n // A second activation while this Close's own run is in flight starts nothing (the hook's\n // guard; the click is already cancelled above, #81). A close whose open was superseded by a\n // newer one (dismissed and re-opened meanwhile) is dropped by the hook.\n run(\n () => action(details),\n () => close(native, returnValue),\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close. Absent in the focusable branch.\n disabled: focusable ? undefined : disabled,\n commandfor: noInvoker ? undefined : ids.dialog,\n command: noInvoker ? undefined : \"request-close\",\n value,\n ...(installClick && { onClick }),\n // Exactly while `data-pending` (#81): a Close that will not act is unavailable to AT. Owned,\n // so an idle Close removes a render element's own `aria-disabled`.\n \"aria-disabled\": pending || (focusable && disabled) ? true : undefined,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;;AAmDA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;;AAevC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,EAAE,gBAAgB,IAAI,eAAe,CAAC;CAChF,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,IAAI,SAAS;CAE/B,MAAM,eAAe,aADH,WAAW,KAAA;CAK7B,MAAM,YAAY,gBAAgB,CAAC,aAAa,MAAM;CAItD,MAAM,YAAY,aAAa;CAE/B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,gBAAgB,QAAQ,YAAY,UAChD,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gIAG7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,YAAY,CACf;CAEA,MAAM,SAAS,OAAc,gBAA8B;EACzD,IAAI,WAAW;GAGb,IAAI,aAAa,OAAO,YAAY,WAAW;GAC/C;EACF;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY;EAAM,CAAC;EAC/C,OAAO,MAAM,WAAW;CAC1B;CAEA,MAAM,WAAW,UAAmC;EAElD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,QAAQ,WAAW;GACzB;EACF;EACA,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAI3D,UACQ,OAAO,OAAO,SACd,MAAM,QAAQ,WAAW,CACjC;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD,UAAU,YAAY,KAAA,IAAY;EAClC,YAAY,YAAY,KAAA,IAAY,IAAI;EACxC,SAAS,YAAY,KAAA,IAAY;EACjC;EACA,GAAI,gBAAgB,EAAE,QAAQ;EAG9B,iBAAiB,WAAY,aAAa,WAAY,OAAO,KAAA;EAC7D,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
+ import { useDevWarning } from "../internal/use-dev-warning.mjs";
2
3
  import { resolveStateValue } from "../internal/render-element.mjs";
3
4
  import { useRender } from "../internal/use-render.mjs";
4
- import { useDevWarning } from "../internal/use-dev-warning.mjs";
5
5
  import { FormContext } from "../internal/field-context.mjs";
6
6
  import { useDialogContext } from "./dialog-context.mjs";
7
7
  import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
3
2
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { resolveStateValue } from "../internal/render-element.mjs";
5
- import { useRender } from "../internal/use-render.mjs";
6
3
  import { ToolbarContext } from "../internal/toolbar-context.mjs";
4
+ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
7
5
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
6
+ import { resolveStateValue } from "../internal/render-element.mjs";
7
+ import { useRender } from "../internal/use-render.mjs";
8
8
  import { useDialogContext } from "./dialog-context.mjs";
9
9
  import { useContentFocusProbe } from "../internal/content-focus-probe.mjs";
10
10
  import { useExitAnimation } from "../internal/use-exit-animation.mjs";
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "../internal/action-details.mjs";
3
+ import { useDevWarning } from "../internal/use-dev-warning.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
3
5
  import { transitionTypes } from "../internal/transition-types.mjs";
4
6
  import { useLatest } from "../internal/use-latest.mjs";
5
7
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
- import { resolveStateValue } from "../internal/render-element.mjs";
7
- import { useDevWarning } from "../internal/use-dev-warning.mjs";
8
8
  import { DialogContext } from "./dialog-context.mjs";
9
9
  import { useCallback, useId, useInsertionEffect, useMemo, useRef, useState } from "react";
10
10
  import { jsx } from "react/jsx-runtime";
@@ -13,7 +13,11 @@ export interface DialogTriggerState {
13
13
  readonly disabled: boolean;
14
14
  }
15
15
  export interface DialogTriggerProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "className" | "style" | "children"> {
16
- /** Native `disabled`: a disabled button is not a valid invoker and never dispatches. */
16
+ /**
17
+ * Never dispatches. Native `disabled` on a `<button>` (no longer an invoker); with a link or
18
+ * component `render` where the Trigger handles the click (library mode, or a non-modal
19
+ * dialog), `aria-disabled="true"` instead: the element stays focusable.
20
+ */
17
21
  disabled?: boolean;
18
22
  className?: StateValue<string | undefined, DialogTriggerState>;
19
23
  style?: StateValue<CSSProperties | undefined, DialogTriggerState>;
@@ -29,8 +33,9 @@ export interface DialogTriggerProps extends Omit<ComponentPropsWithRef<"button">
29
33
  * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a
30
34
  * click handler (`show()`).
31
35
  *
32
- * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
33
- * `command`, `onClick`, `data-open`, `data-pending`, `data-disabled`.
36
+ * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `aria-disabled` (a
37
+ * link or component `render` with the click handler installed), `commandfor`, `command`,
38
+ * `onClick`, `data-open`, `data-pending`, `data-disabled`.
34
39
  */
35
40
  export declare function DialogTrigger({ disabled, className, style, children, render, ...rest }: DialogTriggerProps): ReactNode;
36
41
  export declare namespace DialogTrigger {
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-trigger.d.mts","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"mappings":";;;iBASiB;;WAEN;;;;;;WAMA;WACA;;iBAGM,2BAA2B,KAC1C;;EAIA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAevC,gBACd,UACA,WACA,OACA,UACA,WACG,QACF,qBAAkB;yBAPL"}
1
+ {"version":3,"file":"dialog-trigger.d.mts","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"mappings":";;;iBASiB;;WAEN;;;;;;WAMA;WACA;;iBAGM,2BAA2B,KAC1C;;;;;;EAQA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;wBAgBvC,gBACd,UACA,WACA,OACA,UACA,WACG,QACF,qBAAkB;yBAPL"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { rendersButton } from "../internal/render-element.mjs";
3
+ import { isHostButton, rendersButton } from "../internal/render-element.mjs";
4
4
  import { useRender } from "../internal/use-render.mjs";
5
5
  import { useDialogContext } from "./dialog-context.mjs";
6
6
  import { useCallback, useEffect } from "react";
@@ -13,8 +13,9 @@ const isDev = process.env.NODE_ENV !== "production";
13
13
  * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a
14
14
  * click handler (`show()`).
15
15
  *
16
- * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
17
- * `command`, `onClick`, `data-open`, `data-pending`, `data-disabled`.
16
+ * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `aria-disabled` (a
17
+ * link or component `render` with the click handler installed), `commandfor`, `command`,
18
+ * `onClick`, `data-open`, `data-pending`, `data-disabled`.
18
19
  */
19
20
  function DialogTrigger({ disabled = false, className, style, children, render, ...rest }) {
20
21
  const ctx = useDialogContext("Dialog.Trigger");
@@ -41,6 +42,8 @@ function DialogTrigger({ disabled = false, className, style, children, render, .
41
42
  if (document.getElementById(ids.dialog) === null) console.error("Dialog.Trigger: no Dialog.Popup is rendered for this Dialog.Root; the trigger opens nothing.");
42
43
  }, [ids.dialog]);
43
44
  const installClick = isLibrary || !modal;
45
+ const focusable = installClick && !isHostButton(render);
46
+ const invoker = modal && !(focusable && disabled);
44
47
  const onClick = (event) => {
45
48
  if (isLibrary) {
46
49
  event.preventDefault();
@@ -48,19 +51,23 @@ function DialogTrigger({ disabled = false, className, style, children, render, .
48
51
  ctx.requestOpen(event.nativeEvent, "activate");
49
52
  return;
50
53
  }
51
- if (disabled) return;
54
+ if (disabled) {
55
+ event.preventDefault();
56
+ return;
57
+ }
52
58
  const dialog = dialogRef.current;
53
59
  if (dialog !== null && !dialog.open && dialog.isConnected) dialog.show();
54
60
  };
55
61
  const libraryProps = {
56
62
  ...rendersButton(render) && { type: "button" },
57
- disabled,
58
- commandfor: modal ? ids.dialog : void 0,
59
- command: modal ? "show-modal" : void 0,
63
+ disabled: focusable ? void 0 : disabled,
64
+ ...focusable && { "aria-disabled": disabled ? true : void 0 },
65
+ commandfor: invoker ? ids.dialog : void 0,
66
+ command: invoker ? "show-modal" : void 0,
60
67
  ...installClick && { onClick },
61
68
  "data-open": dataAttr(state.open),
62
69
  "data-pending": dataAttr(state.pending),
63
- "data-disabled": dataAttr(isLibrary && disabled)
70
+ "data-disabled": dataAttr(disabled)
64
71
  };
65
72
  return useRender({
66
73
  defaultElement: "button",
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-trigger.mjs","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogTriggerState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /**\n * The Root's transition is pending: an open in flight, a close a part asked for in flight\n * (`openChangeAction(false)` awaited while the dialog is still open), a close notification in\n * flight, or a re-open requested while a close notification is still settling.\n */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface DialogTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: a disabled button is not a valid invoker and never dispatches. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogTriggerState>;\n style?: StateValue<CSSProperties | undefined, DialogTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, DialogTriggerState>;\n /** Should render a `<button>`; another element works only in library mode (click handler). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * The opener: `<button type=\"button\" commandfor command=\"show-modal\">`. In JS-free mode the\n * browser runs the command. In library mode the click is cancelled (so the command does not run)\n * and dispatched through the Root's transition; the button shows optimistic `data-open` and\n * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a\n * click handler (`show()`).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `onClick`, `data-open`, `data-pending`, `data-disabled`.\n */\nexport function DialogTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogTriggerProps) {\n const ctx = useDialogContext(\"Dialog.Trigger\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, triggerRef, dialogRef } = ctx;\n\n const state: DialogTriggerState = {\n open: ctx.optimistic,\n // Every Root dispatch is an open, a part-initiated close or a dismissal notification, and\n // all of them belong to the Trigger (design 4.2). Not `optimistic !== committed`: a re-open\n // while a close notification is still settling has both `true` (the close never committed)\n // and is pending all the same.\n pending: ctx.pending,\n disabled,\n };\n\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !isLibrary && modal && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Trigger: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a ` +\n '<button> runs `command=\"show-modal\"`; this trigger will not open the dialog. Render ' +\n \"a <button>, or put the Dialog in library mode (`defaultOpen={false}`).\",\n );\n }\n triggerRef.current = element;\n return () => {\n if (triggerRef.current === element) triggerRef.current = null;\n };\n },\n [isLibrary, modal, triggerRef],\n );\n\n // A Trigger without a Popup opens nothing (design 14, dev warnings).\n useEffect(() => {\n if (!isDev) return;\n if (document.getElementById(ids.dialog) === null) {\n console.error(\n \"Dialog.Trigger: no Dialog.Popup is rendered for this Dialog.Root; the trigger opens nothing.\",\n );\n }\n }, [ids.dialog]);\n\n // Library mode (both modal and non-modal) and non-modal JS-free mode need a click handler;\n // modal JS-free mode is the browser's command.\n const installClick = isLibrary || !modal;\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (isLibrary) {\n // Cancelling the click cancels the command (it runs as the button's activation\n // behaviour, only if the click was not cancelled).\n event.preventDefault();\n if (disabled) return;\n ctx.requestOpen(event.nativeEvent, \"activate\");\n return;\n }\n if (disabled) return;\n const dialog = dialogRef.current;\n if (dialog !== null && !dialog.open && dialog.isConnected) dialog.show();\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // `type` only applies to a <button>; on any other element the key is not owned.\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Trigger (in JS-free mode the\n // browser would run its command).\n disabled,\n commandfor: modal ? ids.dialog : undefined,\n command: modal ? \"show-modal\" : undefined,\n // Only owned when installed, so a render element keeps its own `onClick` in modal JS-free\n // mode (Accordion's rule).\n ...(installClick && { onClick }),\n \"data-open\": dataAttr(state.open),\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(isLibrary && disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n}\n\nDialogTrigger.displayName = \"DialogTrigger\";\n"],"mappings":";;;;;;;AAmCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,cAAc,EAC5B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,iBAAiB,gBAAgB;CAC7C,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,EAAE,OAAO,KAAK,YAAY,cAAc;CAE9C,MAAM,QAA4B;EAChC,MAAM,IAAI;EAKV,SAAS,IAAI;EACb;CACF;CAEA,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,SAAS,QAAQ,YAAY,UACtD,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,yLAG/D;EAEF,WAAW,UAAU;EACrB,aAAa;GACX,IAAI,WAAW,YAAY,SAAS,WAAW,UAAU;EAC3D;CACF,GACA;EAAC;EAAW;EAAO;CAAU,CAC/B;CAGA,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,IAAI,SAAS,eAAe,IAAI,MAAM,MAAM,MAC1C,QAAQ,MACN,8FACF;CAEJ,GAAG,CAAC,IAAI,MAAM,CAAC;CAIf,MAAM,eAAe,aAAa,CAAC;CACnC,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW;GAGb,MAAM,eAAe;GACrB,IAAI,UAAU;GACd,IAAI,YAAY,MAAM,aAAa,UAAU;GAC7C;EACF;EACA,IAAI,UAAU;EACd,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,QAAQ,OAAO,aAAa,OAAO,KAAK;CACzE;CAEA,MAAM,eAAiE;EAErE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAGvD;EACA,YAAY,QAAQ,IAAI,SAAS,KAAA;EACjC,SAAS,QAAQ,eAAe,KAAA;EAGhC,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,aAAa,SAAS,MAAM,IAAI;EAChC,gBAAgB,SAAS,MAAM,OAAO;EACtC,iBAAiB,SAAS,aAAa,QAAQ;CACjD;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"dialog-trigger.mjs","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { isHostButton, rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogTriggerState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /**\n * The Root's transition is pending: an open in flight, a close a part asked for in flight\n * (`openChangeAction(false)` awaited while the dialog is still open), a close notification in\n * flight, or a re-open requested while a close notification is still settling.\n */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface DialogTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Never dispatches. Native `disabled` on a `<button>` (no longer an invoker); with a link or\n * component `render` where the Trigger handles the click (library mode, or a non-modal\n * dialog), `aria-disabled=\"true\"` instead: the element stays focusable.\n */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogTriggerState>;\n style?: StateValue<CSSProperties | undefined, DialogTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, DialogTriggerState>;\n /** Should render a `<button>`; another element works only in library mode (click handler). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * The opener: `<button type=\"button\" commandfor command=\"show-modal\">`. In JS-free mode the\n * browser runs the command. In library mode the click is cancelled (so the command does not run)\n * and dispatched through the Root's transition; the button shows optimistic `data-open` and\n * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a\n * click handler (`show()`).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `aria-disabled` (a\n * link or component `render` with the click handler installed), `commandfor`, `command`,\n * `onClick`, `data-open`, `data-pending`, `data-disabled`.\n */\nexport function DialogTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogTriggerProps) {\n const ctx = useDialogContext(\"Dialog.Trigger\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, triggerRef, dialogRef } = ctx;\n\n const state: DialogTriggerState = {\n open: ctx.optimistic,\n // Every Root dispatch is an open, a part-initiated close or a dismissal notification, and\n // all of them belong to the Trigger (design 4.2). Not `optimistic !== committed`: a re-open\n // while a close notification is still settling has both `true` (the close never committed)\n // and is pending all the same.\n pending: ctx.pending,\n disabled,\n };\n\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !isLibrary && modal && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Trigger: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a ` +\n '<button> runs `command=\"show-modal\"`; this trigger will not open the dialog. Render ' +\n \"a <button>, or put the Dialog in library mode (`defaultOpen={false}`).\",\n );\n }\n triggerRef.current = element;\n return () => {\n if (triggerRef.current === element) triggerRef.current = null;\n };\n },\n [isLibrary, modal, triggerRef],\n );\n\n // A Trigger without a Popup opens nothing (design 14, dev warnings).\n useEffect(() => {\n if (!isDev) return;\n if (document.getElementById(ids.dialog) === null) {\n console.error(\n \"Dialog.Trigger: no Dialog.Popup is rendered for this Dialog.Root; the trigger opens nothing.\",\n );\n }\n }, [ids.dialog]);\n\n // Library mode (both modal and non-modal) and non-modal JS-free mode need a click handler;\n // modal JS-free mode is the browser's command.\n const installClick = isLibrary || !modal;\n // A link or component render whose click this part handles: native `disabled` would mean\n // nothing on a link; the handler refuses and cancels, so `aria-disabled` is safe (#83). The\n // modal JS-free command needs native `disabled` to stop it.\n const focusable = installClick && !isHostButton(render);\n // Disabled in the `aria-disabled` branch: no `commandfor` / `command` either. Without native\n // `disabled` the element would still be a live invoker before hydration (a component render\n // spreading onto a real <button>), and the command would run (wave 9 review M1).\n const invoker = modal && !(focusable && disabled);\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (isLibrary) {\n // Cancelling the click cancels the command (it runs as the button's activation\n // behaviour, only if the click was not cancelled).\n event.preventDefault();\n if (disabled) return;\n ctx.requestOpen(event.nativeEvent, \"activate\");\n return;\n }\n if (disabled) {\n // A disabled link must not navigate either.\n event.preventDefault();\n return;\n }\n const dialog = dialogRef.current;\n if (dialog !== null && !dialog.open && dialog.isConnected) dialog.show();\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // `type` only applies to a <button>; on any other element the key is not owned.\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Trigger (in JS-free mode the\n // browser would run its command). Owned as absent in the focusable branch.\n disabled: focusable ? undefined : disabled,\n ...(focusable && { \"aria-disabled\": disabled ? true : undefined }),\n commandfor: invoker ? ids.dialog : undefined,\n command: invoker ? \"show-modal\" : undefined,\n // Only owned when installed, so a render element keeps its own `onClick` in modal JS-free\n // mode (Accordion's rule).\n ...(installClick && { onClick }),\n \"data-open\": dataAttr(state.open),\n \"data-pending\": dataAttr(state.pending),\n // Every mode: the styling hook agrees with the rendered `disabled` / `aria-disabled`\n // (conventions 1.2, wave 9 fold W8).\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n}\n\nDialogTrigger.displayName = \"DialogTrigger\";\n"],"mappings":";;;;;;;AAuCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;AAavC,SAAgB,cAAc,EAC5B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,iBAAiB,gBAAgB;CAC7C,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,EAAE,OAAO,KAAK,YAAY,cAAc;CAE9C,MAAM,QAA4B;EAChC,MAAM,IAAI;EAKV,SAAS,IAAI;EACb;CACF;CAEA,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,SAAS,QAAQ,YAAY,UACtD,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,yLAG/D;EAEF,WAAW,UAAU;EACrB,aAAa;GACX,IAAI,WAAW,YAAY,SAAS,WAAW,UAAU;EAC3D;CACF,GACA;EAAC;EAAW;EAAO;CAAU,CAC/B;CAGA,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,IAAI,SAAS,eAAe,IAAI,MAAM,MAAM,MAC1C,QAAQ,MACN,8FACF;CAEJ,GAAG,CAAC,IAAI,MAAM,CAAC;CAIf,MAAM,eAAe,aAAa,CAAC;CAInC,MAAM,YAAY,gBAAgB,CAAC,aAAa,MAAM;CAItD,MAAM,UAAU,SAAS,EAAE,aAAa;CACxC,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW;GAGb,MAAM,eAAe;GACrB,IAAI,UAAU;GACd,IAAI,YAAY,MAAM,aAAa,UAAU;GAC7C;EACF;EACA,IAAI,UAAU;GAEZ,MAAM,eAAe;GACrB;EACF;EACA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,QAAQ,OAAO,aAAa,OAAO,KAAK;CACzE;CAEA,MAAM,eAAiE;EAErE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAGvD,UAAU,YAAY,KAAA,IAAY;EAClC,GAAI,aAAa,EAAE,iBAAiB,WAAW,OAAO,KAAA,EAAU;EAChE,YAAY,UAAU,IAAI,SAAS,KAAA;EACnC,SAAS,UAAU,eAAe,KAAA;EAGlC,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,aAAa,SAAS,MAAM,IAAI;EAChC,gBAAgB,SAAS,MAAM,OAAO;EAGtC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,cAAc,cAAc"}
@@ -1,15 +1,15 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { useLatest } from "../internal/use-latest.mjs";
4
- import { useRender } from "../internal/use-render.mjs";
5
3
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
4
+ import { useRender } from "../internal/use-render.mjs";
5
+ import { useLatest } from "../internal/use-latest.mjs";
6
6
  import { FieldContext, FormContext } from "../internal/field-context.mjs";
7
7
  import { useFormOwnerEvent } from "../internal/use-form-owner-event.mjs";
8
8
  import { VALID, arraysEqual, focusTargetOf, normaliseErrorValue, snapshotValidity, supportsUserInvalid, validityElementOf, validityEquals } from "../internal/validity.mjs";
9
9
  import { collectInvalid } from "../internal/focus-first-invalid.mjs";
10
10
  import { use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
11
- import { jsx } from "react/jsx-runtime";
12
11
  import { useFormStatus } from "react-dom";
12
+ import { jsx } from "react/jsx-runtime";
13
13
  //#region src/field/field-root.tsx
14
14
  const isDev = process.env.NODE_ENV !== "production";
15
15
  const NONE = Object.freeze([]);
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { useLatest } from "../internal/use-latest.mjs";
4
3
  import { resolveStateValue } from "../internal/render-element.mjs";
5
4
  import { useRender } from "../internal/use-render.mjs";
5
+ import { useLatest } from "../internal/use-latest.mjs";
6
6
  import { FormContext } from "../internal/field-context.mjs";
7
7
  import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
8
8
  import { hasAnyError, sortByDocumentOrder } from "../internal/validity.mjs";
@@ -7,7 +7,10 @@ export interface FormSubmitState {
7
7
  readonly disabled: boolean;
8
8
  }
9
9
  export interface FormSubmitProps extends Omit<ComponentPropsWithRef<"button">, "type" | "className" | "style" | "children"> {
10
- /** Native `disabled`; owned. Never set by the library (conventions 3.6). */
10
+ /**
11
+ * Native `disabled`; owned. Never set by the library (conventions 3.6). Always native, also
12
+ * with a component `render`: a submitter has no handler to honour `aria-disabled` (#83).
13
+ */
11
14
  disabled?: boolean;
12
15
  className?: StateValue<string | undefined, FormSubmitState>;
13
16
  style?: StateValue<CSSProperties | undefined, FormSubmitState>;
@@ -1 +1 @@
1
- {"version":3,"file":"form-submit.d.mts","names":[],"sources":["../../src/form/form-submit.tsx"],"mappings":";;;iBAUiB;;WAEN;WACA;;iBAGM,wBAAwB,KACvC;;EAIA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;wBAYvC,aACd,UACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBAPF"}
1
+ {"version":3,"file":"form-submit.d.mts","names":[],"sources":["../../src/form/form-submit.tsx"],"mappings":";;;iBAUiB;;WAEN;WACA;;iBAGM,wBAAwB,KACvC;;;;;EAOA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;wBAYvC,aACd,UACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBAPF"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-submit.mjs","names":[],"sources":["../../src/form/form-submit.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormSubmitState {\n /** The enclosing form's submission is in flight (`useFormStatus().pending`). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface FormSubmitProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"type\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`; owned. Never set by the library (conventions 3.6). */\n disabled?: boolean;\n className?: StateValue<string | undefined, FormSubmitState>;\n style?: StateValue<CSSProperties | undefined, FormSubmitState>;\n children?: StateValue<ReactNode, FormSubmitState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, FormSubmitState>;\n}\n\n/**\n * The form's submit button: `<button type=\"submit\">` with `data-pending` while the enclosing\n * form's submission is in flight (`useFormStatus`). Works in any React-rendered form\n * (`Form.Root`, `Dialog.Form`, a plain `<form action>`). Never disabled while pending: block\n * repeat clicks with CSS on `[data-pending]` if you need to.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `disabled`, `data-pending`,\n * `data-disabled`.\n */\nexport function FormSubmit({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: FormSubmitProps) {\n const { pending } = useFormStatus();\n const warned = useRef(false);\n const ref = useCallback((el: HTMLElement) => {\n const submitter =\n el.tagName === \"BUTTON\" ||\n (el.tagName === \"INPUT\" && [\"submit\", \"image\"].includes((el as HTMLInputElement).type));\n if (isDev && !warned.current && !submitter) {\n warned.current = true;\n console.error(\n `Form.Submit: expected to render a <button> (or <input type=\"submit\">) but got ` +\n `<${el.tagName.toLowerCase()}>. Only a submit button submits the form natively.`,\n );\n }\n // A cleanup, so the merged ref never calls this with `null`.\n return () => {};\n }, []);\n const state: FormSubmitState = { pending, disabled };\n return useRender<ComponentPropsWithRef<\"button\">, FormSubmitState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"submit\" as const }),\n disabled,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref,\n });\n}\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;;AAQA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;AA6BvC,SAAgB,WAAW,EACzB,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,SAAS,OAAO,KAAK;CAC3B,MAAM,MAAM,aAAa,OAAoB;EAC3C,MAAM,YACJ,GAAG,YAAY,YACd,GAAG,YAAY,WAAW,CAAC,UAAU,OAAO,CAAC,CAAC,SAAU,GAAwB,IAAI;EACvF,IAAI,SAAS,CAAC,OAAO,WAAW,CAAC,WAAW;GAC1C,OAAO,UAAU;GACjB,QAAQ,MACN,kFACM,GAAG,QAAQ,YAAY,EAAE,mDACjC;EACF;EAEA,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CAEL,OAAO,UAA4D;EACjE,gBAAgB;EAChB;EACA,OAAA;GAJ+B;GAAS;EAIpC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"form-submit.mjs","names":[],"sources":["../../src/form/form-submit.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormSubmitState {\n /** The enclosing form's submission is in flight (`useFormStatus().pending`). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface FormSubmitProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"type\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Native `disabled`; owned. Never set by the library (conventions 3.6). Always native, also\n * with a component `render`: a submitter has no handler to honour `aria-disabled` (#83).\n */\n disabled?: boolean;\n className?: StateValue<string | undefined, FormSubmitState>;\n style?: StateValue<CSSProperties | undefined, FormSubmitState>;\n children?: StateValue<ReactNode, FormSubmitState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, FormSubmitState>;\n}\n\n/**\n * The form's submit button: `<button type=\"submit\">` with `data-pending` while the enclosing\n * form's submission is in flight (`useFormStatus`). Works in any React-rendered form\n * (`Form.Root`, `Dialog.Form`, a plain `<form action>`). Never disabled while pending: block\n * repeat clicks with CSS on `[data-pending]` if you need to.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `disabled`, `data-pending`,\n * `data-disabled`.\n */\nexport function FormSubmit({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: FormSubmitProps) {\n const { pending } = useFormStatus();\n const warned = useRef(false);\n const ref = useCallback((el: HTMLElement) => {\n const submitter =\n el.tagName === \"BUTTON\" ||\n (el.tagName === \"INPUT\" && [\"submit\", \"image\"].includes((el as HTMLInputElement).type));\n if (isDev && !warned.current && !submitter) {\n warned.current = true;\n console.error(\n `Form.Submit: expected to render a <button> (or <input type=\"submit\">) but got ` +\n `<${el.tagName.toLowerCase()}>. Only a submit button submits the form natively.`,\n );\n }\n // A cleanup, so the merged ref never calls this with `null`.\n return () => {};\n }, []);\n const state: FormSubmitState = { pending, disabled };\n return useRender<ComponentPropsWithRef<\"button\">, FormSubmitState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"submit\" as const }),\n disabled,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref,\n });\n}\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;;AAQA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;AAgCvC,SAAgB,WAAW,EACzB,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,SAAS,OAAO,KAAK;CAC3B,MAAM,MAAM,aAAa,OAAoB;EAC3C,MAAM,YACJ,GAAG,YAAY,YACd,GAAG,YAAY,WAAW,CAAC,UAAU,OAAO,CAAC,CAAC,SAAU,GAAwB,IAAI;EACvF,IAAI,SAAS,CAAC,OAAO,WAAW,CAAC,WAAW;GAC1C,OAAO,UAAU;GACjB,QAAQ,MACN,kFACM,GAAG,QAAQ,YAAY,EAAE,mDACjC;EACF;EAEA,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CAEL,OAAO,UAA4D;EACjE,gBAAgB;EAChB;EACA,OAAA;GAJ+B;GAAS;EAIpC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
package/dist/index.d.mts CHANGED
@@ -5,6 +5,8 @@ import { AccordionPanelProps, AccordionPanelState } from "./accordion/accordion-
5
5
  import { AccordionRootExclusiveProps, AccordionRootMultipleProps, AccordionRootProps, AccordionRootState, AccordionValueChangeDetails } from "./accordion/accordion-root.mjs";
6
6
  import { AccordionTriggerProps, AccordionTriggerState } from "./accordion/accordion-trigger.mjs";
7
7
  import { Accordion } from "./accordion/index.mjs";
8
+ import { Button, ButtonActionDetails, ButtonProps, ButtonState } from "./button/button.mjs";
9
+ import "./button/index.mjs";
8
10
  import { CheckboxGroupItemProps, CheckboxGroupItemState } from "./checkbox-group/checkbox-group-item.mjs";
9
11
  import { CheckboxGroupRootProps, CheckboxGroupRootState, CheckboxGroupValueChangeDetails } from "./checkbox-group/checkbox-group-root.mjs";
10
12
  import { CheckboxGroupLabelProps } from "./checkbox-group/checkbox-group-label.mjs";
@@ -47,7 +49,6 @@ import { ToggleGroupOrientation } from "./toggle-group/toggle-group-context.mjs"
47
49
  import { ToggleGroupItemProps, ToggleGroupItemState } from "./toggle-group/toggle-group-item.mjs";
48
50
  import { ToggleGroupExclusiveProps, ToggleGroupMultipleProps, ToggleGroupRootProps, ToggleGroupRootState, ToggleGroupValueChangeDetails } from "./toggle-group/toggle-group-root.mjs";
49
51
  import { ToggleGroup } from "./toggle-group/index.mjs";
50
- import { ToolbarButtonActionDetails, ToolbarButtonProps, ToolbarButtonState } from "./toolbar/toolbar-button.mjs";
51
52
  import { ToolbarOrientation, ToolbarRootProps, ToolbarRootState } from "./toolbar/toolbar-root.mjs";
52
53
  import { ToolbarSeparatorProps, ToolbarSeparatorState } from "./toolbar/toolbar-separator.mjs";
53
54
  import { Toolbar } from "./toolbar/index.mjs";
@@ -127,4 +128,4 @@ import { MeterLabelProps } from "./meter/meter-label.mjs";
127
128
  import { MeterTrackProps } from "./meter/meter-track.mjs";
128
129
  import { MeterValueProps } from "./meter/meter-value.mjs";
129
130
  import { Meter } from "./meter/index.mjs";
130
- export { Accordion, type AccordionItemProps, type AccordionItemState, type AccordionPanelProps, type AccordionPanelState, type AccordionRootExclusiveProps, type AccordionRootMultipleProps, type AccordionRootProps, type AccordionRootState, type AccordionTriggerProps, type AccordionTriggerState, type AccordionValueChangeDetails, type Action, type ActionDetails, type ChangeAction, type ChangeReason, Checkbox, type CheckboxCheckedChangeDetails, CheckboxGroup, type CheckboxGroupItemProps, type CheckboxGroupItemState, type CheckboxGroupLabelProps, type CheckboxGroupParentProps, type CheckboxGroupParentState, type CheckboxGroupRootProps, type CheckboxGroupRootState, type CheckboxGroupValueChangeDetails, type CheckboxProps, type CheckboxState, Combobox, type ComboboxClearProps, type ComboboxClearState, type ComboboxEmptyProps, type ComboboxEmptyState, type ComboboxGroupLabelProps, type ComboboxGroupLabelState, type ComboboxGroupProps, type ComboboxGroupState, type ComboboxInputProps, type ComboboxInputState, type ComboboxInputValueChangeDetails, type ComboboxLabelProps, type ComboboxLabelState, type ComboboxListProps, type ComboboxListState, type ComboboxOpenChangeDetails, type ComboboxOptionProps, type ComboboxOptionState, type ComboboxPopupProps, type ComboboxPopupState, type ComboboxRootProps, type ComboboxRootState, type ComboboxStatusProps, type ComboboxStatusState, type ComboboxTriggerProps, type ComboboxTriggerState, type ComboboxValueChangeDetails, Dialog, type DialogCloseDetails, type DialogCloseProps, type DialogCloseState, type DialogDescriptionProps, type DialogDescriptionState, type DialogFormProps, type DialogFormState, type DialogOpenChangeDetails, type DialogPopupProps, type DialogPopupState, type DialogRootProps, type DialogRootState, type DialogTitleProps, type DialogTitleState, type DialogTriggerProps, type DialogTriggerState, Field, type FieldControlProps, type FieldDescriptionProps, type FieldErrorProps, type FieldErrorValue, type FieldLabelProps, type FieldRootProps, type FieldState, type FieldValidate, type FieldValidity, Form, type FormErrors, type FormRootProps, type FormRootState, type FormSubmitProps, type FormSubmitState, Menu, type MenuAlign, type MenuCheckboxItemProps, type MenuCheckboxItemState, type MenuGroupLabelProps, type MenuGroupLabelState, type MenuGroupProps, type MenuGroupState, type MenuItemDetails, type MenuItemProps, type MenuItemState, type MenuOpenChangeDetails, type MenuPopupProps, type MenuPopupState, type MenuRadioGroupProps, type MenuRadioGroupState, type MenuRadioItemProps, type MenuRadioItemState, type MenuRootProps, type MenuRootState, type MenuSeparatorProps, type MenuSeparatorState, type MenuSide, type MenuSubmenuTriggerProps, type MenuSubmenuTriggerState, type MenuTriggerProps, type MenuTriggerState, Meter, type MeterIndicatorProps, type MeterLabelProps, type MeterRegion, type MeterRootProps, type MeterState, type MeterTrackProps, type MeterValueProps, NumberField, type NumberFieldButtonState, type NumberFieldDecrementProps, type NumberFieldIncrementProps, type NumberFieldInputProps, type NumberFieldLabelProps, type NumberFieldRootProps, type NumberFieldState, type NumberFieldValueChangeDetails, Popover, type PopoverAlign, type PopoverCloseProps, type PopoverCloseState, type PopoverDescriptionProps, type PopoverDescriptionState, type PopoverOpenChangeDetails, type PopoverPopupProps, type PopoverPopupState, type PopoverRootProps, type PopoverRootState, type PopoverSide, type PopoverTitleProps, type PopoverTitleState, type PopoverTriggerProps, type PopoverTriggerState, Progress, type ProgressIndicatorProps, type ProgressLabelProps, type ProgressRootProps, type ProgressState, type ProgressTrackProps, type ProgressValueProps, RadioGroup, type RadioGroupItemProps, type RadioGroupItemState, type RadioGroupLabelProps, type RadioGroupRootProps, type RadioGroupRootState, type RadioGroupValueChangeDetails, type RenderProp, Select, type SelectGroupLabelProps, type SelectGroupProps, type SelectOptionProps, type SelectOptionState, type SelectRootProps, type SelectRootState, type SelectSeparatorProps, type SelectTriggerProps, type SelectValueChangeDetails, type SelectValueProps, Slider, type SliderIndicatorProps, type SliderIndicatorState, type SliderLabelProps, type SliderOrientation, type SliderRootProps, type SliderRootState, type SliderThumbProps, type SliderThumbState, type SliderTrackProps, type SliderTrackState, type SliderValue, type SliderValueChangeDetails, type SliderValueCommitDetails, type SliderValueProps, type SliderValueState, type StateValue, Switch, type SwitchCheckedChangeDetails, type SwitchProps, type SwitchState, Tabs, type TabsActivationMode, type TabsIndicatorProps, type TabsIndicatorState, type TabsListProps, type TabsListState, type TabsOrientation, type TabsPanelProps, type TabsPanelState, type TabsRootProps, type TabsRootState, type TabsTabProps, type TabsTabState, type TabsValueChangeDetails, Toast, type ToastActionDetails, type ToastActionOptions, type ToastActionProps, type ToastActionState, type ToastCloseDetails, type ToastCloseProps, type ToastCloseState, type ToastDescriptionProps, type ToastItem, type ToastManager, type ToastOptions, type ToastPriority, type ToastPromiseOptions, type ToastRootProps, type ToastRootState, type ToastTitleProps, type ToastViewportProps, type ToastViewportState, Toggle, ToggleGroup, type ToggleGroupExclusiveProps, type ToggleGroupItemProps, type ToggleGroupItemState, type ToggleGroupMultipleProps, type ToggleGroupOrientation, type ToggleGroupRootProps, type ToggleGroupRootState, type ToggleGroupValueChangeDetails, type TogglePressedChangeDetails, type ToggleProps, type ToggleState, Toolbar, type ToolbarButtonActionDetails, type ToolbarButtonProps, type ToolbarButtonState, type ToolbarOrientation, type ToolbarRootProps, type ToolbarRootState, type ToolbarSeparatorProps, type ToolbarSeparatorState, Tooltip, type TooltipAlign, type TooltipPopupProps, type TooltipPopupState, type TooltipProvides, type TooltipRootProps, type TooltipRootState, type TooltipSide, type TooltipTriggerProps, type TooltipTriggerState, type UrgentChangeAction, createToastManager, toast };
131
+ export { Accordion, type AccordionItemProps, type AccordionItemState, type AccordionPanelProps, type AccordionPanelState, type AccordionRootExclusiveProps, type AccordionRootMultipleProps, type AccordionRootProps, type AccordionRootState, type AccordionTriggerProps, type AccordionTriggerState, type AccordionValueChangeDetails, type Action, type ActionDetails, Button, type ButtonActionDetails, type ButtonProps, type ButtonState, type ChangeAction, type ChangeReason, Checkbox, type CheckboxCheckedChangeDetails, CheckboxGroup, type CheckboxGroupItemProps, type CheckboxGroupItemState, type CheckboxGroupLabelProps, type CheckboxGroupParentProps, type CheckboxGroupParentState, type CheckboxGroupRootProps, type CheckboxGroupRootState, type CheckboxGroupValueChangeDetails, type CheckboxProps, type CheckboxState, Combobox, type ComboboxClearProps, type ComboboxClearState, type ComboboxEmptyProps, type ComboboxEmptyState, type ComboboxGroupLabelProps, type ComboboxGroupLabelState, type ComboboxGroupProps, type ComboboxGroupState, type ComboboxInputProps, type ComboboxInputState, type ComboboxInputValueChangeDetails, type ComboboxLabelProps, type ComboboxLabelState, type ComboboxListProps, type ComboboxListState, type ComboboxOpenChangeDetails, type ComboboxOptionProps, type ComboboxOptionState, type ComboboxPopupProps, type ComboboxPopupState, type ComboboxRootProps, type ComboboxRootState, type ComboboxStatusProps, type ComboboxStatusState, type ComboboxTriggerProps, type ComboboxTriggerState, type ComboboxValueChangeDetails, Dialog, type DialogCloseDetails, type DialogCloseProps, type DialogCloseState, type DialogDescriptionProps, type DialogDescriptionState, type DialogFormProps, type DialogFormState, type DialogOpenChangeDetails, type DialogPopupProps, type DialogPopupState, type DialogRootProps, type DialogRootState, type DialogTitleProps, type DialogTitleState, type DialogTriggerProps, type DialogTriggerState, Field, type FieldControlProps, type FieldDescriptionProps, type FieldErrorProps, type FieldErrorValue, type FieldLabelProps, type FieldRootProps, type FieldState, type FieldValidate, type FieldValidity, Form, type FormErrors, type FormRootProps, type FormRootState, type FormSubmitProps, type FormSubmitState, Menu, type MenuAlign, type MenuCheckboxItemProps, type MenuCheckboxItemState, type MenuGroupLabelProps, type MenuGroupLabelState, type MenuGroupProps, type MenuGroupState, type MenuItemDetails, type MenuItemProps, type MenuItemState, type MenuOpenChangeDetails, type MenuPopupProps, type MenuPopupState, type MenuRadioGroupProps, type MenuRadioGroupState, type MenuRadioItemProps, type MenuRadioItemState, type MenuRootProps, type MenuRootState, type MenuSeparatorProps, type MenuSeparatorState, type MenuSide, type MenuSubmenuTriggerProps, type MenuSubmenuTriggerState, type MenuTriggerProps, type MenuTriggerState, Meter, type MeterIndicatorProps, type MeterLabelProps, type MeterRegion, type MeterRootProps, type MeterState, type MeterTrackProps, type MeterValueProps, NumberField, type NumberFieldButtonState, type NumberFieldDecrementProps, type NumberFieldIncrementProps, type NumberFieldInputProps, type NumberFieldLabelProps, type NumberFieldRootProps, type NumberFieldState, type NumberFieldValueChangeDetails, Popover, type PopoverAlign, type PopoverCloseProps, type PopoverCloseState, type PopoverDescriptionProps, type PopoverDescriptionState, type PopoverOpenChangeDetails, type PopoverPopupProps, type PopoverPopupState, type PopoverRootProps, type PopoverRootState, type PopoverSide, type PopoverTitleProps, type PopoverTitleState, type PopoverTriggerProps, type PopoverTriggerState, Progress, type ProgressIndicatorProps, type ProgressLabelProps, type ProgressRootProps, type ProgressState, type ProgressTrackProps, type ProgressValueProps, RadioGroup, type RadioGroupItemProps, type RadioGroupItemState, type RadioGroupLabelProps, type RadioGroupRootProps, type RadioGroupRootState, type RadioGroupValueChangeDetails, type RenderProp, Select, type SelectGroupLabelProps, type SelectGroupProps, type SelectOptionProps, type SelectOptionState, type SelectRootProps, type SelectRootState, type SelectSeparatorProps, type SelectTriggerProps, type SelectValueChangeDetails, type SelectValueProps, Slider, type SliderIndicatorProps, type SliderIndicatorState, type SliderLabelProps, type SliderOrientation, type SliderRootProps, type SliderRootState, type SliderThumbProps, type SliderThumbState, type SliderTrackProps, type SliderTrackState, type SliderValue, type SliderValueChangeDetails, type SliderValueCommitDetails, type SliderValueProps, type SliderValueState, type StateValue, Switch, type SwitchCheckedChangeDetails, type SwitchProps, type SwitchState, Tabs, type TabsActivationMode, type TabsIndicatorProps, type TabsIndicatorState, type TabsListProps, type TabsListState, type TabsOrientation, type TabsPanelProps, type TabsPanelState, type TabsRootProps, type TabsRootState, type TabsTabProps, type TabsTabState, type TabsValueChangeDetails, Toast, type ToastActionDetails, type ToastActionOptions, type ToastActionProps, type ToastActionState, type ToastCloseDetails, type ToastCloseProps, type ToastCloseState, type ToastDescriptionProps, type ToastItem, type ToastManager, type ToastOptions, type ToastPriority, type ToastPromiseOptions, type ToastRootProps, type ToastRootState, type ToastTitleProps, type ToastViewportProps, type ToastViewportState, Toggle, ToggleGroup, type ToggleGroupExclusiveProps, type ToggleGroupItemProps, type ToggleGroupItemState, type ToggleGroupMultipleProps, type ToggleGroupOrientation, type ToggleGroupRootProps, type ToggleGroupRootState, type ToggleGroupValueChangeDetails, type TogglePressedChangeDetails, type ToggleProps, type ToggleState, Toolbar, type ToolbarOrientation, type ToolbarRootProps, type ToolbarRootState, type ToolbarSeparatorProps, type ToolbarSeparatorState, Tooltip, type TooltipAlign, type TooltipPopupProps, type TooltipPopupState, type TooltipProvides, type TooltipRootProps, type TooltipRootState, type TooltipSide, type TooltipTriggerProps, type TooltipTriggerState, type UrgentChangeAction, createToastManager, toast };
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { Button } from "./button/button.mjs";
1
2
  import { Toggle } from "./toggle/toggle.mjs";
2
3
  import { ToggleGroup } from "./toggle-group/index.mjs";
3
4
  import { Toolbar } from "./toolbar/index.mjs";
@@ -21,4 +22,4 @@ import { Field } from "./field/index.mjs";
21
22
  import { Form } from "./form/index.mjs";
22
23
  import { Progress } from "./progress/index.mjs";
23
24
  import { Meter } from "./meter/index.mjs";
24
- export { Accordion, Checkbox, CheckboxGroup, Combobox, Dialog, Field, Form, Menu, Meter, NumberField, Popover, Progress, RadioGroup, Select, Slider, Switch, Tabs, Toast, Toggle, ToggleGroup, Toolbar, Tooltip, createToastManager, toast };
25
+ export { Accordion, Button, Checkbox, CheckboxGroup, Combobox, Dialog, Field, Form, Menu, Meter, NumberField, Popover, Progress, RadioGroup, Select, Slider, Switch, Tabs, Toast, Toggle, ToggleGroup, Toolbar, Tooltip, createToastManager, toast };
@@ -5,12 +5,25 @@ function resolveStateValue(value, state) {
5
5
  return typeof value === "function" ? value(state) : value;
6
6
  }
7
7
  /**
8
- * Whether a part whose default element is a `<button>` should add `type="button"`: no `render`,
9
- * a `render` element whose type is the intrinsic `"button"`, or the function form (it receives
10
- * button props and decides itself what to spread). An element of any other type (an `<a>`, a
11
- * router `Link`) does not get it: `type` means something else, or nothing, there.
8
+ * Whether a part whose default element is a `<button>` should add `type="button"` (conventions
9
+ * 1.2): no `render`, the function form (it receives button props and decides itself what to
10
+ * spread), a host `<button>` element, or a **component** element (assumed to render a
11
+ * `<button>`; if it renders a link, `type` is a harmless ignored attribute, while a missing
12
+ * `type` on a `<button>` in a form is a silent submit). Only a host element of another tag (an
13
+ * `<a>`) does not get it.
12
14
  */
13
15
  function rendersButton(render) {
16
+ if (render === void 0 || typeof render === "function") return true;
17
+ if (!isValidElement(render)) return false;
18
+ return typeof render.type !== "string" || render.type === "button";
19
+ }
20
+ /**
21
+ * Whether the rendered element is certainly a host `<button>`: no `render`, the function form, or
22
+ * a host `"button"` element. The strict predicate for native `disabled`, which means nothing on a
23
+ * link and would expose it as enabled to assistive technology; anything else (a link, a
24
+ * component) takes the `aria-disabled` branch (design/components/button.md, review fold F1).
25
+ */
26
+ function isHostButton(render) {
14
27
  if (render === void 0 || typeof render === "function") return true;
15
28
  return isValidElement(render) && render.type === "button";
16
29
  }
@@ -49,6 +62,6 @@ function renderElement(options) {
49
62
  return createElement(defaultElement, resolved);
50
63
  }
51
64
  //#endregion
52
- export { mergeRenderRef, renderElement, rendersButton, resolveStateValue };
65
+ export { isHostButton, mergeRenderRef, renderElement, rendersButton, resolveStateValue };
53
66
 
54
67
  //# sourceMappingURL=render-element.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-element.mjs","names":[],"sources":["../../src/internal/render-element.ts"],"sourcesContent":["// No \"use client\": the pure half of `useRender`, usable from a directive-free part (a Root that\n// may run as a Server Component, design/components/progress.md 8.1 and implementation note 14.1).\nimport { cloneElement, createElement, isValidElement } from \"react\";\nimport type { CSSProperties, ReactNode, Ref } from \"react\";\nimport { mergeProps, mergeRefs } from \"./merge-props.js\";\nimport type { RenderProp, StateValue } from \"./types.js\";\n\nexport interface ElementLikeProps {\n className?: string | undefined;\n style?: CSSProperties | undefined;\n children?: ReactNode;\n ref?: Ref<unknown> | undefined;\n}\n\n/** `data-*` attributes are valid JSX but absent from the element prop types. */\nexport type DataAttributes = { [key: `data-${string}`]: string | undefined };\n\nexport interface RenderElementOptions<Props extends ElementLikeProps, State> {\n /** Intrinsic element rendered when `render` is not given. */\n defaultElement: string;\n render: RenderProp<Props, State> | undefined;\n state: State;\n className: StateValue<string | undefined, State> | undefined;\n style: StateValue<CSSProperties | undefined, State> | undefined;\n children: StateValue<ReactNode, State> | undefined;\n /**\n * The part's pass-through props for the element. A `render` element's own props win over\n * these. Their `ref` is replaced by `ref` below.\n */\n props: Omit<Props, \"className\" | \"style\" | \"children\"> & DataAttributes;\n /**\n * Library-controlled attributes the part computes (`role`, `aria-*` state, id wiring, `data-*`\n * state, the library's own event handlers). These win over a `render` element's props\n * (conventions 1.2).\n */\n libraryProps?: Partial<Omit<Props, \"className\" | \"style\" | \"children\">> & DataAttributes;\n /**\n * The element's ref, already merged from the library's ref, the consumer's `ref` and the\n * `render` element's ref. The `render` element's own ref is not attached separately.\n */\n ref: Ref<unknown> | undefined;\n}\n\nexport function resolveStateValue<T, State>(value: StateValue<T, State>, state: State): T {\n return typeof value === \"function\" ? (value as (state: State) => T)(state) : value;\n}\n\n/**\n * Whether a part whose default element is a `<button>` should add `type=\"button\"`: no `render`,\n * a `render` element whose type is the intrinsic `\"button\"`, or the function form (it receives\n * button props and decides itself what to spread). An element of any other type (an `<a>`, a\n * router `Link`) does not get it: `type` means something else, or nothing, there.\n */\nexport function rendersButton<Props, State>(render: RenderProp<Props, State> | undefined): boolean {\n if (render === undefined || typeof render === \"function\") return true;\n return isValidElement(render) && render.type === \"button\";\n}\n\n/**\n * The ref of a part with no library ref, merged from the consumer's `ref` and a `render`\n * element's ref without memoisation (a directive-free part cannot `useMemo`, design\n * progress.md 14.1). A fresh callback ref per render is correct because every merged callback\n * returns a cleanup; a single ref is passed through untouched.\n */\nexport function mergeRenderRef(\n consumerRef: Ref<unknown> | undefined,\n render: unknown,\n): Ref<unknown> | undefined {\n const elementRef = isValidElement<ElementLikeProps>(render) ? render.props.ref : undefined;\n if (consumerRef != null && elementRef != null) return mergeRefs(consumerRef, elementRef);\n return consumerRef ?? elementRef;\n}\n\n/**\n * Resolves `render` and the function-form `className` / `style` / `children` of a part against\n * its state and produces the element (conventions 1.2, 1.3, 1.4). Pure: the caller merges the\n * refs (`useRender` memoises the merge; a directive-free part merges unmemoised).\n */\nexport function renderElement<Props extends ElementLikeProps, State>(\n options: RenderElementOptions<Props, State>,\n): ReactNode {\n const { defaultElement, render, state, className, style, children, props, libraryProps, ref } =\n options;\n\n const partProps = {\n ...props,\n ref,\n className: resolveStateValue(className, state),\n style: resolveStateValue(style, state),\n children: resolveStateValue(children, state),\n } as Record<string, unknown>;\n const controlled = (libraryProps ?? {}) as Record<string, unknown>;\n\n if (isValidElement<ElementLikeProps>(render)) {\n // The element's ref is already part of `ref`.\n const { ref: _ignored, ...elementProps } = render.props as Record<string, unknown>;\n return cloneElement(render, mergeProps(partProps, elementProps, controlled) as Partial<Props>);\n }\n const resolved = mergeProps(partProps, {}, controlled) as unknown as Props;\n if (typeof render === \"function\") {\n return render(resolved, state);\n }\n return createElement(defaultElement, resolved);\n}\n"],"mappings":";;;AA2CA,SAAgB,kBAA4B,OAA6B,OAAiB;CACxF,OAAO,OAAO,UAAU,aAAc,MAA8B,KAAK,IAAI;AAC/E;;;;;;;AAQA,SAAgB,cAA4B,QAAuD;CACjG,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,YAAY,OAAO;CACjE,OAAO,eAAe,MAAM,KAAK,OAAO,SAAS;AACnD;;;;;;;AAQA,SAAgB,eACd,aACA,QAC0B;CAC1B,MAAM,aAAa,eAAiC,MAAM,IAAI,OAAO,MAAM,MAAM,KAAA;CACjF,IAAI,eAAe,QAAQ,cAAc,MAAM,OAAO,UAAU,aAAa,UAAU;CACvF,OAAO,eAAe;AACxB;;;;;;AAOA,SAAgB,cACd,SACW;CACX,MAAM,EAAE,gBAAgB,QAAQ,OAAO,WAAW,OAAO,UAAU,OAAO,cAAc,QACtF;CAEF,MAAM,YAAY;EAChB,GAAG;EACH;EACA,WAAW,kBAAkB,WAAW,KAAK;EAC7C,OAAO,kBAAkB,OAAO,KAAK;EACrC,UAAU,kBAAkB,UAAU,KAAK;CAC7C;CACA,MAAM,aAAc,gBAAgB,CAAC;CAErC,IAAI,eAAiC,MAAM,GAAG;EAE5C,MAAM,EAAE,KAAK,UAAU,GAAG,iBAAiB,OAAO;EAClD,OAAO,aAAa,QAAQ,WAAW,WAAW,cAAc,UAAU,CAAmB;CAC/F;CACA,MAAM,WAAW,WAAW,WAAW,CAAC,GAAG,UAAU;CACrD,IAAI,OAAO,WAAW,YACpB,OAAO,OAAO,UAAU,KAAK;CAE/B,OAAO,cAAc,gBAAgB,QAAQ;AAC/C"}
1
+ {"version":3,"file":"render-element.mjs","names":[],"sources":["../../src/internal/render-element.ts"],"sourcesContent":["// No \"use client\": the pure half of `useRender`, usable from a directive-free part (a Root that\n// may run as a Server Component, design/components/progress.md 8.1 and implementation note 14.1).\nimport { cloneElement, createElement, isValidElement } from \"react\";\nimport type { CSSProperties, ReactNode, Ref } from \"react\";\nimport { mergeProps, mergeRefs } from \"./merge-props.js\";\nimport type { RenderProp, StateValue } from \"./types.js\";\n\nexport interface ElementLikeProps {\n className?: string | undefined;\n style?: CSSProperties | undefined;\n children?: ReactNode;\n ref?: Ref<unknown> | undefined;\n}\n\n/** `data-*` attributes are valid JSX but absent from the element prop types. */\nexport type DataAttributes = { [key: `data-${string}`]: string | undefined };\n\nexport interface RenderElementOptions<Props extends ElementLikeProps, State> {\n /** Intrinsic element rendered when `render` is not given. */\n defaultElement: string;\n render: RenderProp<Props, State> | undefined;\n state: State;\n className: StateValue<string | undefined, State> | undefined;\n style: StateValue<CSSProperties | undefined, State> | undefined;\n children: StateValue<ReactNode, State> | undefined;\n /**\n * The part's pass-through props for the element. A `render` element's own props win over\n * these. Their `ref` is replaced by `ref` below.\n */\n props: Omit<Props, \"className\" | \"style\" | \"children\"> & DataAttributes;\n /**\n * Library-controlled attributes the part computes (`role`, `aria-*` state, id wiring, `data-*`\n * state, the library's own event handlers). These win over a `render` element's props\n * (conventions 1.2).\n */\n libraryProps?: Partial<Omit<Props, \"className\" | \"style\" | \"children\">> & DataAttributes;\n /**\n * The element's ref, already merged from the library's ref, the consumer's `ref` and the\n * `render` element's ref. The `render` element's own ref is not attached separately.\n */\n ref: Ref<unknown> | undefined;\n}\n\nexport function resolveStateValue<T, State>(value: StateValue<T, State>, state: State): T {\n return typeof value === \"function\" ? (value as (state: State) => T)(state) : value;\n}\n\n/**\n * Whether a part whose default element is a `<button>` should add `type=\"button\"` (conventions\n * 1.2): no `render`, the function form (it receives button props and decides itself what to\n * spread), a host `<button>` element, or a **component** element (assumed to render a\n * `<button>`; if it renders a link, `type` is a harmless ignored attribute, while a missing\n * `type` on a `<button>` in a form is a silent submit). Only a host element of another tag (an\n * `<a>`) does not get it.\n */\nexport function rendersButton<Props, State>(render: RenderProp<Props, State> | undefined): boolean {\n if (render === undefined || typeof render === \"function\") return true;\n if (!isValidElement(render)) return false;\n return typeof render.type !== \"string\" || render.type === \"button\";\n}\n\n/**\n * Whether the rendered element is certainly a host `<button>`: no `render`, the function form, or\n * a host `\"button\"` element. The strict predicate for native `disabled`, which means nothing on a\n * link and would expose it as enabled to assistive technology; anything else (a link, a\n * component) takes the `aria-disabled` branch (design/components/button.md, review fold F1).\n */\nexport function isHostButton<Props, State>(render: RenderProp<Props, State> | undefined): boolean {\n if (render === undefined || typeof render === \"function\") return true;\n return isValidElement(render) && render.type === \"button\";\n}\n\n/**\n * The ref of a part with no library ref, merged from the consumer's `ref` and a `render`\n * element's ref without memoisation (a directive-free part cannot `useMemo`, design\n * progress.md 14.1). A fresh callback ref per render is correct because every merged callback\n * returns a cleanup; a single ref is passed through untouched.\n */\nexport function mergeRenderRef(\n consumerRef: Ref<unknown> | undefined,\n render: unknown,\n): Ref<unknown> | undefined {\n const elementRef = isValidElement<ElementLikeProps>(render) ? render.props.ref : undefined;\n if (consumerRef != null && elementRef != null) return mergeRefs(consumerRef, elementRef);\n return consumerRef ?? elementRef;\n}\n\n/**\n * Resolves `render` and the function-form `className` / `style` / `children` of a part against\n * its state and produces the element (conventions 1.2, 1.3, 1.4). Pure: the caller merges the\n * refs (`useRender` memoises the merge; a directive-free part merges unmemoised).\n */\nexport function renderElement<Props extends ElementLikeProps, State>(\n options: RenderElementOptions<Props, State>,\n): ReactNode {\n const { defaultElement, render, state, className, style, children, props, libraryProps, ref } =\n options;\n\n const partProps = {\n ...props,\n ref,\n className: resolveStateValue(className, state),\n style: resolveStateValue(style, state),\n children: resolveStateValue(children, state),\n } as Record<string, unknown>;\n const controlled = (libraryProps ?? {}) as Record<string, unknown>;\n\n if (isValidElement<ElementLikeProps>(render)) {\n // The element's ref is already part of `ref`.\n const { ref: _ignored, ...elementProps } = render.props as Record<string, unknown>;\n return cloneElement(render, mergeProps(partProps, elementProps, controlled) as Partial<Props>);\n }\n const resolved = mergeProps(partProps, {}, controlled) as unknown as Props;\n if (typeof render === \"function\") {\n return render(resolved, state);\n }\n return createElement(defaultElement, resolved);\n}\n"],"mappings":";;;AA2CA,SAAgB,kBAA4B,OAA6B,OAAiB;CACxF,OAAO,OAAO,UAAU,aAAc,MAA8B,KAAK,IAAI;AAC/E;;;;;;;;;AAUA,SAAgB,cAA4B,QAAuD;CACjG,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,YAAY,OAAO;CACjE,IAAI,CAAC,eAAe,MAAM,GAAG,OAAO;CACpC,OAAO,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS;AAC5D;;;;;;;AAQA,SAAgB,aAA2B,QAAuD;CAChG,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,YAAY,OAAO;CACjE,OAAO,eAAe,MAAM,KAAK,OAAO,SAAS;AACnD;;;;;;;AAQA,SAAgB,eACd,aACA,QAC0B;CAC1B,MAAM,aAAa,eAAiC,MAAM,IAAI,OAAO,MAAM,MAAM,KAAA;CACjF,IAAI,eAAe,QAAQ,cAAc,MAAM,OAAO,UAAU,aAAa,UAAU;CACvF,OAAO,eAAe;AACxB;;;;;;AAOA,SAAgB,cACd,SACW;CACX,MAAM,EAAE,gBAAgB,QAAQ,OAAO,WAAW,OAAO,UAAU,OAAO,cAAc,QACtF;CAEF,MAAM,YAAY;EAChB,GAAG;EACH;EACA,WAAW,kBAAkB,WAAW,KAAK;EAC7C,OAAO,kBAAkB,OAAO,KAAK;EACrC,UAAU,kBAAkB,UAAU,KAAK;CAC7C;CACA,MAAM,aAAc,gBAAgB,CAAC;CAErC,IAAI,eAAiC,MAAM,GAAG;EAE5C,MAAM,EAAE,KAAK,UAAU,GAAG,iBAAiB,OAAO;EAClD,OAAO,aAAa,QAAQ,WAAW,WAAW,cAAc,UAAU,CAAmB;CAC/F;CACA,MAAM,WAAW,WAAW,WAAW,CAAC,GAAG,UAAU;CACrD,IAAI,OAAO,WAAW,YACpB,OAAO,OAAO,UAAU,KAAK;CAE/B,OAAO,cAAc,gBAAgB,QAAQ;AAC/C"}
@@ -7,19 +7,25 @@ function report(error) {
7
7
  else console.error(error);
8
8
  }
9
9
  /**
10
- * The transition of a pure-trigger part: an `action` with no state of its own (`Dialog.Close`,
11
- * `Popover.Close`, `Menu.Item`; conventions 3.4). Same superseded-rejection surfacing as
12
- * `useControllableAction`: React keeps only the newest thenable per `useTransition`, so the
13
- * rejection of a run that a later run superseded would be dropped. The hook catches it and throws
14
- * it from render, so it reaches the same boundary as the latest run's rejection does.
10
+ * The transition of a pure-trigger part: an `action` with no state of its own (`Button`,
11
+ * `Dialog.Close`, `Popover.Close`, `Menu.Item`, `Toast.Action`; conventions 3.4). A second `run`
12
+ * while one is in flight is ignored by default (conventions 2.5, design/components/button.md 16).
13
+ *
14
+ * With `ignoreWhilePending: false` (the continuous controls' steps) runs overlap, and the hook
15
+ * surfaces superseded rejections as `useControllableAction` does: React keeps only the newest
16
+ * thenable per `useTransition`, so the rejection of a run that a later run superseded would be
17
+ * dropped. The hook catches it and throws it from render, so it reaches the same boundary as the
18
+ * latest run's rejection does.
15
19
  */
16
- function useActionTrigger(openGeneration) {
20
+ function useActionTrigger(options = {}) {
21
+ const { openGeneration, ignoreWhilePending = true } = options;
17
22
  const [supersededError, setSupersededError] = useState(null);
18
23
  const hasSupersededError = useRef(false);
19
24
  if (supersededError !== null) throw supersededError.error;
20
25
  const [pending, startOwnTransition] = useTransition();
21
26
  const latest = useRef(0);
22
27
  const mounted = useRef(false);
28
+ const inFlight = useRef(0);
23
29
  useEffect(() => {
24
30
  mounted.current = true;
25
31
  return () => {
@@ -27,12 +33,15 @@ function useActionTrigger(openGeneration) {
27
33
  };
28
34
  }, []);
29
35
  const run = (work, then) => {
36
+ if (ignoreWhilePending && inFlight.current > 0) return false;
37
+ inFlight.current++;
30
38
  const id = ++latest.current;
31
39
  const generation = openGeneration?.();
32
40
  startOwnTransition(async () => {
33
41
  try {
34
42
  await work();
35
43
  } catch (error) {
44
+ inFlight.current--;
36
45
  if (id === latest.current) throw error;
37
46
  if (!mounted.current) return;
38
47
  if (hasSupersededError.current) {
@@ -43,9 +52,11 @@ function useActionTrigger(openGeneration) {
43
52
  setSupersededError({ error });
44
53
  return;
45
54
  }
55
+ inFlight.current--;
46
56
  if (openGeneration !== void 0 && openGeneration() !== generation) return;
47
57
  then?.();
48
58
  });
59
+ return true;
49
60
  };
50
61
  return {
51
62
  pending,
@@ -1 +1 @@
1
- {"version":3,"file":"use-action-trigger.mjs","names":[],"sources":["../../src/internal/use-action-trigger.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState, useTransition } from \"react\";\n\nexport interface ActionTrigger {\n /** A run started through this hook is in flight (the part's own transition). */\n readonly pending: boolean;\n /**\n * Runs `work` inside the part's own transition and, once it resolves, `then` (still inside the\n * transition: a Close part's close, a menu item's close). A rejection skips `then` and reaches\n * the nearest Error Boundary above the part, also when a later run superseded this one. With\n * `openGeneration` (the hook's argument), `then` is skipped when a newer open of the overlay\n * started after the run began.\n */\n readonly run: (work: () => unknown, then?: () => void) => void;\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The transition of a pure-trigger part: an `action` with no state of its own (`Dialog.Close`,\n * `Popover.Close`, `Menu.Item`; conventions 3.4). Same superseded-rejection surfacing as\n * `useControllableAction`: React keeps only the newest thenable per `useTransition`, so the\n * rejection of a run that a later run superseded would be dropped. The hook catches it and throws\n * it from render, so it reaches the same boundary as the latest run's rejection does.\n */\nexport function useActionTrigger(openGeneration?: () => number): ActionTrigger {\n // Thrown before `useTransition` on purpose (see use-controllable-action.ts): in the retry of\n // an errored render `useTransition` would suspend on the newer run's pending thenable.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const mounted = useRef(false);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = (work: () => unknown, then?: () => void): void => {\n const id = ++latest.current;\n // The open this run belongs to (design/components/popover.md, review M2 / PO rule): if the\n // user dismisses and re-opens while the action runs, its close must not close the new open.\n const generation = openGeneration?.();\n startOwnTransition(async () => {\n try {\n await work();\n } catch (error) {\n // The latest run's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // After an await, outside the transition: urgent, so the part re-renders and throws.\n setSupersededError({ error });\n return;\n }\n if (openGeneration !== undefined && openGeneration() !== generation) return;\n then?.();\n });\n };\n\n return { pending, run };\n}\n"],"mappings":";;;;AAiBA,SAAS,OAAO,OAAsB;CACpC,IAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK;MACnD,QAAQ,MAAM,KAAK;AAC1B;;;;;;;;AASA,SAAgB,iBAAiB,gBAA8C;CAG7E,MAAM,CAAC,iBAAiB,sBAAsB,SAA6C,IAAI;CAC/F,MAAM,qBAAqB,OAAO,KAAK;CACvC,IAAI,oBAAoB,MAAM,MAAM,gBAAgB;CAEpD,MAAM,CAAC,SAAS,sBAAsB,cAAc;CACpD,MAAM,SAAS,OAAO,CAAC;CACvB,MAAM,UAAU,OAAO,KAAK;CAE5B,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,OAAO,MAAqB,SAA4B;EAC5D,MAAM,KAAK,EAAE,OAAO;EAGpB,MAAM,aAAa,iBAAiB;EACpC,mBAAmB,YAAY;GAC7B,IAAI;IACF,MAAM,KAAK;GACb,SAAS,OAAO;IAEd,IAAI,OAAO,OAAO,SAAS,MAAM;IACjC,IAAI,CAAC,QAAQ,SAAS;IACtB,IAAI,mBAAmB,SAAS;KAC9B,OAAO,KAAK;KACZ;IACF;IACA,mBAAmB,UAAU;IAE7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B;GACF;GACA,IAAI,mBAAmB,KAAA,KAAa,eAAe,MAAM,YAAY;GACrE,OAAO;EACT,CAAC;CACH;CAEA,OAAO;EAAE;EAAS;CAAI;AACxB"}
1
+ {"version":3,"file":"use-action-trigger.mjs","names":[],"sources":["../../src/internal/use-action-trigger.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState, useTransition } from \"react\";\n\nexport interface ActionTriggerOptions {\n /**\n * Open generation of the overlay a `then` belongs to: `then` is skipped when a newer open of\n * the overlay started after the run began.\n */\n readonly openGeneration?: (() => number) | undefined;\n /**\n * Ignore `run` while a run is in flight (conventions 2.5): `run` returns `false` and starts\n * nothing. In flight means the hook's synchronous in-flight counter is non-zero (it also sees\n * two activations in one task, before React re-rendered); the last render's `pending` is not\n * consulted, so a click in a settled-but-held commit window runs. Default `true`: every pure\n * trigger ignores re-entry (#79, #81). Only the continuous controls (`Slider`, `NumberField`)\n * pass `false`, because a step is not a re-entry: every step must run.\n */\n readonly ignoreWhilePending?: boolean | undefined;\n}\n\nexport interface ActionTrigger {\n /** A run started through this hook is in flight (the part's own transition). */\n readonly pending: boolean;\n /**\n * Runs `work` inside the part's own transition and, once it resolves, `then` (still inside the\n * transition: a Close part's close, a menu item's close). A rejection skips `then` and reaches\n * the nearest Error Boundary above the part, also when a later run superseded this one. With\n * `openGeneration`, `then` is skipped when a newer open of the overlay started after the run\n * began. Returns `false` when the run was ignored (a run is in flight and `ignoreWhilePending`\n * is on, the default), so the part can\n * `preventDefault()` the activation, and `true` otherwise.\n */\n readonly run: (work: () => unknown, then?: () => void) => boolean;\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The transition of a pure-trigger part: an `action` with no state of its own (`Button`,\n * `Dialog.Close`, `Popover.Close`, `Menu.Item`, `Toast.Action`; conventions 3.4). A second `run`\n * while one is in flight is ignored by default (conventions 2.5, design/components/button.md 16).\n *\n * With `ignoreWhilePending: false` (the continuous controls' steps) runs overlap, and the hook\n * surfaces superseded rejections as `useControllableAction` does: React keeps only the newest\n * thenable per `useTransition`, so the rejection of a run that a later run superseded would be\n * dropped. The hook catches it and throws it from render, so it reaches the same boundary as the\n * latest run's rejection does.\n */\nexport function useActionTrigger(options: ActionTriggerOptions = {}): ActionTrigger {\n const { openGeneration, ignoreWhilePending = true } = options;\n\n // Thrown before `useTransition` on purpose (see use-controllable-action.ts): in the retry of\n // an errored render `useTransition` would suspend on the newer run's pending thenable.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const mounted = useRef(false);\n // Runs whose work has not settled yet. Updated synchronously, so a second activation in the\n // same task (React's `pending` is still the stale render value) is seen (conventions 2.5).\n const inFlight = useRef(0);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = (work: () => unknown, then?: () => void): boolean => {\n // The own counter only, not the rendered `pending` (design/components/button.md 5.2): after\n // the work settled, a commit held by entanglement must not drop a new activation.\n if (ignoreWhilePending && inFlight.current > 0) return false;\n inFlight.current++;\n const id = ++latest.current;\n // The open this run belongs to (design/components/popover.md, review M2 / PO rule): if the\n // user dismisses and re-opens while the action runs, its close must not close the new open.\n const generation = openGeneration?.();\n startOwnTransition(async () => {\n try {\n await work();\n } catch (error) {\n inFlight.current--;\n // The latest run's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // After an await, outside the transition: urgent, so the part re-renders and throws.\n setSupersededError({ error });\n return;\n }\n inFlight.current--;\n if (openGeneration !== undefined && openGeneration() !== generation) return;\n then?.();\n });\n return true;\n };\n\n return { pending, run };\n}\n"],"mappings":";;;;AAoCA,SAAS,OAAO,OAAsB;CACpC,IAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK;MACnD,QAAQ,MAAM,KAAK;AAC1B;;;;;;;;;;;;AAaA,SAAgB,iBAAiB,UAAgC,CAAC,GAAkB;CAClF,MAAM,EAAE,gBAAgB,qBAAqB,SAAS;CAItD,MAAM,CAAC,iBAAiB,sBAAsB,SAA6C,IAAI;CAC/F,MAAM,qBAAqB,OAAO,KAAK;CACvC,IAAI,oBAAoB,MAAM,MAAM,gBAAgB;CAEpD,MAAM,CAAC,SAAS,sBAAsB,cAAc;CACpD,MAAM,SAAS,OAAO,CAAC;CACvB,MAAM,UAAU,OAAO,KAAK;CAG5B,MAAM,WAAW,OAAO,CAAC;CAEzB,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,OAAO,MAAqB,SAA+B;EAG/D,IAAI,sBAAsB,SAAS,UAAU,GAAG,OAAO;EACvD,SAAS;EACT,MAAM,KAAK,EAAE,OAAO;EAGpB,MAAM,aAAa,iBAAiB;EACpC,mBAAmB,YAAY;GAC7B,IAAI;IACF,MAAM,KAAK;GACb,SAAS,OAAO;IACd,SAAS;IAET,IAAI,OAAO,OAAO,SAAS,MAAM;IACjC,IAAI,CAAC,QAAQ,SAAS;IACtB,IAAI,mBAAmB,SAAS;KAC9B,OAAO,KAAK;KACZ;IACF;IACA,mBAAmB,UAAU;IAE7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B;GACF;GACA,SAAS;GACT,IAAI,mBAAmB,KAAA,KAAa,eAAe,MAAM,YAAY;GACrE,OAAO;EACT,CAAC;EACD,OAAO;CACT;CAEA,OAAO;EAAE;EAAS;CAAI;AACxB"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { dataAttr } from "./data-attr.mjs";
3
- import { mergeRefs } from "./merge-props.mjs";
4
3
  import { useDevWarning } from "./use-dev-warning.mjs";
4
+ import { mergeRefs } from "./merge-props.mjs";
5
5
  import { FieldContext } from "./field-context.mjs";
6
6
  import { use, useCallback, useLayoutEffect, useMemo, useRef } from "react";
7
7
  //#region src/internal/use-field-control.ts
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "./action-details.mjs";
3
+ import { useDevWarning } from "./use-dev-warning.mjs";
3
4
  import { transitionTypes } from "./transition-types.mjs";
4
5
  import { useLatest } from "./use-latest.mjs";
5
6
  import { useControllableAction } from "./use-controllable-action.mjs";
6
- import { useDevWarning } from "./use-dev-warning.mjs";
7
7
  import { useCallback, useId, useMemo, useRef, useState } from "react";
8
8
  //#region src/internal/use-popover-root.ts
9
9
  function isPopoverOpen(element) {