@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.
Files changed (85) hide show
  1. package/README.md +45 -1
  2. package/dist/alert-dialog.js +1 -1
  3. package/dist/calendar-period-grid.d.ts +17 -0
  4. package/dist/calendar-period-grid.js +13 -0
  5. package/dist/calendar-value.d.ts +10 -0
  6. package/dist/calendar-value.js +48 -0
  7. package/dist/calendar.d.ts +21 -0
  8. package/dist/calendar.js +259 -0
  9. package/dist/checkbox.js +1 -1
  10. package/dist/choice-group.js +1 -1
  11. package/dist/collection-cards.d.ts +4 -0
  12. package/dist/collection-cards.js +21 -0
  13. package/dist/color-controls.d.ts +5 -0
  14. package/dist/color-controls.js +110 -0
  15. package/dist/combobox.js +22 -13
  16. package/dist/command-list.d.ts +3 -0
  17. package/dist/command-list.js +93 -0
  18. package/dist/content-elements.js +7 -7
  19. package/dist/context-menu.d.ts +2 -0
  20. package/dist/context-menu.js +76 -0
  21. package/dist/data-display.js +2 -2
  22. package/dist/data-table.d.ts +2 -0
  23. package/dist/data-table.js +58 -0
  24. package/dist/date-field.d.ts +2 -0
  25. package/dist/date-field.js +172 -0
  26. package/dist/date-picker.d.ts +3 -0
  27. package/dist/date-picker.js +108 -0
  28. package/dist/date-range-field.d.ts +3 -0
  29. package/dist/date-range-field.js +84 -0
  30. package/dist/date-range-picker.d.ts +2 -0
  31. package/dist/date-range-picker.js +81 -0
  32. package/dist/date-range-value.d.ts +8 -0
  33. package/dist/date-range-value.js +13 -0
  34. package/dist/date-time-picker.d.ts +3 -0
  35. package/dist/date-time-picker.js +31 -0
  36. package/dist/date-time-range-picker.d.ts +2 -0
  37. package/dist/date-time-range-picker.js +51 -0
  38. package/dist/date-value.d.ts +10 -0
  39. package/dist/date-value.js +31 -0
  40. package/dist/disclosure.js +1 -1
  41. package/dist/editable.d.ts +3 -0
  42. package/dist/editable.js +135 -0
  43. package/dist/error-summary.d.ts +3 -0
  44. package/dist/error-summary.js +39 -0
  45. package/dist/feedback-actions.js +2 -2
  46. package/dist/feedback.js +2 -2
  47. package/dist/file-input.js +1 -1
  48. package/dist/file-records.d.ts +5 -0
  49. package/dist/file-records.js +55 -0
  50. package/dist/form-controls.js +25 -13
  51. package/dist/foundations.css +401 -3
  52. package/dist/hover-card.d.ts +3 -0
  53. package/dist/hover-card.js +144 -0
  54. package/dist/index.d.ts +661 -2
  55. package/dist/index.js +190 -86
  56. package/dist/menu.d.ts +27 -1
  57. package/dist/menu.js +327 -89
  58. package/dist/navigation-layout.js +6 -6
  59. package/dist/navigation-tree.d.ts +3 -0
  60. package/dist/navigation-tree.js +98 -0
  61. package/dist/navigation.js +3 -3
  62. package/dist/numeric-input.js +1 -1
  63. package/dist/overlays.js +11 -10
  64. package/dist/range-calendar.d.ts +2 -0
  65. package/dist/range-calendar.js +35 -0
  66. package/dist/resizable-panels.d.ts +3 -0
  67. package/dist/resizable-panels.js +205 -0
  68. package/dist/scroll-area.js +1 -1
  69. package/dist/select.js +17 -6
  70. package/dist/slider.js +1 -1
  71. package/dist/tag-input.d.ts +3 -0
  72. package/dist/tag-input.js +181 -0
  73. package/dist/text-inputs.js +2 -2
  74. package/dist/time-field.d.ts +2 -0
  75. package/dist/time-field.js +176 -0
  76. package/dist/time-picker.d.ts +2 -0
  77. package/dist/time-picker.js +83 -0
  78. package/dist/time-value.d.ts +12 -0
  79. package/dist/time-value.js +36 -0
  80. package/dist/toolbar.js +1 -1
  81. package/dist/tree-select.d.ts +3 -0
  82. package/dist/tree-select.js +113 -0
  83. package/dist/tree.d.ts +3 -0
  84. package/dist/tree.js +206 -0
  85. package/package.json +2 -2
@@ -0,0 +1,144 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { IconButton } from "./index.js";
4
+ import { positionLayer, registerLayer, returnFocus, tabbables } from "./overlay-runtime.js";
5
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
+ const delay = (value, fallback) => Number.isFinite(value) ? Math.max(0, value) : fallback;
7
+ /** A focus-preserving preview; explicit activation pins it for pointer/touch use. */
8
+ export const HoverPreview = /* @__PURE__ */ forwardRef(function HoverPreview({ trigger, title, children, open: controlled, defaultOpen = false, onOpenChange, openDelay = 300, closeDelay = 180, side = "bottom", align = "start", disabled = false, closeLabel = "Close preview", id: explicitId, className, onPointerEnter, onPointerLeave, onFocus, onBlur, ...attributes }, forwardedRef) {
9
+ const [local, setLocal] = useState(defaultOpen);
10
+ const open = !disabled && (controlled ?? local);
11
+ const anchor = useRef(null), panel = useRef(null);
12
+ const generated = useId(), id = explicitId ?? `flood-hover-card-${generated}`;
13
+ const timer = useRef(undefined);
14
+ const hovered = useRef(false), overPanel = useRef(false), focused = useRef(false);
15
+ const pinned = useRef(defaultOpen), suppressed = useRef(false), focusRequested = useRef(false);
16
+ const current = useRef({ controlled, onOpenChange, open, disabled });
17
+ useBrowserLayoutEffect(() => { current.current = { controlled, onOpenChange, open, disabled }; });
18
+ const setAnchor = useCallback((element) => { anchor.current = element; }, []);
19
+ useImperativeHandle(forwardedRef, () => panel.current, []);
20
+ const clear = () => { clearTimeout(timer.current); timer.current = undefined; };
21
+ const request = (next) => {
22
+ clear();
23
+ if (next && (current.current.disabled || anchor.current?.matches(":disabled")))
24
+ return;
25
+ if (current.current.controlled === undefined)
26
+ setLocal(next);
27
+ if (current.current.open !== next)
28
+ current.current.onOpenChange?.(next);
29
+ };
30
+ const close = () => { pinned.current = false; suppressed.current = true; focusRequested.current = false; request(false); };
31
+ const latest = useRef({ request, close });
32
+ useBrowserLayoutEffect(() => { latest.current = { request, close }; });
33
+ const leave = () => {
34
+ clear();
35
+ if (hovered.current || overPanel.current || focused.current || pinned.current)
36
+ return;
37
+ suppressed.current = false;
38
+ timer.current = setTimeout(() => latest.current.request(false), delay(closeDelay, 180));
39
+ };
40
+ const enter = (immediate = false) => {
41
+ clear();
42
+ if (suppressed.current || disabled)
43
+ return;
44
+ if (immediate)
45
+ request(true);
46
+ else
47
+ timer.current = setTimeout(() => latest.current.request(true), delay(openDelay, 300));
48
+ };
49
+ useEffect(() => () => clearTimeout(timer.current), []);
50
+ useEffect(() => { if (disabled) {
51
+ clear();
52
+ pinned.current = false;
53
+ suppressed.current = false;
54
+ setLocal(false);
55
+ } }, [disabled]);
56
+ useBrowserLayoutEffect(() => {
57
+ if (!open || !panel.current)
58
+ return;
59
+ const element = panel.current, document = element.ownerDocument, view = document.defaultView;
60
+ let alive = true, frame = 0, unregister = () => { };
61
+ const position = () => {
62
+ if (!alive)
63
+ return;
64
+ if (!anchor.current?.isConnected) {
65
+ latest.current.close();
66
+ return;
67
+ }
68
+ positionLayer(anchor.current, element, side, align);
69
+ frame = view.requestAnimationFrame(position);
70
+ };
71
+ const start = () => {
72
+ if (!alive || !anchor.current?.isConnected)
73
+ return;
74
+ const parent = anchor.current.closest("dialog");
75
+ if (parent && !parent.open) {
76
+ frame = view.requestAnimationFrame(start);
77
+ return;
78
+ }
79
+ if (typeof element.showPopover !== "function")
80
+ throw new Error("Flood HoverCard requires the native Popover API");
81
+ element.style.visibility = "hidden";
82
+ element.showPopover();
83
+ unregister = registerLayer(document, { panel: element, anchor: () => anchor.current, close: () => latest.current.close() });
84
+ position();
85
+ if (focusRequested.current) {
86
+ focusRequested.current = false;
87
+ (tabbables(element)[0] ?? element).focus({ preventScroll: true });
88
+ }
89
+ };
90
+ const toggle = (event) => {
91
+ if (!alive || event.newState !== "closed" || element.matches(":popover-open") || !current.current.open)
92
+ return;
93
+ latest.current.close();
94
+ // A controlled owner may reject dismissal. Keep its rendered state authoritative.
95
+ queueMicrotask(() => { if (alive && current.current.open && element.isConnected && !element.matches(":popover-open")) {
96
+ element.showPopover();
97
+ if (anchor.current)
98
+ positionLayer(anchor.current, element, side, align);
99
+ } });
100
+ };
101
+ element.addEventListener("toggle", toggle);
102
+ start();
103
+ return () => {
104
+ alive = false;
105
+ view.cancelAnimationFrame(frame);
106
+ unregister();
107
+ element.removeEventListener("toggle", toggle);
108
+ returnFocus(element, anchor.current);
109
+ if (element.matches(":popover-open"))
110
+ element.hidePopover();
111
+ };
112
+ }, [open, side, align]);
113
+ const triggerProps = {
114
+ ref: setAnchor, type: "button", disabled, "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": id,
115
+ onPointerEnter: event => { if (event.pointerType === "touch")
116
+ return; hovered.current = true; enter(); },
117
+ onPointerLeave: () => { hovered.current = false; leave(); },
118
+ onFocus: () => { focused.current = true; enter(true); },
119
+ onBlur: event => { focused.current = Boolean(panel.current?.contains(event.relatedTarget)); leave(); },
120
+ onClick: () => {
121
+ if (pinned.current && open)
122
+ close();
123
+ else {
124
+ suppressed.current = false;
125
+ pinned.current = true;
126
+ request(true);
127
+ }
128
+ },
129
+ onKeyDown: event => {
130
+ if (event.defaultPrevented || event.key !== "ArrowDown" || disabled)
131
+ return;
132
+ event.preventDefault();
133
+ suppressed.current = false;
134
+ pinned.current = true;
135
+ if (open && panel.current)
136
+ (tabbables(panel.current)[0] ?? panel.current).focus({ preventScroll: true });
137
+ else {
138
+ focusRequested.current = true;
139
+ request(true);
140
+ }
141
+ },
142
+ };
143
+ return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsxs("div", { ...attributes, ref: panel, id: id, popover: "manual", role: "dialog", "aria-modal": "false", "aria-labelledby": `${id}-title`, tabIndex: -1, className: ["flood-overlay-panel", "flood-hover-card", className].filter(Boolean).join(" "), onPointerEnter: event => { onPointerEnter?.(event); overPanel.current = true; clear(); }, onPointerLeave: event => { onPointerLeave?.(event); overPanel.current = false; leave(); }, onFocus: event => { onFocus?.(event); focused.current = true; clear(); }, onBlur: event => { onBlur?.(event); const target = event.relatedTarget; focused.current = Boolean(panel.current?.contains(target) || anchor.current?.contains(target)); leave(); }, children: [_jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: title }), _jsx(IconButton, { label: closeLabel, size: "xs", variant: "ghost", className: "flood-overlay-close", onClick: close, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] }), open && _jsx("div", { className: "flood-hover-card__body", children: children })] })] });
144
+ });