@flood-md/react 0.2.6 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/alert-dialog.js +1 -1
- package/dist/calendar-period-grid.d.ts +17 -0
- package/dist/calendar-period-grid.js +13 -0
- package/dist/calendar-value.d.ts +10 -0
- package/dist/calendar-value.js +48 -0
- package/dist/calendar.d.ts +21 -0
- package/dist/calendar.js +259 -0
- package/dist/checkbox.js +1 -1
- package/dist/choice-group.js +1 -1
- package/dist/collection-cards.d.ts +4 -0
- package/dist/collection-cards.js +21 -0
- package/dist/color-controls.d.ts +5 -0
- package/dist/color-controls.js +110 -0
- package/dist/combobox.js +22 -13
- package/dist/command-list.d.ts +3 -0
- package/dist/command-list.js +93 -0
- package/dist/content-elements.js +7 -7
- package/dist/context-menu.d.ts +2 -0
- package/dist/context-menu.js +76 -0
- package/dist/data-display.js +2 -2
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.js +58 -0
- package/dist/date-field.d.ts +2 -0
- package/dist/date-field.js +172 -0
- package/dist/date-picker.d.ts +3 -0
- package/dist/date-picker.js +108 -0
- package/dist/date-range-field.d.ts +3 -0
- package/dist/date-range-field.js +84 -0
- package/dist/date-range-picker.d.ts +2 -0
- package/dist/date-range-picker.js +81 -0
- package/dist/date-range-value.d.ts +8 -0
- package/dist/date-range-value.js +13 -0
- package/dist/date-time-picker.d.ts +3 -0
- package/dist/date-time-picker.js +31 -0
- package/dist/date-time-range-picker.d.ts +2 -0
- package/dist/date-time-range-picker.js +51 -0
- package/dist/date-value.d.ts +10 -0
- package/dist/date-value.js +31 -0
- package/dist/disclosure.js +1 -1
- package/dist/editable.d.ts +3 -0
- package/dist/editable.js +135 -0
- package/dist/error-summary.d.ts +3 -0
- package/dist/error-summary.js +39 -0
- package/dist/feedback-actions.js +2 -2
- package/dist/feedback.js +2 -2
- package/dist/file-input.js +1 -1
- package/dist/file-records.d.ts +5 -0
- package/dist/file-records.js +55 -0
- package/dist/form-controls.js +25 -13
- package/dist/foundations.css +401 -3
- package/dist/hover-card.d.ts +3 -0
- package/dist/hover-card.js +144 -0
- package/dist/index.d.ts +661 -2
- package/dist/index.js +190 -86
- package/dist/menu.d.ts +27 -1
- package/dist/menu.js +327 -89
- package/dist/navigation-layout.js +6 -6
- package/dist/navigation-tree.d.ts +3 -0
- package/dist/navigation-tree.js +98 -0
- package/dist/navigation.js +3 -3
- package/dist/numeric-input.js +1 -1
- package/dist/overlays.js +11 -10
- package/dist/range-calendar.d.ts +2 -0
- package/dist/range-calendar.js +35 -0
- package/dist/resizable-panels.d.ts +3 -0
- package/dist/resizable-panels.js +205 -0
- package/dist/scroll-area.js +1 -1
- package/dist/select.js +17 -6
- package/dist/slider.js +1 -1
- package/dist/tag-input.d.ts +3 -0
- package/dist/tag-input.js +181 -0
- package/dist/text-inputs.js +2 -2
- package/dist/time-field.d.ts +2 -0
- package/dist/time-field.js +176 -0
- package/dist/time-picker.d.ts +2 -0
- package/dist/time-picker.js +83 -0
- package/dist/time-value.d.ts +12 -0
- package/dist/time-value.js +36 -0
- package/dist/toolbar.js +1 -1
- package/dist/tree-select.d.ts +3 -0
- package/dist/tree-select.js +113 -0
- package/dist/tree.d.ts +3 -0
- package/dist/tree.js +206 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,9 +3,35 @@
|
|
|
3
3
|
Original React 18.3/19 components for TypeScript and Next.js App Router. Native
|
|
4
4
|
HTML semantics and refs, no third-party component library or Tailwind dependency.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Named imports support tree shaking in optimizing ES-module bundlers. Import the
|
|
7
|
+
shared stylesheet explicitly; styles are not split by component.
|
|
8
|
+
|
|
9
|
+
This package documents the **0.2.9** source API. Keep Flood package versions aligned and follow the guide matching
|
|
7
10
|
your installed declarations; source changes do not modify an existing npm release.
|
|
8
11
|
|
|
12
|
+
Version 0.2.9 improves constrained file rows, Drawer readability and Select pointer
|
|
13
|
+
focus without changing public signatures or token values.
|
|
14
|
+
|
|
15
|
+
Version 0.2.8 adds TimePicker, DateTimeRangePicker, unified Calendar day/month/year views,
|
|
16
|
+
HoverCard, ColorSwatch, ColorPicker, TreeSelect,
|
|
17
|
+
DateTimePicker, DataList, DataListItem, CheckCard, RadioCard, DataTable, FileItem/FileList, FileMeta, UploadItem/UploadList and ImagePreview. Color values are opaque RGB hex; TreeSelect selects one item;
|
|
18
|
+
DateTimePicker retains separate civil date and clock fields without inferring a
|
|
19
|
+
timezone. Each component has a live catalog example and public prop reference.
|
|
20
|
+
|
|
21
|
+
ListBox in the 0.2.8 source also supports native required validation, descriptions,
|
|
22
|
+
semantic feedback and host-owned validation messages. Readonly bypasses validity;
|
|
23
|
+
disabled excludes form values. See its guide for controlled selection and reset.
|
|
24
|
+
|
|
25
|
+
Version 0.2.7 adds Tree for application object selection and NavigationTree for
|
|
26
|
+
nested native links. Both use globally unique item values and independent
|
|
27
|
+
controlled/uncontrolled expansion. Tree keeps keyboard focus separate from
|
|
28
|
+
single/multiple selection, with host-owned deferred branches. NavigationTree
|
|
29
|
+
preserves native links, modifier navigation and Tab order with separate disclosure
|
|
30
|
+
buttons. Neither component fetches, virtualizes or writes form values. See the
|
|
31
|
+
[Tree](https://flood.md/components/tree) and
|
|
32
|
+
[NavigationTree](https://flood.md/components/navigation-tree) guides; check their
|
|
33
|
+
release status against your installed version.
|
|
34
|
+
|
|
9
35
|
```tsx
|
|
10
36
|
import "@flood-md/react/styles.css";
|
|
11
37
|
import { Button, FloodRoot, Heading, Stack, Text } from "@flood-md/react";
|
|
@@ -902,3 +928,21 @@ The CSS build assembles the original component styles into the shared stylesheet
|
|
|
902
928
|
Section headingLevel controls native h2–h6 semantics. Its independent headingSize
|
|
903
929
|
uses the existing Heading roles (section/page/large/display), defaulting to section.
|
|
904
930
|
Keep document hierarchy meaningful when changing visual emphasis.
|
|
931
|
+
|
|
932
|
+
## Editable and TagInput · 0.2.7
|
|
933
|
+
|
|
934
|
+
Editable provides inline single-line editing with a separate draft, save/cancel actions and committed named form values. See [Editable](../../docs/components/editable.md) for controlled ownership and blur behavior. TagInput creates free-form text tokens and submits repeated named values; it is distinct from fixed-option MultiSelect. See [TagInput](../../docs/components/tag-input.md) for delimiters, paste and validation.
|
|
935
|
+
|
|
936
|
+
Menu adds `MenuCheckboxItem` and `MenuRadioItem`. Both take controlled `checked` and `onCheckedChange`; radio items also require a `group` label and the host enforces one selected value per group. Checkbox `checked` also accepts `"mixed"`. Keep radio groups contiguous. Preferences stay open by default; set `closeOnSelect` to close after activation. Preference changes do not invoke `onAction`. Existing action items retain their behavior.
|
|
937
|
+
|
|
938
|
+
CommandList (0.2.7) provides grouped command filtering and invocation without a modal or form value. See [CommandList](../../docs/components/command-list.md) for query ownership, keyboard navigation and loading/empty states.
|
|
939
|
+
|
|
940
|
+
DateField (0.2.7) edits a Gregorian civil date through native text segments, with ISO canonical values, bounds, explicit segment order and native invalid-draft form handling. It has no operating-system date popup. See [DateField](../../docs/components/date-field.md).
|
|
941
|
+
|
|
942
|
+
TimeField, Calendar and DatePicker (0.2.7) complete the first single-date/time entry family. See [TimeField](../../docs/components/time-field.md), [Calendar](../../docs/components/calendar.md) and [DatePicker](../../docs/components/date-picker.md). Calendar onDateSelect reports enabled activation, including the already selected date; onValueChange requests only changed values. DateField end accepts an optional trailing action.
|
|
943
|
+
|
|
944
|
+
Popover positionReference optionally supplies a connected same-document element ref for geometry while trigger semantics, dismissal and focus restoration remain on its button. DatePicker uses this to align its panel to the whole field, tracks field width responsively and clamps to the viewport. Available in the 0.2.7 source candidate.
|
|
945
|
+
|
|
946
|
+
DateRangeField, RangeCalendar and DateRangePicker (0.2.7) share ordered range values and the original Calendar grid. ResizablePanels adds real pointer/keyboard geometry above static Split. ErrorSummary links host validation to native controls. See each generated catalog page and its guide under docs/components/. DateField validationMessage adds an external native constraint after the field's own date validity.
|
|
947
|
+
|
|
948
|
+
Menu (0.2.7) supports recursive submenu items and ContextMenu reuses its native layer engine. Context triggers preserve the actual pointer/keyboard source; nested searches stay scoped, Escape unwinds one level and Tab exits through the root. See docs/components/menu.md and context-menu.md.
|
package/dist/alert-dialog.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef, useRef, useState } from "react";
|
|
3
3
|
import { Button } from "./index.js";
|
|
4
4
|
import { ModalOverlay } from "./overlays.js";
|
|
5
|
-
export const ConfirmationDialog = forwardRef(function ConfirmationDialog({ open: controlled, defaultOpen = false, onOpenChange, onConfirm, onCancel, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, className, ...props }, ref) {
|
|
5
|
+
export const ConfirmationDialog = /* @__PURE__ */ forwardRef(function ConfirmationDialog({ open: controlled, defaultOpen = false, onOpenChange, onConfirm, onCancel, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, className, ...props }, ref) {
|
|
6
6
|
const [local, setLocal] = useState(defaultOpen);
|
|
7
7
|
const cancel = useRef(null);
|
|
8
8
|
const request = (next) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface CalendarPeriodChoice {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
name: string;
|
|
5
|
+
selected: boolean;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Internal Calendar view, not another picker or overlay. */
|
|
9
|
+
export declare function CalendarPeriodGrid({ choices, label, onChoose, autoFocus, disabled, focusValue, readOnly }: {
|
|
10
|
+
choices: CalendarPeriodChoice[];
|
|
11
|
+
label: string;
|
|
12
|
+
onChoose: (value: string) => void;
|
|
13
|
+
autoFocus: boolean;
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
focusValue?: string;
|
|
16
|
+
readOnly?: boolean;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button, Grid } from "./index.js";
|
|
4
|
+
/** Internal Calendar view, not another picker or overlay. */
|
|
5
|
+
export function CalendarPeriodGrid({ choices, label, onChoose, autoFocus, disabled, focusValue, readOnly }) {
|
|
6
|
+
const root = useRef(null);
|
|
7
|
+
const first = choices.find(choice => choice.value === focusValue && !choice.disabled) ?? choices.find(choice => choice.selected && !choice.disabled) ?? choices.find(choice => !choice.disabled);
|
|
8
|
+
const [active, setActive] = useState(first?.value);
|
|
9
|
+
const roving = choices.some(choice => choice.value === active && !choice.disabled) ? active : first?.value;
|
|
10
|
+
useEffect(() => { if (autoFocus)
|
|
11
|
+
root.current?.querySelector('button[tabindex="0"]')?.focus({ preventScroll: true }); }, [autoFocus]);
|
|
12
|
+
return _jsx(Grid, { ref: root, columns: 3, gap: "1", className: "flood-calendar__periods", role: "group", "aria-label": label, children: choices.map(choice => _jsx(Button, { "data-calendar-period": choice.value, className: "flood-calendar__period-choice", size: "sm", variant: choice.selected ? "subtle" : "ghost", tabIndex: !disabled && choice.value === roving ? 0 : -1, disabled: disabled || choice.disabled, "aria-disabled": readOnly || undefined, "aria-pressed": choice.selected, "aria-label": choice.name, onFocus: () => setActive(choice.value), onClick: () => onChoose(choice.value), children: choice.label }, choice.value)) });
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DateParts } from "./date-value.js";
|
|
2
|
+
export declare function monthParts(month: string): DateParts | undefined;
|
|
3
|
+
export declare function monthShift(month: string, delta: number): string;
|
|
4
|
+
export declare function dateOrdinal(date: string): number;
|
|
5
|
+
export declare const lastDateOrdinal: number;
|
|
6
|
+
export declare function dateFromOrdinal(ordinal: number): string | undefined;
|
|
7
|
+
export declare const dateWeekday: (date: string) => number;
|
|
8
|
+
export declare function moveDateMonth(date: string, delta: number): string;
|
|
9
|
+
/** Intl receives an actual UTC date without Date.UTC's special handling of years 0–99. */
|
|
10
|
+
export declare function formattingDate(date: string): Date;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { canonicalDate, daysInMonth, parseDate } from "./date-value.js";
|
|
2
|
+
export function monthParts(month) {
|
|
3
|
+
return /^\d{4}-\d{2}$/.test(month) ? parseDate(`${month}-01`) : undefined;
|
|
4
|
+
}
|
|
5
|
+
export function monthShift(month, delta) {
|
|
6
|
+
const parts = monthParts(month);
|
|
7
|
+
const index = Math.min(9999 * 12 - 1, Math.max(0, (Number(parts.year) - 1) * 12 + Number(parts.month) - 1 + delta));
|
|
8
|
+
return `${String(Math.floor(index / 12) + 1).padStart(4, "0")}-${String(index % 12 + 1).padStart(2, "0")}`;
|
|
9
|
+
}
|
|
10
|
+
export function dateOrdinal(date) {
|
|
11
|
+
const parts = parseDate(date);
|
|
12
|
+
const year = Number(parts.year), month = Number(parts.month), previous = year - 1;
|
|
13
|
+
let days = previous * 365 + Math.floor(previous / 4) - Math.floor(previous / 100) + Math.floor(previous / 400);
|
|
14
|
+
for (let m = 1; m < month; m++)
|
|
15
|
+
days += daysInMonth(year, m);
|
|
16
|
+
return days + Number(parts.day) - 1;
|
|
17
|
+
}
|
|
18
|
+
export const lastDateOrdinal = dateOrdinal("9999-12-31");
|
|
19
|
+
export function dateFromOrdinal(ordinal) {
|
|
20
|
+
if (ordinal < 0 || ordinal > lastDateOrdinal)
|
|
21
|
+
return;
|
|
22
|
+
let low = 1, high = 9999;
|
|
23
|
+
while (low < high) {
|
|
24
|
+
const middle = Math.ceil((low + high) / 2);
|
|
25
|
+
if (dateOrdinal(`${String(middle).padStart(4, "0")}-01-01`) <= ordinal)
|
|
26
|
+
low = middle;
|
|
27
|
+
else
|
|
28
|
+
high = middle - 1;
|
|
29
|
+
}
|
|
30
|
+
let remaining = ordinal - dateOrdinal(`${String(low).padStart(4, "0")}-01-01`), month = 1;
|
|
31
|
+
while (remaining >= daysInMonth(low, month)) {
|
|
32
|
+
remaining -= daysInMonth(low, month);
|
|
33
|
+
month++;
|
|
34
|
+
}
|
|
35
|
+
return canonicalDate({ year: String(low), month: String(month), day: String(remaining + 1) });
|
|
36
|
+
}
|
|
37
|
+
export const dateWeekday = (date) => (dateOrdinal(date) + 1) % 7;
|
|
38
|
+
export function moveDateMonth(date, delta) {
|
|
39
|
+
const parts = parseDate(date), month = monthShift(date.slice(0, 7), delta), next = monthParts(month);
|
|
40
|
+
return `${month}-${String(Math.min(Number(parts.day), daysInMonth(Number(next.year), Number(next.month)))).padStart(2, "0")}`;
|
|
41
|
+
}
|
|
42
|
+
/** Intl receives an actual UTC date without Date.UTC's special handling of years 0–99. */
|
|
43
|
+
export function formattingDate(date) {
|
|
44
|
+
const parts = parseDate(date), result = new Date(0);
|
|
45
|
+
result.setUTCFullYear(Number(parts.year), Number(parts.month) - 1, Number(parts.day));
|
|
46
|
+
result.setUTCHours(12, 0, 0, 0);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { CalendarProps } from "./index.js";
|
|
3
|
+
export interface CalendarDayMetadata {
|
|
4
|
+
rangeStart?: boolean;
|
|
5
|
+
rangeEnd?: boolean;
|
|
6
|
+
inRange?: boolean;
|
|
7
|
+
preview?: boolean;
|
|
8
|
+
unavailable?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface CalendarGridProps extends Omit<CalendarProps, "value" | "defaultValue" | "onValueChange" | "onDateSelect"> {
|
|
11
|
+
selectedDate?: string;
|
|
12
|
+
isSelected?: (date: string) => boolean;
|
|
13
|
+
dayMetadata?: (date: string) => CalendarDayMetadata;
|
|
14
|
+
onDayActivate?: (date: string) => void;
|
|
15
|
+
onDayPreview?: (date: string | undefined) => void;
|
|
16
|
+
multiple?: boolean;
|
|
17
|
+
footer?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/** One original grid/focus engine shared by single-date and range calendars. */
|
|
20
|
+
export declare const CalendarGrid: import("react").ForwardRefExoticComponent<CalendarGridProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
21
|
+
export declare const DateCalendar: import("react").ForwardRefExoticComponent<CalendarProps & import("react").RefAttributes<HTMLDivElement>>;
|
package/dist/calendar.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createElement as _createElement } from "react";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Button, ButtonGroup, IconButton, VisuallyHidden } from "./index.js";
|
|
5
|
+
import { CalendarPeriodGrid } from "./calendar-period-grid.js";
|
|
6
|
+
import { dateInRange, parseDate, daysInMonth } from "./date-value.js";
|
|
7
|
+
import { dateFromOrdinal, dateOrdinal, dateWeekday, formattingDate, lastDateOrdinal, monthParts, monthShift, moveDateMonth } from "./calendar-value.js";
|
|
8
|
+
const cx = (...values) => values.filter(Boolean).join(" ");
|
|
9
|
+
const titleCase = (text, locale) => text.replace(/^\p{L}/u, letter => letter.toLocaleUpperCase(locale));
|
|
10
|
+
/** One original grid/focus engine shared by single-date and range calendars. */
|
|
11
|
+
export const CalendarGrid = /* @__PURE__ */ forwardRef(function CalendarGrid({ label, selectedDate = "", isSelected, dayMetadata, onDayActivate, onDayPreview, multiple = false, footer, visibleMonth, defaultVisibleMonth, onVisibleMonthChange, min, max, disabled = false, readOnly = false, isDateDisabled, weekStartsOn = 0, locale = "en-US", autoFocus = false, granularity = "day", showViewControls = true, viewLabels, today, previousMonthLabel = "Previous month", nextMonthLabel = "Next month", id: explicitId, className, onKeyDown, onBlur, onPointerLeave, ...props }, forwardedRef) {
|
|
12
|
+
if (!label.trim() || !previousMonthLabel.trim() || !nextMonthLabel.trim())
|
|
13
|
+
throw new Error("Flood Calendar requires nonempty calendar and navigation labels");
|
|
14
|
+
for (const [key, date] of [["selectedDate", selectedDate], ["min", min], ["max", max], ["today", today]])
|
|
15
|
+
if (date !== undefined && (date !== "" || key !== "selectedDate") && !parseDate(date))
|
|
16
|
+
throw new Error(`Flood Calendar ${key} must be a valid ISO Gregorian date`);
|
|
17
|
+
for (const month of [visibleMonth, defaultVisibleMonth])
|
|
18
|
+
if (month !== undefined && !monthParts(month))
|
|
19
|
+
throw new Error("Flood Calendar month must be YYYY-MM from 0001 to 9999");
|
|
20
|
+
if (min && max && min > max)
|
|
21
|
+
throw new Error("Flood Calendar min must not follow max");
|
|
22
|
+
if (!Number.isInteger(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6)
|
|
23
|
+
throw new Error("Flood Calendar weekStartsOn must be 0 to 6");
|
|
24
|
+
const [clock] = useState(() => today ?? new Date().toISOString().slice(0, 10));
|
|
25
|
+
const actualToday = today ?? clock, selected = selectedDate;
|
|
26
|
+
const clampMonth = (month) => min && month < min.slice(0, 7) ? min.slice(0, 7) : max && month > max.slice(0, 7) ? max.slice(0, 7) : month;
|
|
27
|
+
const initialMonth = useRef(defaultVisibleMonth ?? clampMonth((selected || actualToday).slice(0, 7)));
|
|
28
|
+
const [localMonth, setLocalMonth] = useState(initialMonth.current), month = visibleMonth ?? localMonth;
|
|
29
|
+
const baseView = granularity === "day" ? "days" : granularity === "month" ? "months" : "years";
|
|
30
|
+
const [view, setView] = useState(baseView), [focusView, setFocusView] = useState(false);
|
|
31
|
+
const year = Number(month.slice(0, 4)), yearPage = (value) => Math.min(9988, 1 + Math.floor((value - 1) / 12) * 12);
|
|
32
|
+
const [yearStart, setYearStart] = useState(() => yearPage(year));
|
|
33
|
+
const monthName = titleCase(new Intl.DateTimeFormat(locale, { calendar: "gregory", timeZone: "UTC", month: "long" }).format(formattingDate(`${month}-01`)), locale);
|
|
34
|
+
const viewNames = { month: viewLabels?.month ?? "Month", year: viewLabels?.year ?? "Year", previous: viewLabels?.previous ?? "Previous", next: viewLabels?.next ?? "Next" };
|
|
35
|
+
if (!Object.values(viewNames).every(text => text.trim()))
|
|
36
|
+
throw new Error("Flood Calendar view labels must be nonempty");
|
|
37
|
+
const firstDate = `${month}-01`, firstOrdinal = dateOrdinal(firstDate), startOrdinal = firstOrdinal - ((dateWeekday(firstDate) - weekStartsOn + 7) % 7);
|
|
38
|
+
const [focused, setFocused] = useState(selected && selected.slice(0, 7) === month ? selected : firstDate);
|
|
39
|
+
const root = useRef(null), buttons = useRef(new Map()), pendingFocus = useRef(undefined);
|
|
40
|
+
const generated = useId(), id = explicitId ?? `flood-calendar-${generated}`;
|
|
41
|
+
useImperativeHandle(forwardedRef, () => root.current, []);
|
|
42
|
+
const [inheritedDisabled, setInheritedDisabled] = useState(false), effectiveDisabled = disabled || inheritedDisabled;
|
|
43
|
+
const [direction, setDirection] = useState(props.dir ?? "ltr");
|
|
44
|
+
const formatters = useMemo(() => ({ month: new Intl.DateTimeFormat(locale, { calendar: "gregory", timeZone: "UTC", month: "long", year: "numeric" }), day: new Intl.DateTimeFormat(locale, { calendar: "gregory", timeZone: "UTC", weekday: "long", month: "long", day: "numeric", year: "numeric" }), weekday: new Intl.DateTimeFormat(locale, { calendar: "gregory", timeZone: "UTC", weekday: "short" }), weekdayLong: new Intl.DateTimeFormat(locale, { calendar: "gregory", timeZone: "UTC", weekday: "long" }), number: new Intl.NumberFormat(locale, { useGrouping: false }) }), [locale]);
|
|
45
|
+
const monthLabel = formatters.month.format(formattingDate(firstDate));
|
|
46
|
+
const dates = Array.from({ length: 42 }, (_, index) => dateFromOrdinal(startOrdinal + index));
|
|
47
|
+
const roving = dates.includes(focused) ? focused : selected && dates.includes(selected) ? selected : firstDate;
|
|
48
|
+
const blocked = (date) => !dateInRange(date, min, max) || !!isDateDisabled?.(date);
|
|
49
|
+
const daySelected = (date) => isSelected ? isSelected(date) : selected === date;
|
|
50
|
+
const attributes = (date) => { const state = date ? dayMetadata?.(date) : undefined; return { "data-range-start": state?.rangeStart || undefined, "data-range-end": state?.rangeEnd || undefined, "data-in-range": state?.inRange || undefined, "data-range-preview": state?.preview || undefined, "data-range-unavailable": state?.unavailable || undefined }; };
|
|
51
|
+
const focusDay = (date) => { setFocused(date); root.current?.ownerDocument.defaultView?.requestAnimationFrame(() => buttons.current.get(date)?.focus({ preventScroll: true })); };
|
|
52
|
+
const requestMonth = (next) => { if (next === month)
|
|
53
|
+
return; if (visibleMonth === undefined)
|
|
54
|
+
setLocalMonth(next); onVisibleMonthChange?.(next); };
|
|
55
|
+
const switchView = (next) => { if (next === "years")
|
|
56
|
+
setYearStart(yearPage(year)); setFocusView(true); setView(next); };
|
|
57
|
+
const restoreDays = () => {
|
|
58
|
+
setView(baseView);
|
|
59
|
+
setFocusView(false);
|
|
60
|
+
root.current?.ownerDocument.defaultView?.requestAnimationFrame(() => {
|
|
61
|
+
const target = baseView === "days" ? buttons.current.get(roving) : root.current?.querySelector('[data-calendar-period][tabindex="0"]');
|
|
62
|
+
target?.focus({ preventScroll: true });
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const choosePeriod = (period) => {
|
|
66
|
+
if (effectiveDisabled)
|
|
67
|
+
return;
|
|
68
|
+
const target = clampMonth(view === "years" ? `${period}-${month.slice(5)}` : period);
|
|
69
|
+
pendingFocus.current = undefined;
|
|
70
|
+
if ((granularity === "month" && view === "months") || granularity === "year") {
|
|
71
|
+
if (readOnly)
|
|
72
|
+
return;
|
|
73
|
+
requestMonth(target);
|
|
74
|
+
onDayActivate?.(view === "years" ? `${period}-01-01` : `${period}-01`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (baseView === "days")
|
|
78
|
+
pendingFocus.current = `${target}-01`;
|
|
79
|
+
requestMonth(target);
|
|
80
|
+
setView(baseView);
|
|
81
|
+
setFocusView(false);
|
|
82
|
+
root.current?.ownerDocument.defaultView?.requestAnimationFrame(() => {
|
|
83
|
+
if (baseView === "days") {
|
|
84
|
+
const pending = pendingFocus.current;
|
|
85
|
+
if (pending && pending.slice(0, 7) === root.current?.dataset.calendarMonth && buttons.current.has(pending)) {
|
|
86
|
+
pendingFocus.current = undefined;
|
|
87
|
+
focusDay(pending);
|
|
88
|
+
}
|
|
89
|
+
else
|
|
90
|
+
root.current?.querySelector('button[data-calendar-date][tabindex="0"]')?.focus({ preventScroll: true });
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
root.current?.querySelector('[data-calendar-period][tabindex="0"]')?.focus({ preventScroll: true });
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
useEffect(() => { setView(baseView); setYearStart(yearPage(year)); }, [granularity]);
|
|
97
|
+
useEffect(() => { if (baseView === "years")
|
|
98
|
+
setYearStart(yearPage(year)); }, [month]);
|
|
99
|
+
const move = (date) => {
|
|
100
|
+
const targetMonth = date.slice(0, 7);
|
|
101
|
+
if (targetMonth !== month) {
|
|
102
|
+
pendingFocus.current = date;
|
|
103
|
+
requestMonth(targetMonth);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
focusDay(date);
|
|
107
|
+
};
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const pending = pendingFocus.current;
|
|
110
|
+
if (pending?.slice(0, 7) === month) {
|
|
111
|
+
pendingFocus.current = undefined;
|
|
112
|
+
focusDay(pending);
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
setFocused(selected && selected.slice(0, 7) === month ? selected : `${month}-01`);
|
|
116
|
+
}, [month]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!autoFocus || effectiveDisabled)
|
|
119
|
+
return;
|
|
120
|
+
const frame = root.current?.ownerDocument.defaultView?.requestAnimationFrame(() => { const node = root.current; if (node && node.getClientRects().length && node.ownerDocument.defaultView.getComputedStyle(node).visibility !== "hidden")
|
|
121
|
+
buttons.current.get(roving)?.focus({ preventScroll: true }); });
|
|
122
|
+
return () => { if (frame !== undefined)
|
|
123
|
+
root.current?.ownerDocument.defaultView?.cancelAnimationFrame(frame); };
|
|
124
|
+
}, [autoFocus, effectiveDisabled]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const node = root.current?.querySelector("input[data-calendar-disabled-probe]");
|
|
127
|
+
if (!node)
|
|
128
|
+
return;
|
|
129
|
+
const update = () => setInheritedDisabled(node.matches(":disabled"));
|
|
130
|
+
update();
|
|
131
|
+
const observer = new node.ownerDocument.defaultView.MutationObserver(update);
|
|
132
|
+
let ancestor = root.current?.parentElement;
|
|
133
|
+
while (ancestor) {
|
|
134
|
+
if (ancestor.localName === "fieldset")
|
|
135
|
+
observer.observe(ancestor, { attributes: true, attributeFilter: ["disabled"] });
|
|
136
|
+
ancestor = ancestor.parentElement;
|
|
137
|
+
}
|
|
138
|
+
return () => observer.disconnect();
|
|
139
|
+
}, [disabled]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const node = root.current;
|
|
142
|
+
if (!node)
|
|
143
|
+
return;
|
|
144
|
+
const update = () => setDirection(node.ownerDocument.defaultView.getComputedStyle(node).direction);
|
|
145
|
+
update();
|
|
146
|
+
const observer = new node.ownerDocument.defaultView.MutationObserver(update);
|
|
147
|
+
let ancestor = node;
|
|
148
|
+
while (ancestor) {
|
|
149
|
+
observer.observe(ancestor, { attributes: true, attributeFilter: ["dir"] });
|
|
150
|
+
ancestor = ancestor.parentElement;
|
|
151
|
+
}
|
|
152
|
+
return () => observer.disconnect();
|
|
153
|
+
}, [props.dir]);
|
|
154
|
+
const previous = clampMonth(monthShift(month, -1)), next = clampMonth(monthShift(month, 1));
|
|
155
|
+
const previousDisabled = effectiveDisabled || month === "0001-01" || !!min && month <= min.slice(0, 7), nextDisabled = effectiveDisabled || month === "9999-12" || !!max && month >= max.slice(0, 7);
|
|
156
|
+
const minYear = Number(min?.slice(0, 4) ?? 1), maxYear = Number(max?.slice(0, 4) ?? 9999);
|
|
157
|
+
const periodPreviousDisabled = effectiveDisabled || (view === "years" ? yearStart <= minYear : year <= minYear), periodNextDisabled = effectiveDisabled || (view === "years" ? yearStart + 11 >= maxYear : year >= maxYear);
|
|
158
|
+
const navigatePeriod = (offset) => { if (view === "years")
|
|
159
|
+
setYearStart(current => Math.max(1, Math.min(9988, current + offset * 12)));
|
|
160
|
+
else
|
|
161
|
+
requestMonth(clampMonth(monthShift(month, offset * 12))); };
|
|
162
|
+
const periodChoices = view === "days" ? [] : Array.from({ length: 12 }, (_, index) => {
|
|
163
|
+
const value = view === "years" ? String(yearStart + index).padStart(4, "0") : `${month.slice(0, 4)}-${String(index + 1).padStart(2, "0")}`;
|
|
164
|
+
const monthStart = `${value}-01`, monthEnd = view === "months" ? `${value}-${String(daysInMonth(year, index + 1)).padStart(2, "0")}` : "";
|
|
165
|
+
return { value, label: view === "years" ? String(yearStart + index) : titleCase(new Intl.DateTimeFormat(locale, { month: "short", calendar: "gregory", timeZone: "UTC" }).format(formattingDate(`2000-${String(index + 1).padStart(2, "0")}-01`)), locale),
|
|
166
|
+
name: view === "years" ? String(yearStart + index) : new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", calendar: "gregory", timeZone: "UTC" }).format(formattingDate(monthStart)),
|
|
167
|
+
selected: view === "years" ? selected.slice(0, 4) === value : selected.slice(0, 7) === value,
|
|
168
|
+
disabled: view === "years" ? yearStart + index < minYear || yearStart + index > maxYear : Boolean(min && monthEnd < min || max && monthStart > max) };
|
|
169
|
+
});
|
|
170
|
+
const headingControls = _jsxs(_Fragment, { children: [showViewControls && granularity === "day" ? _jsx(Button, { variant: "outline", size: "sm", disabled: effectiveDisabled, "aria-label": `${viewNames.month}: ${monthName}`, "aria-pressed": view === "months", onClick: () => view === "months" ? restoreDays() : switchView("months"), children: monthName }) : granularity === "day" ? _jsx("span", { children: monthName }) : null, granularity === "day" ? " " : null, showViewControls && granularity !== "year" ? _jsx(Button, { variant: "outline", size: "sm", disabled: effectiveDisabled, "aria-label": `${viewNames.year}: ${year}`, "aria-pressed": view === "years", onClick: () => view === "years" ? restoreDays() : switchView("years"), children: view === "years" ? `${yearStart}–${Math.min(9999, yearStart + 11)}` : year }) : _jsx("span", { children: granularity === "year" ? `${yearStart}–${Math.min(9999, yearStart + 11)}` : year })] });
|
|
171
|
+
return _jsxs("div", { ...props, id: id, ref: root, className: cx("flood-calendar", className), role: "group", "aria-label": label, "data-disabled": effectiveDisabled || undefined, "data-readonly": readOnly || undefined, "data-view": view, "data-calendar-month": month, "data-calendar-direction": direction, onBlur: event => { onBlur?.(event); if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget))
|
|
172
|
+
pendingFocus.current = undefined; }, onPointerLeave: event => { onPointerLeave?.(event); if (event.defaultPrevented)
|
|
173
|
+
return; const focused = root.current?.ownerDocument.activeElement; onDayPreview?.(focused && root.current?.contains(focused) ? focused.dataset.calendarDate : undefined); }, onKeyDown: event => {
|
|
174
|
+
onKeyDown?.(event);
|
|
175
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing || event.keyCode === 229 || event.altKey || event.metaKey)
|
|
176
|
+
return;
|
|
177
|
+
if (event.key === "Escape" && view !== baseView) {
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
event.stopPropagation();
|
|
180
|
+
restoreDays();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const period = event.target.dataset.calendarPeriod;
|
|
184
|
+
if (period && view !== "days") {
|
|
185
|
+
if (effectiveDisabled || event.ctrlKey)
|
|
186
|
+
return;
|
|
187
|
+
const index = periodChoices.findIndex(choice => choice.value === period), rtl = direction === "rtl";
|
|
188
|
+
const step = event.key === "ArrowUp" ? -3 : event.key === "ArrowDown" ? 3 : event.key === "ArrowLeft" ? (rtl ? 1 : -1) : event.key === "ArrowRight" ? (rtl ? -1 : 1) : 0;
|
|
189
|
+
if (!step && event.key !== "Home" && event.key !== "End")
|
|
190
|
+
return;
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
let next = event.key === "Home" ? 0 : event.key === "End" ? periodChoices.length - 1 : index + step;
|
|
193
|
+
while (next >= 0 && next < periodChoices.length && periodChoices[next].disabled)
|
|
194
|
+
next += step || (event.key === "Home" ? 1 : -1);
|
|
195
|
+
if (next >= 0 && next < periodChoices.length)
|
|
196
|
+
root.current?.querySelector(`[data-calendar-period="${periodChoices[next].value}"]`)?.focus({ preventScroll: true });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const target = event.target, date = target.dataset.calendarDate;
|
|
200
|
+
if (!date || target.matches(":disabled") || effectiveDisabled)
|
|
201
|
+
return;
|
|
202
|
+
const rtl = root.current?.ownerDocument.defaultView?.getComputedStyle(root.current).direction === "rtl";
|
|
203
|
+
let candidate;
|
|
204
|
+
const ordinal = dateOrdinal(date);
|
|
205
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight")
|
|
206
|
+
candidate = dateFromOrdinal(Math.min(lastDateOrdinal, Math.max(0, ordinal + (event.key === "ArrowLeft" ? -1 : 1) * (rtl ? -1 : 1))));
|
|
207
|
+
else if (event.key === "ArrowUp" || event.key === "ArrowDown")
|
|
208
|
+
candidate = dateFromOrdinal(Math.min(lastDateOrdinal, Math.max(0, ordinal + (event.key === "ArrowUp" ? -7 : 7))));
|
|
209
|
+
else if (event.key === "Home" || event.key === "End") {
|
|
210
|
+
const offset = (dateWeekday(date) - weekStartsOn + 7) % 7;
|
|
211
|
+
candidate = dateFromOrdinal(Math.min(lastDateOrdinal, Math.max(0, ordinal + (event.key === "Home" ? -offset : 6 - offset))));
|
|
212
|
+
}
|
|
213
|
+
else if (event.key === "PageUp" || event.key === "PageDown")
|
|
214
|
+
candidate = moveDateMonth(date, (event.key === "PageUp" ? -1 : 1) * (event.shiftKey ? 12 : 1));
|
|
215
|
+
if (!candidate || event.ctrlKey)
|
|
216
|
+
return;
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
if (min && candidate < min)
|
|
219
|
+
candidate = min;
|
|
220
|
+
if (max && candidate > max)
|
|
221
|
+
candidate = max;
|
|
222
|
+
move(candidate);
|
|
223
|
+
}, children: [_jsx("span", { id: `${id}-label`, className: "flood-visually-hidden", children: label }), _jsx("input", { type: "hidden", "data-calendar-disabled-probe": true, disabled: disabled }), _jsxs("div", { className: "flood-calendar__header", children: [_jsx(VisuallyHidden, { id: `${id}-month`, "aria-live": "polite", "aria-atomic": "true", children: view === "years" ? `${yearStart}–${Math.min(9999, yearStart + 11)}` : view === "months" ? year : monthLabel }), showViewControls && granularity !== "year" ? _jsx(ButtonGroup, { label: `${viewNames.month} / ${viewNames.year}`, className: "flood-calendar__month", children: headingControls }) : _jsx("div", { className: "flood-calendar__month", children: headingControls }), _jsxs("div", { className: "flood-calendar__navigation", children: [_jsx(IconButton, { label: view === "days" ? previousMonthLabel : `${viewNames.previous} ${viewNames.year}`, variant: "ghost", size: "sm", disabled: view === "days" ? previousDisabled : periodPreviousDisabled, onClick: event => { pendingFocus.current = undefined; if (!event.currentTarget.matches(":disabled")) {
|
|
224
|
+
if (view === "days")
|
|
225
|
+
requestMonth(previous);
|
|
226
|
+
else
|
|
227
|
+
navigatePeriod(-1);
|
|
228
|
+
} }, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "m14 6-6 6 6 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx(IconButton, { label: view === "days" ? nextMonthLabel : `${viewNames.next} ${viewNames.year}`, variant: "ghost", size: "sm", disabled: view === "days" ? nextDisabled : periodNextDisabled, onClick: event => { pendingFocus.current = undefined; if (!event.currentTarget.matches(":disabled")) {
|
|
229
|
+
if (view === "days")
|
|
230
|
+
requestMonth(next);
|
|
231
|
+
else
|
|
232
|
+
navigatePeriod(1);
|
|
233
|
+
} }, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "m10 6 6 6-6 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] }), _jsx("div", { className: "flood-calendar__body", children: view === "days" ? _jsxs("div", { className: "flood-calendar__grid", role: "grid", "aria-labelledby": `${id}-label ${id}-month`, "aria-readonly": readOnly || undefined, "aria-multiselectable": multiple || undefined, children: [_jsx("div", { className: "flood-calendar__week", role: "row", children: Array.from({ length: 7 }, (_, index) => { const date = formattingDate(dateFromOrdinal(7 + ((weekStartsOn + index + 6) % 7))); return _jsx("span", { className: "flood-calendar__weekday", role: "columnheader", children: _jsx("abbr", { title: formatters.weekdayLong.format(date), children: titleCase(formatters.weekday.format(date), locale) }) }, index); }) }), Array.from({ length: 6 }, (_, week) => _jsx("div", { className: "flood-calendar__week", role: "row", children: dates.slice(week * 7, week * 7 + 7).map((date, index) => _createElement("div", { ...attributes(date), className: "flood-calendar__cell", role: "gridcell", key: date ?? `empty-${index}`, "aria-selected": date ? daySelected(date) : undefined, "aria-disabled": date ? blocked(date) || undefined : undefined }, date && _jsx("button", { type: "button", className: "flood-calendar__day", "data-calendar-date": date, "data-selected": daySelected(date) || undefined, "data-outside": date.slice(0, 7) !== month || undefined, "data-today": date === actualToday || undefined, ref: node => { if (node)
|
|
234
|
+
buttons.current.set(date, node);
|
|
235
|
+
else
|
|
236
|
+
buttons.current.delete(date); }, "data-flood-autofocus": autoFocus && date === roving || undefined, tabIndex: !effectiveDisabled && date === roving ? 0 : -1, disabled: disabled, "aria-disabled": blocked(date) || readOnly || undefined, "aria-label": formatters.day.format(formattingDate(date)), "aria-current": date === actualToday ? "date" : undefined, onFocus: () => { setFocused(date); if (!effectiveDisabled && !readOnly)
|
|
237
|
+
onDayPreview?.(date); }, onPointerEnter: event => { if (event.pointerType !== "touch" && !effectiveDisabled && !readOnly)
|
|
238
|
+
onDayPreview?.(date); }, onClick: event => { if (effectiveDisabled || readOnly || blocked(date) || event.currentTarget.matches(":disabled"))
|
|
239
|
+
return; move(date); onDayActivate?.(date); }, children: formatters.number.format(Number(date.slice(8))) }))) }, week))] })
|
|
240
|
+
: _jsx(CalendarPeriodGrid, { focusValue: view === "years" ? String(year).padStart(4, "0") : month, choices: periodChoices, label: view === "years" ? viewNames.year : `${viewNames.month}: ${year}`, onChoose: choosePeriod, autoFocus: focusView || autoFocus, disabled: effectiveDisabled, readOnly: readOnly && (granularity === "year" || granularity === "month" && view === "months") }, view) }), footer] });
|
|
241
|
+
});
|
|
242
|
+
export const DateCalendar = /* @__PURE__ */ forwardRef(function DateCalendar({ value, defaultValue = "", onValueChange, onDateSelect, granularity = "day", min, max, isDateDisabled, ...props }, ref) {
|
|
243
|
+
const valid = (date) => granularity === "day" ? !!parseDate(date) : granularity === "month" ? !!monthParts(date) : /^\d{4}$/.test(date) && Number(date) >= 1 && Number(date) <= 9999;
|
|
244
|
+
if (!["day", "month", "year"].includes(granularity))
|
|
245
|
+
throw new Error("Flood Calendar granularity must be day, month or year");
|
|
246
|
+
for (const date of [value, defaultValue, min, max])
|
|
247
|
+
if (date !== undefined && date !== "" && !valid(date))
|
|
248
|
+
throw new Error("Flood Calendar values and bounds must match granularity");
|
|
249
|
+
if (isDateDisabled && granularity !== "day")
|
|
250
|
+
throw new Error("Flood Calendar isDateDisabled applies only to day selection");
|
|
251
|
+
const [local, setLocal] = useState(defaultValue), selected = value ?? local;
|
|
252
|
+
const start = (date) => granularity === "day" ? date : granularity === "month" ? `${date}-01` : `${date}-01-01`;
|
|
253
|
+
const end = (date) => granularity === "day" ? date : granularity === "year" ? `${date}-12-31` : `${date}-${String(daysInMonth(Number(date.slice(0, 4)), Number(date.slice(5)))).padStart(2, "0")}`;
|
|
254
|
+
return _jsx(CalendarGrid, { ...props, ref: ref, granularity: granularity, min: min ? start(min) : undefined, max: max ? end(max) : undefined, isDateDisabled: isDateDisabled, selectedDate: selected ? start(selected) : "", onDayActivate: date => { const next = granularity === "day" ? date : granularity === "month" ? date.slice(0, 7) : date.slice(0, 4); if (selected !== next) {
|
|
255
|
+
if (value === undefined)
|
|
256
|
+
setLocal(next);
|
|
257
|
+
onValueChange?.(next);
|
|
258
|
+
} onDateSelect?.(next); } });
|
|
259
|
+
});
|
package/dist/checkbox.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef } from "react";
|
|
3
3
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
|
-
export const MixedCheckbox = forwardRef(function MixedCheckbox({ indeterminate, onChange, ...props }, ref) {
|
|
4
|
+
export const MixedCheckbox = /* @__PURE__ */ forwardRef(function MixedCheckbox({ indeterminate, onChange, ...props }, ref) {
|
|
5
5
|
const node = useRef(null), mixed = useRef(indeterminate);
|
|
6
6
|
useImperativeHandle(ref, () => node.current, []);
|
|
7
7
|
useBrowserLayoutEffect(() => { mixed.current = indeterminate; if (node.current)
|
package/dist/choice-group.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { forwardRef, useEffect, useLayoutEffect, useImperativeHandle, useRef, useState } from "react";
|
|
3
3
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
4
|
/** Private selection model; native inputs own keyboard, focus, forms and validity. */
|
|
5
|
-
export const ChoiceGroup = forwardRef(function ChoiceGroup({ kind, name, options, selected, initial, onSelectionChange, required, orientation, fieldsetComponent: Group, choiceComponent: Choice, legend, ...props }, forwardedRef) {
|
|
5
|
+
export const ChoiceGroup = /* @__PURE__ */ forwardRef(function ChoiceGroup({ kind, name, options, selected, initial, onSelectionChange, required, orientation, fieldsetComponent: Group, choiceComponent: Choice, legend, ...props }, forwardedRef) {
|
|
6
6
|
if (new Set(options.map(option => option.value)).size !== options.length)
|
|
7
7
|
throw new Error("Flood choice group option values must be unique");
|
|
8
8
|
const root = useRef(null);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CheckCardProps, type RadioCardProps } from "./index.js";
|
|
2
|
+
/** Cards retain native choice semantics; their content must be noninteractive. */
|
|
3
|
+
export declare const CheckableCard: import("react").ForwardRefExoticComponent<CheckCardProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
4
|
+
export declare const ExclusiveCard: import("react").ForwardRefExoticComponent<RadioCardProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId } from "react";
|
|
3
|
+
import { Checkbox, Radio, IconSlot } from "./index.js";
|
|
4
|
+
import { hasFieldContent, mergeDescriptions } from "./field-state.js";
|
|
5
|
+
/** Cards retain native choice semantics; their content must be noninteractive. */
|
|
6
|
+
export const CheckableCard = /* @__PURE__ */ forwardRef(function CheckableCard({ label, description, icon, metadata, density = "comfortable", className, readOnly, onClick, onChange, "aria-describedby": describedBy, "aria-labelledby": labelledBy, "aria-label": ariaLabel, ...props }, ref) {
|
|
7
|
+
const id = useId();
|
|
8
|
+
if (!label.trim())
|
|
9
|
+
throw new Error("Flood CheckCard requires a nonempty label");
|
|
10
|
+
return _jsx(Checkbox, { ...props, ref: ref, readOnly: readOnly, required: props.required && !readOnly, "aria-disabled": readOnly || props["aria-disabled"] || undefined, "aria-label": ariaLabel, "aria-labelledby": labelledBy ?? (ariaLabel ? undefined : `${id}-title`), "aria-describedby": mergeDescriptions(describedBy, hasFieldContent(description) && `${id}-description`, hasFieldContent(metadata) && `${id}-metadata`), className: ["flood-choice-card", `flood-choice-card--${density}`, className].filter(Boolean).join(" "), onClick: event => { onClick?.(event); if (readOnly)
|
|
11
|
+
event.preventDefault(); }, onChange: event => { if (!readOnly)
|
|
12
|
+
onChange?.(event); }, label: _jsxs("span", { className: "flood-choice-card__content", children: [hasFieldContent(icon) && _jsx("span", { className: "flood-choice-card__icon", "aria-hidden": "true", children: _jsx(IconSlot, { size: "md", children: icon }) }), _jsxs("span", { className: "flood-choice-card__copy", children: [_jsx("span", { id: `${id}-title`, className: "flood-choice-card__title", children: label }), hasFieldContent(description) && _jsx("span", { id: `${id}-description`, className: "flood-choice-card__description", children: description })] }), hasFieldContent(metadata) && _jsx("span", { id: `${id}-metadata`, className: "flood-choice-card__metadata", children: metadata })] }) });
|
|
13
|
+
});
|
|
14
|
+
export const ExclusiveCard = /* @__PURE__ */ forwardRef(function ExclusiveCard({ label, description, icon, metadata, density = "comfortable", className, readOnly, onClick, onChange, "aria-describedby": describedBy, "aria-labelledby": labelledBy, "aria-label": ariaLabel, ...props }, ref) {
|
|
15
|
+
const id = useId();
|
|
16
|
+
if (!label.trim())
|
|
17
|
+
throw new Error("Flood RadioCard requires a nonempty label");
|
|
18
|
+
return _jsx(Radio, { ...props, ref: ref, readOnly: readOnly, required: props.required && !readOnly, "aria-disabled": readOnly || props["aria-disabled"] || undefined, "aria-label": ariaLabel, "aria-labelledby": labelledBy ?? (ariaLabel ? undefined : `${id}-title`), "aria-describedby": mergeDescriptions(describedBy, hasFieldContent(description) && `${id}-description`, hasFieldContent(metadata) && `${id}-metadata`), className: ["flood-choice-card", `flood-choice-card--${density}`, className].filter(Boolean).join(" "), onClick: event => { onClick?.(event); if (readOnly)
|
|
19
|
+
event.preventDefault(); }, onChange: event => { if (!readOnly)
|
|
20
|
+
onChange?.(event); }, label: _jsxs("span", { className: "flood-choice-card__content", children: [hasFieldContent(icon) && _jsx("span", { className: "flood-choice-card__icon", "aria-hidden": "true", children: _jsx(IconSlot, { size: "md", children: icon }) }), _jsxs("span", { className: "flood-choice-card__copy", children: [_jsx("span", { id: `${id}-title`, className: "flood-choice-card__title", children: label }), hasFieldContent(description) && _jsx("span", { id: `${id}-description`, className: "flood-choice-card__description", children: description })] }), hasFieldContent(metadata) && _jsx("span", { id: `${id}-metadata`, className: "flood-choice-card__metadata", children: metadata })] }) });
|
|
21
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ColorPickerProps, type ColorSwatchProps } from "./index.js";
|
|
2
|
+
/** The first color contract deliberately accepts only opaque RGB hex values. */
|
|
3
|
+
export declare function normalizeHexColor(value: string): string | undefined;
|
|
4
|
+
export declare const ColorPreview: import("react").ForwardRefExoticComponent<ColorSwatchProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
5
|
+
export declare const ColorEditor: import("react").ForwardRefExoticComponent<ColorPickerProps & import("react").RefAttributes<HTMLInputElement>>;
|