@proyecto-viviana/solidaria-components 0.2.4 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ActionBar.d.ts +71 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Breadcrumbs.d.ts +10 -2
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +4 -0
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +13 -0
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +2 -2
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +125 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +114 -2
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +64 -0
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +27 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +67 -2
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +2 -0
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +23 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +27 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +40 -1
- package/dist/GridList.d.ts.map +1 -1
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +43 -1
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +20 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +2 -2
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +2 -0
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +2 -0
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +4 -2
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +2 -2
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +5 -0
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +2 -3
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -0
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/SharedElementTransition.d.ts +39 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +6 -3
- package/dist/Slider.d.ts.map +1 -1
- package/dist/Table.d.ts +39 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -3
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -2
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +4 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +26 -1
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +30 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +44 -2
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +3 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +1 -0
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +57 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13961 -5946
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +9612 -2401
- package/dist/index.ssr.js.map +1 -7
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +8 -6
- package/src/ActionBar.tsx +248 -0
- package/src/ActionGroup.tsx +285 -0
- package/src/Alert.tsx +177 -0
- package/src/Autocomplete.tsx +1 -1
- package/src/Breadcrumbs.tsx +103 -17
- package/src/Button.tsx +65 -21
- package/src/Calendar.tsx +179 -53
- package/src/Checkbox.tsx +1 -2
- package/src/Collection.tsx +341 -0
- package/src/Color.tsx +652 -34
- package/src/ColorEditor.tsx +231 -0
- package/src/ComboBox.tsx +315 -81
- package/src/ContextualHelpTrigger.tsx +183 -0
- package/src/DateField.tsx +93 -19
- package/src/DatePicker.tsx +495 -25
- package/src/Dialog.tsx +40 -9
- package/src/Disclosure.tsx +33 -27
- package/src/DragAndDrop.tsx +334 -0
- package/src/DragPreview.tsx +45 -0
- package/src/DropZone.tsx +213 -0
- package/src/FieldError.tsx +67 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +106 -0
- package/src/Form.tsx +85 -0
- package/src/GridList.tsx +379 -41
- package/src/Icon.tsx +154 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Link.tsx +14 -1
- package/src/ListBox.tsx +484 -33
- package/src/ListDropTargetDelegate.ts +282 -0
- package/src/Menu.tsx +388 -35
- package/src/Meter.tsx +7 -3
- package/src/Modal.tsx +32 -4
- package/src/NumberField.tsx +163 -43
- package/src/Popover.tsx +136 -180
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +7 -3
- package/src/RadioGroup.tsx +35 -25
- package/src/RangeCalendar.tsx +100 -68
- package/src/RouterProvider.tsx +240 -0
- package/src/SearchField.tsx +142 -34
- package/src/Select.tsx +221 -73
- package/src/SelectionIndicator.tsx +105 -0
- package/src/SharedElementTransition.tsx +258 -0
- package/src/Slider.tsx +16 -6
- package/src/Table.tsx +417 -57
- package/src/Tabs.tsx +68 -35
- package/src/TagGroup.tsx +121 -36
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +25 -8
- package/src/TimeField.tsx +101 -151
- package/src/Toast.tsx +108 -14
- package/src/ToggleButton.tsx +159 -0
- package/src/ToggleButtonGroup.tsx +136 -0
- package/src/Toolbar.tsx +14 -8
- package/src/Tooltip.tsx +108 -19
- package/src/Tree.tsx +1143 -87
- package/src/Virtualizer.tsx +702 -0
- package/src/VirtualizerLayouts.ts +265 -0
- package/src/VisuallyHidden.tsx +15 -21
- package/src/contexts.ts +1 -0
- package/src/index.ts +1057 -620
- package/src/useDragAndDrop.ts +351 -0
- package/src/utils.tsx +37 -3
- package/src/virtualizer/Layout.ts +200 -0
package/src/TimeField.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from 'solid-js';
|
|
18
18
|
import {
|
|
19
19
|
createTimeField,
|
|
20
|
+
createTimeSegment,
|
|
20
21
|
type AriaTimeFieldProps,
|
|
21
22
|
} from '@proyecto-viviana/solidaria';
|
|
22
23
|
import {
|
|
@@ -109,9 +110,20 @@ export interface TimeSegmentProps extends SlotProps {
|
|
|
109
110
|
// CONTEXT
|
|
110
111
|
// ============================================
|
|
111
112
|
|
|
112
|
-
export
|
|
113
|
+
export interface TimeFieldContextValue {
|
|
114
|
+
state: TimeFieldState<TimeValue>;
|
|
115
|
+
aria: {
|
|
116
|
+
labelProps: Record<string, unknown>;
|
|
117
|
+
inputProps: Record<string, unknown>;
|
|
118
|
+
descriptionProps: Record<string, unknown>;
|
|
119
|
+
errorMessageProps: Record<string, unknown>;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
113
122
|
|
|
114
|
-
export
|
|
123
|
+
export const TimeFieldContext = createContext<TimeFieldContextValue | null>(null);
|
|
124
|
+
export const TimeFieldStateContext = createContext<TimeFieldState<TimeValue> | null>(null);
|
|
125
|
+
|
|
126
|
+
function useTimeFieldContextValue(): TimeFieldContextValue {
|
|
115
127
|
const context = useContext(TimeFieldContext);
|
|
116
128
|
if (!context) {
|
|
117
129
|
throw new Error('TimeField components must be used within a TimeField');
|
|
@@ -119,6 +131,10 @@ export function useTimeFieldContext(): TimeFieldState<TimeValue> {
|
|
|
119
131
|
return context;
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
export function useTimeFieldContext(): TimeFieldState<TimeValue> {
|
|
135
|
+
return useTimeFieldContextValue().state;
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
// ============================================
|
|
123
139
|
// TIME FIELD COMPONENT
|
|
124
140
|
// ============================================
|
|
@@ -205,20 +221,32 @@ function TimeFieldInner<T extends TimeValue = TimeValue>(
|
|
|
205
221
|
);
|
|
206
222
|
|
|
207
223
|
return (
|
|
208
|
-
<
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
<TimeFieldStateContext.Provider value={state as unknown as TimeFieldState<TimeValue>}>
|
|
225
|
+
<TimeFieldContext.Provider
|
|
226
|
+
value={{
|
|
227
|
+
state: state as unknown as TimeFieldState<TimeValue>,
|
|
228
|
+
aria: {
|
|
229
|
+
labelProps: fieldAria.labelProps,
|
|
230
|
+
inputProps: fieldAria.inputProps,
|
|
231
|
+
descriptionProps: fieldAria.descriptionProps,
|
|
232
|
+
errorMessageProps: fieldAria.errorMessageProps,
|
|
233
|
+
},
|
|
234
|
+
}}
|
|
218
235
|
>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
236
|
+
<div
|
|
237
|
+
ref={setFieldRef}
|
|
238
|
+
{...fieldAria.fieldProps}
|
|
239
|
+
class={renderProps.class()}
|
|
240
|
+
style={renderProps.style()}
|
|
241
|
+
data-disabled={dataAttr(state.isDisabled())}
|
|
242
|
+
data-readonly={dataAttr(state.isReadOnly())}
|
|
243
|
+
data-required={dataAttr(state.isRequired())}
|
|
244
|
+
data-invalid={dataAttr(state.isInvalid())}
|
|
245
|
+
>
|
|
246
|
+
{props.children as JSX.Element}
|
|
247
|
+
</div>
|
|
248
|
+
</TimeFieldContext.Provider>
|
|
249
|
+
</TimeFieldStateContext.Provider>
|
|
222
250
|
);
|
|
223
251
|
}
|
|
224
252
|
|
|
@@ -230,7 +258,7 @@ function TimeFieldInner<T extends TimeValue = TimeValue>(
|
|
|
230
258
|
* The input area containing time segments.
|
|
231
259
|
*/
|
|
232
260
|
export function TimeInput(props: TimeInputProps): JSX.Element {
|
|
233
|
-
const state =
|
|
261
|
+
const { state, aria } = useTimeFieldContextValue();
|
|
234
262
|
const [isFocused, setIsFocused] = createSignal(false);
|
|
235
263
|
|
|
236
264
|
// Render props values
|
|
@@ -251,7 +279,7 @@ export function TimeInput(props: TimeInputProps): JSX.Element {
|
|
|
251
279
|
|
|
252
280
|
return (
|
|
253
281
|
<div
|
|
254
|
-
|
|
282
|
+
{...aria.inputProps}
|
|
255
283
|
class={renderProps.class()}
|
|
256
284
|
style={renderProps.style()}
|
|
257
285
|
data-disabled={dataAttr(state.isDisabled())}
|
|
@@ -275,126 +303,21 @@ export function TimeInput(props: TimeInputProps): JSX.Element {
|
|
|
275
303
|
*/
|
|
276
304
|
export function TimeSegment(props: TimeSegmentProps): JSX.Element {
|
|
277
305
|
const state = useTimeFieldContext();
|
|
278
|
-
const [
|
|
279
|
-
|
|
280
|
-
// Create segment ARIA props
|
|
281
|
-
// We use a simplified version for time segments
|
|
282
|
-
const [isFocused, setIsFocused] = createSignal(false);
|
|
283
|
-
const [enteredKeys, setEnteredKeys] = createSignal('');
|
|
284
|
-
|
|
285
|
-
const isEditable = createMemo(() => {
|
|
286
|
-
const seg = props.segment;
|
|
287
|
-
return seg.isEditable && !state.isDisabled() && !state.isReadOnly();
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
291
|
-
if (!isEditable()) return;
|
|
292
|
-
|
|
293
|
-
const seg = props.segment;
|
|
294
|
-
const type = seg.type;
|
|
295
|
-
|
|
296
|
-
if (type === 'literal') return;
|
|
297
|
-
|
|
298
|
-
switch (e.key) {
|
|
299
|
-
case 'ArrowUp':
|
|
300
|
-
e.preventDefault();
|
|
301
|
-
state.incrementSegment(type);
|
|
302
|
-
break;
|
|
303
|
-
case 'ArrowDown':
|
|
304
|
-
e.preventDefault();
|
|
305
|
-
state.decrementSegment(type);
|
|
306
|
-
break;
|
|
307
|
-
case 'Backspace':
|
|
308
|
-
case 'Delete':
|
|
309
|
-
e.preventDefault();
|
|
310
|
-
state.clearSegment(type);
|
|
311
|
-
setEnteredKeys('');
|
|
312
|
-
break;
|
|
313
|
-
default:
|
|
314
|
-
if (/^\d$/.test(e.key)) {
|
|
315
|
-
e.preventDefault();
|
|
316
|
-
const newKeys = enteredKeys() + e.key;
|
|
317
|
-
const numValue = parseInt(newKeys, 10);
|
|
318
|
-
const maxValue = seg.maxValue ?? 59;
|
|
319
|
-
const minValue = seg.minValue ?? 0;
|
|
320
|
-
|
|
321
|
-
if (numValue <= maxValue) {
|
|
322
|
-
state.setSegment(type, numValue);
|
|
323
|
-
if (numValue * 10 > maxValue || newKeys.length >= 2) {
|
|
324
|
-
setEnteredKeys('');
|
|
325
|
-
} else {
|
|
326
|
-
setEnteredKeys(newKeys);
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
const singleValue = parseInt(e.key, 10);
|
|
330
|
-
if (singleValue >= minValue && singleValue <= maxValue) {
|
|
331
|
-
state.setSegment(type, singleValue);
|
|
332
|
-
}
|
|
333
|
-
setEnteredKeys(e.key);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const handleFocus = () => {
|
|
341
|
-
setIsFocused(true);
|
|
342
|
-
setEnteredKeys('');
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const handleBlur = () => {
|
|
346
|
-
setIsFocused(false);
|
|
347
|
-
setEnteredKeys('');
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
// Segment props
|
|
351
|
-
const segmentProps = createMemo(() => {
|
|
352
|
-
const seg = props.segment;
|
|
353
|
-
const type = seg.type;
|
|
306
|
+
const [segmentRef, setSegmentRef] = createSignal<HTMLDivElement | null>(null);
|
|
354
307
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return {
|
|
362
|
-
role: 'spinbutton' as const,
|
|
363
|
-
tabIndex: isEditable() ? 0 : -1,
|
|
364
|
-
'aria-label': getTimeSegmentLabel(type),
|
|
365
|
-
'aria-valuenow': seg.value,
|
|
366
|
-
'aria-valuemin': seg.minValue,
|
|
367
|
-
'aria-valuemax': seg.maxValue,
|
|
368
|
-
'aria-valuetext': seg.isPlaceholder ? seg.placeholder : seg.text,
|
|
369
|
-
'aria-readonly': state.isReadOnly() || undefined,
|
|
370
|
-
'aria-disabled': state.isDisabled() || undefined,
|
|
371
|
-
'aria-invalid': state.isInvalid() || undefined,
|
|
372
|
-
contentEditable: isEditable(),
|
|
373
|
-
inputMode: 'numeric' as const,
|
|
374
|
-
autoCorrect: 'off',
|
|
375
|
-
enterKeyHint: 'next' as const,
|
|
376
|
-
spellCheck: false,
|
|
377
|
-
onKeyDown: handleKeyDown,
|
|
378
|
-
onFocus: handleFocus,
|
|
379
|
-
onBlur: handleBlur,
|
|
380
|
-
onMouseDown: (e: MouseEvent) => {
|
|
381
|
-
e.preventDefault();
|
|
382
|
-
},
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
const text = createMemo(() => {
|
|
387
|
-
const seg = props.segment;
|
|
388
|
-
return seg.isPlaceholder ? seg.placeholder : seg.text;
|
|
389
|
-
});
|
|
308
|
+
const segmentAria = createTimeSegment(
|
|
309
|
+
{ segment: props.segment },
|
|
310
|
+
state as unknown as TimeFieldState,
|
|
311
|
+
segmentRef
|
|
312
|
+
);
|
|
390
313
|
|
|
391
314
|
// Render props values
|
|
392
315
|
const renderValues = createMemo<TimeSegmentRenderProps>(() => ({
|
|
393
|
-
isFocused: isFocused
|
|
394
|
-
isEditable: isEditable
|
|
395
|
-
isPlaceholder:
|
|
316
|
+
isFocused: segmentAria.isFocused,
|
|
317
|
+
isEditable: segmentAria.isEditable,
|
|
318
|
+
isPlaceholder: segmentAria.isPlaceholder,
|
|
396
319
|
type: props.segment.type,
|
|
397
|
-
text: text
|
|
320
|
+
text: segmentAria.text,
|
|
398
321
|
}));
|
|
399
322
|
|
|
400
323
|
// Resolve render props
|
|
@@ -413,18 +336,18 @@ export function TimeSegment(props: TimeSegmentProps): JSX.Element {
|
|
|
413
336
|
if (typeof props.children === 'function') {
|
|
414
337
|
return renderProps.renderChildren();
|
|
415
338
|
}
|
|
416
|
-
return text
|
|
339
|
+
return segmentAria.text;
|
|
417
340
|
};
|
|
418
341
|
|
|
419
342
|
return (
|
|
420
343
|
<div
|
|
421
344
|
ref={setSegmentRef}
|
|
422
|
-
{...segmentProps
|
|
345
|
+
{...segmentAria.segmentProps}
|
|
423
346
|
class={renderProps.class()}
|
|
424
347
|
style={renderProps.style()}
|
|
425
|
-
data-focused={dataAttr(isFocused
|
|
426
|
-
data-editable={dataAttr(isEditable
|
|
427
|
-
data-placeholder={dataAttr(
|
|
348
|
+
data-focused={dataAttr(segmentAria.isFocused)}
|
|
349
|
+
data-editable={dataAttr(segmentAria.isEditable)}
|
|
350
|
+
data-placeholder={dataAttr(segmentAria.isPlaceholder)}
|
|
428
351
|
data-type={props.segment.type}
|
|
429
352
|
>
|
|
430
353
|
{getChildren()}
|
|
@@ -433,22 +356,49 @@ export function TimeSegment(props: TimeSegmentProps): JSX.Element {
|
|
|
433
356
|
}
|
|
434
357
|
|
|
435
358
|
// ============================================
|
|
436
|
-
//
|
|
359
|
+
// LABEL / DESCRIPTION / ERROR
|
|
437
360
|
// ============================================
|
|
438
361
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
362
|
+
export interface TimeFieldLabelProps {
|
|
363
|
+
children?: JSX.Element;
|
|
364
|
+
class?: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function TimeFieldLabel(props: TimeFieldLabelProps): JSX.Element {
|
|
368
|
+
const { aria } = useTimeFieldContextValue();
|
|
369
|
+
return (
|
|
370
|
+
<span {...aria.labelProps} class={props.class}>
|
|
371
|
+
{props.children}
|
|
372
|
+
</span>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface TimeFieldDescriptionProps {
|
|
377
|
+
children?: JSX.Element;
|
|
378
|
+
class?: string;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function TimeFieldDescription(props: TimeFieldDescriptionProps): JSX.Element {
|
|
382
|
+
const { aria } = useTimeFieldContextValue();
|
|
383
|
+
return (
|
|
384
|
+
<p {...aria.descriptionProps} class={props.class}>
|
|
385
|
+
{props.children}
|
|
386
|
+
</p>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface TimeFieldErrorMessageProps {
|
|
391
|
+
children?: JSX.Element;
|
|
392
|
+
class?: string;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function TimeFieldErrorMessage(props: TimeFieldErrorMessageProps): JSX.Element {
|
|
396
|
+
const { aria } = useTimeFieldContextValue();
|
|
397
|
+
return (
|
|
398
|
+
<p {...aria.errorMessageProps} class={props.class}>
|
|
399
|
+
{props.children}
|
|
400
|
+
</p>
|
|
401
|
+
);
|
|
452
402
|
}
|
|
453
403
|
|
|
454
404
|
// Re-export types
|
package/src/Toast.tsx
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
type JSX,
|
|
10
10
|
createContext,
|
|
11
11
|
createMemo,
|
|
12
|
+
createEffect,
|
|
13
|
+
onCleanup,
|
|
12
14
|
splitProps,
|
|
13
15
|
Show,
|
|
14
16
|
useContext,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
import {
|
|
25
27
|
createToast,
|
|
26
28
|
createToastRegion,
|
|
29
|
+
useUNSAFE_PortalContext,
|
|
27
30
|
} from '@proyecto-viviana/solidaria';
|
|
28
31
|
import {
|
|
29
32
|
type RenderChildren,
|
|
@@ -101,6 +104,13 @@ export interface ToastProps {
|
|
|
101
104
|
|
|
102
105
|
export const ToastContext = createContext<ToastState<ToastContent> | null>(null);
|
|
103
106
|
|
|
107
|
+
interface ToastAriaContextValue {
|
|
108
|
+
titleProps: JSX.HTMLAttributes<HTMLElement>;
|
|
109
|
+
descriptionProps: JSX.HTMLAttributes<HTMLElement>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const ToastAriaContext = createContext<ToastAriaContextValue | null>(null);
|
|
113
|
+
|
|
104
114
|
export function useToastContext(): ToastState<ToastContent> {
|
|
105
115
|
const context = useContext(ToastContext);
|
|
106
116
|
if (!context) {
|
|
@@ -116,7 +126,7 @@ export function useToastContext(): ToastState<ToastContent> {
|
|
|
116
126
|
/** Default global toast queue that can be used for app-wide toasts. */
|
|
117
127
|
export const globalToastQueue = new ToastQueue<ToastContent>({
|
|
118
128
|
maxVisibleToasts: 5,
|
|
119
|
-
hasExitAnimation:
|
|
129
|
+
hasExitAnimation: true,
|
|
120
130
|
});
|
|
121
131
|
|
|
122
132
|
/**
|
|
@@ -202,6 +212,8 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
202
212
|
'portal',
|
|
203
213
|
'placement',
|
|
204
214
|
]);
|
|
215
|
+
const portalContext = useUNSAFE_PortalContext();
|
|
216
|
+
const portalContainer = () => portalContext.getContainer?.() ?? undefined;
|
|
205
217
|
|
|
206
218
|
// Get state from context if not provided
|
|
207
219
|
const contextState = useContext(ToastContext);
|
|
@@ -301,7 +313,7 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
301
313
|
return (
|
|
302
314
|
<Show when={hasToasts()}>
|
|
303
315
|
<Show when={local.portal !== false} fallback={regionContent()}>
|
|
304
|
-
<Portal>{regionContent()}</Portal>
|
|
316
|
+
<Portal mount={portalContainer()}>{regionContent()}</Portal>
|
|
305
317
|
</Show>
|
|
306
318
|
</Show>
|
|
307
319
|
);
|
|
@@ -334,6 +346,8 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
334
346
|
'style',
|
|
335
347
|
]);
|
|
336
348
|
|
|
349
|
+
let toastRef!: HTMLDivElement;
|
|
350
|
+
|
|
337
351
|
// Get state from context
|
|
338
352
|
const state = useToastContext();
|
|
339
353
|
|
|
@@ -341,6 +355,8 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
341
355
|
const toastAria = createToast({
|
|
342
356
|
toast: local.toast,
|
|
343
357
|
state,
|
|
358
|
+
hasTitle: !!local.toast.content.title,
|
|
359
|
+
hasDescription: !!local.toast.content.description,
|
|
344
360
|
});
|
|
345
361
|
|
|
346
362
|
// Render props values
|
|
@@ -372,20 +388,92 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
372
388
|
return { 'pointer-events': 'auto' as const, ...custom } as JSX.CSSProperties;
|
|
373
389
|
};
|
|
374
390
|
|
|
391
|
+
// Exit animation lifecycle:
|
|
392
|
+
// When animation becomes 'exiting', wait for CSS animations/transitions to finish,
|
|
393
|
+
// then call state.remove() to finalize removal from the queue.
|
|
394
|
+
// In JSDOM or when no animations are running, remove immediately.
|
|
395
|
+
// Reduced-motion is handled by CSS (shorter/no animations), so the lifecycle
|
|
396
|
+
// naturally completes faster when the user prefers reduced motion.
|
|
397
|
+
createEffect(() => {
|
|
398
|
+
if (local.toast.animation !== 'exiting') return;
|
|
399
|
+
if (!toastRef) {
|
|
400
|
+
state.remove(local.toast.key);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Check if the element supports the Web Animations API
|
|
405
|
+
if (!('getAnimations' in toastRef)) {
|
|
406
|
+
state.remove(local.toast.key);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const animations = toastRef.getAnimations();
|
|
411
|
+
if (animations.length === 0) {
|
|
412
|
+
// No CSS animations/transitions running - remove immediately
|
|
413
|
+
state.remove(local.toast.key);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Wait for all running animations to finish, then remove
|
|
418
|
+
let canceled = false;
|
|
419
|
+
Promise.all(animations.map((a) => a.finished))
|
|
420
|
+
.then(() => {
|
|
421
|
+
if (!canceled) {
|
|
422
|
+
state.remove(local.toast.key);
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
.catch(() => {
|
|
426
|
+
// Animation was canceled (e.g. element removed) - still clean up
|
|
427
|
+
if (!canceled) {
|
|
428
|
+
state.remove(local.toast.key);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
onCleanup(() => {
|
|
433
|
+
canceled = true;
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
375
437
|
// Extract ref from toastProps to avoid type conflicts
|
|
376
438
|
const { ref: _ref, ...cleanToastProps } = toastAria.toastProps as Record<string, unknown>;
|
|
377
439
|
|
|
440
|
+
// Ensure ARIA title/description IDs are present on rendered sub-components,
|
|
441
|
+
// even when children are pre-composed outside the Toast provider owner.
|
|
442
|
+
createEffect(() => {
|
|
443
|
+
if (!toastRef) return;
|
|
444
|
+
|
|
445
|
+
const titleId = (toastAria.titleProps as Record<string, unknown>).id as string | undefined;
|
|
446
|
+
const descriptionId = (toastAria.descriptionProps as Record<string, unknown>).id as string | undefined;
|
|
447
|
+
|
|
448
|
+
if (titleId) {
|
|
449
|
+
const titleEl = toastRef.querySelector('[data-solidaria-toast-title]');
|
|
450
|
+
if (titleEl instanceof HTMLElement) {
|
|
451
|
+
titleEl.id = titleId;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (descriptionId) {
|
|
456
|
+
const descriptionEl = toastRef.querySelector('[data-solidaria-toast-description]');
|
|
457
|
+
if (descriptionEl instanceof HTMLElement) {
|
|
458
|
+
descriptionEl.id = descriptionId;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
378
463
|
return (
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
464
|
+
<ToastAriaContext.Provider value={{ titleProps: toastAria.titleProps, descriptionProps: toastAria.descriptionProps }}>
|
|
465
|
+
<div
|
|
466
|
+
ref={toastRef}
|
|
467
|
+
{...domProps()}
|
|
468
|
+
{...cleanToastProps}
|
|
469
|
+
class={renderProps.class()}
|
|
470
|
+
style={mergedStyle()}
|
|
471
|
+
data-animation={local.toast.animation}
|
|
472
|
+
data-type={local.toast.content.type}
|
|
473
|
+
>
|
|
474
|
+
{renderProps.renderChildren()}
|
|
475
|
+
</div>
|
|
476
|
+
</ToastAriaContext.Provider>
|
|
389
477
|
);
|
|
390
478
|
}
|
|
391
479
|
|
|
@@ -403,8 +491,11 @@ export interface ToastTitleProps {
|
|
|
403
491
|
* ToastTitle renders the toast title with proper accessibility attributes.
|
|
404
492
|
*/
|
|
405
493
|
export function ToastTitle(props: ToastTitleProps): JSX.Element {
|
|
494
|
+
const context = useContext(ToastAriaContext);
|
|
495
|
+
const { ref: _ref, ...ariaTitleProps } = (context?.titleProps ?? {}) as Record<string, unknown>;
|
|
496
|
+
|
|
406
497
|
return (
|
|
407
|
-
<div class={props.class} style={props.style}>
|
|
498
|
+
<div data-solidaria-toast-title="" {...ariaTitleProps} class={props.class} style={props.style}>
|
|
408
499
|
{props.children}
|
|
409
500
|
</div>
|
|
410
501
|
);
|
|
@@ -420,8 +511,11 @@ export interface ToastDescriptionProps {
|
|
|
420
511
|
* ToastDescription renders the toast description with proper accessibility attributes.
|
|
421
512
|
*/
|
|
422
513
|
export function ToastDescription(props: ToastDescriptionProps): JSX.Element {
|
|
514
|
+
const context = useContext(ToastAriaContext);
|
|
515
|
+
const { ref: _ref, ...ariaDescriptionProps } = (context?.descriptionProps ?? {}) as Record<string, unknown>;
|
|
516
|
+
|
|
423
517
|
return (
|
|
424
|
-
<div class={props.class} style={props.style}>
|
|
518
|
+
<div data-solidaria-toast-description="" {...ariaDescriptionProps} class={props.class} style={props.style}>
|
|
425
519
|
{props.children}
|
|
426
520
|
</div>
|
|
427
521
|
);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleButton component for solidaria-components
|
|
3
|
+
*
|
|
4
|
+
* A pre-wired headless toggle button that combines pressed + selected state.
|
|
5
|
+
* Port direction: react-aria-components/src/ToggleButton.tsx
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type JSX,
|
|
10
|
+
createContext,
|
|
11
|
+
createMemo,
|
|
12
|
+
splitProps,
|
|
13
|
+
useContext,
|
|
14
|
+
} from 'solid-js';
|
|
15
|
+
import {
|
|
16
|
+
createToggleButton,
|
|
17
|
+
createToggleButtonGroupItem,
|
|
18
|
+
createFocusRing,
|
|
19
|
+
createHover,
|
|
20
|
+
mergeProps,
|
|
21
|
+
type AriaToggleButtonProps,
|
|
22
|
+
} from '@proyecto-viviana/solidaria';
|
|
23
|
+
import type { Key } from '@proyecto-viviana/solid-stately';
|
|
24
|
+
import {
|
|
25
|
+
type RenderChildren,
|
|
26
|
+
type ClassNameOrFunction,
|
|
27
|
+
type StyleOrFunction,
|
|
28
|
+
type SlotProps,
|
|
29
|
+
useRenderProps,
|
|
30
|
+
filterDOMProps,
|
|
31
|
+
} from './utils';
|
|
32
|
+
import { useToggleButtonGroupStateContext } from './ToggleButtonGroup';
|
|
33
|
+
|
|
34
|
+
export interface ToggleButtonRenderProps {
|
|
35
|
+
isHovered: boolean;
|
|
36
|
+
isPressed: boolean;
|
|
37
|
+
isFocused: boolean;
|
|
38
|
+
isFocusVisible: boolean;
|
|
39
|
+
isDisabled: boolean;
|
|
40
|
+
isSelected: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ToggleButtonProps
|
|
44
|
+
extends Omit<AriaToggleButtonProps, 'children'>,
|
|
45
|
+
SlotProps {
|
|
46
|
+
/** Key used when inside ToggleButtonGroup selection state. */
|
|
47
|
+
toggleKey?: Key;
|
|
48
|
+
/** Preferred group key prop, parity with RAC item id usage. */
|
|
49
|
+
id?: Key;
|
|
50
|
+
children?: RenderChildren<ToggleButtonRenderProps>;
|
|
51
|
+
class?: ClassNameOrFunction<ToggleButtonRenderProps>;
|
|
52
|
+
style?: StyleOrFunction<ToggleButtonRenderProps>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const ToggleButtonContext = createContext<ToggleButtonProps | null>(null);
|
|
56
|
+
|
|
57
|
+
function resolveDisabledValue(
|
|
58
|
+
isDisabled: AriaToggleButtonProps['isDisabled']
|
|
59
|
+
): boolean {
|
|
60
|
+
if (typeof isDisabled === 'function') {
|
|
61
|
+
return isDisabled();
|
|
62
|
+
}
|
|
63
|
+
return !!isDisabled;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function ToggleButton(props: ToggleButtonProps): JSX.Element {
|
|
67
|
+
const contextProps = useContext(ToggleButtonContext);
|
|
68
|
+
const mergedProps = (contextProps ? mergeProps(contextProps, props) : props) as ToggleButtonProps;
|
|
69
|
+
|
|
70
|
+
const [local, ariaProps] = splitProps(mergedProps, [
|
|
71
|
+
'children',
|
|
72
|
+
'class',
|
|
73
|
+
'style',
|
|
74
|
+
'slot',
|
|
75
|
+
'toggleKey',
|
|
76
|
+
'id',
|
|
77
|
+
]);
|
|
78
|
+
const groupState = useToggleButtonGroupStateContext();
|
|
79
|
+
const groupKey = local.id ?? local.toggleKey;
|
|
80
|
+
|
|
81
|
+
const toggleAria = groupState && groupKey != null
|
|
82
|
+
? createToggleButtonGroupItem(
|
|
83
|
+
{
|
|
84
|
+
...ariaProps,
|
|
85
|
+
id: groupKey,
|
|
86
|
+
},
|
|
87
|
+
groupState
|
|
88
|
+
)
|
|
89
|
+
: createToggleButton(ariaProps);
|
|
90
|
+
|
|
91
|
+
const isDisabled = () =>
|
|
92
|
+
resolveDisabledValue(ariaProps.isDisabled) || !!groupState?.isDisabled;
|
|
93
|
+
|
|
94
|
+
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
95
|
+
const { isHovered, hoverProps } = createHover({
|
|
96
|
+
get isDisabled() {
|
|
97
|
+
return isDisabled();
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const renderValues = createMemo<ToggleButtonRenderProps>(() => ({
|
|
102
|
+
isHovered: isHovered(),
|
|
103
|
+
isPressed: toggleAria.isPressed(),
|
|
104
|
+
isFocused: isFocused(),
|
|
105
|
+
isFocusVisible: isFocusVisible(),
|
|
106
|
+
isDisabled: isDisabled(),
|
|
107
|
+
isSelected: toggleAria.isSelected(),
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const renderProps = useRenderProps(
|
|
111
|
+
{
|
|
112
|
+
children: local.children,
|
|
113
|
+
class: local.class,
|
|
114
|
+
style: local.style,
|
|
115
|
+
defaultClassName: 'solidaria-ToggleButton',
|
|
116
|
+
},
|
|
117
|
+
renderValues
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const domProps = createMemo(() => {
|
|
121
|
+
const filtered = filterDOMProps(ariaProps, { global: true });
|
|
122
|
+
delete (filtered as Record<string, unknown>).onClick;
|
|
123
|
+
delete (filtered as Record<string, unknown>).id;
|
|
124
|
+
return filtered;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const cleanButtonProps = () => {
|
|
128
|
+
const { ref: _ref1, ...rest } = toggleAria.buttonProps as Record<string, unknown>;
|
|
129
|
+
return rest;
|
|
130
|
+
};
|
|
131
|
+
const cleanFocusProps = () => {
|
|
132
|
+
const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
|
|
133
|
+
return rest;
|
|
134
|
+
};
|
|
135
|
+
const cleanHoverProps = () => {
|
|
136
|
+
const { ref: _ref3, ...rest } = hoverProps as Record<string, unknown>;
|
|
137
|
+
return rest;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<button
|
|
142
|
+
{...domProps()}
|
|
143
|
+
{...cleanButtonProps()}
|
|
144
|
+
{...cleanFocusProps()}
|
|
145
|
+
{...cleanHoverProps()}
|
|
146
|
+
class={renderProps.class()}
|
|
147
|
+
style={renderProps.style()}
|
|
148
|
+
slot={local.slot}
|
|
149
|
+
data-pressed={toggleAria.isPressed() || undefined}
|
|
150
|
+
data-hovered={isHovered() || undefined}
|
|
151
|
+
data-focused={isFocused() || undefined}
|
|
152
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
153
|
+
data-disabled={isDisabled() || undefined}
|
|
154
|
+
data-selected={toggleAria.isSelected() || undefined}
|
|
155
|
+
>
|
|
156
|
+
{renderProps.renderChildren()}
|
|
157
|
+
</button>
|
|
158
|
+
);
|
|
159
|
+
}
|