@proyecto-viviana/solidaria-components 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. package/dist/index.ssr.js.map +0 -7
package/src/Button.tsx CHANGED
@@ -8,16 +8,23 @@
8
8
  import {
9
9
  type JSX,
10
10
  createContext,
11
+ createEffect,
11
12
  createMemo,
13
+ createSignal,
12
14
  splitProps,
13
15
  useContext,
14
- } from 'solid-js';
16
+ } from "solid-js";
15
17
  import {
18
+ announce,
16
19
  createButton,
17
20
  createFocusRing,
18
21
  createHover,
22
+ createId,
23
+ mergeProps,
19
24
  type AriaButtonProps,
20
- } from '@proyecto-viviana/solidaria';
25
+ type HoverEvent,
26
+ type PressEvent,
27
+ } from "@proyecto-viviana/solidaria";
21
28
  import {
22
29
  type RenderChildren,
23
30
  type ClassNameOrFunction,
@@ -25,12 +32,70 @@ import {
25
32
  type SlotProps,
26
33
  useRenderProps,
27
34
  filterDOMProps,
28
- } from './utils';
29
- import { DialogTriggerContext, PopoverTriggerContext } from './contexts';
35
+ } from "./utils";
36
+ import { DialogTriggerContext, PopoverTriggerContext } from "./contexts";
37
+ import { ProgressBarContext } from "./ProgressBar";
30
38
 
31
- // ============================================
32
- // TYPES
33
- // ============================================
39
+ type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
40
+
41
+ function assignRef<T>(ref: RefLike<T>, el: T): void {
42
+ if (!ref) return;
43
+ if (typeof ref === "function") {
44
+ ref(el);
45
+ } else {
46
+ ref.current = el;
47
+ }
48
+ }
49
+
50
+ function createLiveButtonRenderProps(values: () => ButtonRenderProps): ButtonRenderProps {
51
+ return {
52
+ get isHovered() {
53
+ return values().isHovered;
54
+ },
55
+ get isPressed() {
56
+ return values().isPressed;
57
+ },
58
+ get isFocused() {
59
+ return values().isFocused;
60
+ },
61
+ get isFocusVisible() {
62
+ return values().isFocusVisible;
63
+ },
64
+ get isDisabled() {
65
+ return values().isDisabled;
66
+ },
67
+ get isPending() {
68
+ return values().isPending;
69
+ },
70
+ };
71
+ }
72
+
73
+ function createLiveCustomRootProps(
74
+ getProps: () => JSX.ButtonHTMLAttributes<HTMLButtonElement>,
75
+ getChildren: () => JSX.Element,
76
+ ref: (el: HTMLButtonElement) => void,
77
+ ): JSX.ButtonHTMLAttributes<HTMLButtonElement> {
78
+ const props = {} as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
79
+ const keys = new Set([...Object.keys(getProps()), "children", "ref"]);
80
+
81
+ for (const key of keys) {
82
+ Object.defineProperty(props, key, {
83
+ enumerable: true,
84
+ configurable: true,
85
+ get() {
86
+ if (key === "children") {
87
+ return getChildren();
88
+ }
89
+ if (key === "ref") {
90
+ return ref;
91
+ }
92
+ return (getProps() as Record<string, unknown>)[key];
93
+ },
94
+ });
95
+ }
96
+
97
+ return props;
98
+ }
34
99
 
35
100
  export interface ButtonRenderProps {
36
101
  /** Whether the button is currently hovered with a mouse. */
@@ -43,28 +108,41 @@ export interface ButtonRenderProps {
43
108
  isFocusVisible: boolean;
44
109
  /** Whether the button is disabled. */
45
110
  isDisabled: boolean;
111
+ /** Whether the button is currently in a pending state. */
112
+ isPending: boolean;
46
113
  }
47
114
 
48
- export interface ButtonProps
49
- extends Omit<AriaButtonProps, 'children'>,
50
- SlotProps {
115
+ export interface ButtonProps extends Omit<AriaButtonProps, "children">, SlotProps {
51
116
  /** The children of the component. A function may be provided to receive render props. */
52
117
  children?: RenderChildren<ButtonRenderProps>;
53
118
  /** The CSS className for the element. */
54
119
  class?: ClassNameOrFunction<ButtonRenderProps>;
55
120
  /** The inline style for the element. */
56
121
  style?: StyleOrFunction<ButtonRenderProps>;
122
+ /** Custom renderer for the outer button element. */
123
+ render?: (
124
+ props: JSX.ButtonHTMLAttributes<HTMLButtonElement>,
125
+ renderProps: ButtonRenderProps,
126
+ ) => JSX.Element;
127
+ /** Ref for the underlying button element. */
128
+ ref?: RefLike<HTMLButtonElement>;
129
+ /** Whether the button is in a pending state. */
130
+ isPending?: boolean;
131
+ /** Keeps pending buttons focusable by using aria-disabled without a native disabled attribute. */
132
+ isPendingFocusable?: boolean;
133
+ /** Handler called when hover starts. */
134
+ onHoverStart?: (e: HoverEvent) => void;
135
+ /** Handler called when hover ends. */
136
+ onHoverEnd?: (e: HoverEvent) => void;
137
+ /** Handler called when hover state changes. */
138
+ onHoverChange?: (isHovered: boolean) => void;
57
139
  }
58
140
 
59
- // ============================================
60
- // CONTEXT
61
- // ============================================
62
-
63
- export const ButtonContext = createContext<ButtonProps | null>(null);
141
+ export interface ButtonContextValue extends ButtonProps {
142
+ slots?: Record<string, ButtonProps>;
143
+ }
64
144
 
65
- // ============================================
66
- // COMPONENT
67
- // ============================================
145
+ export const ButtonContext = createContext<ButtonContextValue | null>(null);
68
146
 
69
147
  /**
70
148
  * A button allows a user to perform an action.
@@ -84,12 +162,29 @@ export const ButtonContext = createContext<ButtonProps | null>(null);
84
162
  * ```
85
163
  */
86
164
  export function Button(props: ButtonProps): JSX.Element {
87
- // Split props
88
- const [local, ariaProps] = splitProps(props, [
89
- 'children',
90
- 'class',
91
- 'style',
92
- 'slot',
165
+ const contextProps = useContext(ButtonContext);
166
+ const contextSlotProps = contextProps?.slots?.[props.slot ?? "default"];
167
+ const contextBaseProps = createMemo<ButtonProps>(() => {
168
+ if (!contextProps) return {};
169
+ const { slots: _slots, ...rest } = contextProps;
170
+ return rest;
171
+ });
172
+ const mergedProps = (
173
+ contextProps ? mergeProps(contextBaseProps(), contextSlotProps ?? {}, props) : props
174
+ ) as ButtonProps;
175
+
176
+ const [local, ariaProps] = splitProps(mergedProps, [
177
+ "children",
178
+ "class",
179
+ "style",
180
+ "render",
181
+ "ref",
182
+ "slot",
183
+ "isPending",
184
+ "isPendingFocusable",
185
+ "onHoverStart",
186
+ "onHoverEnd",
187
+ "onHoverChange",
93
188
  ]);
94
189
 
95
190
  // Check if inside a DialogTrigger or PopoverTrigger - if so, toggle on press
@@ -103,79 +198,102 @@ export function Button(props: ButtonProps): JSX.Element {
103
198
  // Helper to resolve isDisabled (handles both boolean and Accessor<boolean>)
104
199
  const resolveDisabled = (): boolean => {
105
200
  const disabled = ariaProps.isDisabled;
106
- if (typeof disabled === 'function') {
201
+ if (typeof disabled === "function") {
107
202
  return disabled();
108
203
  }
109
204
  return !!disabled;
110
205
  };
111
206
 
112
- // Determine if this button should act as a dialog/popover trigger
113
- // We only toggle if:
114
- // 1. We have DialogTriggerContext or PopoverTriggerContext (we're inside a trigger)
115
- // 2. AND there is NO onPress handler (the trigger button typically has no onPress,
116
- // while close buttons inside dialogs have onPress={close})
117
- // This heuristic works because:
118
- // - Trigger buttons: don't have onPress, should toggle
119
- // - Close buttons: have onPress={close}, should NOT toggle (just call onPress)
120
- const isDialogTrigger = () => dialogTriggerContext && !ariaProps.onPress;
121
- const isPopoverTrigger = () => popoverTriggerContext && !ariaProps.onPress;
122
-
123
- // Wrap onPress to also toggle dialog/popover if this is a trigger button
124
- const handlePress = (e: any) => {
125
- // Call original onPress if provided
126
- if (typeof ariaProps.onPress === 'function') {
207
+ const resolvePending = (): boolean => !!local.isPending;
208
+ const isPendingFocusable = () => local.isPendingFocusable !== false;
209
+
210
+ const [resolvedButtonEl, setResolvedButtonEl] = createSignal<HTMLButtonElement | null>(null);
211
+ const buttonId = createId((ariaProps as Record<string, unknown>).id as string | undefined);
212
+ const progressId = createId();
213
+
214
+ // Explicit trigger ownership: a button toggles overlays only when it is the
215
+ // registered trigger element for the surrounding trigger context.
216
+ const isDialogTrigger = () =>
217
+ !!dialogTriggerContext &&
218
+ !!resolvedButtonEl() &&
219
+ dialogTriggerContext.triggerRef() === resolvedButtonEl();
220
+ const isPopoverTrigger = () =>
221
+ !!popoverTriggerContext &&
222
+ !!resolvedButtonEl() &&
223
+ popoverTriggerContext.triggerRef() === resolvedButtonEl();
224
+
225
+ const handlePress = (e: PressEvent) => {
226
+ if (resolvePending()) {
227
+ return;
228
+ }
229
+ if (typeof ariaProps.onPress === "function") {
127
230
  ariaProps.onPress(e);
128
231
  }
129
- // Toggle dialog only if this is a trigger button (has no onPress handler)
232
+ // Toggle only when this exact button is the registered trigger element.
130
233
  if (isDialogTrigger()) {
131
234
  dialogTriggerContext!.state.toggle();
132
235
  }
133
- // Toggle popover only if this is a trigger button (has no onPress handler)
134
236
  if (isPopoverTrigger()) {
135
237
  popoverTriggerContext!.state.toggle();
136
238
  }
137
239
  };
138
240
 
139
- // Create button aria props
140
241
  const buttonAria = createButton({
141
242
  ...ariaProps,
142
243
  onPress: handlePress,
244
+ get onPressStart() {
245
+ return resolvePending() ? undefined : ariaProps.onPressStart;
246
+ },
247
+ get onPressEnd() {
248
+ return resolvePending() ? undefined : ariaProps.onPressEnd;
249
+ },
250
+ get onPressUp() {
251
+ return resolvePending() ? undefined : ariaProps.onPressUp;
252
+ },
253
+ get onPressChange() {
254
+ return resolvePending() ? undefined : ariaProps.onPressChange;
255
+ },
256
+ get onClick() {
257
+ return resolvePending() ? undefined : ariaProps.onClick;
258
+ },
143
259
  get isDisabled() {
144
- return resolveDisabled();
260
+ return resolveDisabled() || resolvePending();
145
261
  },
146
262
  });
147
263
 
148
- // Create focus ring
149
264
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
150
265
 
151
- // Create hover
152
266
  const { isHovered, hoverProps } = createHover({
153
267
  get isDisabled() {
154
- return resolveDisabled();
268
+ return resolveDisabled() || resolvePending();
155
269
  },
270
+ onHoverStart: local.onHoverStart,
271
+ onHoverEnd: local.onHoverEnd,
272
+ onHoverChange: local.onHoverChange,
156
273
  });
157
274
 
158
- // Render props values
159
275
  const renderValues = createMemo<ButtonRenderProps>(() => ({
160
276
  isHovered: isHovered(),
161
- isPressed: buttonAria.isPressed(),
277
+ isPressed: buttonAria.isPressed() && !resolvePending(),
162
278
  isFocused: isFocused(),
163
279
  isFocusVisible: isFocusVisible(),
164
280
  isDisabled: resolveDisabled(),
281
+ isPending: resolvePending(),
165
282
  }));
166
283
 
167
- // Resolve render props
168
284
  const renderProps = useRenderProps(
169
285
  {
170
- children: props.children,
286
+ // Use merged children so ButtonContext can supply slot/default content.
287
+ get children() {
288
+ return local.children;
289
+ },
171
290
  class: local.class,
172
291
  style: local.style,
173
- defaultClassName: 'solidaria-Button',
292
+ defaultClassName: "solidaria-Button",
174
293
  },
175
- renderValues
294
+ renderValues,
176
295
  );
177
296
 
178
- // Filter DOM props
179
297
  // Remove onClick from DOM props - it's already handled by createPress
180
298
  // This matches React Aria Components behavior (Button.tsx line 144: delete DOMProps.onClick)
181
299
  const domProps = createMemo(() => {
@@ -185,12 +303,16 @@ export function Button(props: ButtonProps): JSX.Element {
185
303
  return filtered;
186
304
  });
187
305
 
188
- // Extract refs from props to combine them manually
189
- const buttonPropsRef = (buttonAria.buttonProps as Record<string, unknown>).ref as ((el: HTMLElement) => void) | undefined;
190
- const focusPropsRef = (focusProps as Record<string, unknown>).ref as ((el: HTMLElement) => void) | undefined;
191
- const hoverPropsRef = (hoverProps as Record<string, unknown>).ref as ((el: HTMLElement) => void) | undefined;
306
+ const buttonPropsRef = (buttonAria.buttonProps as Record<string, unknown>).ref as
307
+ | ((el: HTMLElement) => void)
308
+ | undefined;
309
+ const focusPropsRef = (focusProps as Record<string, unknown>).ref as
310
+ | ((el: HTMLElement) => void)
311
+ | undefined;
312
+ const hoverPropsRef = (hoverProps as Record<string, unknown>).ref as
313
+ | ((el: HTMLElement) => void)
314
+ | undefined;
192
315
 
193
- // Remove ref from spread props to avoid type conflicts
194
316
  const cleanButtonProps = () => {
195
317
  const { ref: _ref1, ...rest } = buttonAria.buttonProps as Record<string, unknown>;
196
318
  return rest;
@@ -203,36 +325,155 @@ export function Button(props: ButtonProps): JSX.Element {
203
325
  const { ref: _ref3, ...rest } = hoverProps as Record<string, unknown>;
204
326
  return rest;
205
327
  };
328
+ const ariaLabelledBy = createMemo(() => {
329
+ const labelledBy = cleanButtonProps()["aria-labelledby"] as string | undefined;
330
+ if (!resolvePending()) {
331
+ return labelledBy;
332
+ }
333
+
334
+ if (labelledBy) {
335
+ return `${labelledBy} ${progressId}`;
336
+ }
337
+
338
+ if (cleanButtonProps()["aria-label"]) {
339
+ return `${buttonId} ${progressId}`;
340
+ }
341
+
342
+ return labelledBy;
343
+ });
206
344
 
207
- // Ref callback that combines all refs
208
345
  const handleRef = (el: HTMLButtonElement) => {
209
- // Call the focusable ref for autoFocus support
346
+ setResolvedButtonEl(el);
347
+ assignRef(local.ref, el);
348
+
210
349
  buttonPropsRef?.(el);
211
350
  focusPropsRef?.(el);
212
351
  hoverPropsRef?.(el);
213
352
 
214
- // If this button is a popover trigger, register it
215
- if (isPopoverTrigger() && popoverTriggerContext?.setTriggerRef) {
353
+ // Register trigger ownership for surrounding trigger contexts.
354
+ if (dialogTriggerContext?.setTriggerRef) {
355
+ if (!el.id) {
356
+ el.id = dialogTriggerContext.triggerId;
357
+ }
358
+ dialogTriggerContext.setTriggerRef(el);
359
+ }
360
+ if (popoverTriggerContext?.setTriggerRef) {
216
361
  popoverTriggerContext.setTriggerRef(el);
217
362
  }
218
363
  };
364
+ const buttonType = () =>
365
+ (buttonAria.buttonProps as Record<string, unknown>).type === "submit" && resolvePending()
366
+ ? "button"
367
+ : ((buttonAria.buttonProps as Record<string, unknown>).type as
368
+ | "button"
369
+ | "submit"
370
+ | "reset"
371
+ | undefined);
372
+ const triggerAriaProps = () => {
373
+ const dialogTriggerProps = dialogTriggerContext?.triggerProps;
374
+ if (dialogTriggerProps && isDialogTrigger()) {
375
+ const next: Record<string, unknown> = {};
376
+ for (const name of ["aria-haspopup", "aria-expanded", "aria-controls"]) {
377
+ if (dialogTriggerProps[name] != null) {
378
+ next[name] = dialogTriggerProps[name];
379
+ }
380
+ }
381
+ return next;
382
+ }
383
+
384
+ const triggerProps = popoverTriggerContext?.triggerProps;
385
+ if (!triggerProps || !isPopoverTrigger()) {
386
+ return {};
387
+ }
388
+
389
+ const next: Record<string, unknown> = {};
390
+ for (const name of ["aria-haspopup", "aria-expanded", "aria-controls"]) {
391
+ if (triggerProps[name] != null) {
392
+ next[name] = triggerProps[name];
393
+ }
394
+ }
395
+ return next;
396
+ };
397
+ const disablePendingInteractions = (props: Record<string, unknown>) => {
398
+ if (!resolvePending()) {
399
+ return props;
400
+ }
401
+
402
+ const next = { ...props };
403
+ for (const key of Object.keys(next)) {
404
+ if (key.startsWith("on") && !key.includes("Focus") && !key.includes("Blur")) {
405
+ next[key] = undefined;
406
+ }
407
+ }
408
+ next.href = undefined;
409
+ next.target = undefined;
410
+ return next;
411
+ };
412
+ const dataState = (value: boolean) => (value ? "true" : undefined);
413
+ const buttonChildren = () => renderProps.renderChildren();
414
+ const buttonContent = () => (
415
+ <ProgressBarContext.Provider value={{ id: progressId }}>
416
+ {buttonChildren()}
417
+ </ProgressBarContext.Provider>
418
+ );
419
+ let wasPending = resolvePending();
420
+ createEffect(() => {
421
+ const pending = resolvePending();
422
+ const message = { "aria-labelledby": ariaLabelledBy() || buttonId };
423
+
424
+ if (!wasPending && isFocused() && pending) {
425
+ announce(message, "assertive");
426
+ } else if (wasPending && isFocused() && !pending) {
427
+ announce(message, "assertive");
428
+ }
429
+
430
+ wasPending = pending;
431
+ });
432
+ const rootProps = () =>
433
+ ({
434
+ ...domProps(),
435
+ ...disablePendingInteractions(cleanButtonProps()),
436
+ ...triggerAriaProps(),
437
+ ...cleanFocusProps(),
438
+ ...cleanHoverProps(),
439
+ type: buttonType(),
440
+ id: buttonId,
441
+ class: renderProps.class(),
442
+ style: renderProps.style(),
443
+ slot: local.slot,
444
+ disabled: resolvePending() && isPendingFocusable() ? undefined : cleanButtonProps().disabled,
445
+ "aria-labelledby": ariaLabelledBy(),
446
+ "aria-disabled": resolvePending()
447
+ ? "true"
448
+ : (cleanButtonProps()["aria-disabled"] ?? ariaProps["aria-disabled"]),
449
+ "data-pressed": dataState(buttonAria.isPressed() && !resolvePending()),
450
+ "data-hovered": dataState(isHovered()),
451
+ "data-focused": dataState(isFocused()),
452
+ "data-focus-visible": dataState(isFocusVisible()),
453
+ "data-disabled": dataState(resolveDisabled()),
454
+ "data-pending": dataState(resolvePending()),
455
+ }) as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
456
+ const customRootProps = createLiveCustomRootProps(rootProps, buttonContent, handleRef);
457
+ const customRenderValues = createLiveButtonRenderProps(renderValues);
219
458
 
220
- return (
459
+ return local.render ? (
460
+ local.render(customRootProps, customRenderValues)
461
+ ) : (
221
462
  <button
222
463
  ref={handleRef}
223
- {...domProps()}
224
- {...cleanButtonProps()}
225
- {...cleanFocusProps()}
226
- {...cleanHoverProps()}
464
+ {...rootProps()}
227
465
  class={renderProps.class()}
228
466
  style={renderProps.style()}
229
- data-pressed={buttonAria.isPressed() || undefined}
230
- data-hovered={isHovered() || undefined}
231
- data-focused={isFocused() || undefined}
232
- data-focus-visible={isFocusVisible() || undefined}
233
- data-disabled={resolveDisabled() || undefined}
467
+ attr:data-pressed={(rootProps() as Record<string, unknown>)["data-pressed"] as string}
468
+ attr:data-hovered={(rootProps() as Record<string, unknown>)["data-hovered"] as string}
469
+ attr:data-focused={(rootProps() as Record<string, unknown>)["data-focused"] as string}
470
+ attr:data-focus-visible={
471
+ (rootProps() as Record<string, unknown>)["data-focus-visible"] as string
472
+ }
473
+ attr:data-disabled={(rootProps() as Record<string, unknown>)["data-disabled"] as string}
474
+ attr:data-pending={(rootProps() as Record<string, unknown>)["data-pending"] as string}
234
475
  >
235
- {renderProps.renderChildren()}
476
+ {buttonContent()}
236
477
  </button>
237
478
  );
238
479
  }