@proyecto-viviana/solidaria-components 0.4.0 → 0.4.1
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/ActionBar.d.ts.map +1 -1
- package/dist/ActionBar.js +2 -11
- package/dist/ActionBar.js.map +1 -1
- package/dist/ActionBar.jsx +2 -11
- package/dist/ActionBar.jsx.map +1 -1
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/ActionGroup.js +1 -1
- package/dist/ActionGroup.js.map +1 -1
- package/dist/ActionGroup.jsx +1 -1
- package/dist/ActionGroup.jsx.map +1 -1
- package/dist/Autocomplete.d.ts +2 -2
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Autocomplete.js +7 -7
- package/dist/Autocomplete.js.map +1 -1
- package/dist/Autocomplete.jsx +7 -7
- package/dist/Autocomplete.jsx.map +1 -1
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Breadcrumbs.js +15 -16
- package/dist/Breadcrumbs.js.map +1 -1
- package/dist/Breadcrumbs.jsx +19 -24
- package/dist/Breadcrumbs.jsx.map +1 -1
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Calendar.js +100 -65
- package/dist/Calendar.js.map +1 -1
- package/dist/Calendar.jsx +10 -1
- package/dist/Calendar.jsx.map +1 -1
- package/dist/Color.d.ts.map +1 -1
- package/dist/Color.js +15 -18
- package/dist/Color.js.map +1 -1
- package/dist/Color.jsx +15 -19
- package/dist/Color.jsx.map +1 -1
- package/dist/ComboBox.d.ts +4 -0
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ComboBox.js +21 -13
- package/dist/ComboBox.js.map +1 -1
- package/dist/ComboBox.jsx +23 -13
- package/dist/ComboBox.jsx.map +1 -1
- package/dist/DateField.d.ts +11 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DateField2.js +237 -234
- package/dist/DateField2.js.map +1 -1
- package/dist/DateField2.jsx +134 -97
- package/dist/DateField2.jsx.map +1 -1
- package/dist/DatePicker.d.ts +42 -3
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DatePicker.js +6 -990
- package/dist/DatePicker.jsx +6 -681
- package/dist/DatePicker2.js +1171 -0
- package/dist/DatePicker2.js.map +1 -0
- package/dist/DatePicker2.jsx +820 -0
- package/dist/DatePicker2.jsx.map +1 -0
- package/dist/DateRangePickerContext.d.ts +2 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -1
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/Disclosure.js +3 -0
- package/dist/Disclosure.js.map +1 -1
- package/dist/Disclosure.jsx +1 -1
- package/dist/Disclosure.jsx.map +1 -1
- package/dist/GridList.d.ts.map +1 -1
- package/dist/GridList.js +2 -2
- package/dist/GridList.js.map +1 -1
- package/dist/GridList.jsx +2 -2
- package/dist/GridList.jsx.map +1 -1
- package/dist/ListBox.d.ts +2 -2
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListBox.js +327 -166
- package/dist/ListBox.js.map +1 -1
- package/dist/ListBox.jsx +161 -25
- package/dist/ListBox.jsx.map +1 -1
- package/dist/Modal.d.ts.map +1 -1
- package/dist/Modal.js +7 -5
- package/dist/Modal.js.map +1 -1
- package/dist/Modal.jsx +5 -5
- package/dist/Modal.jsx.map +1 -1
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Popover.js +16 -9
- package/dist/Popover.js.map +1 -1
- package/dist/Popover.jsx +25 -4
- package/dist/Popover.jsx.map +1 -1
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RangeCalendar.js +182 -46
- package/dist/RangeCalendar.js.map +1 -1
- package/dist/RangeCalendar.jsx +72 -2
- package/dist/RangeCalendar.jsx.map +1 -1
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/SearchField.js +26 -2
- package/dist/SearchField.js.map +1 -1
- package/dist/SearchField.jsx +26 -2
- package/dist/SearchField.jsx.map +1 -1
- package/dist/Select.d.ts +7 -0
- package/dist/Select.d.ts.map +1 -1
- package/dist/Select.js +62 -49
- package/dist/Select.js.map +1 -1
- package/dist/Select.jsx +33 -18
- package/dist/Select.jsx.map +1 -1
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/SharedElementTransition.js +35 -33
- package/dist/SharedElementTransition.js.map +1 -1
- package/dist/SharedElementTransition.jsx +35 -33
- package/dist/SharedElementTransition.jsx.map +1 -1
- package/dist/StepList.d.ts.map +1 -1
- package/dist/StepList.js +2 -9
- package/dist/StepList.js.map +1 -1
- package/dist/StepList.jsx +1 -6
- package/dist/StepList.jsx.map +1 -1
- package/dist/Table.d.ts +2 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js +10 -3
- package/dist/Table.js.map +1 -1
- package/dist/Table.jsx +10 -3
- package/dist/Table.jsx.map +1 -1
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/Tabs.js +3 -0
- package/dist/Tabs.js.map +1 -1
- package/dist/Tabs.jsx +1 -1
- package/dist/Tabs.jsx.map +1 -1
- package/dist/TagGroup.d.ts +3 -1
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/TagGroup.js +12 -8
- package/dist/TagGroup.js.map +1 -1
- package/dist/TagGroup.jsx +11 -8
- package/dist/TagGroup.jsx.map +1 -1
- package/dist/TextField.js +4 -1
- package/dist/TextField.js.map +1 -1
- package/dist/TextField.jsx +4 -1
- package/dist/TextField.jsx.map +1 -1
- package/dist/TimeField.d.ts +14 -50
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/TimeField.js +111 -205
- package/dist/TimeField.js.map +1 -1
- package/dist/TimeField.jsx +76 -82
- package/dist/TimeField.jsx.map +1 -1
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Tree.js +3 -3
- package/dist/Tree.js.map +1 -1
- package/dist/Tree.jsx +3 -3
- package/dist/Tree.jsx.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.jsx +5 -6
- package/dist/useDragAndDrop.d.ts +0 -8
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/useDragAndDrop2.js +6 -2
- package/dist/useDragAndDrop2.js.map +1 -1
- package/dist/useDragAndDrop2.jsx +6 -2
- package/dist/useDragAndDrop2.jsx.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/dist/utils.jsx +2 -2
- package/dist/utils.jsx.map +1 -1
- package/package.json +3 -9
- package/src/ActionBar.tsx +10 -12
- package/src/ActionGroup.tsx +5 -3
- package/src/Autocomplete.tsx +14 -9
- package/src/Breadcrumbs.tsx +55 -38
- package/src/Calendar.tsx +30 -1
- package/src/Color.tsx +42 -27
- package/src/ComboBox.tsx +49 -17
- package/src/DateField.tsx +285 -52
- package/src/DatePicker.tsx +345 -57
- package/src/DateRangePickerContext.tsx +2 -0
- package/src/Disclosure.tsx +4 -0
- package/src/GridList.tsx +8 -2
- package/src/ListBox.tsx +323 -21
- package/src/Modal.tsx +21 -5
- package/src/Popover.tsx +28 -7
- package/src/RangeCalendar.tsx +120 -1
- package/src/SearchField.tsx +53 -1
- package/src/Select.tsx +101 -20
- package/src/SharedElementTransition.tsx +92 -36
- package/src/StepList.tsx +0 -7
- package/src/Table.tsx +19 -4
- package/src/Tabs.tsx +1 -0
- package/src/TagGroup.tsx +26 -6
- package/src/TextField.tsx +1 -1
- package/src/TimeField.tsx +113 -196
- package/src/Tree.tsx +14 -4
- package/src/index.ts +1 -8
- package/src/useDragAndDrop.ts +8 -10
- package/src/utils.tsx +3 -3
- package/dist/DatePicker.js.map +0 -1
- package/dist/DatePicker.jsx.map +0 -1
- package/dist/HiddenTimeInput.d.ts +0 -25
- package/dist/HiddenTimeInput.d.ts.map +0 -1
- package/dist/HiddenTimeInput.js +0 -104
- package/dist/HiddenTimeInput.js.map +0 -1
- package/dist/HiddenTimeInput.jsx +0 -69
- package/dist/HiddenTimeInput.jsx.map +0 -1
- package/src/HiddenTimeInput.tsx +0 -133
package/src/Popover.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
createOverlayTrigger,
|
|
23
23
|
createPopover,
|
|
24
24
|
FocusScope,
|
|
25
|
+
useLocale,
|
|
25
26
|
useUNSAFE_PortalContext,
|
|
26
27
|
visuallyHiddenStyles,
|
|
27
28
|
type AriaLabelingProps,
|
|
@@ -280,12 +281,29 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
280
281
|
"isExiting",
|
|
281
282
|
]);
|
|
282
283
|
|
|
283
|
-
let
|
|
284
|
+
// A reactive ref (not a plain `let`) so the overlay-position effect in
|
|
285
|
+
// createOverlayPosition — which tracks `overlayRef()` as a dependency —
|
|
286
|
+
// re-runs once the portal node actually mounts. React gets this timing for
|
|
287
|
+
// free from useLayoutEffect (the ref is populated before the position effect
|
|
288
|
+
// fires); in Solid the portal node is created lazily on open, AFTER the
|
|
289
|
+
// position effect first ran with a null ref, so a non-reactive ref left the
|
|
290
|
+
// popover stranded at the createOverlayPosition fallback (position:fixed;
|
|
291
|
+
// top:0; left:0) whenever no other dependency happened to re-fire the effect
|
|
292
|
+
// after mount. Mirrors the sibling groupRef signal below.
|
|
293
|
+
const [popoverRef, setPopoverRef] = createSignal<HTMLDivElement | null>(null);
|
|
284
294
|
const [groupRef, setGroupRef] = createSignal<HTMLDivElement | null>(null);
|
|
285
295
|
// False on the server and during hydration; true after onMount. Gates the Portal
|
|
286
296
|
// so overlay content only ever renders client-side, post-hydration.
|
|
287
297
|
const isHydrated = useIsHydrated();
|
|
288
298
|
|
|
299
|
+
// Mirrors upstream S2 Popover (Popover.mjs: `el.lang = locale; el.dir =
|
|
300
|
+
// direction`): the overlay portals out of the app root, so it must carry both
|
|
301
|
+
// the locale `lang` AND writing `direction` — otherwise the portaled
|
|
302
|
+
// listbox/dialog stays LTR and keeps the Latin font under an RTL Provider (the
|
|
303
|
+
// `:lang(ar)` font swap needs a `lang` ancestor the portal would otherwise
|
|
304
|
+
// escape). RAC's own Popover only threads `dir`; S2 adds `lang` on top.
|
|
305
|
+
const locale = useLocale();
|
|
306
|
+
|
|
289
307
|
const triggerContext = useContext(PopoverTriggerContext);
|
|
290
308
|
const dialogTriggerContext = useContext(DialogTriggerContext);
|
|
291
309
|
const popoverGroupContext = useContext(PopoverGroupContext);
|
|
@@ -333,7 +351,7 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
333
351
|
const popoverAria = createPopover(
|
|
334
352
|
{
|
|
335
353
|
triggerRef: getTriggerRef,
|
|
336
|
-
popoverRef: () => popoverRef
|
|
354
|
+
popoverRef: () => popoverRef(),
|
|
337
355
|
groupRef: () => (isSubPopover() ? (popoverGroupContext?.() ?? null) : groupRef()),
|
|
338
356
|
get placement() {
|
|
339
357
|
return local.placement;
|
|
@@ -514,7 +532,7 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
514
532
|
createEffect(() => {
|
|
515
533
|
if (!isOpen() || !shouldBeDialog()) return;
|
|
516
534
|
if ((local.autoFocus ?? true) === false) return;
|
|
517
|
-
if (!popoverRef) return;
|
|
535
|
+
if (!popoverRef()) return;
|
|
518
536
|
if (resolvedTrigger() === "SubmenuTrigger") return;
|
|
519
537
|
|
|
520
538
|
let timeout: number | undefined;
|
|
@@ -522,11 +540,12 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
522
540
|
|
|
523
541
|
const focusIfNeeded = () => {
|
|
524
542
|
if (!isOpen() || !shouldBeDialog()) return;
|
|
525
|
-
|
|
526
|
-
if (
|
|
543
|
+
const node = popoverRef();
|
|
544
|
+
if (!node || resolvedTrigger() === "SubmenuTrigger") return;
|
|
545
|
+
if (document.activeElement === node || node.contains(document.activeElement)) {
|
|
527
546
|
return;
|
|
528
547
|
}
|
|
529
|
-
|
|
548
|
+
node.focus();
|
|
530
549
|
};
|
|
531
550
|
|
|
532
551
|
const scheduleFocus = () => {
|
|
@@ -572,12 +591,14 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
572
591
|
<div
|
|
573
592
|
{...domProps()}
|
|
574
593
|
{...cleanPopoverProps()}
|
|
575
|
-
ref={
|
|
594
|
+
ref={setPopoverRef}
|
|
576
595
|
id={overlayId()}
|
|
577
596
|
role={shouldBeDialog() ? "dialog" : undefined}
|
|
578
597
|
tabIndex={shouldBeDialog() ? -1 : undefined}
|
|
579
598
|
class={renderProps.class()}
|
|
580
599
|
style={mergedStyle()}
|
|
600
|
+
lang={locale().locale}
|
|
601
|
+
dir={locale().direction}
|
|
581
602
|
data-trigger={resolvedTrigger()}
|
|
582
603
|
data-placement={popoverAria.placement()}
|
|
583
604
|
data-entering={dataAttr(local.isEntering)}
|
package/src/RangeCalendar.tsx
CHANGED
|
@@ -196,6 +196,13 @@ export function useRangeCalendarContext(): RangeCalendarState<DateValue> {
|
|
|
196
196
|
export function RangeCalendar<T extends DateValue = CalendarDate>(
|
|
197
197
|
props: RangeCalendarProps<T>,
|
|
198
198
|
): JSX.Element {
|
|
199
|
+
// When rendered inside a composite that owns the range state (e.g.
|
|
200
|
+
// DateRangePicker provides `RangeCalendarContext`), reuse that shared state
|
|
201
|
+
// instead of minting a fresh one — otherwise the composite's focus/anchor
|
|
202
|
+
// flags (e.g. the popover-open `setFocused(true)`) drive a different state
|
|
203
|
+
// instance than the one this grid renders. Mirrors the single `Calendar`.
|
|
204
|
+
const inheritedState = useContext(RangeCalendarContext);
|
|
205
|
+
|
|
199
206
|
// Use hydration-safe pattern for client-only rendering
|
|
200
207
|
const isHydrated = useIsHydrated();
|
|
201
208
|
|
|
@@ -209,11 +216,106 @@ export function RangeCalendar<T extends DateValue = CalendarDate>(
|
|
|
209
216
|
/>
|
|
210
217
|
}
|
|
211
218
|
>
|
|
212
|
-
<RangeCalendarInner {...props} />
|
|
219
|
+
<Show when={inheritedState} fallback={<RangeCalendarInner {...props} />}>
|
|
220
|
+
<RangeCalendarWithState
|
|
221
|
+
state={inheritedState as RangeCalendarState<DateValue>}
|
|
222
|
+
{...props}
|
|
223
|
+
/>
|
|
224
|
+
</Show>
|
|
213
225
|
</Show>
|
|
214
226
|
);
|
|
215
227
|
}
|
|
216
228
|
|
|
229
|
+
/**
|
|
230
|
+
* RangeCalendar variant that renders against an externally-owned state (from
|
|
231
|
+
* `RangeCalendarContext`) rather than creating its own. Mirrors the single
|
|
232
|
+
* `CalendarWithState`.
|
|
233
|
+
*/
|
|
234
|
+
function RangeCalendarWithState<T extends DateValue = CalendarDate>(
|
|
235
|
+
props: RangeCalendarProps<T> & { state: RangeCalendarState<DateValue> },
|
|
236
|
+
): JSX.Element {
|
|
237
|
+
const [local, , rest] = splitProps(
|
|
238
|
+
props,
|
|
239
|
+
["children", "class", "style", "slot", "state", "ref"],
|
|
240
|
+
[
|
|
241
|
+
"value",
|
|
242
|
+
"defaultValue",
|
|
243
|
+
"onChange",
|
|
244
|
+
"minValue",
|
|
245
|
+
"maxValue",
|
|
246
|
+
"isDisabled",
|
|
247
|
+
"isReadOnly",
|
|
248
|
+
"focusedValue",
|
|
249
|
+
"defaultFocusedValue",
|
|
250
|
+
"onFocusChange",
|
|
251
|
+
"locale",
|
|
252
|
+
"createCalendar",
|
|
253
|
+
"isDateUnavailable",
|
|
254
|
+
"visibleMonths",
|
|
255
|
+
"pageBehavior",
|
|
256
|
+
"selectionAlignment",
|
|
257
|
+
"isDateDisabled",
|
|
258
|
+
"validationState",
|
|
259
|
+
"allowsNonContiguousRanges",
|
|
260
|
+
"firstDayOfWeek",
|
|
261
|
+
],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const state = () => props.state;
|
|
265
|
+
|
|
266
|
+
const calendarAria = createRangeCalendar(rest, state());
|
|
267
|
+
|
|
268
|
+
const renderValues = createMemo<RangeCalendarRenderProps>(() => ({
|
|
269
|
+
isDisabled: state().isDisabled(),
|
|
270
|
+
isReadOnly: state().isReadOnly(),
|
|
271
|
+
isInvalid: state().isValueInvalid(),
|
|
272
|
+
isDragging: state().isDragging(),
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
const renderProps = useRenderProps(
|
|
276
|
+
{
|
|
277
|
+
class: local.class,
|
|
278
|
+
style: local.style,
|
|
279
|
+
defaultClassName: "solidaria-RangeCalendar",
|
|
280
|
+
},
|
|
281
|
+
renderValues,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<RangeCalendarStateContext.Provider value={state()}>
|
|
286
|
+
<RangeCalendarContext.Provider value={state()}>
|
|
287
|
+
<div
|
|
288
|
+
{...calendarAria.calendarProps}
|
|
289
|
+
ref={(el) => assignRef(local.ref, el)}
|
|
290
|
+
class={renderProps.class()}
|
|
291
|
+
style={renderProps.style()}
|
|
292
|
+
data-disabled={dataAttr(state().isDisabled())}
|
|
293
|
+
data-readonly={dataAttr(state().isReadOnly())}
|
|
294
|
+
data-invalid={dataAttr(state().isValueInvalid())}
|
|
295
|
+
data-dragging={dataAttr(state().isDragging())}
|
|
296
|
+
>
|
|
297
|
+
<VisuallyHidden>
|
|
298
|
+
<h2>{String(calendarAria.calendarProps["aria-label"] ?? "")}</h2>
|
|
299
|
+
</VisuallyHidden>
|
|
300
|
+
{props.children}
|
|
301
|
+
{/* For touch screen readers, a visually hidden next button after the
|
|
302
|
+
* month grid so it's easy to navigate after reaching the end without
|
|
303
|
+
* going all the way back to the start. Mirrors react-aria-components
|
|
304
|
+
* Calendar (and the headless Calendar). */}
|
|
305
|
+
<VisuallyHidden>
|
|
306
|
+
<button
|
|
307
|
+
aria-label={String(calendarAria.nextButtonProps["aria-label"] ?? "")}
|
|
308
|
+
disabled={Boolean(calendarAria.nextButtonProps.disabled)}
|
|
309
|
+
onClick={() => state().focusNextPage()}
|
|
310
|
+
tabIndex={-1}
|
|
311
|
+
/>
|
|
312
|
+
</VisuallyHidden>
|
|
313
|
+
</div>
|
|
314
|
+
</RangeCalendarContext.Provider>
|
|
315
|
+
</RangeCalendarStateContext.Provider>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
217
319
|
/**
|
|
218
320
|
* Internal RangeCalendar component that renders after client mount.
|
|
219
321
|
*/
|
|
@@ -284,6 +386,18 @@ function RangeCalendarInner<T extends DateValue = CalendarDate>(
|
|
|
284
386
|
<h2>{String(calendarAria.calendarProps["aria-label"] ?? "")}</h2>
|
|
285
387
|
</VisuallyHidden>
|
|
286
388
|
{props.children}
|
|
389
|
+
{/* For touch screen readers, a visually hidden next button after the
|
|
390
|
+
* month grid so it's easy to navigate after reaching the end without
|
|
391
|
+
* going all the way back to the start. Mirrors react-aria-components
|
|
392
|
+
* Calendar (and the headless Calendar). */}
|
|
393
|
+
<VisuallyHidden>
|
|
394
|
+
<button
|
|
395
|
+
aria-label={String(calendarAria.nextButtonProps["aria-label"] ?? "")}
|
|
396
|
+
disabled={Boolean(calendarAria.nextButtonProps.disabled)}
|
|
397
|
+
onClick={() => state.focusNextPage()}
|
|
398
|
+
tabIndex={-1}
|
|
399
|
+
/>
|
|
400
|
+
</VisuallyHidden>
|
|
287
401
|
</div>
|
|
288
402
|
</RangeCalendarContext.Provider>
|
|
289
403
|
</RangeCalendarStateContext.Provider>
|
|
@@ -350,6 +464,11 @@ export function RangeCalendarButton(props: RangeCalendarButtonProps): JSX.Elemen
|
|
|
350
464
|
class={props.class ?? "solidaria-RangeCalendarButton"}
|
|
351
465
|
style={props.style}
|
|
352
466
|
disabled={isDisabled()}
|
|
467
|
+
// Mirror useFocusable (same as CalendarButton): always set an explicit
|
|
468
|
+
// tabIndex on an enabled focusable nav button (0), and drop it entirely
|
|
469
|
+
// when disabled — so the prev/next buttons appear in the roving tab order
|
|
470
|
+
// exactly as the S2 oracle does.
|
|
471
|
+
tabindex={isDisabled() ? undefined : 0}
|
|
353
472
|
>
|
|
354
473
|
{props.children}
|
|
355
474
|
</button>
|
package/src/SearchField.tsx
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
Provider,
|
|
43
43
|
} from "./utils";
|
|
44
44
|
import { TextContext } from "./Text";
|
|
45
|
+
import { useAutocompleteInput } from "./Autocomplete";
|
|
45
46
|
|
|
46
47
|
export interface SearchFieldRenderProps {
|
|
47
48
|
/** Whether the search field is empty. */
|
|
@@ -247,7 +248,35 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
|
|
|
247
248
|
const baseProps = (
|
|
248
249
|
contextProps ? mergeProps(contextBaseProps(), contextSlotProps ?? {}, props) : props
|
|
249
250
|
) as SearchFieldProps;
|
|
250
|
-
|
|
251
|
+
|
|
252
|
+
// When rendered inside an <Autocomplete>, the FieldInputContext (AutocompleteContext)
|
|
253
|
+
// controls the input's value and keyboard handling: value + onChange make the field's
|
|
254
|
+
// state controlled by the shared autocomplete state (which drives the collection
|
|
255
|
+
// filter), and onKeyDown/onFocus/onBlur chain the autocomplete's virtual-focus
|
|
256
|
+
// navigation into the input. This mirrors RAC's
|
|
257
|
+
// useContextProps(props, inputRef, FieldInputContext). The remaining passthroughs
|
|
258
|
+
// (aria-activedescendant/aria-controls/aria-autocomplete) and the input-ref
|
|
259
|
+
// registration are applied at the <input> in SearchFieldInput.
|
|
260
|
+
const autocompleteInput = useAutocompleteInput();
|
|
261
|
+
const autocompleteProps = autocompleteInput
|
|
262
|
+
? ({
|
|
263
|
+
get value() {
|
|
264
|
+
return autocompleteInput.inputProps.value();
|
|
265
|
+
},
|
|
266
|
+
onChange: autocompleteInput.inputProps.onChange,
|
|
267
|
+
onKeyDown: autocompleteInput.inputProps
|
|
268
|
+
.onKeyDown as unknown as SearchFieldProps["onKeyDown"],
|
|
269
|
+
onFocus: autocompleteInput.inputProps.onFocus as unknown as SearchFieldProps["onFocus"],
|
|
270
|
+
onBlur: autocompleteInput.inputProps.onBlur as unknown as SearchFieldProps["onBlur"],
|
|
271
|
+
} as Partial<SearchFieldProps>)
|
|
272
|
+
: undefined;
|
|
273
|
+
|
|
274
|
+
const mergedProps = withFormValidationBehavior(
|
|
275
|
+
autocompleteProps
|
|
276
|
+
? (mergeProps(baseProps, autocompleteProps) as SearchFieldProps)
|
|
277
|
+
: baseProps,
|
|
278
|
+
formContext,
|
|
279
|
+
);
|
|
251
280
|
|
|
252
281
|
const [local, stateProps, ariaProps, rest] = splitProps(
|
|
253
282
|
mergedProps,
|
|
@@ -629,6 +658,15 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
|
|
|
629
658
|
throw new Error("SearchFieldInput must be used within a SearchField");
|
|
630
659
|
}
|
|
631
660
|
|
|
661
|
+
// Inside an <Autocomplete>, expose the ARIA attributes that identify the
|
|
662
|
+
// controlled collection and its virtually-focused option. value/onChange and the
|
|
663
|
+
// keyboard handlers are already merged upstream in SearchField(); here we only add
|
|
664
|
+
// the passthrough attributes that createTextField doesn't forward, and register the
|
|
665
|
+
// input element as the autocomplete inputRef so the beforeinput + reverse-path
|
|
666
|
+
// (aria-activedescendant mirror) listeners bind. Mirrors RAC's
|
|
667
|
+
// [InputContext, {...inputProps, ref: inputRef}].
|
|
668
|
+
const autocompleteInput = useAutocompleteInput();
|
|
669
|
+
|
|
632
670
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
633
671
|
|
|
634
672
|
const { isHovered, hoverProps } = createHover({
|
|
@@ -667,12 +705,25 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
|
|
|
667
705
|
return rest;
|
|
668
706
|
};
|
|
669
707
|
|
|
708
|
+
const autocompleteInputAttrs = (): Record<string, unknown> => {
|
|
709
|
+
if (!autocompleteInput) {
|
|
710
|
+
return {};
|
|
711
|
+
}
|
|
712
|
+
const inputProps = autocompleteInput.inputProps;
|
|
713
|
+
return {
|
|
714
|
+
"aria-controls": inputProps["aria-controls"],
|
|
715
|
+
"aria-autocomplete": inputProps["aria-autocomplete"],
|
|
716
|
+
"aria-activedescendant": inputProps["aria-activedescendant"](),
|
|
717
|
+
};
|
|
718
|
+
};
|
|
719
|
+
|
|
670
720
|
const mergedInputProps = () =>
|
|
671
721
|
({
|
|
672
722
|
...domProps,
|
|
673
723
|
...cleanInputProps(),
|
|
674
724
|
...cleanFocusProps(),
|
|
675
725
|
...cleanHoverProps(),
|
|
726
|
+
...autocompleteInputAttrs(),
|
|
676
727
|
}) as Record<string, unknown>;
|
|
677
728
|
|
|
678
729
|
onMount(() => {
|
|
@@ -723,6 +774,7 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
|
|
|
723
774
|
ref={(element) => {
|
|
724
775
|
inputElement = element;
|
|
725
776
|
context.setInputRef?.(element);
|
|
777
|
+
autocompleteInput?.inputRef(element);
|
|
726
778
|
const ref = (domProps as { ref?: unknown }).ref;
|
|
727
779
|
if (typeof ref === "function") {
|
|
728
780
|
ref(element);
|
package/src/Select.tsx
CHANGED
|
@@ -186,11 +186,15 @@ export interface SelectTriggerRenderProps {
|
|
|
186
186
|
isFocusVisible: boolean;
|
|
187
187
|
/** Whether the trigger is hovered. */
|
|
188
188
|
isHovered: boolean;
|
|
189
|
+
/** Whether the trigger is pressed. */
|
|
190
|
+
isPressed: boolean;
|
|
189
191
|
/** Whether the trigger is disabled. */
|
|
190
192
|
isDisabled: boolean;
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
export interface SelectTriggerProps extends SlotProps {
|
|
196
|
+
/** A ref callback/object for the underlying trigger button element. */
|
|
197
|
+
ref?: RefLike<HTMLButtonElement>;
|
|
194
198
|
/** The children of the trigger. A function may be provided to receive render props. */
|
|
195
199
|
children?: RenderChildren<SelectTriggerRenderProps>;
|
|
196
200
|
/** The CSS className for the element. */
|
|
@@ -241,6 +245,8 @@ export interface SelectOptionRenderProps {
|
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
export interface SelectOptionProps<T> extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
|
|
248
|
+
/** A ref callback/object for the underlying option element. */
|
|
249
|
+
ref?: RefLike<HTMLDivElement>;
|
|
244
250
|
/** The unique key for the option. */
|
|
245
251
|
id: Key;
|
|
246
252
|
/** The item value. */
|
|
@@ -269,6 +275,7 @@ interface SelectContextValue<T> {
|
|
|
269
275
|
isOpen: Accessor<boolean>;
|
|
270
276
|
isFocused: Accessor<boolean>;
|
|
271
277
|
isFocusVisible: Accessor<boolean>;
|
|
278
|
+
isPressed: Accessor<boolean>;
|
|
272
279
|
isDisabled: Accessor<boolean>;
|
|
273
280
|
placeholder?: string;
|
|
274
281
|
items: T[];
|
|
@@ -398,8 +405,16 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
398
405
|
return clean as typeof ariaProps;
|
|
399
406
|
});
|
|
400
407
|
|
|
401
|
-
const {
|
|
402
|
-
|
|
408
|
+
const {
|
|
409
|
+
labelProps,
|
|
410
|
+
triggerProps,
|
|
411
|
+
valueProps,
|
|
412
|
+
menuProps,
|
|
413
|
+
isFocused,
|
|
414
|
+
isFocusVisible,
|
|
415
|
+
isOpen,
|
|
416
|
+
isPressed,
|
|
417
|
+
} = createSelect<T>(selectAriaProps, state);
|
|
403
418
|
|
|
404
419
|
const { isHovered, hoverProps } = createHover({
|
|
405
420
|
get isDisabled() {
|
|
@@ -579,6 +594,17 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
579
594
|
|
|
580
595
|
return (
|
|
581
596
|
<>
|
|
597
|
+
<Show when={ariaProps.label}>
|
|
598
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
599
|
+
</Show>
|
|
600
|
+
{selectChildren}
|
|
601
|
+
{/*
|
|
602
|
+
The HiddenSelect (native `<select>` for form autofill/submission) renders
|
|
603
|
+
AFTER the trigger button, mirroring upstream RAC `Select` which emits
|
|
604
|
+
`{renderProps.children}` then `<HiddenSelect>` (Select.tsx:288-289). Order
|
|
605
|
+
matters for the DOM tab/focus trail: the visible button must precede the
|
|
606
|
+
form-only hidden control.
|
|
607
|
+
*/}
|
|
582
608
|
<div {...containerProps}>
|
|
583
609
|
<select
|
|
584
610
|
{...hiddenSelectProps}
|
|
@@ -652,10 +678,6 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
652
678
|
/>
|
|
653
679
|
</Show>
|
|
654
680
|
</div>
|
|
655
|
-
<Show when={ariaProps.label}>
|
|
656
|
-
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
657
|
-
</Show>
|
|
658
|
-
{selectChildren}
|
|
659
681
|
</>
|
|
660
682
|
);
|
|
661
683
|
};
|
|
@@ -711,6 +733,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
711
733
|
isOpen,
|
|
712
734
|
isFocused,
|
|
713
735
|
isFocusVisible,
|
|
736
|
+
isPressed,
|
|
714
737
|
isDisabled: resolveDisabled,
|
|
715
738
|
placeholder: ariaProps.placeholder,
|
|
716
739
|
items: stateProps.items,
|
|
@@ -731,17 +754,18 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
731
754
|
* The trigger button for a select.
|
|
732
755
|
*/
|
|
733
756
|
export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
734
|
-
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
757
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
|
|
735
758
|
|
|
736
759
|
const context = useContext(SelectContext);
|
|
737
760
|
if (!context) {
|
|
738
761
|
throw new Error("SelectTrigger must be used within a Select");
|
|
739
762
|
}
|
|
740
|
-
const { isOpen, isFocused, isFocusVisible, state } = context;
|
|
763
|
+
const { isOpen, isFocused, isFocusVisible, isPressed, state } = context;
|
|
741
764
|
let triggerRef: HTMLButtonElement | undefined;
|
|
742
765
|
const setTriggerRef = (el: HTMLButtonElement) => {
|
|
743
766
|
triggerRef = el;
|
|
744
767
|
context.setTriggerRef(el);
|
|
768
|
+
assignRef(local.ref, el);
|
|
745
769
|
};
|
|
746
770
|
|
|
747
771
|
createEffect(() => {
|
|
@@ -761,6 +785,7 @@ export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
|
761
785
|
isFocused: isFocused(),
|
|
762
786
|
isFocusVisible: isFocusVisible(),
|
|
763
787
|
isHovered: isHovered(),
|
|
788
|
+
isPressed: isPressed(),
|
|
764
789
|
isDisabled: state.isDisabled,
|
|
765
790
|
}));
|
|
766
791
|
|
|
@@ -926,6 +951,14 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
926
951
|
if (!isOpen()) {
|
|
927
952
|
return;
|
|
928
953
|
}
|
|
954
|
+
// On open the listbox takes collection focus. Upstream `useSelect` sets
|
|
955
|
+
// `menuProps.autoFocus = state.focusStrategy || true`, so the ListBox's
|
|
956
|
+
// `useSelectableCollection` auto-focuses on mount — setting the selection
|
|
957
|
+
// manager's `isFocused` true and focusing the selected option. Our
|
|
958
|
+
// `createListBox` reimplements keyboard nav inline (no `autoFocus` effect),
|
|
959
|
+
// so we set the manager focus here to match. Without it the selected option
|
|
960
|
+
// never paints its focus-visible background (`data-focused` stays unset).
|
|
961
|
+
state.selectionManager.setFocused(true);
|
|
929
962
|
if (state.focusedKey() != null) {
|
|
930
963
|
return;
|
|
931
964
|
}
|
|
@@ -935,7 +968,7 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
935
968
|
}
|
|
936
969
|
});
|
|
937
970
|
|
|
938
|
-
let listBoxRef:
|
|
971
|
+
let listBoxRef: HTMLDivElement | undefined;
|
|
939
972
|
|
|
940
973
|
createInteractOutside({
|
|
941
974
|
ref: () => rootRef() ?? listBoxRef ?? null,
|
|
@@ -949,7 +982,14 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
949
982
|
},
|
|
950
983
|
});
|
|
951
984
|
|
|
952
|
-
|
|
985
|
+
// Keep the hook result intact — destructuring `{ listBoxProps }` invokes the
|
|
986
|
+
// `get listBoxProps()` getter ONCE and freezes it (the Solid "destructuring a
|
|
987
|
+
// get-prop freezes reactivity" gotcha). The listbox `tabIndex` is computed from
|
|
988
|
+
// `focusedKey` (`focusedKey != null ? -1 : 0`); on open the effect above sets a
|
|
989
|
+
// focused key, so a frozen snapshot would leave the listbox tabbable (`0`) when
|
|
990
|
+
// upstream flips it to `-1`. Reading `listBoxHook.listBoxProps` per access keeps
|
|
991
|
+
// it reactive.
|
|
992
|
+
const listBoxHook = createListBox(
|
|
953
993
|
{
|
|
954
994
|
...(menuProps as unknown as AriaListBoxProps),
|
|
955
995
|
shouldSelectOnPressUp: true,
|
|
@@ -982,8 +1022,37 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
982
1022
|
return rest;
|
|
983
1023
|
};
|
|
984
1024
|
const cleanListBoxProps = () => {
|
|
985
|
-
const {
|
|
986
|
-
|
|
1025
|
+
const {
|
|
1026
|
+
ref: _ref2,
|
|
1027
|
+
"aria-activedescendant": _activeDescendant,
|
|
1028
|
+
onFocus: _onFocus,
|
|
1029
|
+
...rest
|
|
1030
|
+
} = listBoxHook.listBoxProps as Record<string, unknown>;
|
|
1031
|
+
// Faithful Select listbox focus model: the focused option receives REAL DOM
|
|
1032
|
+
// focus (the open effect above + `createSelectableItem`'s self-focus move
|
|
1033
|
+
// `document.activeElement` onto it), mirroring upstream `useSelect`. So the
|
|
1034
|
+
// listbox must (a) leave the tab sequence once an option is focused —
|
|
1035
|
+
// `useSelectableCollection` uses `tabIndex = focusedKey == null ? 0 : -1`
|
|
1036
|
+
// (useSelectableCollection.ts:687-690) — and (b) NOT expose
|
|
1037
|
+
// `aria-activedescendant`, which is the virtual-focus channel upstream Select
|
|
1038
|
+
// never uses. These are overridden here, not in the shared `createListBox`,
|
|
1039
|
+
// because the standalone `ListBox` keeps its container-focus model (focus
|
|
1040
|
+
// stays on the listbox, `aria-activedescendant` is its AT channel).
|
|
1041
|
+
//
|
|
1042
|
+
// We also drop `createListBox`'s container-focus TRAMPOLINE (`onFocus`): it
|
|
1043
|
+
// marshals the first key when the container is focused with `focusedKey ==
|
|
1044
|
+
// null`, which is exactly Select's deliberate click-open state (the open
|
|
1045
|
+
// effect sets `isFocused` true but leaves `focusedKey` null when nothing is
|
|
1046
|
+
// selected, so the first arrow enters the first item). Upstream's own
|
|
1047
|
+
// trampoline no-ops in this case because it guards on `manager.isFocused`
|
|
1048
|
+
// (already true); our shared guard uses `focusedKey == null` for the
|
|
1049
|
+
// standalone ListBox, so we strip it here to preserve Select's faithful
|
|
1050
|
+
// model. (Auto-selecting on focus under replace behavior would also toggle
|
|
1051
|
+
// the first item selected on open — the bug this override prevents.)
|
|
1052
|
+
return {
|
|
1053
|
+
...rest,
|
|
1054
|
+
tabIndex: state.isDisabled ? undefined : state.focusedKey() != null ? -1 : 0,
|
|
1055
|
+
};
|
|
987
1056
|
};
|
|
988
1057
|
|
|
989
1058
|
const items = () => Array.from(state.collection());
|
|
@@ -1003,7 +1072,12 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
1003
1072
|
});
|
|
1004
1073
|
|
|
1005
1074
|
const listBox = () => (
|
|
1006
|
-
|
|
1075
|
+
// Upstream RAC ListBox (and our own Menu.tsx) render a `<div role="listbox">`
|
|
1076
|
+
// over `<div role="option">` rows, NOT `<ul>`/`<li>` — the collection is
|
|
1077
|
+
// div-based for virtualization parity. `<ul>`/`<li>` here was a self-inflicted
|
|
1078
|
+
// structural divergence surfaced by the Picker recertification (D5/D6/D8 saw
|
|
1079
|
+
// `li[option]`/`ul[listbox]` where the React oracle sees `div`).
|
|
1080
|
+
<div
|
|
1007
1081
|
ref={(el) => (listBoxRef = el)}
|
|
1008
1082
|
{...domProps}
|
|
1009
1083
|
{...cleanMenuProps()}
|
|
@@ -1014,9 +1088,9 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
1014
1088
|
data-empty={state.collection().size === 0 || undefined}
|
|
1015
1089
|
>
|
|
1016
1090
|
{state.collection().size === 0 && local.renderEmptyState ? (
|
|
1017
|
-
<
|
|
1091
|
+
<div role="option" style={{ display: "contents" }} data-empty-state>
|
|
1018
1092
|
{local.renderEmptyState()}
|
|
1019
|
-
</
|
|
1093
|
+
</div>
|
|
1020
1094
|
) : (
|
|
1021
1095
|
<Show
|
|
1022
1096
|
when={local.children}
|
|
@@ -1040,7 +1114,7 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
1040
1114
|
{local.renderLoadMore?.()}
|
|
1041
1115
|
</ListBoxLoadMoreItem>
|
|
1042
1116
|
</Show>
|
|
1043
|
-
</
|
|
1117
|
+
</div>
|
|
1044
1118
|
);
|
|
1045
1119
|
|
|
1046
1120
|
return (
|
|
@@ -1070,6 +1144,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
1070
1144
|
"id",
|
|
1071
1145
|
"item",
|
|
1072
1146
|
"textValue",
|
|
1147
|
+
"ref",
|
|
1073
1148
|
]);
|
|
1074
1149
|
|
|
1075
1150
|
const context = useContext(SelectStateContext);
|
|
@@ -1078,7 +1153,11 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
1078
1153
|
}
|
|
1079
1154
|
const state = context as SelectState<T>;
|
|
1080
1155
|
const selectContext = useContext(SelectContext) as SelectContextValue<T> | null;
|
|
1081
|
-
const [ref,
|
|
1156
|
+
const [ref, setRefSignal] = createSignal<HTMLDivElement | null>(null);
|
|
1157
|
+
const setRef = (el: HTMLDivElement | null) => {
|
|
1158
|
+
setRefSignal(el);
|
|
1159
|
+
assignRef(local.ref, el as HTMLDivElement);
|
|
1160
|
+
};
|
|
1082
1161
|
|
|
1083
1162
|
const optionAria = createOption<T>(
|
|
1084
1163
|
{
|
|
@@ -1187,7 +1266,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
1187
1266
|
selectOption();
|
|
1188
1267
|
}
|
|
1189
1268
|
});
|
|
1190
|
-
}) as JSX.EventHandler<
|
|
1269
|
+
}) as JSX.EventHandler<HTMLDivElement, MouseEvent>;
|
|
1191
1270
|
return rest;
|
|
1192
1271
|
};
|
|
1193
1272
|
const selectOption = () => {
|
|
@@ -1209,7 +1288,9 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
1209
1288
|
|
|
1210
1289
|
return (
|
|
1211
1290
|
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
1212
|
-
|
|
1291
|
+
{/* `<div role="option">`, not `<li>` — see the SelectListBox note; upstream
|
|
1292
|
+
RAC options are div-based. */}
|
|
1293
|
+
<div
|
|
1213
1294
|
ref={setRef}
|
|
1214
1295
|
{...cleanOptionProps()}
|
|
1215
1296
|
class={renderProps.class()}
|
|
@@ -1226,7 +1307,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
1226
1307
|
) : (
|
|
1227
1308
|
renderProps.renderChildren()
|
|
1228
1309
|
)}
|
|
1229
|
-
</
|
|
1310
|
+
</div>
|
|
1230
1311
|
</SelectionIndicatorContext.Provider>
|
|
1231
1312
|
);
|
|
1232
1313
|
}
|