@scalewing/react 1.19.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/dist/components/CalendarButton.d.ts +6 -0
- package/dist/components/CalendarButton.js +2 -1
- package/dist/components/DateField.d.ts +8 -1
- package/dist/components/DateField.js +2 -1
- package/dist/components/Tooltip.d.ts +17 -4
- package/dist/components/Tooltip.js +4 -5
- package/dist/components/date-field/use-calendar-popup.d.ts +6 -1
- package/dist/components/date-field/use-calendar-popup.js +5 -3
- package/dist/components/tooltip-trigger-aria.d.ts +12 -0
- package/dist/components/tooltip-trigger-aria.js +23 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2d54950: `DateField` and `CalendarButton` take an optional `today` (`YYYY-MM-DD`): the day the calendar marks as today (`aria-current="date"`), opens on when the field is empty, and picks with **Today**. It defaults to the device's local date, as before. Pass the business's own day when it keeps a time zone the device may not share, so a phone in UTC at 10 PM in New York no longer rings tomorrow while `max` disables it. An empty or malformed `today` throws a `RangeError`.
|
|
8
|
+
|
|
9
|
+
## 1.20.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- cfcac1a: `Tooltip` `relationship` (`docs/requests/teisoro-tooltip-labels.md`, Teisoro's workspace navigation). `relationship?: 'description' | 'label'`, default `'description'`, and the exported type `TooltipRelationship`. `'label'` makes the tooltip its trigger's accessible name: the trigger gets `aria-labelledby` with the tooltip's id (after its own, if any) instead of `aria-describedby`, so an icon-only control's name is read once rather than as both name and description. The tooltip stays in the page, hidden, so it names the trigger while not shown. With `disabled` there is no tooltip and no reference to it; the trigger then needs its own name (visible text or `aria-label`). Without `relationship` the markup and behavior are unchanged. No new classes, tokens or dependencies.
|
|
14
|
+
|
|
3
15
|
## 1.19.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -247,7 +247,7 @@ An option's optional `description` (a `ReactNode`) is secondary text for that op
|
|
|
247
247
|
|
|
248
248
|
`Progress` shows a known value between zero and a positive maximum. It uses native progressbar semantics and displays the value and maximum beside the localized label. Invalid bounds throw instead of silently clamping. Optional `tone` is `accent`, `success`, or `danger`. `showCount={false}` hides the visible `value / max` count when the page shows its own count caption, so the count appears once; the progress bar still exposes its value and maximum.
|
|
249
249
|
|
|
250
|
-
`Tooltip` adds supplementary plain-text help to one labelled, focusable trigger. Supply localized `content` and an existing trigger element
|
|
250
|
+
`Tooltip` adds supplementary plain-text help to one labelled, focusable trigger, or names an icon-only one (`relationship="label"`, below). Supply localized `content` and an existing trigger element; by default it has its own accessible name. It opens on hover or focus, closes on pointer leave, blur, Escape, or outside touch, and toggles on touch. Keep required instructions visible outside the tooltip. `disabled` turns the tooltip off, not its trigger: the trigger stays mounted, enabled and focusable, and there is no tooltip, no `aria-describedby` from it, and Escape is left to the page. Use it when what the tooltip says is visible at some widths, such as text shown only from `md` up, instead of rendering the trigger with and without a `Tooltip`, which mounts a new trigger and drops its focus. Focus, hover and touch are still followed, so a trigger that still has focus, the pointer or an open touch toggle shows the tooltip as soon as it is enabled again (Escape pressed while disabled reaches the page and does not stop that). `disabled` must be the same in the server render and the first client render, or hydration mismatches; read a breakpoint through a hydration-safe store, such as `useSyncExternalStore` with a server snapshot. `relationship="label"` makes the tooltip its trigger's name instead of its description, for an icon-only control whose tooltip says what it is: the trigger gets `aria-labelledby` (after its own, if any) and no `aria-describedby` from the tooltip, so the text is read once. The tooltip stays in the page, hidden, so it names the trigger while it is not shown, and it wins over an `aria-label`. While `disabled` there is no tooltip to name the trigger, so give the trigger its own name for that state: visible text, such as a label shown only from `md` up, or an `aria-label`. Do not use `relationship="label"` while the trigger shows visible text that differs from `content`: the tooltip would replace that visible label as the name (WCAG 2.5.3, label in name). The default, `relationship="description"`, is the supplementary help above. `relationship` must also be the same in the server render and the first client render.
|
|
251
251
|
|
|
252
252
|
`Separator` divides sections with a token-colored line. It is horizontal by default; use `orientation="vertical"` in a flex row. Set `decorative` when the line only supports layout so assistive technology ignores it.
|
|
253
253
|
|
|
@@ -291,7 +291,7 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
|
|
|
291
291
|
|
|
292
292
|
`DateField` is a controlled date-only field that draws its own calendar. Supply `value`, `min`, and `max` as valid `YYYY-MM-DD` dates; use an empty `value` for a blank field. Its callback returns a date-only string or an empty string, without timezone conversion. Invalid serialized dates and inverted bounds throw; a value outside the bounds remains visible with invalid styling. `required` marks the label with the same `aria-hidden` asterisk as `Field` and sets the entry's native `required`. Its label is drawn as `Field`'s (the same label words and mark inside a `<label>` that keeps the canvas type), so its label row and its gap to the entry match a `Field` beside it. For a blank required field, supply `error` when form validation runs to show the message and invalid styling.
|
|
293
293
|
|
|
294
|
-
People type the date into a text entry in the locale's numeric order (`MM/DD/YYYY` for en-US, `DD/MM/YYYY` for es), with any non-digit separator, eight bare digits, or ISO `YYYY-MM-DD`. A keystroke calls `onChange` once the entry is complete, meaning its last field is at full width (four year digits, or two digits for a trailing day or month, as in ISO); otherwise the date commits when the person leaves the field or presses Enter, so typing `2024-03-10` never sends `2024-03-01` on the way. If the parent keeps the old value, the typed text stays on screen; any other outside change replaces it. Text that is not a date keeps the last value and, once the person leaves the field, sets `aria-invalid` and shows `labels.invalidEntry`. Like the native date input, the entry blocks form submission through `setCustomValidity`: `labels.invalidEntry` while its text is not a date, and `labels.outOfRange` while its date, typed or given, is outside `min`/`max`. The calendar button beside it opens a WAI-ARIA date picker dialog anchored under the field on the popover layer: the month grid shows today, the selected day, and days outside `min`/`max` as `aria-disabled` (padding before `0001-01-01` or after `9999-12-31` is blank and inert); arrows move by day and week, Home and End to the week's edges, PageUp and PageDown by month, Shift with them by year, Enter or Space selects and closes, and Escape closes and returns focus to the button. Month and year selectors in the header jump decades (the year list spans 120 years back and 20 ahead of today and the shown month, cut to `min` and `max`; the month list offers only months with a day inside them). A new `value`, `min`, or `max` while the calendar is open moves it to the new value or back inside the bounds. **Today** selects today; **Clear** appears only when the field is not `required`.
|
|
294
|
+
People type the date into a text entry in the locale's numeric order (`MM/DD/YYYY` for en-US, `DD/MM/YYYY` for es), with any non-digit separator, eight bare digits, or ISO `YYYY-MM-DD`. A keystroke calls `onChange` once the entry is complete, meaning its last field is at full width (four year digits, or two digits for a trailing day or month, as in ISO); otherwise the date commits when the person leaves the field or presses Enter, so typing `2024-03-10` never sends `2024-03-01` on the way. If the parent keeps the old value, the typed text stays on screen; any other outside change replaces it. Text that is not a date keeps the last value and, once the person leaves the field, sets `aria-invalid` and shows `labels.invalidEntry`. Like the native date input, the entry blocks form submission through `setCustomValidity`: `labels.invalidEntry` while its text is not a date, and `labels.outOfRange` while its date, typed or given, is outside `min`/`max`. The calendar button beside it opens a WAI-ARIA date picker dialog anchored under the field on the popover layer: the month grid shows today, the selected day, and days outside `min`/`max` as `aria-disabled` (padding before `0001-01-01` or after `9999-12-31` is blank and inert); arrows move by day and week, Home and End to the week's edges, PageUp and PageDown by month, Shift with them by year, Enter or Space selects and closes, and Escape closes and returns focus to the button. Month and year selectors in the header jump decades (the year list spans 120 years back and 20 ahead of today and the shown month, cut to `min` and `max`; the month list offers only months with a day inside them). A new `value`, `min`, or `max` while the calendar is open moves it to the new value or back inside the bounds. **Today** selects today; **Clear** appears only when the field is not `required`. Today is the device's local date unless `today` (a `YYYY-MM-DD` date) is given: pass the business's own day when it keeps a time zone the device may not share, so a phone in another zone after the business's midnight does not mark tomorrow as today, open on it, or pick it while `max` is the business's day. `today` moves the today mark, the empty field's starting day, **Today**, and the year list's span; it may lie outside `min`/`max`, where **Today** is disabled as before. An empty or malformed `today` throws a `RangeError`.
|
|
295
295
|
|
|
296
296
|
`locale` (BCP 47) sets month and weekday names, spoken dates, and the typed order; it defaults to the nearest `lang` attribute when the field mounts, then `en-US`, and a malformed tag throws. `weekStartsOn` is `0` (Sunday, the default) or `1` (Monday). `labels` overrides the control's own words, English by default: `chooseDate`, `previousMonth`, `nextMonth`, `month`, `year`, `today`, `clear`, `invalidEntry`, `outOfRange`, and the placeholder letters `dayPlaceholder`, `monthPlaceholder`, `yearPlaceholder`. The field label names only the text entry; the calendar button is named `labels.chooseDate` and described by the field label, so tests find the entry with `getByLabel` or `getByRole('textbox', { name })`.
|
|
297
297
|
|
|
@@ -314,7 +314,7 @@ People type the date into a text entry in the locale's numeric order (`MM/DD/YYY
|
|
|
314
314
|
/>
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
`CalendarButton` is an icon-only `Button` that opens the same calendar dialog for a date the page already shows, such as a day heading with its own previous and next steps: `‹ Tuesday, September 22, 2026 [calendar] ›`. It has no text entry and no empty value. Supply `label` (what pressing it does, such as "Choose survey day"), a `YYYY-MM-DD` `value`, and `onChange`; optional `min`, `max`, `disabled`, `locale`, and `weekStartsOn` behave as on `DateField`, and `id` and a `ref` reach the `<button>`. `labels` takes the calendar's words (`previousMonth`, `nextMonth`, `month`, `year`, `today`) and `nameSeparator`. The button's accessible name is `label`, `labels.nameSeparator` (default `", "`), then the spoken date (`"Choose survey day, Tuesday, September 22, 2026"`); pass the locale's own pause where a comma does not fit, such as `"、"` in Japanese. It comes with `aria-haspopup="dialog"`, `aria-expanded`, and `aria-controls` while open; the calendar dialog is named by `label`. Enter, Space, or a press opens it on `value`. As on `DateField`, a `value` outside `min`/`max` is kept rather than refused, and the calendar then opens on the nearest allowed day with `value` shown selected and disabled; a button has no invalid state, so the page that shows the date flags it if it must. Choosing a day or **Today** calls `onChange` with the new date only when it changed, closes, and returns focus to the button; Escape closes without a change and returns focus; a press outside closes without a change and leaves focus where it landed. An empty or malformed `value`, `min`, or `
|
|
317
|
+
`CalendarButton` is an icon-only `Button` that opens the same calendar dialog for a date the page already shows, such as a day heading with its own previous and next steps: `‹ Tuesday, September 22, 2026 [calendar] ›`. It has no text entry and no empty value. Supply `label` (what pressing it does, such as "Choose survey day"), a `YYYY-MM-DD` `value`, and `onChange`; optional `min`, `max`, `today`, `disabled`, `locale`, and `weekStartsOn` behave as on `DateField`, and `id` and a `ref` reach the `<button>`. `labels` takes the calendar's words (`previousMonth`, `nextMonth`, `month`, `year`, `today`) and `nameSeparator`. The button's accessible name is `label`, `labels.nameSeparator` (default `", "`), then the spoken date (`"Choose survey day, Tuesday, September 22, 2026"`); pass the locale's own pause where a comma does not fit, such as `"、"` in Japanese. It comes with `aria-haspopup="dialog"`, `aria-expanded`, and `aria-controls` while open; the calendar dialog is named by `label`. Enter, Space, or a press opens it on `value`. As on `DateField`, a `value` outside `min`/`max` is kept rather than refused, and the calendar then opens on the nearest allowed day with `value` shown selected and disabled; a button has no invalid state, so the page that shows the date flags it if it must. Choosing a day or **Today** calls `onChange` with the new date only when it changed, closes, and returns focus to the button; Escape closes without a change and returns focus; a press outside closes without a change and leaves focus where it landed. An empty or malformed `value`, `min`, `max`, or `today`, inverted bounds, a bad `locale` or `weekStartsOn`, an empty `label`, or an empty label word throws a `RangeError`. `size` (`xs`, `sm`, `md`; default `md`) and `variant` (default `ghost`) follow `Button`; the button is square at every size, and on a coarse pointer it is at least 44 px at every size. Place it beside the heading, not inside it, so the heading's text stays only the date.
|
|
318
318
|
|
|
319
319
|
```tsx
|
|
320
320
|
<Inline gap={1}>
|
|
@@ -19,6 +19,12 @@ export type CalendarButtonProps = {
|
|
|
19
19
|
onChange: (value: string) => void;
|
|
20
20
|
min?: string;
|
|
21
21
|
max?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The calendar's today `YYYY-MM-DD`: the day it marks as today and picks
|
|
24
|
+
* with **Today**. Defaults to the device's local date; pass the business's
|
|
25
|
+
* day when it keeps its own time zone.
|
|
26
|
+
*/
|
|
27
|
+
today?: string;
|
|
22
28
|
disabled?: boolean;
|
|
23
29
|
/** The `<button>` element's id. */
|
|
24
30
|
id?: string;
|
|
@@ -26,7 +26,7 @@ function setForwardedRef(ref, node) {
|
|
|
26
26
|
* date for something the page already shows, such as a day heading. It has
|
|
27
27
|
* no text entry and no empty value. The ref reaches the `<button>`.
|
|
28
28
|
*/
|
|
29
|
-
export const CalendarButton = forwardRef(function CalendarButton({ label, value, onChange, min, max, disabled = false, id, locale, weekStartsOn = 0, labels, size = 'md', variant = 'ghost', }, ref) {
|
|
29
|
+
export const CalendarButton = forwardRef(function CalendarButton({ label, value, onChange, min, max, today, disabled = false, id, locale, weekStartsOn = 0, labels, size = 'md', variant = 'ghost', }, ref) {
|
|
30
30
|
assertLabel(label);
|
|
31
31
|
assertDateOnly('value', value);
|
|
32
32
|
assertDateBounds(value, min, max);
|
|
@@ -36,6 +36,7 @@ export const CalendarButton = forwardRef(function CalendarButton({ label, value,
|
|
|
36
36
|
value,
|
|
37
37
|
disabled,
|
|
38
38
|
onChange,
|
|
39
|
+
today,
|
|
39
40
|
});
|
|
40
41
|
const resolvedLocale = useLangLocale(locale, buttonRef);
|
|
41
42
|
const labelId = useId();
|
|
@@ -9,6 +9,13 @@ export type DateFieldProps = {
|
|
|
9
9
|
onChange: (value: string) => void;
|
|
10
10
|
min?: string;
|
|
11
11
|
max?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The calendar's today `YYYY-MM-DD`: the day it marks as today, opens on
|
|
14
|
+
* when the field is empty, and picks with **Today**. Defaults to the
|
|
15
|
+
* device's local date; pass the business's day when it keeps its own
|
|
16
|
+
* time zone.
|
|
17
|
+
*/
|
|
18
|
+
today?: string;
|
|
12
19
|
disabled?: boolean;
|
|
13
20
|
required?: boolean;
|
|
14
21
|
description?: string;
|
|
@@ -23,4 +30,4 @@ export type DateFieldProps = {
|
|
|
23
30
|
/** The control's own words; English by default. */
|
|
24
31
|
labels?: Partial<DateFieldLabels>;
|
|
25
32
|
};
|
|
26
|
-
export declare function DateField({ label, value, onChange, min, max, disabled, required, description, error, locale, weekStartsOn, labels, }: DateFieldProps): import("react").JSX.Element;
|
|
33
|
+
export declare function DateField({ label, value, onChange, min, max, today, disabled, required, description, error, locale, weekStartsOn, labels, }: DateFieldProps): import("react").JSX.Element;
|
|
@@ -13,7 +13,7 @@ import { DateFieldGlyph } from './date-field/DateFieldGlyph.js';
|
|
|
13
13
|
import { useCalendarPopup } from './date-field/use-calendar-popup.js';
|
|
14
14
|
import { useDateEntry } from './date-field/use-date-entry.js';
|
|
15
15
|
import { useLangLocale } from './date-field/use-lang-locale.js';
|
|
16
|
-
export function DateField({ label, value, onChange, min, max, disabled = false, required = false, description, error, locale, weekStartsOn = 0, labels, }) {
|
|
16
|
+
export function DateField({ label, value, onChange, min, max, today, disabled = false, required = false, description, error, locale, weekStartsOn = 0, labels, }) {
|
|
17
17
|
assertDateBounds(value, min, max);
|
|
18
18
|
assertWeekStart(weekStartsOn);
|
|
19
19
|
const words = resolveDateFieldLabels(labels);
|
|
@@ -35,6 +35,7 @@ export function DateField({ label, value, onChange, min, max, disabled = false,
|
|
|
35
35
|
value,
|
|
36
36
|
disabled,
|
|
37
37
|
onChange,
|
|
38
|
+
today,
|
|
38
39
|
beforeSelect: entry.reset,
|
|
39
40
|
});
|
|
40
41
|
const inputId = useId();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
|
+
import { type TooltipRelationship, type TooltipTriggerAria } from './tooltip-trigger-aria.js';
|
|
3
|
+
export type { TooltipRelationship };
|
|
2
4
|
export type TooltipProps = {
|
|
3
5
|
content: string;
|
|
4
6
|
/**
|
|
@@ -11,8 +13,19 @@ export type TooltipProps = {
|
|
|
11
13
|
* render and the first client render.
|
|
12
14
|
*/
|
|
13
15
|
disabled?: boolean;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `'description'` (default) adds supplementary help to a trigger that has
|
|
18
|
+
* its own name, through `aria-describedby`. `'label'` makes `content` the
|
|
19
|
+
* trigger's accessible name, through `aria-labelledby`, for an icon-only
|
|
20
|
+
* control: the text is read once, as its name. The tooltip stays in the
|
|
21
|
+
* DOM, hidden, so it names the trigger while it is not shown. While
|
|
22
|
+
* `disabled` there is no tooltip to name it, so the trigger then needs its
|
|
23
|
+
* own name: visible text, or an `aria-label` (which the tooltip overrides
|
|
24
|
+
* while enabled). The trigger's own `aria-labelledby` and `aria-describedby`
|
|
25
|
+
* are kept. It must match between the server render and the first client
|
|
26
|
+
* render.
|
|
27
|
+
*/
|
|
28
|
+
relationship?: TooltipRelationship;
|
|
29
|
+
trigger: ReactElement<TooltipTriggerAria>;
|
|
17
30
|
};
|
|
18
|
-
export declare function Tooltip({ content, disabled, trigger }: TooltipProps): import("react").JSX.Element;
|
|
31
|
+
export declare function Tooltip({ content, disabled, relationship, trigger, }: TooltipProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cloneElement, useEffect, useId, useRef, useState, } from 'react';
|
|
4
|
-
|
|
4
|
+
import { tooltipTriggerAria, } from './tooltip-trigger-aria.js';
|
|
5
|
+
export function Tooltip({ content, disabled = false, relationship = 'description', trigger, }) {
|
|
5
6
|
if (!content.trim())
|
|
6
7
|
throw new RangeError('content must not be empty');
|
|
7
8
|
// `open` follows focus, hover and touch even while disabled; `shown` is
|
|
@@ -10,9 +11,7 @@ export function Tooltip({ content, disabled = false, trigger }) {
|
|
|
10
11
|
const shown = open && !disabled;
|
|
11
12
|
const id = useId();
|
|
12
13
|
const wrapperRef = useRef(null);
|
|
13
|
-
const
|
|
14
|
-
.filter(Boolean)
|
|
15
|
-
.join(' ') || undefined;
|
|
14
|
+
const triggerAria = tooltipTriggerAria(trigger.props, disabled ? undefined : id, relationship);
|
|
16
15
|
useEffect(() => {
|
|
17
16
|
if (!open)
|
|
18
17
|
return;
|
|
@@ -43,5 +42,5 @@ export function Tooltip({ content, disabled = false, trigger }) {
|
|
|
43
42
|
}, onPointerDownCapture: (event) => {
|
|
44
43
|
if (event.pointerType === 'touch')
|
|
45
44
|
setOpen((current) => !current);
|
|
46
|
-
}, ref: wrapperRef, children: [cloneElement(trigger,
|
|
45
|
+
}, ref: wrapperRef, children: [cloneElement(trigger, triggerAria), disabled ? null : (_jsx("span", { className: "sw-tooltip", hidden: !shown, id: id, role: "tooltip", children: content }))] }));
|
|
47
46
|
}
|
|
@@ -3,6 +3,11 @@ export type CalendarPopupOptions = {
|
|
|
3
3
|
value: string;
|
|
4
4
|
disabled: boolean;
|
|
5
5
|
onChange: (value: string) => void;
|
|
6
|
+
/**
|
|
7
|
+
* The calendar's today (`YYYY-MM-DD`); the device's local date when left
|
|
8
|
+
* out. An empty or malformed date throws a RangeError.
|
|
9
|
+
*/
|
|
10
|
+
today?: string;
|
|
6
11
|
/** Runs before a chosen date closes the calendar, such as a text reset. */
|
|
7
12
|
beforeSelect?: () => void;
|
|
8
13
|
};
|
|
@@ -33,4 +38,4 @@ export type CalendarPopup = {
|
|
|
33
38
|
* DateField and CalendarButton. Disabling closes it for good; choosing a
|
|
34
39
|
* date closes it, returns focus to the button, and reports a changed date.
|
|
35
40
|
*/
|
|
36
|
-
export declare function useCalendarPopup({ value, disabled, onChange, beforeSelect, }: CalendarPopupOptions): CalendarPopup;
|
|
41
|
+
export declare function useCalendarPopup({ value, disabled, onChange, today, beforeSelect, }: CalendarPopupOptions): CalendarPopup;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { useId, useRef, useState } from 'react';
|
|
2
|
-
import { todayDateOnly } from '../../date-only.js';
|
|
2
|
+
import { assertDateOnly, todayDateOnly } from '../../date-only.js';
|
|
3
3
|
/**
|
|
4
4
|
* The open state of a calendar dialog toggled by one button, shared by
|
|
5
5
|
* DateField and CalendarButton. Disabling closes it for good; choosing a
|
|
6
6
|
* date closes it, returns focus to the button, and reports a changed date.
|
|
7
7
|
*/
|
|
8
|
-
export function useCalendarPopup({ value, disabled, onChange, beforeSelect, }) {
|
|
8
|
+
export function useCalendarPopup({ value, disabled, onChange, today, beforeSelect, }) {
|
|
9
|
+
if (today !== undefined)
|
|
10
|
+
assertDateOnly('today', today);
|
|
9
11
|
const buttonRef = useRef(null);
|
|
10
12
|
const [open, setOpen] = useState(false);
|
|
11
13
|
// Disabling closes the calendar for good, not just while disabled.
|
|
@@ -37,7 +39,7 @@ export function useCalendarPopup({ value, disabled, onChange, beforeSelect, }) {
|
|
|
37
39
|
id,
|
|
38
40
|
onClose: close,
|
|
39
41
|
onSelect: select,
|
|
40
|
-
today: todayDateOnly(),
|
|
42
|
+
today: today ?? todayDateOnly(),
|
|
41
43
|
},
|
|
42
44
|
};
|
|
43
45
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tooltip is to its trigger. A `description` supplements a trigger
|
|
3
|
+
* that already has a name; a `label` is the trigger's name, for an icon-only
|
|
4
|
+
* control whose tooltip says what it is.
|
|
5
|
+
*/
|
|
6
|
+
export type TooltipRelationship = 'description' | 'label';
|
|
7
|
+
/** The trigger's own references, kept and joined with the tooltip's id. */
|
|
8
|
+
export type TooltipTriggerAria = {
|
|
9
|
+
'aria-describedby'?: string;
|
|
10
|
+
'aria-labelledby'?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function tooltipTriggerAria(own: TooltipTriggerAria, tooltipId: string | undefined, relationship: TooltipRelationship): TooltipTriggerAria;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A description tooltip adds its id to the trigger's own `aria-describedby`
|
|
3
|
+
* and leaves its `aria-labelledby` exactly as it was. A label tooltip adds
|
|
4
|
+
* its id to the trigger's own `aria-labelledby` instead and leaves its
|
|
5
|
+
* `aria-describedby` as it was, so its text is the trigger's name (it wins
|
|
6
|
+
* over an `aria-label`) and is read once, not as both name and description.
|
|
7
|
+
* A disabled tooltip is not rendered (`tooltipId` undefined), so the trigger
|
|
8
|
+
* never points at a missing element.
|
|
9
|
+
*/
|
|
10
|
+
export function tooltipTriggerAria(own, tooltipId, relationship) {
|
|
11
|
+
if (relationship === 'label')
|
|
12
|
+
return {
|
|
13
|
+
'aria-describedby': own['aria-describedby'],
|
|
14
|
+
'aria-labelledby': joinIdRefs(own['aria-labelledby'], tooltipId),
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
'aria-describedby': joinIdRefs(own['aria-describedby'], tooltipId),
|
|
18
|
+
'aria-labelledby': own['aria-labelledby'],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function joinIdRefs(...ids) {
|
|
22
|
+
return ids.filter(Boolean).join(' ') || undefined;
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { CalendarButton, type CalendarButtonLabels, type CalendarButtonProps, }
|
|
|
8
8
|
export { Checkbox, type CheckboxProps } from './components/Checkbox.js';
|
|
9
9
|
export { Spinner, type SpinnerProps, type SpinnerSize, } from './components/Spinner.js';
|
|
10
10
|
export { Progress, type ProgressProps, type ProgressTone, } from './components/Progress.js';
|
|
11
|
-
export { Tooltip, type TooltipProps } from './components/Tooltip.js';
|
|
11
|
+
export { Tooltip, type TooltipProps, type TooltipRelationship, } from './components/Tooltip.js';
|
|
12
12
|
export { RadioGroup, type RadioGroupOption, type RadioGroupProps, } from './components/RadioGroup.js';
|
|
13
13
|
export { AppHeader, type AppHeaderProps } from './components/AppHeader.js';
|
|
14
14
|
export { Badge, type BadgeProps, type BadgeSize, type BadgeTone, badgeSizes, badgeTones, } from './components/Badge.js';
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { CalendarButton, } from './components/CalendarButton.js';
|
|
|
7
7
|
export { Checkbox } from './components/Checkbox.js';
|
|
8
8
|
export { Spinner, } from './components/Spinner.js';
|
|
9
9
|
export { Progress, } from './components/Progress.js';
|
|
10
|
-
export { Tooltip } from './components/Tooltip.js';
|
|
10
|
+
export { Tooltip, } from './components/Tooltip.js';
|
|
11
11
|
export { RadioGroup, } from './components/RadioGroup.js';
|
|
12
12
|
export { AppHeader } from './components/AppHeader.js';
|
|
13
13
|
export { Badge, badgeSizes, badgeTones, } from './components/Badge.js';
|