@proyecto-viviana/solidaria-components 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Button.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +4 -1
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Menu.d.ts.map +1 -1
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts.map +1 -1
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/TextField.d.ts +1 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/index.js +254 -33
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +254 -33
- package/dist/index.jsx.map +1 -1
- package/package.json +1 -1
- package/src/Button.tsx +69 -22
- package/src/ComboBox.tsx +77 -9
- package/src/DatePicker.tsx +60 -5
- package/src/Menu.tsx +49 -6
- package/src/NumberField.tsx +22 -2
- package/src/Popover.tsx +9 -1
- package/src/TagGroup.tsx +1 -0
- package/src/TextField.tsx +32 -7
- package/src/Tooltip.tsx +52 -7
package/package.json
CHANGED
package/src/Button.tsx
CHANGED
|
@@ -97,6 +97,44 @@ function createLiveCustomRootProps(
|
|
|
97
97
|
return props;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
const buttonAriaOverrideProps = [
|
|
101
|
+
"aria-label",
|
|
102
|
+
"aria-labelledby",
|
|
103
|
+
"aria-describedby",
|
|
104
|
+
"aria-details",
|
|
105
|
+
"aria-haspopup",
|
|
106
|
+
"aria-expanded",
|
|
107
|
+
"aria-controls",
|
|
108
|
+
"aria-pressed",
|
|
109
|
+
"aria-current",
|
|
110
|
+
"aria-disabled",
|
|
111
|
+
] as const;
|
|
112
|
+
|
|
113
|
+
function createForwardedAriaButtonProps(
|
|
114
|
+
source: AriaButtonProps,
|
|
115
|
+
overrides: AriaButtonProps,
|
|
116
|
+
): AriaButtonProps {
|
|
117
|
+
const result = {} as AriaButtonProps;
|
|
118
|
+
|
|
119
|
+
for (const key in source) {
|
|
120
|
+
Object.defineProperty(result, key, {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
configurable: true,
|
|
123
|
+
get() {
|
|
124
|
+
return (source as Record<string, unknown>)[key];
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const key in overrides) {
|
|
130
|
+
const descriptor = Object.getOwnPropertyDescriptor(overrides, key);
|
|
131
|
+
if (!descriptor) continue;
|
|
132
|
+
Object.defineProperty(result, key, { ...descriptor, enumerable: true, configurable: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
100
138
|
export interface ButtonRenderProps {
|
|
101
139
|
/** Whether the button is currently hovered with a mouse. */
|
|
102
140
|
isHovered: boolean;
|
|
@@ -238,28 +276,29 @@ export function Button(props: ButtonProps): JSX.Element {
|
|
|
238
276
|
}
|
|
239
277
|
};
|
|
240
278
|
|
|
241
|
-
const buttonAria = createButton(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
279
|
+
const buttonAria = createButton(
|
|
280
|
+
createForwardedAriaButtonProps(ariaProps, {
|
|
281
|
+
onPress: handlePress,
|
|
282
|
+
get onPressStart() {
|
|
283
|
+
return resolvePending() ? undefined : ariaProps.onPressStart;
|
|
284
|
+
},
|
|
285
|
+
get onPressEnd() {
|
|
286
|
+
return resolvePending() ? undefined : ariaProps.onPressEnd;
|
|
287
|
+
},
|
|
288
|
+
get onPressUp() {
|
|
289
|
+
return resolvePending() ? undefined : ariaProps.onPressUp;
|
|
290
|
+
},
|
|
291
|
+
get onPressChange() {
|
|
292
|
+
return resolvePending() ? undefined : ariaProps.onPressChange;
|
|
293
|
+
},
|
|
294
|
+
get onClick() {
|
|
295
|
+
return resolvePending() ? undefined : ariaProps.onClick;
|
|
296
|
+
},
|
|
297
|
+
get isDisabled() {
|
|
298
|
+
return resolveDisabled() || resolvePending();
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
263
302
|
|
|
264
303
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
265
304
|
|
|
@@ -394,6 +433,13 @@ export function Button(props: ButtonProps): JSX.Element {
|
|
|
394
433
|
}
|
|
395
434
|
return next;
|
|
396
435
|
};
|
|
436
|
+
const directAriaProps = () => {
|
|
437
|
+
const next: Record<string, unknown> = {};
|
|
438
|
+
for (const name of buttonAriaOverrideProps) {
|
|
439
|
+
next[name] = (ariaProps as Record<string, unknown>)[name];
|
|
440
|
+
}
|
|
441
|
+
return next;
|
|
442
|
+
};
|
|
397
443
|
const disablePendingInteractions = (props: Record<string, unknown>) => {
|
|
398
444
|
if (!resolvePending()) {
|
|
399
445
|
return props;
|
|
@@ -433,6 +479,7 @@ export function Button(props: ButtonProps): JSX.Element {
|
|
|
433
479
|
({
|
|
434
480
|
...domProps(),
|
|
435
481
|
...disablePendingInteractions(cleanButtonProps()),
|
|
482
|
+
...directAriaProps(),
|
|
436
483
|
...triggerAriaProps(),
|
|
437
484
|
...cleanFocusProps(),
|
|
438
485
|
...cleanHoverProps(),
|
package/src/ComboBox.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type JSX,
|
|
10
10
|
type Accessor,
|
|
11
11
|
createContext,
|
|
12
|
+
createEffect,
|
|
12
13
|
createMemo,
|
|
13
14
|
onCleanup,
|
|
14
15
|
splitProps,
|
|
@@ -290,6 +291,7 @@ export interface ComboBoxOptionProps<T>
|
|
|
290
291
|
|
|
291
292
|
interface ComboBoxContextValue<T> {
|
|
292
293
|
state: ComboBoxState<T>;
|
|
294
|
+
listState: ListState<T>;
|
|
293
295
|
inputProps: () => JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
294
296
|
buttonProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
295
297
|
listBoxProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
@@ -308,13 +310,31 @@ interface ComboBoxContextValue<T> {
|
|
|
308
310
|
setTriggerRef: (el: HTMLElement | null) => void;
|
|
309
311
|
listBoxRef: () => HTMLElement | null;
|
|
310
312
|
setListBoxRef: (el: HTMLElement | null) => void;
|
|
313
|
+
registerOptionAction: (key: Key, action: (() => void) | undefined) => void;
|
|
314
|
+
runOptionAction: (key: Key) => void;
|
|
311
315
|
slots?: Record<string, Partial<ComboBoxProps<T>>>;
|
|
312
316
|
}
|
|
313
317
|
|
|
318
|
+
type InputKeyboardEvent = KeyboardEvent & {
|
|
319
|
+
currentTarget: HTMLInputElement;
|
|
320
|
+
target: Element;
|
|
321
|
+
};
|
|
322
|
+
|
|
314
323
|
export const ComboBoxContext = createContext<ComboBoxContextValue<unknown> | null>(null);
|
|
315
324
|
export const ComboBoxStateContext = createContext<ComboBoxState<unknown> | null>(null);
|
|
316
325
|
export const ComboBoxValueContext = ComboBoxContext;
|
|
317
326
|
|
|
327
|
+
function callInputKeyDown(
|
|
328
|
+
handler: JSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent> | undefined,
|
|
329
|
+
event: InputKeyboardEvent,
|
|
330
|
+
) {
|
|
331
|
+
if (typeof handler === "function") {
|
|
332
|
+
handler(event);
|
|
333
|
+
} else if (handler) {
|
|
334
|
+
handler[0](handler[1], event);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
318
338
|
/**
|
|
319
339
|
* A combobox combines a text input with a listbox, allowing users to filter a list of options.
|
|
320
340
|
*/
|
|
@@ -360,6 +380,10 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
360
380
|
let buttonRef: HTMLElement | null = null;
|
|
361
381
|
let triggerRef: HTMLElement | null = null;
|
|
362
382
|
let listBoxRef: HTMLElement | null = null;
|
|
383
|
+
const optionActions = new Map<Key, () => void>();
|
|
384
|
+
const runOptionAction = (key: Key) => {
|
|
385
|
+
optionActions.get(key)?.();
|
|
386
|
+
};
|
|
363
387
|
|
|
364
388
|
const state = createComboBoxState<T>({
|
|
365
389
|
get items() {
|
|
@@ -441,6 +465,7 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
441
465
|
return ariaProps.isRequired;
|
|
442
466
|
},
|
|
443
467
|
});
|
|
468
|
+
const listState = createComboBoxListStateAdapter(state);
|
|
444
469
|
|
|
445
470
|
const effectiveFormValue = createMemo<"key" | "text">(() => {
|
|
446
471
|
if (stateProps.allowsCustomValue) {
|
|
@@ -472,6 +497,28 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
472
497
|
() => listBoxRef,
|
|
473
498
|
);
|
|
474
499
|
|
|
500
|
+
const getInputProps = () => {
|
|
501
|
+
const inputProps = comboBoxAria.inputProps;
|
|
502
|
+
const originalOnKeyDown = inputProps.onKeyDown;
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
...inputProps,
|
|
506
|
+
onKeyDown: (event: InputKeyboardEvent) => {
|
|
507
|
+
const focusedKey = state.focusedKey();
|
|
508
|
+
const shouldRunAction =
|
|
509
|
+
event.key === "Enter" &&
|
|
510
|
+
state.isOpen() &&
|
|
511
|
+
focusedKey != null &&
|
|
512
|
+
!state.isKeyDisabled(focusedKey);
|
|
513
|
+
const optionAction = shouldRunAction ? optionActions.get(focusedKey) : undefined;
|
|
514
|
+
|
|
515
|
+
callInputKeyDown(originalOnKeyDown, event);
|
|
516
|
+
|
|
517
|
+
optionAction?.();
|
|
518
|
+
},
|
|
519
|
+
} as JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
520
|
+
};
|
|
521
|
+
|
|
475
522
|
const { isHovered, hoverProps } = createHover({
|
|
476
523
|
get isDisabled() {
|
|
477
524
|
return ariaProps.isDisabled;
|
|
@@ -519,9 +566,13 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
519
566
|
value={
|
|
520
567
|
{
|
|
521
568
|
state,
|
|
522
|
-
|
|
569
|
+
listState,
|
|
570
|
+
inputProps: getInputProps,
|
|
523
571
|
buttonProps: () => comboBoxAria.buttonProps,
|
|
524
|
-
listBoxProps: () =>
|
|
572
|
+
listBoxProps: () => ({
|
|
573
|
+
...comboBoxAria.listBoxProps,
|
|
574
|
+
onAction: runOptionAction,
|
|
575
|
+
}),
|
|
525
576
|
labelProps: () => comboBoxAria.labelProps,
|
|
526
577
|
descriptionProps: () => comboBoxAria.descriptionProps,
|
|
527
578
|
errorMessageProps: () => comboBoxAria.errorMessageProps,
|
|
@@ -545,6 +596,14 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
545
596
|
setListBoxRef: (el) => {
|
|
546
597
|
listBoxRef = el;
|
|
547
598
|
},
|
|
599
|
+
registerOptionAction: (key, action) => {
|
|
600
|
+
if (action) {
|
|
601
|
+
optionActions.set(key, action);
|
|
602
|
+
} else {
|
|
603
|
+
optionActions.delete(key);
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
runOptionAction,
|
|
548
607
|
slots: local.slots,
|
|
549
608
|
} as ComboBoxContextValue<unknown>
|
|
550
609
|
}
|
|
@@ -850,7 +909,7 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
850
909
|
throw new Error("ComboBoxListBox must be used within a ComboBox");
|
|
851
910
|
}
|
|
852
911
|
const context = rawContext as ComboBoxContextValue<T>;
|
|
853
|
-
const { state: comboBoxState, isOpen, inputRef, buttonRef, setListBoxRef } = context;
|
|
912
|
+
const { state: comboBoxState, listState, isOpen, inputRef, buttonRef, setListBoxRef } = context;
|
|
854
913
|
const state = comboBoxState;
|
|
855
914
|
|
|
856
915
|
let listBoxRef: HTMLUListElement | undefined;
|
|
@@ -880,7 +939,7 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
880
939
|
// Create listbox aria props using ComboBoxState's ListState-compatible interface
|
|
881
940
|
const { listBoxProps } = createListBox(
|
|
882
941
|
context.listBoxProps as unknown as AriaListBoxProps,
|
|
883
|
-
|
|
942
|
+
listState,
|
|
884
943
|
);
|
|
885
944
|
|
|
886
945
|
const renderValues = createMemo<ComboBoxListBoxRenderProps>(() => ({
|
|
@@ -976,15 +1035,24 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
976
1035
|
|
|
977
1036
|
const stateContext = useContext(ComboBoxStateContext);
|
|
978
1037
|
const comboBoxContext = useContext(ComboBoxContext);
|
|
979
|
-
if (!stateContext) {
|
|
1038
|
+
if (!stateContext || !comboBoxContext) {
|
|
980
1039
|
throw new Error("ComboBoxOption must be used within a ComboBox");
|
|
981
1040
|
}
|
|
982
1041
|
const state = stateContext as ComboBoxState<T>;
|
|
1042
|
+
const listState = (comboBoxContext as ComboBoxContextValue<T>).listState;
|
|
983
1043
|
const optionId = () => {
|
|
984
1044
|
const listBoxId = getComboBoxData(state as ComboBoxState<unknown>)?.listBoxId;
|
|
985
1045
|
return listBoxId ? `${listBoxId}-option-${local.id}` : String(local.id);
|
|
986
1046
|
};
|
|
987
1047
|
|
|
1048
|
+
createEffect(() => {
|
|
1049
|
+
const key = local.id;
|
|
1050
|
+
comboBoxContext?.registerOptionAction(key, local.onAction);
|
|
1051
|
+
onCleanup(() => {
|
|
1052
|
+
comboBoxContext?.registerOptionAction(key, undefined);
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
|
|
988
1056
|
// Create option aria props using ComboBoxState's ListState-compatible interface
|
|
989
1057
|
const optionAria = createOption<T>(
|
|
990
1058
|
{
|
|
@@ -998,9 +1066,6 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
998
1066
|
get "aria-label"() {
|
|
999
1067
|
return ariaProps["aria-label"];
|
|
1000
1068
|
},
|
|
1001
|
-
get onAction() {
|
|
1002
|
-
return local.onAction;
|
|
1003
|
-
},
|
|
1004
1069
|
shouldSelectOnPressUp: true,
|
|
1005
1070
|
shouldFocusOnHover: true,
|
|
1006
1071
|
shouldUseVirtualFocus: true,
|
|
@@ -1014,8 +1079,11 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
1014
1079
|
get onHoverChange() {
|
|
1015
1080
|
return ariaProps.onHoverChange;
|
|
1016
1081
|
},
|
|
1082
|
+
get onAction() {
|
|
1083
|
+
return local.onAction;
|
|
1084
|
+
},
|
|
1017
1085
|
},
|
|
1018
|
-
|
|
1086
|
+
listState,
|
|
1019
1087
|
);
|
|
1020
1088
|
|
|
1021
1089
|
const isOptionFocusVisible = () =>
|
package/src/DatePicker.tsx
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type CalendarState,
|
|
40
40
|
type RangeCalendarState,
|
|
41
41
|
type DateFieldStateProps,
|
|
42
|
+
type DatePickerStateOptions,
|
|
42
43
|
type CalendarDate,
|
|
43
44
|
type DateValue,
|
|
44
45
|
type RangeCalendarStateProps,
|
|
@@ -349,11 +350,65 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
|
|
|
349
350
|
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | null>(null);
|
|
350
351
|
const [fieldRef, setFieldRef] = createSignal<HTMLDivElement | null>(null);
|
|
351
352
|
|
|
352
|
-
// Unified state using createDatePickerState as single source of truth
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
// Unified state using createDatePickerState as single source of truth.
|
|
354
|
+
// Use getters here so controlled props keep tracking after splitProps.
|
|
355
|
+
const datePickerStateProps = {
|
|
356
|
+
get value() {
|
|
357
|
+
return stateProps.value;
|
|
358
|
+
},
|
|
359
|
+
get defaultValue() {
|
|
360
|
+
return stateProps.defaultValue;
|
|
361
|
+
},
|
|
362
|
+
get onChange() {
|
|
363
|
+
return stateProps.onChange;
|
|
364
|
+
},
|
|
365
|
+
get minValue() {
|
|
366
|
+
return stateProps.minValue;
|
|
367
|
+
},
|
|
368
|
+
get maxValue() {
|
|
369
|
+
return stateProps.maxValue;
|
|
370
|
+
},
|
|
371
|
+
get isDisabled() {
|
|
372
|
+
return stateProps.isDisabled;
|
|
373
|
+
},
|
|
374
|
+
get isReadOnly() {
|
|
375
|
+
return stateProps.isReadOnly;
|
|
376
|
+
},
|
|
377
|
+
get isRequired() {
|
|
378
|
+
return stateProps.isRequired;
|
|
379
|
+
},
|
|
380
|
+
get granularity() {
|
|
381
|
+
return stateProps.granularity;
|
|
382
|
+
},
|
|
383
|
+
get hourCycle() {
|
|
384
|
+
return stateProps.hourCycle;
|
|
385
|
+
},
|
|
386
|
+
get hideTimeZone() {
|
|
387
|
+
return stateProps.hideTimeZone;
|
|
388
|
+
},
|
|
389
|
+
get placeholderValue() {
|
|
390
|
+
return stateProps.placeholderValue;
|
|
391
|
+
},
|
|
392
|
+
get shouldCloseOnSelect() {
|
|
393
|
+
return local.shouldCloseOnSelect;
|
|
394
|
+
},
|
|
395
|
+
get defaultOpen() {
|
|
396
|
+
return stateProps.defaultOpen;
|
|
397
|
+
},
|
|
398
|
+
get isOpen() {
|
|
399
|
+
return stateProps.isOpen;
|
|
400
|
+
},
|
|
401
|
+
get onOpenChange() {
|
|
402
|
+
return stateProps.onOpenChange;
|
|
403
|
+
},
|
|
404
|
+
get isDateUnavailable() {
|
|
405
|
+
return stateProps.isDateUnavailable;
|
|
406
|
+
},
|
|
407
|
+
get validationState() {
|
|
408
|
+
return stateProps.validationState;
|
|
409
|
+
},
|
|
410
|
+
} satisfies DatePickerStateOptions<T>;
|
|
411
|
+
const datePickerState = createDatePickerState<T>(datePickerStateProps);
|
|
357
412
|
|
|
358
413
|
const overlayState = {
|
|
359
414
|
get isOpen() {
|
package/src/Menu.tsx
CHANGED
|
@@ -373,6 +373,7 @@ export function SubmenuTrigger(props: SubmenuTriggerProps): JSX.Element {
|
|
|
373
373
|
const triggerId = createUniqueId();
|
|
374
374
|
const menuId = createUniqueId();
|
|
375
375
|
let hoverTimeout: number | undefined;
|
|
376
|
+
let hasPointerHover = false;
|
|
376
377
|
const delay = () => props.delay ?? 200;
|
|
377
378
|
|
|
378
379
|
const clearHoverTimeout = () => {
|
|
@@ -387,6 +388,16 @@ export function SubmenuTrigger(props: SubmenuTriggerProps): JSX.Element {
|
|
|
387
388
|
state.open();
|
|
388
389
|
};
|
|
389
390
|
|
|
391
|
+
const queueOpenSubmenu = () => {
|
|
392
|
+
clearHoverTimeout();
|
|
393
|
+
const open = () => state.open();
|
|
394
|
+
if (typeof queueMicrotask === "function") {
|
|
395
|
+
queueMicrotask(open);
|
|
396
|
+
} else {
|
|
397
|
+
Promise.resolve().then(open);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
390
401
|
const scheduleOpen = () => {
|
|
391
402
|
clearHoverTimeout();
|
|
392
403
|
hoverTimeout = window.setTimeout(() => {
|
|
@@ -395,6 +406,28 @@ export function SubmenuTrigger(props: SubmenuTriggerProps): JSX.Element {
|
|
|
395
406
|
}, delay());
|
|
396
407
|
};
|
|
397
408
|
|
|
409
|
+
const schedulePointerOpen = (event: PointerEvent) => {
|
|
410
|
+
hasPointerHover = true;
|
|
411
|
+
if (event.isTrusted === false) {
|
|
412
|
+
queueOpenSubmenu();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
scheduleOpen();
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const openFromMouseHover = () => {
|
|
420
|
+
if (state.isOpen()) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (hasPointerHover) {
|
|
425
|
+
scheduleOpen();
|
|
426
|
+
} else {
|
|
427
|
+
queueOpenSubmenu();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
398
431
|
onCleanup(clearHoverTimeout);
|
|
399
432
|
|
|
400
433
|
const menuTriggerContext = createMemo<MenuTriggerContextValue>(() => ({
|
|
@@ -430,17 +463,22 @@ export function SubmenuTrigger(props: SubmenuTriggerProps): JSX.Element {
|
|
|
430
463
|
props: () => ({
|
|
431
464
|
id: triggerId,
|
|
432
465
|
"aria-haspopup": "menu",
|
|
433
|
-
"aria-expanded"
|
|
434
|
-
|
|
466
|
+
get "aria-expanded"() {
|
|
467
|
+
return state.isOpen() || undefined;
|
|
468
|
+
},
|
|
469
|
+
get "aria-controls"() {
|
|
470
|
+
return state.isOpen() ? menuId : undefined;
|
|
471
|
+
},
|
|
435
472
|
onPointerEnter: (event: PointerEvent) => {
|
|
436
473
|
if (event.pointerType === "touch") return;
|
|
437
|
-
|
|
474
|
+
schedulePointerOpen(event);
|
|
438
475
|
},
|
|
439
476
|
onPointerOver: (event: PointerEvent) => {
|
|
440
477
|
if (event.pointerType === "touch") return;
|
|
441
|
-
|
|
478
|
+
schedulePointerOpen(event);
|
|
442
479
|
},
|
|
443
|
-
onMouseEnter:
|
|
480
|
+
onMouseEnter: openFromMouseHover,
|
|
481
|
+
onMouseOver: openFromMouseHover,
|
|
444
482
|
onKeyDown: (event: KeyboardEvent) => {
|
|
445
483
|
if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
|
|
446
484
|
event.preventDefault();
|
|
@@ -1028,6 +1066,11 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
1028
1066
|
dndDropIndicator(index, position) ??
|
|
1029
1067
|
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
1030
1068
|
}));
|
|
1069
|
+
const menuItemContextValue = createMemo<MenuItemContextValue>(() =>
|
|
1070
|
+
local.shouldCloseOnSelect !== undefined
|
|
1071
|
+
? { closeOnSelect: local.shouldCloseOnSelect }
|
|
1072
|
+
: {},
|
|
1073
|
+
);
|
|
1031
1074
|
const menuListChildren = () => (
|
|
1032
1075
|
<SharedElementTransition>
|
|
1033
1076
|
{state.collection().size === 0 && !usesStaticChildren() && local.renderEmptyState ? (
|
|
@@ -1156,7 +1199,7 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
1156
1199
|
<StaticMenuCollectionContext.Provider
|
|
1157
1200
|
value={usesStaticChildren() ? staticCollectionContext : null}
|
|
1158
1201
|
>
|
|
1159
|
-
<MenuItemContext.Provider value={
|
|
1202
|
+
<MenuItemContext.Provider value={menuItemContextValue()}>
|
|
1160
1203
|
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
1161
1204
|
<>
|
|
1162
1205
|
<Show when={ariaProps.label}>
|
package/src/NumberField.tsx
CHANGED
|
@@ -525,9 +525,19 @@ export function NumberFieldIncrementButton(props: NumberFieldIncrementButtonProp
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
const isDisabled = () => context.isDisabled || !context.state.canIncrement();
|
|
528
|
+
const pressButtonProps = () => {
|
|
529
|
+
const {
|
|
530
|
+
onClick: _onClick,
|
|
531
|
+
disabled: _disabled,
|
|
532
|
+
type: _type,
|
|
533
|
+
tabIndex: _tabIndex,
|
|
534
|
+
...rest
|
|
535
|
+
} = context.incrementButtonProps as Record<string, unknown>;
|
|
536
|
+
return rest;
|
|
537
|
+
};
|
|
528
538
|
|
|
529
539
|
const buttonAria = createButton({
|
|
530
|
-
...(
|
|
540
|
+
...pressButtonProps(),
|
|
531
541
|
elementType: "div",
|
|
532
542
|
get isDisabled() {
|
|
533
543
|
return isDisabled();
|
|
@@ -594,9 +604,19 @@ export function NumberFieldDecrementButton(props: NumberFieldDecrementButtonProp
|
|
|
594
604
|
}
|
|
595
605
|
|
|
596
606
|
const isDisabled = () => context.isDisabled || !context.state.canDecrement();
|
|
607
|
+
const pressButtonProps = () => {
|
|
608
|
+
const {
|
|
609
|
+
onClick: _onClick,
|
|
610
|
+
disabled: _disabled,
|
|
611
|
+
type: _type,
|
|
612
|
+
tabIndex: _tabIndex,
|
|
613
|
+
...rest
|
|
614
|
+
} = context.decrementButtonProps as Record<string, unknown>;
|
|
615
|
+
return rest;
|
|
616
|
+
};
|
|
597
617
|
|
|
598
618
|
const buttonAria = createButton({
|
|
599
|
-
...(
|
|
619
|
+
...pressButtonProps(),
|
|
600
620
|
elementType: "div",
|
|
601
621
|
get isDisabled() {
|
|
602
622
|
return isDisabled();
|
package/src/Popover.tsx
CHANGED
|
@@ -480,6 +480,14 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
480
480
|
};
|
|
481
481
|
|
|
482
482
|
const shouldBeDialog = () => !local.isNonModal || resolvedTrigger() === "SubmenuTrigger";
|
|
483
|
+
const shouldContainFocus = () => {
|
|
484
|
+
if (!shouldBeDialog()) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const trigger = resolvedTrigger();
|
|
489
|
+
return trigger !== "MenuTrigger" && trigger !== "SubmenuTrigger";
|
|
490
|
+
};
|
|
483
491
|
const portalContext = useUNSAFE_PortalContext();
|
|
484
492
|
const portalContainer = () => {
|
|
485
493
|
if (isSubPopover()) {
|
|
@@ -547,7 +555,7 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
547
555
|
<PopoverContext.Provider
|
|
548
556
|
value={{ placement: popoverAria.placement, arrowProps: () => popoverAria.arrowProps }}
|
|
549
557
|
>
|
|
550
|
-
<FocusScope contain={
|
|
558
|
+
<FocusScope contain={shouldContainFocus()} restoreFocus>
|
|
551
559
|
<div
|
|
552
560
|
{...domProps()}
|
|
553
561
|
{...cleanPopoverProps()}
|
package/src/TagGroup.tsx
CHANGED
package/src/TextField.tsx
CHANGED
|
@@ -79,6 +79,7 @@ export interface TextFieldContextValue {
|
|
|
79
79
|
isInvalid?: boolean;
|
|
80
80
|
slots?: Record<string, TextFieldProps>;
|
|
81
81
|
inputId?: string;
|
|
82
|
+
setInputId?: (id: string | undefined) => void;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export const TextFieldContext = createContext<TextFieldContextValue | null>(null);
|
|
@@ -204,6 +205,14 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
204
205
|
const context = useContext(TextFieldContext);
|
|
205
206
|
let inputElement: HTMLInputElement | undefined;
|
|
206
207
|
|
|
208
|
+
createEffect(() => {
|
|
209
|
+
context?.setInputId?.(props.id);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
onCleanup(() => {
|
|
213
|
+
context?.setInputId?.(undefined);
|
|
214
|
+
});
|
|
215
|
+
|
|
207
216
|
// Merge context inputProps with local props (local props take precedence)
|
|
208
217
|
const mergedProps = () => {
|
|
209
218
|
if (context) {
|
|
@@ -291,6 +300,14 @@ export function TextArea(props: TextAreaProps): JSX.Element {
|
|
|
291
300
|
const context = useContext(TextFieldContext);
|
|
292
301
|
let textAreaElement: HTMLTextAreaElement | undefined;
|
|
293
302
|
|
|
303
|
+
createEffect(() => {
|
|
304
|
+
context?.setInputId?.(props.id);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
onCleanup(() => {
|
|
308
|
+
context?.setInputId?.(undefined);
|
|
309
|
+
});
|
|
310
|
+
|
|
294
311
|
// Merge context inputProps with local props (local props take precedence)
|
|
295
312
|
// Note: TextArea uses inputProps from context since it's an input variant
|
|
296
313
|
const mergedProps = () => {
|
|
@@ -396,6 +413,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
396
413
|
errorMessageProps: _errorMessageProps,
|
|
397
414
|
isInvalid: _isInvalid,
|
|
398
415
|
slots: _slots,
|
|
416
|
+
setInputId: _setInputId,
|
|
399
417
|
...rest
|
|
400
418
|
} = contextProps;
|
|
401
419
|
return rest as TextFieldProps;
|
|
@@ -449,6 +467,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
449
467
|
return ariaProps.isDisabled;
|
|
450
468
|
},
|
|
451
469
|
});
|
|
470
|
+
const [inputIdOverride, setInputIdOverride] = createSignal<string | undefined>();
|
|
452
471
|
|
|
453
472
|
const renderValues = createMemo<TextFieldRenderProps>(() => ({
|
|
454
473
|
isDisabled: ariaProps.isDisabled || false,
|
|
@@ -551,7 +570,10 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
551
570
|
// onMount effect would flip undefined->id post-mount and re-execute the Label
|
|
552
571
|
// template — crashing hydration if the re-run lands mid-hydration (dev).
|
|
553
572
|
get inputId() {
|
|
554
|
-
return (textFieldAria.inputProps as { id?: string }).id;
|
|
573
|
+
return inputIdOverride() ?? (textFieldAria.inputProps as { id?: string }).id;
|
|
574
|
+
},
|
|
575
|
+
setInputId(id: string | undefined) {
|
|
576
|
+
setInputIdOverride(id);
|
|
555
577
|
},
|
|
556
578
|
};
|
|
557
579
|
// Resolve the render-prop children ONCE (untracked). Re-invoking it on a
|
|
@@ -559,10 +581,11 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
559
581
|
// throws a Hydration Mismatch (worst in dev, where slow unbundled modules widen
|
|
560
582
|
// the hydration window). The children carry their own fine-grained reactivity
|
|
561
583
|
// (render-value getters + <Show>s), so they update without being re-created.
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
584
|
+
const FieldChildren = () =>
|
|
585
|
+
untrack(() => {
|
|
586
|
+
const children = local.children;
|
|
587
|
+
return typeof children === "function" ? children(childRenderValues) : children;
|
|
588
|
+
});
|
|
566
589
|
const rootProps = () =>
|
|
567
590
|
({
|
|
568
591
|
...domProps(),
|
|
@@ -581,7 +604,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
581
604
|
const customRootProps = () =>
|
|
582
605
|
({
|
|
583
606
|
...rootProps(),
|
|
584
|
-
children:
|
|
607
|
+
children: <FieldChildren />,
|
|
585
608
|
}) as JSX.HTMLAttributes<HTMLDivElement>;
|
|
586
609
|
|
|
587
610
|
return (
|
|
@@ -590,7 +613,9 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
590
613
|
{local.render ? (
|
|
591
614
|
local.render(customRootProps(), renderValues())
|
|
592
615
|
) : (
|
|
593
|
-
<div {...rootProps()}>
|
|
616
|
+
<div {...rootProps()}>
|
|
617
|
+
<FieldChildren />
|
|
618
|
+
</div>
|
|
594
619
|
)}
|
|
595
620
|
</TextFieldContext.Provider>
|
|
596
621
|
</FieldErrorContext.Provider>
|