@flood-md/react 0.2.11 → 0.2.12
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 +39 -7
- package/dist/app-shell.js +2 -2
- package/dist/collection-toolbar.js +3 -3
- package/dist/data-display.js +27 -3
- package/dist/data-table-columns.d.ts +25 -0
- package/dist/data-table-columns.js +271 -0
- package/dist/data-table-expansion.d.ts +21 -0
- package/dist/data-table-expansion.js +79 -0
- package/dist/data-table.js +100 -7
- package/dist/editable.js +57 -18
- package/dist/foundations.css +393 -31
- package/dist/index.d.ts +71 -3
- package/dist/index.js +11 -2
- package/dist/overlay-runtime.d.ts +4 -3
- package/dist/overlay-runtime.js +10 -8
- package/dist/overlays.d.ts +1 -0
- package/dist/overlays.js +2 -2
- package/dist/sparkline.d.ts +2 -0
- package/dist/sparkline.js +65 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface CollectionToolbarProps extends Omit<FieldsetProps, "children" |
|
|
|
82
82
|
summaryPrefix?: ReactNode;
|
|
83
83
|
/** At most two task-important quick filters; move additional properties into filterContent. */
|
|
84
84
|
quickFilters?: ReactNode;
|
|
85
|
+
/** Additional commands beside Filters in the same tool row. */
|
|
86
|
+
actions?: ReactNode;
|
|
85
87
|
filterContent?: ReactNode;
|
|
86
88
|
filtersLabel?: string;
|
|
87
89
|
filterTitle?: string;
|
|
@@ -277,10 +279,21 @@ export declare const ImagePreview: import("react").ForwardRefExoticComponent<Ima
|
|
|
277
279
|
export interface DataTableColumn {
|
|
278
280
|
id: string;
|
|
279
281
|
label: string;
|
|
282
|
+
/** Short unit shown alongside the label, such as USD or kg. */
|
|
283
|
+
unit?: string;
|
|
280
284
|
sortable?: boolean;
|
|
281
285
|
numeric?: boolean;
|
|
282
286
|
align?: "start" | "end";
|
|
283
287
|
width?: CSSProperties["width"];
|
|
288
|
+
/** Keep this column visible when the host or Columns menu hides other columns. */
|
|
289
|
+
hideable?: boolean;
|
|
290
|
+
/** Opt into pointer and keyboard width adjustments for this column. */
|
|
291
|
+
resizable?: boolean;
|
|
292
|
+
/** Interactive width bounds in CSS pixels. */
|
|
293
|
+
minWidth?: number;
|
|
294
|
+
maxWidth?: number;
|
|
295
|
+
/** Keep the column at a logical edge when the table scrolls horizontally. */
|
|
296
|
+
pinned?: "start" | "end";
|
|
284
297
|
}
|
|
285
298
|
export interface DataTableRow {
|
|
286
299
|
id: string;
|
|
@@ -288,6 +301,8 @@ export interface DataTableRow {
|
|
|
288
301
|
cells: Readonly<Record<string, ReactNode>>;
|
|
289
302
|
sortValues?: Readonly<Record<string, string | number | null>>;
|
|
290
303
|
selectionDisabled?: boolean;
|
|
304
|
+
/** Related detail rendered in a separate full-width row on disclosure. */
|
|
305
|
+
expandedContent?: ReactNode;
|
|
291
306
|
/** One named record-opening action. Other controls in the row remain independent. */
|
|
292
307
|
primaryAction?: {
|
|
293
308
|
column: string;
|
|
@@ -312,6 +327,13 @@ export interface DataTableLabels {
|
|
|
312
327
|
results?: (from: number, to: number, total: number) => string;
|
|
313
328
|
selection?: (selected: number, total: number) => string;
|
|
314
329
|
sort?: (label: string) => string;
|
|
330
|
+
columns?: string;
|
|
331
|
+
resetColumns?: string;
|
|
332
|
+
sortedColumn?: (label: string) => string;
|
|
333
|
+
resizeColumn?: (label: string) => string;
|
|
334
|
+
expandRow?: (label: string) => string;
|
|
335
|
+
collapseRow?: (label: string) => string;
|
|
336
|
+
rowDetails?: string;
|
|
315
337
|
pagination?: string;
|
|
316
338
|
previousPage?: string;
|
|
317
339
|
nextPage?: string;
|
|
@@ -319,17 +341,33 @@ export interface DataTableLabels {
|
|
|
319
341
|
}
|
|
320
342
|
export interface DataTableProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
321
343
|
/** Host-owned heading, search and filters aligned with the outer table cells. */
|
|
322
|
-
toolbar?: ReactNode
|
|
344
|
+
toolbar?: ReactNode | ((controls: {
|
|
345
|
+
columns: ReactNode;
|
|
346
|
+
}) => ReactNode);
|
|
323
347
|
inset?: "standard" | "roomy";
|
|
324
348
|
/** Ready with no rows only. Compose EmptyState/actions here; loading and error keep their owners. */
|
|
325
349
|
emptyContent?: ReactNode;
|
|
326
350
|
caption: string;
|
|
327
351
|
hideCaption?: boolean;
|
|
328
352
|
columns: readonly DataTableColumn[];
|
|
353
|
+
showColumnVisibility?: boolean;
|
|
354
|
+
hiddenColumnIds?: readonly string[];
|
|
355
|
+
/** Initial hidden columns and the layout restored by Reset columns. */
|
|
356
|
+
defaultHiddenColumnIds?: readonly string[];
|
|
357
|
+
onHiddenColumnIdsChange?: (ids: string[]) => void;
|
|
358
|
+
columnWidths?: Readonly<Record<string, number>>;
|
|
359
|
+
defaultColumnWidths?: Readonly<Record<string, number>>;
|
|
360
|
+
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
|
329
361
|
rows: readonly DataTableRow[];
|
|
362
|
+
expandedIds?: readonly string[];
|
|
363
|
+
defaultExpandedIds?: readonly string[];
|
|
364
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
330
365
|
density?: "compact" | "comfortable";
|
|
331
366
|
striped?: boolean;
|
|
332
367
|
minWidth?: CSSProperties["minWidth"];
|
|
368
|
+
/** Maximum height of the scroll viewport, including the header. */
|
|
369
|
+
maxViewportHeight?: CSSProperties["maxBlockSize"];
|
|
370
|
+
stickyHeader?: boolean;
|
|
333
371
|
sort?: DataTableSort | null;
|
|
334
372
|
defaultSort?: DataTableSort | null;
|
|
335
373
|
onSortChange?: (sort: DataTableSort | null) => void;
|
|
@@ -344,6 +382,8 @@ export interface DataTableProps extends Omit<HTMLAttributes<HTMLDivElement>, "ch
|
|
|
344
382
|
onPageChange?: (page: number) => void;
|
|
345
383
|
/** Hide the result range while preserving selection and pagination controls. */
|
|
346
384
|
showResults?: boolean;
|
|
385
|
+
/** Hide the visible selection count while retaining its accessible status. */
|
|
386
|
+
showSelection?: boolean;
|
|
347
387
|
/** Server mode renders rows as the supplied page, without local sorting/slicing. */
|
|
348
388
|
paginationMode?: "client" | "server";
|
|
349
389
|
totalRows?: number;
|
|
@@ -355,6 +395,20 @@ export interface DataTableProps extends Omit<HTMLAttributes<HTMLDivElement>, "ch
|
|
|
355
395
|
}
|
|
356
396
|
/** A client-side table composed from native table, checkbox and pagination. @since 0.2.8 */
|
|
357
397
|
export declare const DataTable: import("react").ForwardRefExoticComponent<DataTableProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
398
|
+
export interface SparklineProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label" | "aria-labelledby"> {
|
|
399
|
+
/** Equally spaced observations in order. Missing or nonfinite values break the line. */
|
|
400
|
+
values: readonly (number | null)[];
|
|
401
|
+
/** Localized text conveying the trend or the absence of data. */
|
|
402
|
+
label: string;
|
|
403
|
+
showLabel?: boolean;
|
|
404
|
+
/** Shared lower and upper bounds for comparisons. Values outside the bounds are clipped. */
|
|
405
|
+
domain?: readonly [number, number];
|
|
406
|
+
size?: "sm" | "md";
|
|
407
|
+
tone?: "neutral" | "accent" | "success" | "warning" | "error";
|
|
408
|
+
variant?: "line" | "area";
|
|
409
|
+
}
|
|
410
|
+
/** @since 0.2.11 */
|
|
411
|
+
export declare const Sparkline: import("react").ForwardRefExoticComponent<SparklineProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
358
412
|
export interface DataListProps extends Omit<HTMLAttributes<HTMLUListElement>, "role"> {
|
|
359
413
|
density?: "compact" | "comfortable";
|
|
360
414
|
dividers?: boolean;
|
|
@@ -898,6 +952,12 @@ export interface EditableProps extends Omit<HTMLAttributes<HTMLDivElement>, "def
|
|
|
898
952
|
onEditingChange?: (editing: boolean) => void;
|
|
899
953
|
onCommit?: (value: string) => void;
|
|
900
954
|
onCancel?: () => void;
|
|
955
|
+
/** Return a message to keep the draft open and prevent a commit. */
|
|
956
|
+
validate?: (value: string) => string | undefined;
|
|
957
|
+
inputMode?: InputHTMLAttributes<HTMLInputElement>["inputMode"];
|
|
958
|
+
previewVariant?: "subtle" | "ghost";
|
|
959
|
+
/** Render the editor over its trigger at this preferred width. */
|
|
960
|
+
editorOverlayWidth?: CSSProperties["inlineSize"];
|
|
901
961
|
blurAction?: "commit" | "cancel" | "none";
|
|
902
962
|
readOnly?: boolean;
|
|
903
963
|
disabled?: boolean;
|
|
@@ -970,6 +1030,9 @@ export interface TableProps extends Omit<TableHTMLAttributes<HTMLTableElement>,
|
|
|
970
1030
|
density?: DataDensity;
|
|
971
1031
|
striped?: boolean;
|
|
972
1032
|
scrollLabel?: string;
|
|
1033
|
+
/** Maximum height of the scroll viewport, including the header and caption. */
|
|
1034
|
+
maxViewportHeight?: CSSProperties["maxBlockSize"];
|
|
1035
|
+
stickyHeader?: boolean;
|
|
973
1036
|
/** Shared cell alignment. Middle keeps mixed record/action rows coherent; top is an explicit prose-table choice. */
|
|
974
1037
|
cellVerticalAlign?: "top" | "middle" | "bottom";
|
|
975
1038
|
}
|
|
@@ -1206,6 +1269,9 @@ export interface DrawerProps extends DialogProps {
|
|
|
1206
1269
|
footer?: ReactNode;
|
|
1207
1270
|
}
|
|
1208
1271
|
export declare const Drawer: import("react").ForwardRefExoticComponent<DrawerProps & import("react").RefAttributes<HTMLDialogElement>>;
|
|
1272
|
+
export type SheetProps = Omit<DrawerProps, "side">;
|
|
1273
|
+
/** A modal bottom sheet for a short task or a list of choices. @since 0.2.12 */
|
|
1274
|
+
export declare const Sheet: import("react").ForwardRefExoticComponent<SheetProps & import("react").RefAttributes<HTMLDialogElement>>;
|
|
1209
1275
|
export interface AlertDialogProps extends Omit<DialogProps, "children" | "role" | "description" | "closeLabel" | "closeOnOutsideClick" | "initialFocusRef"> {
|
|
1210
1276
|
description: string;
|
|
1211
1277
|
confirmLabel?: string;
|
|
@@ -1504,7 +1570,7 @@ export interface TextProps extends HTMLAttributes<HTMLElement> {
|
|
|
1504
1570
|
export declare const Text: import("react").ForwardRefExoticComponent<TextProps & import("react").RefAttributes<HTMLElement>>;
|
|
1505
1571
|
export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
|
|
1506
1572
|
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
1507
|
-
size?: "display" | "large" | "page" | "section";
|
|
1573
|
+
size?: "display" | "large" | "page" | "section" | "group";
|
|
1508
1574
|
}
|
|
1509
1575
|
export declare const Heading: import("react").ForwardRefExoticComponent<HeadingProps & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
1510
1576
|
export type Space = "0" | "1" | "2" | "3" | "4" | "6" | "8" | "12";
|
|
@@ -1748,7 +1814,7 @@ export interface SectionProps extends Omit<HTMLAttributes<HTMLElement>, "title">
|
|
|
1748
1814
|
description?: ReactNode;
|
|
1749
1815
|
actions?: ReactNode;
|
|
1750
1816
|
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
1751
|
-
headingSize?: "
|
|
1817
|
+
headingSize?: HeadingProps["size"];
|
|
1752
1818
|
}
|
|
1753
1819
|
export declare const Section: import("react").ForwardRefExoticComponent<SectionProps & import("react").RefAttributes<HTMLElement>>;
|
|
1754
1820
|
export interface PageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title" | "children"> {
|
|
@@ -1792,6 +1858,8 @@ export interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
1792
1858
|
mainRef?: Ref<HTMLElement>;
|
|
1793
1859
|
scrollLabel?: string;
|
|
1794
1860
|
scrollOrientation?: ScrollAreaProps["orientation"];
|
|
1861
|
+
/** Keep the content width stable with "none" when the layout already clears the overlay rails. */
|
|
1862
|
+
scrollContentInset?: ScrollAreaProps["contentInset"];
|
|
1795
1863
|
skipLabel?: string;
|
|
1796
1864
|
}
|
|
1797
1865
|
/** Composes application navigation and a main landmark with optional bounded scrolling. @since 0.2.11 */
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ import { ClockTimePicker } from "./time-picker.js";
|
|
|
51
51
|
import { CivilDateTimeRangePicker } from "./date-time-range-picker.js";
|
|
52
52
|
import { FileRecord, UploadRecord, ModalImagePreview, FileMetadata } from "./file-records.js";
|
|
53
53
|
import { ManagedDataTable } from "./data-table.js";
|
|
54
|
+
import { TrendPlot } from "./sparkline.js";
|
|
54
55
|
import { CheckableCard, ExclusiveCard } from "./collection-cards.js";
|
|
55
56
|
import { ColorPreview, ColorEditor } from "./color-controls.js";
|
|
56
57
|
import { HoverPreview } from "./hover-card.js";
|
|
@@ -96,6 +97,10 @@ export const UploadList = /* @__PURE__ */ forwardRef(function UploadList(props,
|
|
|
96
97
|
export const ImagePreview = /* @__PURE__ */ forwardRef(function ImagePreview(props, ref) { return _jsx(ModalImagePreview, { ...props, ref: ref }); });
|
|
97
98
|
/** A client-side table composed from native table, checkbox and pagination. @since 0.2.8 */
|
|
98
99
|
export const DataTable = /* @__PURE__ */ forwardRef(function DataTable(props, ref) { return _jsx(ManagedDataTable, { ...props, ref: ref }); });
|
|
100
|
+
/** @since 0.2.11 */
|
|
101
|
+
export const Sparkline = /* @__PURE__ */ forwardRef(function Sparkline({ showLabel = false, size = "md", tone = "accent", variant = "area", ...props }, ref) {
|
|
102
|
+
return _jsx(TrendPlot, { ...props, showLabel: showLabel, size: size, tone: tone, variant: variant, ref: ref });
|
|
103
|
+
});
|
|
99
104
|
/** A native list; links and actions stay independent. @since 0.2.8 */
|
|
100
105
|
export const DataList = /* @__PURE__ */ forwardRef(function DataList({ density = "comfortable", dividers = true, className, ...props }, ref) {
|
|
101
106
|
return _jsx("ul", { ...props, ref: ref, role: "list", "data-density": density, "data-dividers": dividers, className: classes("flood-data-list", className) });
|
|
@@ -184,8 +189,8 @@ export const DescriptionList = /* @__PURE__ */ forwardRef(function DescriptionLi
|
|
|
184
189
|
return _jsx(DescribedData, { ...props, ref: ref, items: items, layout: layout, density: density, dividers: dividers });
|
|
185
190
|
});
|
|
186
191
|
/** @since 0.2.4 */
|
|
187
|
-
export const Table = /* @__PURE__ */ forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, cellVerticalAlign = "middle", ...props }, ref) {
|
|
188
|
-
return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, cellVerticalAlign: cellVerticalAlign, children: children });
|
|
192
|
+
export const Table = /* @__PURE__ */ forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, cellVerticalAlign = "middle", stickyHeader = false, ...props }, ref) {
|
|
193
|
+
return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, cellVerticalAlign: cellVerticalAlign, stickyHeader: stickyHeader, children: children });
|
|
189
194
|
});
|
|
190
195
|
/** @since 0.2.4 */
|
|
191
196
|
export const Badge = /* @__PURE__ */ forwardRef(function Badge({ size = "md", tone = "neutral", variant = "subtle", className, ...props }, ref) {
|
|
@@ -242,6 +247,10 @@ export const CommandPalette = /* @__PURE__ */ forwardRef(function CommandPalette
|
|
|
242
247
|
export const Drawer = /* @__PURE__ */ forwardRef(function Drawer({ side = "end", size = "md", defaultOpen = false, closeLabel = "Close", closeOnOutsideClick = true, className, footer, ...props }, ref) {
|
|
243
248
|
return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick, className: classes("flood-drawer", className), "data-side": side, "data-size": size, scrollBody: true, footer: footer });
|
|
244
249
|
});
|
|
250
|
+
/** A modal bottom sheet for a short task or a list of choices. @since 0.2.12 */
|
|
251
|
+
export const Sheet = /* @__PURE__ */ forwardRef(function Sheet({ className, size = "md", ...props }, ref) {
|
|
252
|
+
return _jsx(ModalOverlay, { ...props, ref: ref, "data-side": "bottom", "data-size": size, className: classes("flood-drawer", "flood-sheet", className), scrollBody: true, bodyContentInset: "none" });
|
|
253
|
+
});
|
|
245
254
|
export const AlertDialog = /* @__PURE__ */ forwardRef(function AlertDialog({ defaultOpen = false, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, ...props }, ref) {
|
|
246
255
|
return _jsx(ConfirmationDialog, { ...props, ref: ref, defaultOpen: defaultOpen, confirmLabel: confirmLabel, cancelLabel: cancelLabel, confirmDisabled: confirmDisabled });
|
|
247
256
|
});
|
|
@@ -18,7 +18,7 @@ export declare function placeOverlay(anchor: Rect, size: {
|
|
|
18
18
|
top: number;
|
|
19
19
|
width: number;
|
|
20
20
|
height: number;
|
|
21
|
-
}, side: Side, align: Align, gap: number, rtl?: boolean): {
|
|
21
|
+
}, side: Side, align: Align, gap: number, rtl?: boolean, overlap?: boolean): {
|
|
22
22
|
left: number;
|
|
23
23
|
top: number;
|
|
24
24
|
side: Side;
|
|
@@ -29,13 +29,14 @@ type Layer = {
|
|
|
29
29
|
anchor: () => HTMLElement | null;
|
|
30
30
|
close: () => void;
|
|
31
31
|
outside?: () => void;
|
|
32
|
+
closeOnFocusOutside?: boolean;
|
|
32
33
|
modal?: boolean;
|
|
33
34
|
tooltip?: boolean;
|
|
34
35
|
};
|
|
35
36
|
export declare function registerLayer(document: Document, layer: Layer): () => void;
|
|
36
37
|
export declare function lockScroll(document: Document): () => void;
|
|
37
|
-
export declare function positionLayer(anchor: HTMLElement, panel: HTMLElement, side: Side, align: Align): void;
|
|
38
|
+
export declare function positionLayer(anchor: HTMLElement, panel: HTMLElement, side: Side, align: Align, overlap?: boolean): void;
|
|
38
39
|
export declare function returnFocus(panel: HTMLElement, anchor: HTMLElement | null): void;
|
|
39
40
|
/** Retain pixels for a CSS exit after the native layer has released focus. */
|
|
40
|
-
export declare function useLayerExitContent(panel: RefObject<HTMLElement | null>, open: boolean, enabled?: boolean): boolean;
|
|
41
|
+
export declare function useLayerExitContent(panel: RefObject<HTMLElement | null>, open: boolean, enabled?: boolean, transitionProperty?: string): boolean;
|
|
41
42
|
export {};
|
package/dist/overlay-runtime.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useState } from "react";
|
|
2
2
|
/** Pure collision handling in layout-viewport coordinates. */
|
|
3
|
-
export function placeOverlay(anchor, size, viewport, side, align, gap, rtl = false) {
|
|
3
|
+
export function placeOverlay(anchor, size, viewport, side, align, gap, rtl = false, overlap = false) {
|
|
4
4
|
const edge = { left: viewport.left + gap, right: viewport.left + viewport.width - gap, top: viewport.top + gap, bottom: viewport.top + viewport.height - gap };
|
|
5
5
|
const space = { top: anchor.top - edge.top, bottom: edge.bottom - anchor.bottom, left: anchor.left - edge.left, right: edge.right - anchor.right };
|
|
6
6
|
const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
7
7
|
const needed = (side === "top" || side === "bottom" ? size.height : size.width) + gap;
|
|
8
|
-
if (space[side] < needed && space[opposite[side]] > space[side])
|
|
8
|
+
if (!overlap && space[side] < needed && space[opposite[side]] > space[side])
|
|
9
9
|
side = opposite[side];
|
|
10
10
|
const horizontal = side === "top" || side === "bottom";
|
|
11
11
|
const resolved = horizontal && rtl ? (align === "start" ? "end" : align === "end" ? "start" : align) : align;
|
|
12
12
|
let left = horizontal ? (resolved === "start" ? anchor.left : resolved === "end" ? anchor.right - size.width : anchor.left + (anchor.width - size.width) / 2) : side === "left" ? anchor.left - size.width - gap : anchor.right + gap;
|
|
13
13
|
let top = horizontal ? (side === "top" ? anchor.top - size.height - gap : anchor.bottom + gap) : (resolved === "start" ? anchor.top : resolved === "end" ? anchor.bottom - size.height : anchor.top + (anchor.height - size.height) / 2);
|
|
14
|
+
if (overlap)
|
|
15
|
+
top = anchor.top;
|
|
14
16
|
left = Math.max(edge.left, Math.min(left, edge.right - size.width));
|
|
15
17
|
top = Math.max(edge.top, Math.min(top, edge.bottom - size.height));
|
|
16
18
|
return { left, top, side };
|
|
@@ -58,7 +60,7 @@ export function registerLayer(document, layer) {
|
|
|
58
60
|
(layer.outside ?? layer.close)();
|
|
59
61
|
};
|
|
60
62
|
const focus = (event) => {
|
|
61
|
-
if (top() !== layer || layer.modal)
|
|
63
|
+
if (top() !== layer || layer.modal || layer.closeOnFocusOutside === false)
|
|
62
64
|
return;
|
|
63
65
|
const target = event.target;
|
|
64
66
|
if (!layer.panel.contains(target) && !layer.anchor()?.contains(target))
|
|
@@ -109,7 +111,7 @@ export function lockScroll(document) {
|
|
|
109
111
|
locks.delete(document);
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
|
-
export function positionLayer(anchor, panel, side, align) {
|
|
114
|
+
export function positionLayer(anchor, panel, side, align, overlap = false) {
|
|
113
115
|
const view = panel.ownerDocument.defaultView;
|
|
114
116
|
const visual = view.visualViewport;
|
|
115
117
|
const viewport = { left: visual?.offsetLeft ?? 0, top: visual?.offsetTop ?? 0, width: visual?.width ?? view.innerWidth, height: visual?.height ?? view.innerHeight };
|
|
@@ -121,7 +123,7 @@ export function positionLayer(anchor, panel, side, align) {
|
|
|
121
123
|
panel.style.setProperty("--flood-overlay-anchor-width", anchorWidth);
|
|
122
124
|
if (panel.style.getPropertyValue("--flood-overlay-anchor-height") !== anchorHeight)
|
|
123
125
|
panel.style.setProperty("--flood-overlay-anchor-height", anchorHeight);
|
|
124
|
-
const position = placeOverlay(bounds, panel.getBoundingClientRect(), viewport, side, align, gap, getComputedStyle(anchor).direction === "rtl");
|
|
126
|
+
const position = placeOverlay(bounds, panel.getBoundingClientRect(), viewport, side, align, gap, getComputedStyle(anchor).direction === "rtl", overlap);
|
|
125
127
|
const left = `${position.left}px`, top = `${position.top}px`;
|
|
126
128
|
if (panel.style.left !== left)
|
|
127
129
|
panel.style.left = left;
|
|
@@ -136,7 +138,7 @@ export function returnFocus(panel, anchor) {
|
|
|
136
138
|
anchor.focus({ preventScroll: true });
|
|
137
139
|
}
|
|
138
140
|
/** Retain pixels for a CSS exit after the native layer has released focus. */
|
|
139
|
-
export function useLayerExitContent(panel, open, enabled = true) {
|
|
141
|
+
export function useLayerExitContent(panel, open, enabled = true, transitionProperty = "opacity") {
|
|
140
142
|
const [retained, setRetained] = useState(false);
|
|
141
143
|
useLayerLayoutEffect(() => {
|
|
142
144
|
const node = panel.current;
|
|
@@ -149,7 +151,7 @@ export function useLayerExitContent(panel, open, enabled = true) {
|
|
|
149
151
|
return;
|
|
150
152
|
}
|
|
151
153
|
node.inert = true;
|
|
152
|
-
const transitions = node.getAnimations().filter(animation => "transitionProperty" in animation && animation.transitionProperty ===
|
|
154
|
+
const transitions = node.getAnimations().filter(animation => "transitionProperty" in animation && animation.transitionProperty === transitionProperty);
|
|
153
155
|
let active = true;
|
|
154
156
|
if (!transitions.length)
|
|
155
157
|
setRetained(false);
|
|
@@ -159,7 +161,7 @@ export function useLayerExitContent(panel, open, enabled = true) {
|
|
|
159
161
|
setRetained(false);
|
|
160
162
|
});
|
|
161
163
|
return () => { active = false; node.inert = false; };
|
|
162
|
-
}, [panel, open, enabled]);
|
|
164
|
+
}, [panel, open, enabled, transitionProperty]);
|
|
163
165
|
return open || enabled && retained;
|
|
164
166
|
}
|
|
165
167
|
const useLayerLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
package/dist/overlays.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface ModalOverlayProps extends DialogProps {
|
|
|
7
7
|
showCloseButton?: boolean;
|
|
8
8
|
role?: "dialog" | "alertdialog";
|
|
9
9
|
scrollBody?: boolean;
|
|
10
|
+
bodyContentInset?: "none" | "scrollbar";
|
|
10
11
|
footer?: import("react").ReactNode;
|
|
11
12
|
}
|
|
12
13
|
export declare const ModalOverlay: import("react").ForwardRefExoticComponent<ModalOverlayProps & import("react").RefAttributes<HTMLDialogElement>>;
|
package/dist/overlays.js
CHANGED
|
@@ -126,7 +126,7 @@ export const AnchoredOverlay = /* @__PURE__ */ forwardRef(function AnchoredOverl
|
|
|
126
126
|
leave();
|
|
127
127
|
} }, children: kind === "tooltip" ? tip.content : _jsxs(_Fragment, { children: [pop.chrome === "minimal" ? _jsx(VisuallyHidden, { children: _jsx("h2", { id: `${id}-title`, children: pop.title }) }) : _jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: pop.title }), !titleOnly && _jsx(OverlayClose, { label: pop.closeLabel ?? "Close", onClick: close })] }), showContent && _jsx("div", { className: "flood-overlay-body", children: pop.children })] }) })] });
|
|
128
128
|
});
|
|
129
|
-
export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
|
|
129
|
+
export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, bodyContentInset, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
|
|
130
130
|
const [local, setLocal] = useState(defaultOpen), open = controlled ?? local;
|
|
131
131
|
const dialog = useRef(null), anchor = useRef(null), generated = useId(), id = explicitId ?? `flood-dialog-${generated}`;
|
|
132
132
|
const request = (next) => { if (controlled === undefined)
|
|
@@ -194,5 +194,5 @@ export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ s
|
|
|
194
194
|
event.preventDefault();
|
|
195
195
|
first.focus();
|
|
196
196
|
}
|
|
197
|
-
}, children: [_jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: title }), showCloseButton && _jsx(OverlayClose, { label: closeLabel, onClick: () => request(false) })] }), description && _jsx("p", { id: `${id}-description`, className: "flood-overlay-description", children: description }), open && (scrollBody ? _jsx("div", { className: "flood-drawer__body", children: _jsx(ScrollArea, { className: "flood-overlay-body", style: { height: "100%" }, children: children }) }) : _jsx("div", { className: "flood-overlay-body", children: children })), open && footer && _jsx("div", { className: "flood-drawer__footer", children: footer })] })] });
|
|
197
|
+
}, children: [_jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: title }), showCloseButton && _jsx(OverlayClose, { label: closeLabel, onClick: () => request(false) })] }), description && _jsx("p", { id: `${id}-description`, className: "flood-overlay-description", children: description }), open && (scrollBody ? _jsx("div", { className: "flood-drawer__body", children: _jsx(ScrollArea, { className: "flood-overlay-body", contentInset: bodyContentInset, style: { height: "100%" }, children: children }) }) : _jsx("div", { className: "flood-overlay-body", children: children })), open && footer && _jsx("div", { className: "flood-drawer__footer", children: footer })] })] });
|
|
198
198
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId } from "react";
|
|
3
|
+
import { Text } from "./index.js";
|
|
4
|
+
const plotWidth = 96;
|
|
5
|
+
const plotHeight = 32;
|
|
6
|
+
const plotInset = 3;
|
|
7
|
+
function isObservation(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
9
|
+
}
|
|
10
|
+
function getPlot(values, domain, area) {
|
|
11
|
+
let observedMin = Infinity;
|
|
12
|
+
let observedMax = -Infinity;
|
|
13
|
+
for (const value of values) {
|
|
14
|
+
if (isObservation(value)) {
|
|
15
|
+
observedMin = Math.min(observedMin, value);
|
|
16
|
+
observedMax = Math.max(observedMax, value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (observedMin === Infinity)
|
|
20
|
+
return null;
|
|
21
|
+
const [min, max] = domain && Number.isFinite(domain[0]) && Number.isFinite(domain[1]) && domain[0] < domain[1]
|
|
22
|
+
? domain
|
|
23
|
+
: [observedMin, observedMax];
|
|
24
|
+
const span = max - min;
|
|
25
|
+
const commands = [];
|
|
26
|
+
const areas = [];
|
|
27
|
+
const markers = [];
|
|
28
|
+
let lastPoint;
|
|
29
|
+
let segmentStart = 0;
|
|
30
|
+
let segmentStartX = "";
|
|
31
|
+
for (const [index, value] of values.entries()) {
|
|
32
|
+
if (!isObservation(value))
|
|
33
|
+
continue;
|
|
34
|
+
const clamped = Math.min(max, Math.max(min, value));
|
|
35
|
+
// Halve the operands before subtracting if the finite bounds span more than Number.MAX_VALUE.
|
|
36
|
+
const fraction = span === 0 ? 0.5 : Number.isFinite(span)
|
|
37
|
+
? (clamped - min) / span
|
|
38
|
+
: (clamped / 2 - min / 2) / (max / 2 - min / 2);
|
|
39
|
+
const x = values.length === 1 ? plotWidth / 2 : plotInset + index * (plotWidth - 2 * plotInset) / (values.length - 1);
|
|
40
|
+
const y = plotHeight - plotInset - fraction * (plotHeight - 2 * plotInset);
|
|
41
|
+
const continuesLine = isObservation(values[index - 1]);
|
|
42
|
+
const plotX = x.toFixed(2);
|
|
43
|
+
if (!continuesLine) {
|
|
44
|
+
segmentStart = commands.length;
|
|
45
|
+
segmentStartX = plotX;
|
|
46
|
+
}
|
|
47
|
+
commands.push(`${continuesLine ? "L" : "M"}${plotX} ${y.toFixed(2)}`);
|
|
48
|
+
lastPoint = { x, y, index };
|
|
49
|
+
if (!isObservation(values[index + 1])) {
|
|
50
|
+
if (!continuesLine)
|
|
51
|
+
markers.push(lastPoint);
|
|
52
|
+
else if (area)
|
|
53
|
+
areas.push(`${commands.slice(segmentStart).join(" ")} L${plotX} ${plotHeight} H${segmentStartX} Z`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (lastPoint && markers.at(-1) !== lastPoint)
|
|
57
|
+
markers.push(lastPoint);
|
|
58
|
+
return { path: commands.join(" "), area: areas.join(" "), markers, lastPoint };
|
|
59
|
+
}
|
|
60
|
+
const markerPath = ({ x, y }) => `M${x.toFixed(2)} ${y.toFixed(2)}h0`;
|
|
61
|
+
export const TrendPlot = /* @__PURE__ */ forwardRef(function TrendPlot({ values, domain, label, showLabel, size, tone, variant, className, ...props }, ref) {
|
|
62
|
+
const gradientId = useId();
|
|
63
|
+
const plot = getPlot(values, domain, variant === "area");
|
|
64
|
+
return (_jsxs("div", { ...props, ref: ref, className: ["flood-sparkline", className].filter(Boolean).join(" "), "data-size": size, "data-tone": tone, role: "img", "aria-label": label, children: [plot ? (_jsxs("svg", { className: "flood-sparkline__plot", viewBox: `0 0 ${plotWidth} ${plotHeight}`, preserveAspectRatio: "none", "aria-hidden": "true", focusable: "false", children: [plot.area && _jsxs(_Fragment, { children: [_jsx("defs", { children: _jsxs("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1", children: [_jsx("stop", { stopColor: "currentColor", stopOpacity: ".42" }), _jsx("stop", { offset: "100%", stopColor: "currentColor", stopOpacity: "0" })] }) }), _jsx("path", { className: "flood-sparkline__area", d: plot.area, fill: `url(#${gradientId})` })] }), _jsx("path", { className: "flood-sparkline__line", d: plot.path }), plot.lastPoint && _jsx("path", { className: "flood-sparkline__halo", d: markerPath(plot.lastPoint) }), plot.markers.map(point => (_jsx("path", { className: "flood-sparkline__point", d: markerPath(point), "data-latest": point === plot.lastPoint || undefined }, point.index)))] })) : (_jsx(Text, { className: "flood-sparkline__empty", size: "helper", tone: "subtle", "aria-hidden": "true", children: "\u2014" })), showLabel && _jsx(Text, { className: "flood-sparkline__label", size: "helper", tone: "subtle", "aria-hidden": "true", children: label })] }));
|
|
65
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Original Flood components for React and Next.js.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@flood-md/tokens": "0.2.
|
|
33
|
+
"@flood-md/tokens": "0.2.12"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|