@fabio.caffarello/react-design-system 3.13.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/granular/ui/components/Autocomplete/Autocomplete.js +116 -88
- package/dist/granular/ui/components/Autocomplete/Autocomplete.js.map +1 -1
- package/dist/granular/ui/components/Autocomplete/AutocompleteList.js +57 -47
- package/dist/granular/ui/components/Autocomplete/AutocompleteList.js.map +1 -1
- package/dist/granular/ui/components/Autocomplete/AutocompleteOption.js +21 -20
- package/dist/granular/ui/components/Autocomplete/AutocompleteOption.js.map +1 -1
- package/dist/granular/ui/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/granular/ui/components/ColorPicker/ColorPicker.js.map +1 -1
- package/dist/granular/ui/components/CommandPalette/CommandPalette.js +187 -149
- package/dist/granular/ui/components/CommandPalette/CommandPalette.js.map +1 -1
- package/dist/granular/ui/components/DataGrid/DataGrid.js +92 -92
- package/dist/granular/ui/components/DataGrid/DataGrid.js.map +1 -1
- package/dist/granular/ui/components/DatePicker/DatePickerCalendar.js +154 -139
- package/dist/granular/ui/components/DatePicker/DatePickerCalendar.js.map +1 -1
- package/dist/granular/ui/components/Dialog/AlertDialog.js +73 -40
- package/dist/granular/ui/components/Dialog/AlertDialog.js.map +1 -1
- package/dist/granular/ui/components/Dialog/DialogContent.js +54 -48
- package/dist/granular/ui/components/Dialog/DialogContent.js.map +1 -1
- package/dist/granular/ui/components/Dialog/DialogDescription.js +31 -31
- package/dist/granular/ui/components/Dialog/DialogDescription.js.map +1 -1
- package/dist/granular/ui/components/Dialog/DialogTitle.js +30 -30
- package/dist/granular/ui/components/Dialog/DialogTitle.js.map +1 -1
- package/dist/granular/ui/components/Drawer/Drawer.js.map +1 -1
- package/dist/granular/ui/components/Dropdown/Dropdown.js.map +1 -1
- package/dist/granular/ui/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/granular/ui/components/FileUpload/FileUpload.js.map +1 -1
- package/dist/granular/ui/components/Form/Form.js +38 -37
- package/dist/granular/ui/components/Form/Form.js.map +1 -1
- package/dist/granular/ui/components/Form/FormField.js +28 -26
- package/dist/granular/ui/components/Form/FormField.js.map +1 -1
- package/dist/granular/ui/components/Header/Header.js.map +1 -1
- package/dist/granular/ui/components/Header/components/HeaderActions.js.map +1 -1
- package/dist/granular/ui/components/Header/components/HeaderHamburger.js.map +1 -1
- package/dist/granular/ui/components/Header/components/HeaderLogo.js.map +1 -1
- package/dist/granular/ui/components/Header/components/HeaderMobileMenu.js.map +1 -1
- package/dist/granular/ui/components/Header/components/HeaderNavigation.js.map +1 -1
- package/dist/granular/ui/components/Header/contexts/HeaderContext.js.map +1 -1
- package/dist/granular/ui/components/Menu/Menu.js.map +1 -1
- package/dist/granular/ui/components/Modal/Modal.js +98 -86
- package/dist/granular/ui/components/Modal/Modal.js.map +1 -1
- package/dist/granular/ui/components/MultiSelect/MultiSelect.js +122 -106
- package/dist/granular/ui/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/granular/ui/components/Navigation/Navigation.js.map +1 -1
- package/dist/granular/ui/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/granular/ui/components/Pagination/Pagination.js.map +1 -1
- package/dist/granular/ui/components/Popover/Popover.js.map +1 -1
- package/dist/granular/ui/components/Rating/Rating.js.map +1 -1
- package/dist/granular/ui/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/granular/ui/components/SideNavbar/components/Navbar/NavbarGroup.js +82 -64
- package/dist/granular/ui/components/SideNavbar/components/Navbar/NavbarGroup.js.map +1 -1
- package/dist/granular/ui/components/SideNavbar/components/Navbar/NavbarItem.js +30 -29
- package/dist/granular/ui/components/SideNavbar/components/Navbar/NavbarItem.js.map +1 -1
- package/dist/granular/ui/components/SideNavbar/components/SideNavbarResizeHandle.js +37 -35
- package/dist/granular/ui/components/SideNavbar/components/SideNavbarResizeHandle.js.map +1 -1
- package/dist/granular/ui/components/SideNavbar/providers/SideNavbarStateProvider.js +57 -57
- package/dist/granular/ui/components/SideNavbar/providers/SideNavbarStateProvider.js.map +1 -1
- package/dist/granular/ui/components/Stepper/Stepper.js +102 -94
- package/dist/granular/ui/components/Stepper/Stepper.js.map +1 -1
- package/dist/granular/ui/components/Table/Table.js +41 -35
- package/dist/granular/ui/components/Table/Table.js.map +1 -1
- package/dist/granular/ui/components/Table/TableActions/TableActions.js.map +1 -1
- package/dist/granular/ui/components/Table/TableFilters/TableFilters.js +49 -46
- package/dist/granular/ui/components/Table/TableFilters/TableFilters.js.map +1 -1
- package/dist/granular/ui/components/Table/TablePagination/TablePagination.js.map +1 -1
- package/dist/granular/ui/components/Table/TableProvider.js +82 -80
- package/dist/granular/ui/components/Table/TableProvider.js.map +1 -1
- package/dist/granular/ui/components/Table/TableRow.js +57 -53
- package/dist/granular/ui/components/Table/TableRow.js.map +1 -1
- package/dist/granular/ui/components/Table/useColumnResizing.js +53 -53
- package/dist/granular/ui/components/Table/useColumnResizing.js.map +1 -1
- package/dist/granular/ui/components/TimePicker/TimePicker.js +149 -103
- package/dist/granular/ui/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/granular/ui/components/Timeline/Timeline.js.map +1 -1
- package/dist/granular/ui/hooks/useFocusRestore.js +14 -15
- package/dist/granular/ui/hooks/useFocusRestore.js.map +1 -1
- package/dist/granular/ui/primitives/Badge/Badge.js.map +1 -1
- package/dist/granular/ui/primitives/Button/Button.js +86 -104
- package/dist/granular/ui/primitives/Button/Button.js.map +1 -1
- package/dist/granular/ui/primitives/Checkbox/Checkbox.js.map +1 -1
- package/dist/granular/ui/primitives/Chip/Chip.js +91 -71
- package/dist/granular/ui/primitives/Chip/Chip.js.map +1 -1
- package/dist/granular/ui/primitives/ErrorMessage/ErrorMessage.js.map +1 -1
- package/dist/granular/ui/primitives/Input/Input.js.map +1 -1
- package/dist/granular/ui/primitives/Label/Label.js.map +1 -1
- package/dist/granular/ui/primitives/NavLink/NavLink.js.map +1 -1
- package/dist/granular/ui/primitives/Radio/Radio.js.map +1 -1
- package/dist/granular/ui/primitives/Select/Select.js.map +1 -1
- package/dist/granular/ui/primitives/Separator/Separator.js.map +1 -1
- package/dist/granular/ui/primitives/Skeleton/Skeleton.js.map +1 -1
- package/dist/granular/ui/primitives/Slider/Slider.js.map +1 -1
- package/dist/granular/ui/primitives/Spinner/Spinner.js.map +1 -1
- package/dist/granular/ui/primitives/Switch/Switch.js.map +1 -1
- package/dist/granular/ui/primitives/Tooltip/Tooltip.js.map +1 -1
- package/dist/granular/ui/providers/DialogContext.js.map +1 -1
- package/dist/granular/ui/providers/DialogProvider.js +24 -20
- package/dist/granular/ui/providers/DialogProvider.js.map +1 -1
- package/dist/index.cjs +144 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5896 -5609
- package/dist/index.js.map +1 -1
- package/dist/react-design-system.css +1 -1
- package/dist/server/index.cjs +13 -13
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +1050 -789
- package/dist/server/index.js.map +1 -1
- package/dist/ui/components/Autocomplete/Autocomplete.d.ts +21 -0
- package/dist/ui/components/Autocomplete/AutocompleteList.d.ts +4 -0
- package/dist/ui/components/Autocomplete/AutocompleteOption.d.ts +8 -0
- package/dist/ui/components/Breadcrumb/Breadcrumb.d.ts +0 -1
- package/dist/ui/components/ColorPicker/ColorPicker.d.ts +0 -1
- package/dist/ui/components/CommandPalette/CommandPalette.d.ts +0 -1
- package/dist/ui/components/DataGrid/DataGrid.d.ts +0 -1
- package/dist/ui/components/Dialog/DialogContent.d.ts +20 -1
- package/dist/ui/components/Drawer/Drawer.d.ts +0 -1
- package/dist/ui/components/Dropdown/Dropdown.d.ts +0 -1
- package/dist/ui/components/EmptyState/EmptyState.d.ts +0 -1
- package/dist/ui/components/FileUpload/FileUpload.d.ts +0 -1
- package/dist/ui/components/Form/FormField.d.ts +7 -0
- package/dist/ui/components/Header/Header.d.ts +1 -1
- package/dist/ui/components/Header/components/HeaderActions.d.ts +1 -1
- package/dist/ui/components/Header/components/HeaderHamburger.d.ts +1 -1
- package/dist/ui/components/Header/components/HeaderLogo.d.ts +1 -1
- package/dist/ui/components/Header/components/HeaderMobileMenu.d.ts +1 -1
- package/dist/ui/components/Header/components/HeaderNavigation.d.ts +1 -1
- package/dist/ui/components/Header/contexts/HeaderContext.d.ts +1 -1
- package/dist/ui/components/Menu/Menu.d.ts +0 -1
- package/dist/ui/components/Modal/Modal.d.ts +1 -2
- package/dist/ui/components/Navigation/Navigation.d.ts +1 -1
- package/dist/ui/components/PageHeader/PageHeader.d.ts +1 -1
- package/dist/ui/components/Pagination/Pagination.d.ts +0 -1
- package/dist/ui/components/Popover/Popover.d.ts +0 -1
- package/dist/ui/components/Rating/Rating.d.ts +0 -1
- package/dist/ui/components/SearchInput/SearchInput.d.ts +0 -1
- package/dist/ui/components/Stepper/Stepper.d.ts +0 -1
- package/dist/ui/components/Table/TableActions/TableActions.d.ts +0 -1
- package/dist/ui/components/Table/TableFilters/TableFilters.d.ts +0 -1
- package/dist/ui/components/Table/TablePagination/TablePagination.d.ts +0 -1
- package/dist/ui/components/TimePicker/TimePicker.d.ts +0 -1
- package/dist/ui/components/Timeline/Timeline.d.ts +0 -1
- package/dist/ui/primitives/Checkbox/Checkbox.d.ts +0 -1
- package/dist/ui/primitives/Chip/Chip.d.ts +21 -0
- package/dist/ui/primitives/ErrorMessage/ErrorMessage.d.ts +0 -1
- package/dist/ui/primitives/Input/Input.d.ts +0 -1
- package/dist/ui/primitives/Label/Label.d.ts +0 -1
- package/dist/ui/primitives/NavLink/NavLink.d.ts +1 -1
- package/dist/ui/primitives/Radio/Radio.d.ts +0 -1
- package/dist/ui/primitives/Select/Select.d.ts +0 -1
- package/dist/ui/primitives/Skeleton/Skeleton.d.ts +0 -1
- package/dist/ui/primitives/Slider/Slider.d.ts +0 -1
- package/dist/ui/primitives/Switch/Switch.d.ts +0 -1
- package/dist/ui/primitives/Tooltip/Tooltip.d.ts +0 -1
- package/dist/ui/providers/DialogContext.d.ts +8 -0
- package/dist/ui/server.d.ts +2 -0
- package/package.json +7 -7
|
@@ -1,133 +1,161 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as
|
|
3
|
-
import { forwardRef as
|
|
4
|
-
import
|
|
5
|
-
import { Loader2 as
|
|
6
|
-
import
|
|
7
|
-
|
|
2
|
+
import { jsxs as re, jsx as m } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as ne, useId as oe, useState as y, useRef as g, useEffect as p } from "react";
|
|
4
|
+
import se from "../../primitives/Input/Input.js";
|
|
5
|
+
import { Loader2 as ie, ChevronDown as le } from "lucide-react";
|
|
6
|
+
import ae from "./AutocompleteList.js";
|
|
7
|
+
import { mergeRefs as ce } from "../../utils/mergeRefs.js";
|
|
8
|
+
const ue = ne(
|
|
8
9
|
function({
|
|
9
|
-
options:
|
|
10
|
-
value:
|
|
11
|
-
defaultValue:
|
|
12
|
-
onChange:
|
|
13
|
-
onSelect:
|
|
14
|
-
placeholder:
|
|
15
|
-
loading:
|
|
16
|
-
disabled:
|
|
17
|
-
emptyMessage:
|
|
18
|
-
debounceMs:
|
|
19
|
-
filterOptions:
|
|
20
|
-
className:
|
|
21
|
-
inputClassName:
|
|
22
|
-
size:
|
|
23
|
-
label:
|
|
24
|
-
"aria-label":
|
|
25
|
-
"aria-labelledby":
|
|
26
|
-
id:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
10
|
+
options: c,
|
|
11
|
+
value: V,
|
|
12
|
+
defaultValue: F,
|
|
13
|
+
onChange: i,
|
|
14
|
+
onSelect: k,
|
|
15
|
+
placeholder: G = "Type to search...",
|
|
16
|
+
loading: D = !1,
|
|
17
|
+
disabled: J = !1,
|
|
18
|
+
emptyMessage: j = "No options found",
|
|
19
|
+
debounceMs: Q = 300,
|
|
20
|
+
filterOptions: H,
|
|
21
|
+
className: W = "",
|
|
22
|
+
inputClassName: X = "",
|
|
23
|
+
size: Y = "md",
|
|
24
|
+
label: A,
|
|
25
|
+
"aria-label": O,
|
|
26
|
+
"aria-labelledby": h,
|
|
27
|
+
id: R,
|
|
28
|
+
name: K,
|
|
29
|
+
form: Z
|
|
30
|
+
}, _) {
|
|
31
|
+
const M = oe(), E = R != null ? R : M, N = `${E}-listbox`, [B, L] = y(
|
|
32
|
+
typeof F == "string" ? F : ""
|
|
33
|
+
), [u, l] = y(!1), [r, a] = y(-1), [v, f] = y(""), w = g(null), S = g(null), b = g(null), d = g(null), T = V !== void 0, s = T ? V : B, n = v.trim() ? H ? H(c, v) : c.filter(
|
|
34
|
+
(e) => e.label.toLowerCase().includes(v.toLowerCase())
|
|
35
|
+
) : c, C = n.length > 0, P = (e) => {
|
|
33
36
|
const t = e.target.value;
|
|
34
|
-
|
|
35
|
-
l(!0),
|
|
36
|
-
},
|
|
37
|
-
},
|
|
37
|
+
f(t), T || L(t), i == null || i(t), d.current && clearTimeout(d.current), d.current = setTimeout(() => {
|
|
38
|
+
l(!0), a(-1);
|
|
39
|
+
}, Q);
|
|
40
|
+
}, q = (e) => {
|
|
38
41
|
var t;
|
|
39
|
-
e.disabled || (
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
+
e.disabled || (T || L(e.value), f(e.label), l(!1), a(-1), i == null || i(e.value), k == null || k(e), (t = S.current) == null || t.focus());
|
|
43
|
+
}, U = (e, t) => {
|
|
44
|
+
const o = n.length;
|
|
45
|
+
if (o === 0) return -1;
|
|
46
|
+
const x = e < 0 ? t === 1 ? -1 : 0 : e;
|
|
47
|
+
for (let $ = 1; $ <= o; $++) {
|
|
48
|
+
const z = ((x + t * $) % o + o) % o;
|
|
49
|
+
if (!n[z].disabled) return z;
|
|
50
|
+
}
|
|
51
|
+
return e;
|
|
52
|
+
}, ee = (e) => {
|
|
53
|
+
if (!u || n.length === 0) {
|
|
42
54
|
(e.key === "ArrowDown" || e.key === "Enter") && l(!0);
|
|
43
55
|
return;
|
|
44
56
|
}
|
|
45
57
|
switch (e.key) {
|
|
46
58
|
case "ArrowDown":
|
|
47
|
-
e.preventDefault(),
|
|
48
|
-
(t) => t < n.length - 1 ? t + 1 : 0
|
|
49
|
-
);
|
|
59
|
+
e.preventDefault(), a((t) => U(t, 1));
|
|
50
60
|
break;
|
|
51
61
|
case "ArrowUp":
|
|
52
|
-
e.preventDefault(),
|
|
53
|
-
(t) => t > 0 ? t - 1 : n.length - 1
|
|
54
|
-
);
|
|
62
|
+
e.preventDefault(), a((t) => U(t, -1));
|
|
55
63
|
break;
|
|
56
64
|
case "Enter":
|
|
57
|
-
e.preventDefault(), r >= 0 && r < n.length &&
|
|
65
|
+
e.preventDefault(), r >= 0 && r < n.length && q(n[r]);
|
|
58
66
|
break;
|
|
59
67
|
case "Escape":
|
|
60
|
-
e.preventDefault(), l(!1),
|
|
68
|
+
e.preventDefault(), l(!1), a(-1);
|
|
61
69
|
break;
|
|
62
70
|
}
|
|
63
71
|
};
|
|
64
|
-
|
|
65
|
-
if (!
|
|
72
|
+
p(() => {
|
|
73
|
+
if (!u) return;
|
|
66
74
|
const e = (t) => {
|
|
67
|
-
|
|
75
|
+
var x;
|
|
76
|
+
const o = t.target;
|
|
77
|
+
w.current && !w.current.contains(o) && !((x = b.current) != null && x.contains(o)) && (l(!1), a(-1));
|
|
68
78
|
};
|
|
69
79
|
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
70
|
-
}, [
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
}, [u]), p(() => () => {
|
|
81
|
+
d.current && clearTimeout(d.current);
|
|
82
|
+
}, []), p(() => {
|
|
83
|
+
if (r >= 0 && b.current) {
|
|
84
|
+
const e = b.current.querySelectorAll('[role="option"]');
|
|
73
85
|
e[r] && typeof e[r].scrollIntoView == "function" && e[r].scrollIntoView({
|
|
74
86
|
block: "nearest",
|
|
75
87
|
behavior: "smooth"
|
|
76
88
|
});
|
|
77
89
|
}
|
|
78
|
-
}, [r]),
|
|
79
|
-
if (
|
|
80
|
-
const e =
|
|
81
|
-
|
|
90
|
+
}, [r]), p(() => {
|
|
91
|
+
if (s) {
|
|
92
|
+
const e = c.find((t) => t.value === s);
|
|
93
|
+
f(e ? e.label : s);
|
|
82
94
|
} else
|
|
83
|
-
|
|
84
|
-
}, [
|
|
85
|
-
}, [
|
|
86
|
-
const
|
|
87
|
-
return /* @__PURE__ */
|
|
88
|
-
/* @__PURE__ */
|
|
89
|
-
|
|
95
|
+
f("");
|
|
96
|
+
}, [s, c]), p(() => {
|
|
97
|
+
}, [A, O, h, E]);
|
|
98
|
+
const I = u && (C || D || !!j), te = I && r >= 0 && r < n.length ? `${N}-option-${r}` : void 0;
|
|
99
|
+
return /* @__PURE__ */ re("div", { ref: w, className: `relative ${W}`, children: [
|
|
100
|
+
/* @__PURE__ */ m(
|
|
101
|
+
se,
|
|
90
102
|
{
|
|
91
|
-
ref:
|
|
92
|
-
id:
|
|
93
|
-
label:
|
|
94
|
-
"aria-label":
|
|
95
|
-
"aria-labelledby":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
ref: ce(S, _),
|
|
104
|
+
id: E,
|
|
105
|
+
label: A,
|
|
106
|
+
"aria-label": O,
|
|
107
|
+
"aria-labelledby": h,
|
|
108
|
+
role: "combobox",
|
|
109
|
+
"aria-expanded": I,
|
|
110
|
+
"aria-controls": I ? N : void 0,
|
|
111
|
+
"aria-haspopup": "listbox",
|
|
112
|
+
"aria-autocomplete": "list",
|
|
113
|
+
"aria-activedescendant": te,
|
|
114
|
+
value: v,
|
|
115
|
+
onChange: P,
|
|
116
|
+
onKeyDown: ee,
|
|
99
117
|
onFocus: () => l(!0),
|
|
100
|
-
placeholder:
|
|
101
|
-
disabled:
|
|
102
|
-
size:
|
|
103
|
-
rightIcon:
|
|
104
|
-
|
|
118
|
+
placeholder: G,
|
|
119
|
+
disabled: J,
|
|
120
|
+
size: Y,
|
|
121
|
+
rightIcon: D ? /* @__PURE__ */ m(ie, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ m(
|
|
122
|
+
le,
|
|
105
123
|
{
|
|
106
|
-
className: `h-4 w-4 transition-transform ${
|
|
124
|
+
className: `h-4 w-4 transition-transform ${u ? "rotate-180" : ""}`
|
|
107
125
|
}
|
|
108
126
|
),
|
|
109
|
-
className:
|
|
127
|
+
className: X
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
K && /* @__PURE__ */ m(
|
|
131
|
+
"input",
|
|
132
|
+
{
|
|
133
|
+
type: "hidden",
|
|
134
|
+
name: K,
|
|
135
|
+
value: s != null ? s : "",
|
|
136
|
+
form: Z
|
|
110
137
|
}
|
|
111
138
|
),
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
I && /* @__PURE__ */ m(
|
|
140
|
+
ae,
|
|
114
141
|
{
|
|
115
|
-
ref:
|
|
142
|
+
ref: b,
|
|
143
|
+
id: N,
|
|
116
144
|
options: n,
|
|
117
145
|
highlightedIndex: r,
|
|
118
|
-
onSelect:
|
|
119
|
-
loading:
|
|
120
|
-
emptyMessage:
|
|
121
|
-
containerRef:
|
|
122
|
-
"aria-labelledby":
|
|
123
|
-
"aria-label":
|
|
146
|
+
onSelect: q,
|
|
147
|
+
loading: D,
|
|
148
|
+
emptyMessage: j,
|
|
149
|
+
containerRef: w,
|
|
150
|
+
"aria-labelledby": h,
|
|
151
|
+
"aria-label": h ? void 0 : O || A
|
|
124
152
|
}
|
|
125
153
|
)
|
|
126
154
|
] });
|
|
127
155
|
}
|
|
128
156
|
);
|
|
129
|
-
|
|
157
|
+
ue.displayName = "Autocomplete";
|
|
130
158
|
export {
|
|
131
|
-
|
|
159
|
+
ue as default
|
|
132
160
|
};
|
|
133
161
|
//# sourceMappingURL=Autocomplete.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.js","sources":["../../../../../src/ui/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect, useId, forwardRef } from \"react\";\nimport Input from \"../../primitives/Input/Input\";\nimport { ChevronDown, Loader2 } from \"lucide-react\";\nimport AutocompleteList from \"./AutocompleteList\";\nimport type { AutocompleteOptionType } from \"./AutocompleteOption\";\n\nexport interface AutocompleteProps {\n options: AutocompleteOptionType[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onSelect?: (option: AutocompleteOptionType) => void;\n placeholder?: string;\n loading?: boolean;\n disabled?: boolean;\n emptyMessage?: string;\n debounceMs?: number;\n filterOptions?: (\n options: AutocompleteOptionType[],\n searchValue: string,\n ) => AutocompleteOptionType[];\n className?: string;\n inputClassName?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * Visible label rendered above the input via the `Input` primitive's\n * `label` API, which wires `<label htmlFor>` to the inner `<input>`.\n * Provides the accessible name axe `aria-input-field-name` requires.\n */\n label?: string;\n /**\n * Invisible accessible name. Use when the input has no visible label\n * (e.g. a tightly-packed toolbar combobox). One of `label`,\n * `aria-label`, or `aria-labelledby` MUST be present — without any,\n * the input has no programmatic name and axe `aria-input-field-name`\n * (serious) flags it. A dev-only warning fires when all are missing.\n */\n \"aria-label\"?: string;\n /**\n * Override path: point at an id the consumer manages externally.\n */\n \"aria-labelledby\"?: string;\n id?: string;\n}\n\n/**\n * Autocomplete Component\n *\n * An input component with autocomplete suggestions.\n * Supports keyboard navigation, loading states, and custom filtering.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * options={[\n * { value: '1', label: 'Option 1' },\n * { value: '2', label: 'Option 2' },\n * ]}\n * onSelect={(option) => console.log(option)}\n * />\n * ```\n */\nconst Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(\n function Autocomplete(\n {\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n onSelect,\n placeholder = \"Type to search...\",\n loading = false,\n disabled = false,\n emptyMessage = \"No options found\",\n debounceMs = 300,\n filterOptions,\n className = \"\",\n inputClassName = \"\",\n size = \"md\",\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n id: idProp,\n },\n ref,\n ) {\n const autoId = useId();\n const inputId = idProp ?? autoId;\n const [internalValue, setInternalValue] = useState<string>(\n typeof defaultValue === \"string\" ? defaultValue : \"\",\n );\n const [isOpen, setIsOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [searchValue, setSearchValue] = useState(\"\");\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n\n // Filter options\n const getFilteredOptions = (): AutocompleteOptionType[] => {\n if (!searchValue.trim()) {\n return options;\n }\n\n if (filterOptions) {\n return filterOptions(options, searchValue);\n }\n\n // Default filter: case-insensitive search in label\n return options.filter((option) =>\n option.label.toLowerCase().includes(searchValue.toLowerCase()),\n );\n };\n\n const filteredOptions = getFilteredOptions();\n const hasOptions = filteredOptions.length > 0;\n\n // Handle input change\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n setSearchValue(newValue);\n\n if (!isControlled) {\n setInternalValue(newValue);\n }\n\n onChange?.(newValue);\n\n // Debounce search\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n setIsOpen(true);\n setHighlightedIndex(-1);\n }, debounceMs);\n };\n\n // Handle option select\n const handleSelect = (option: AutocompleteOptionType) => {\n if (option.disabled) return;\n\n if (!isControlled) {\n setInternalValue(option.value);\n }\n\n setSearchValue(option.label);\n setIsOpen(false);\n setHighlightedIndex(-1);\n onChange?.(option.value);\n onSelect?.(option);\n inputRef.current?.focus();\n };\n\n // Handle keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!isOpen || filteredOptions.length === 0) {\n if (e.key === \"ArrowDown\" || e.key === \"Enter\") {\n setIsOpen(true);\n }\n return;\n }\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setHighlightedIndex((prev) =>\n prev < filteredOptions.length - 1 ? prev + 1 : 0,\n );\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setHighlightedIndex((prev) =>\n prev > 0 ? prev - 1 : filteredOptions.length - 1,\n );\n break;\n case \"Enter\":\n e.preventDefault();\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length\n ) {\n handleSelect(filteredOptions[highlightedIndex]);\n }\n break;\n case \"Escape\":\n e.preventDefault();\n setIsOpen(false);\n setHighlightedIndex(-1);\n break;\n }\n };\n\n // Close on click outside\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(e.target as Node)\n ) {\n setIsOpen(false);\n setHighlightedIndex(-1);\n }\n };\n\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () =>\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }, [isOpen]);\n\n // Scroll highlighted item into view\n useEffect(() => {\n if (highlightedIndex >= 0 && listRef.current) {\n const items = listRef.current.querySelectorAll('[role=\"option\"]');\n if (\n items[highlightedIndex] &&\n typeof items[highlightedIndex].scrollIntoView === \"function\"\n ) {\n items[highlightedIndex].scrollIntoView({\n block: \"nearest\",\n behavior: \"smooth\",\n });\n }\n }\n }, [highlightedIndex]);\n\n // Sync search value with current value\n useEffect(() => {\n if (currentValue) {\n const option = options.find((opt) => opt.value === currentValue);\n if (option) {\n setSearchValue(option.label);\n } else {\n setSearchValue(currentValue);\n }\n } else {\n setSearchValue(\"\");\n }\n }, [currentValue, options]);\n\n // Dev-only accessible-name warning. Same four-path shape as the\n // Textarea 6c guard: label / aria-label / aria-labelledby / external\n // `<label htmlFor={id}>`. Without any source, axe\n // `aria-input-field-name` (serious) flags the input. Silent in\n // production.\n useEffect(() => {\n if (!import.meta.env.DEV) return;\n if (label || ariaLabel || ariaLabelledBy) return;\n const externalLabel =\n typeof document !== \"undefined\"\n ? document.querySelector(`label[for=\"${CSS.escape(inputId)}\"]`)\n : null;\n if (externalLabel) return;\n console.warn(\n \"[Autocomplete] Missing accessible name. Provide a `label` prop, `aria-label`, `aria-labelledby`, or pair an external `<label htmlFor={id}>` with the same `id`.\",\n );\n }, [label, ariaLabel, ariaLabelledBy, inputId]);\n\n const shouldShowList = isOpen && (hasOptions || loading || emptyMessage);\n\n return (\n <div ref={containerRef} className={`relative ${className}`}>\n <Input\n ref={inputRef || ref}\n id={inputId}\n label={label}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n value={searchValue}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n onFocus={() => setIsOpen(true)}\n placeholder={placeholder}\n disabled={disabled}\n size={size}\n rightIcon={\n loading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <ChevronDown\n className={`h-4 w-4 transition-transform ${\n isOpen ? \"rotate-180\" : \"\"\n }`}\n />\n )\n }\n className={inputClassName}\n />\n\n {shouldShowList && (\n <AutocompleteList\n ref={listRef}\n options={filteredOptions}\n highlightedIndex={highlightedIndex}\n onSelect={handleSelect}\n loading={loading}\n emptyMessage={emptyMessage}\n containerRef={containerRef}\n // Cascade the same accessible-name source the consumer\n // provided for the input to the listbox portal — keeps\n // axe `aria-input-field-name` satisfied on the listbox\n // without making the consumer re-state the name.\n aria-labelledby={ariaLabelledBy}\n aria-label={ariaLabelledBy ? undefined : ariaLabel || label}\n />\n )}\n </div>\n );\n },\n);\n\nAutocomplete.displayName = \"Autocomplete\";\n\nexport default Autocomplete;\n"],"names":["Autocomplete","forwardRef","options","controlledValue","defaultValue","onChange","onSelect","placeholder","loading","disabled","emptyMessage","debounceMs","filterOptions","className","inputClassName","size","label","ariaLabel","ariaLabelledBy","idProp","ref","autoId","useId","inputId","internalValue","setInternalValue","useState","isOpen","setIsOpen","highlightedIndex","setHighlightedIndex","searchValue","setSearchValue","containerRef","useRef","inputRef","listRef","debounceTimerRef","isControlled","currentValue","filteredOptions","option","hasOptions","handleInputChange","newValue","handleSelect","_a","handleKeyDown","prev","useEffect","handleClickOutside","e","items","opt","shouldShowList","jsx","Input","Loader2","ChevronDown","AutocompleteList"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Autocomplete.js","sources":["../../../../../src/ui/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect, useId, forwardRef } from \"react\";\nimport Input from \"../../primitives/Input/Input\";\nimport { mergeRefs } from \"../../utils\";\nimport { ChevronDown, Loader2 } from \"lucide-react\";\nimport AutocompleteList from \"./AutocompleteList\";\nimport type { AutocompleteOptionType } from \"./AutocompleteOption\";\n\nexport interface AutocompleteProps {\n options: AutocompleteOptionType[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onSelect?: (option: AutocompleteOptionType) => void;\n placeholder?: string;\n loading?: boolean;\n disabled?: boolean;\n emptyMessage?: string;\n debounceMs?: number;\n filterOptions?: (\n options: AutocompleteOptionType[],\n searchValue: string,\n ) => AutocompleteOptionType[];\n className?: string;\n inputClassName?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * Visible label rendered above the input via the `Input` primitive's\n * `label` API, which wires `<label htmlFor>` to the inner `<input>`.\n * Provides the accessible name axe `aria-input-field-name` requires.\n */\n label?: string;\n /**\n * Invisible accessible name. Use when the input has no visible label\n * (e.g. a tightly-packed toolbar combobox). One of `label`,\n * `aria-label`, or `aria-labelledby` MUST be present — without any,\n * the input has no programmatic name and axe `aria-input-field-name`\n * (serious) flags it. A dev-only warning fires when all are missing.\n */\n \"aria-label\"?: string;\n /**\n * Override path: point at an id the consumer manages externally.\n */\n \"aria-labelledby\"?: string;\n id?: string;\n /**\n * Name of the form field (issue #225). When set, the component renders\n * a synchronized `<input type=\"hidden\" name={name}>` carrying the\n * **selected option value** (not the visible search text), so the\n * Autocomplete participates in native form submission exactly like a\n * `<select name>` — a `<form method=\"GET\">` picks up `name=value` with\n * no `onChange`/router wiring on the consumer side. This is the\n * form-native integration the brasil-a-vera filter bars need.\n *\n * Caveat: the component is interactive (`\"use client\"`), so the hidden\n * field is populated once hydrated — it is NOT a pre-hydration no-JS\n * fallback. For a true zero-JS fallback, pair it with a `<noscript>`\n * native `<select name>`.\n */\n name?: string;\n /**\n * Associates the hidden field with a form by id (the native `form`\n * attribute), for when the Autocomplete renders outside the `<form>`\n * it submits to. No effect unless `name` is also set.\n */\n form?: string;\n}\n\n/**\n * Autocomplete Component\n *\n * An input component with autocomplete suggestions.\n * Supports keyboard navigation, loading states, and custom filtering.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * options={[\n * { value: '1', label: 'Option 1' },\n * { value: '2', label: 'Option 2' },\n * ]}\n * onSelect={(option) => console.log(option)}\n * />\n * ```\n */\nconst Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(\n function Autocomplete(\n {\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n onSelect,\n placeholder = \"Type to search...\",\n loading = false,\n disabled = false,\n emptyMessage = \"No options found\",\n debounceMs = 300,\n filterOptions,\n className = \"\",\n inputClassName = \"\",\n size = \"md\",\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n id: idProp,\n name,\n form,\n },\n ref,\n ) {\n const autoId = useId();\n const inputId = idProp ?? autoId;\n const listboxId = `${inputId}-listbox`;\n const [internalValue, setInternalValue] = useState<string>(\n typeof defaultValue === \"string\" ? defaultValue : \"\",\n );\n const [isOpen, setIsOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [searchValue, setSearchValue] = useState(\"\");\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n\n // Filter options\n const getFilteredOptions = (): AutocompleteOptionType[] => {\n if (!searchValue.trim()) {\n return options;\n }\n\n if (filterOptions) {\n return filterOptions(options, searchValue);\n }\n\n // Default filter: case-insensitive search in label\n return options.filter((option) =>\n option.label.toLowerCase().includes(searchValue.toLowerCase()),\n );\n };\n\n const filteredOptions = getFilteredOptions();\n const hasOptions = filteredOptions.length > 0;\n\n // Handle input change\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n setSearchValue(newValue);\n\n if (!isControlled) {\n setInternalValue(newValue);\n }\n\n onChange?.(newValue);\n\n // Debounce search\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n setIsOpen(true);\n setHighlightedIndex(-1);\n }, debounceMs);\n };\n\n // Handle option select\n const handleSelect = (option: AutocompleteOptionType) => {\n if (option.disabled) return;\n\n if (!isControlled) {\n setInternalValue(option.value);\n }\n\n setSearchValue(option.label);\n setIsOpen(false);\n setHighlightedIndex(-1);\n onChange?.(option.value);\n onSelect?.(option);\n inputRef.current?.focus();\n };\n\n // Next/previous ENABLED option index, wrapping, skipping disabled\n // options (arrow nav previously landed on disabled options where\n // Enter then did nothing — a dead keypress).\n const moveHighlight = (prev: number, dir: 1 | -1): number => {\n const n = filteredOptions.length;\n if (n === 0) return -1;\n const start = prev < 0 ? (dir === 1 ? -1 : 0) : prev;\n for (let i = 1; i <= n; i++) {\n const idx = (((start + dir * i) % n) + n) % n;\n if (!filteredOptions[idx].disabled) return idx;\n }\n return prev;\n };\n\n // Handle keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!isOpen || filteredOptions.length === 0) {\n if (e.key === \"ArrowDown\" || e.key === \"Enter\") {\n setIsOpen(true);\n }\n return;\n }\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setHighlightedIndex((prev) => moveHighlight(prev, 1));\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setHighlightedIndex((prev) => moveHighlight(prev, -1));\n break;\n case \"Enter\":\n e.preventDefault();\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length\n ) {\n handleSelect(filteredOptions[highlightedIndex]);\n }\n break;\n case \"Escape\":\n e.preventDefault();\n setIsOpen(false);\n setHighlightedIndex(-1);\n break;\n }\n };\n\n // Close on click outside\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n // The list is portalled to document.body, so it is NOT inside\n // containerRef — without the listRef check, a mousedown on an\n // option closed the list before its click fired, losing the\n // selection.\n if (\n containerRef.current &&\n !containerRef.current.contains(target) &&\n !listRef.current?.contains(target)\n ) {\n setIsOpen(false);\n setHighlightedIndex(-1);\n }\n };\n\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () =>\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }, [isOpen]);\n\n // Clear any pending debounce timer on unmount (avoids a state update\n // on an unmounted component / a dangling timer).\n useEffect(() => {\n return () => {\n if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);\n };\n }, []);\n\n // Scroll highlighted item into view\n useEffect(() => {\n if (highlightedIndex >= 0 && listRef.current) {\n const items = listRef.current.querySelectorAll('[role=\"option\"]');\n if (\n items[highlightedIndex] &&\n typeof items[highlightedIndex].scrollIntoView === \"function\"\n ) {\n items[highlightedIndex].scrollIntoView({\n block: \"nearest\",\n behavior: \"smooth\",\n });\n }\n }\n }, [highlightedIndex]);\n\n // Sync search value with current value\n useEffect(() => {\n if (currentValue) {\n const option = options.find((opt) => opt.value === currentValue);\n if (option) {\n setSearchValue(option.label);\n } else {\n setSearchValue(currentValue);\n }\n } else {\n setSearchValue(\"\");\n }\n }, [currentValue, options]);\n\n // Dev-only accessible-name warning. Same four-path shape as the\n // Textarea 6c guard: label / aria-label / aria-labelledby / external\n // `<label htmlFor={id}>`. Without any source, axe\n // `aria-input-field-name` (serious) flags the input. Silent in\n // production.\n useEffect(() => {\n if (!import.meta.env.DEV) return;\n if (label || ariaLabel || ariaLabelledBy) return;\n const externalLabel =\n typeof document !== \"undefined\"\n ? document.querySelector(`label[for=\"${CSS.escape(inputId)}\"]`)\n : null;\n if (externalLabel) return;\n console.warn(\n \"[Autocomplete] Missing accessible name. Provide a `label` prop, `aria-label`, `aria-labelledby`, or pair an external `<label htmlFor={id}>` with the same `id`.\",\n );\n }, [label, ariaLabel, ariaLabelledBy, inputId]);\n\n const shouldShowList =\n isOpen && (hasOptions || loading || Boolean(emptyMessage));\n const activeOptionId =\n shouldShowList &&\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length\n ? `${listboxId}-option-${highlightedIndex}`\n : undefined;\n\n return (\n <div ref={containerRef} className={`relative ${className}`}>\n <Input\n ref={mergeRefs(inputRef, ref)}\n id={inputId}\n label={label}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n role=\"combobox\"\n aria-expanded={shouldShowList}\n aria-controls={shouldShowList ? listboxId : undefined}\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n aria-activedescendant={activeOptionId}\n value={searchValue}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n onFocus={() => setIsOpen(true)}\n placeholder={placeholder}\n disabled={disabled}\n size={size}\n rightIcon={\n loading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <ChevronDown\n className={`h-4 w-4 transition-transform ${\n isOpen ? \"rotate-180\" : \"\"\n }`}\n />\n )\n }\n className={inputClassName}\n />\n\n {/*\n Form-native participation (issue #225). The hidden field carries\n the SELECTED OPTION VALUE (`currentValue`), not the visible\n `searchValue` label, so a native `<form>` submits `name=value`\n like a `<select name>`. Rendered only when `name` is set; `form`\n lets it associate with a form by id when rendered outside it.\n */}\n {name && (\n <input\n type=\"hidden\"\n name={name}\n value={currentValue ?? \"\"}\n form={form}\n />\n )}\n\n {shouldShowList && (\n <AutocompleteList\n ref={listRef}\n id={listboxId}\n options={filteredOptions}\n highlightedIndex={highlightedIndex}\n onSelect={handleSelect}\n loading={loading}\n emptyMessage={emptyMessage}\n containerRef={containerRef}\n // Cascade the same accessible-name source the consumer\n // provided for the input to the listbox portal — keeps\n // axe `aria-input-field-name` satisfied on the listbox\n // without making the consumer re-state the name.\n aria-labelledby={ariaLabelledBy}\n aria-label={ariaLabelledBy ? undefined : ariaLabel || label}\n />\n )}\n </div>\n );\n },\n);\n\nAutocomplete.displayName = \"Autocomplete\";\n\nexport default Autocomplete;\n"],"names":["Autocomplete","forwardRef","options","controlledValue","defaultValue","onChange","onSelect","placeholder","loading","disabled","emptyMessage","debounceMs","filterOptions","className","inputClassName","size","label","ariaLabel","ariaLabelledBy","idProp","name","form","ref","autoId","useId","inputId","listboxId","internalValue","setInternalValue","useState","isOpen","setIsOpen","highlightedIndex","setHighlightedIndex","searchValue","setSearchValue","containerRef","useRef","inputRef","listRef","debounceTimerRef","isControlled","currentValue","filteredOptions","option","hasOptions","handleInputChange","newValue","handleSelect","_a","moveHighlight","prev","dir","n","start","i","idx","handleKeyDown","useEffect","handleClickOutside","e","target","items","opt","shouldShowList","activeOptionId","jsx","Input","mergeRefs","Loader2","ChevronDown","AutocompleteList"],"mappings":"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"}
|
|
@@ -1,51 +1,55 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as e, jsxs as
|
|
3
|
-
import { forwardRef as
|
|
4
|
-
import { createPortal as
|
|
5
|
-
import { getRadiusClass as
|
|
6
|
-
import { getShadowClass as
|
|
7
|
-
import { getZIndexClass as
|
|
2
|
+
import { jsx as e, jsxs as y, Fragment as C } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as A, useState as N, useEffect as L } from "react";
|
|
4
|
+
import { createPortal as S } from "react-dom";
|
|
5
|
+
import { getRadiusClass as j } from "../../tokens/radius.js";
|
|
6
|
+
import { getShadowClass as P } from "../../tokens/shadows.js";
|
|
7
|
+
import { getZIndexClass as k } from "../../tokens/z-index.js";
|
|
8
8
|
import { getSpacingClass as o } from "../../tokens/spacing.js";
|
|
9
|
-
import
|
|
10
|
-
const
|
|
9
|
+
import D from "./AutocompleteOption.js";
|
|
10
|
+
const E = A(
|
|
11
11
|
function({
|
|
12
|
-
options:
|
|
13
|
-
highlightedIndex:
|
|
14
|
-
onSelect:
|
|
15
|
-
loading:
|
|
16
|
-
emptyMessage:
|
|
12
|
+
options: a,
|
|
13
|
+
highlightedIndex: c,
|
|
14
|
+
onSelect: p,
|
|
15
|
+
loading: f = !1,
|
|
16
|
+
emptyMessage: u = "No options found",
|
|
17
17
|
containerRef: l,
|
|
18
|
-
showSelectAll:
|
|
19
|
-
allSelected:
|
|
20
|
-
onSelectAll:
|
|
21
|
-
onDeselectAll:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
showSelectAll: b = !1,
|
|
19
|
+
allSelected: d = !1,
|
|
20
|
+
onSelectAll: g,
|
|
21
|
+
onDeselectAll: h,
|
|
22
|
+
id: r,
|
|
23
|
+
selectedValues: i,
|
|
24
|
+
"aria-label": v,
|
|
25
|
+
"aria-labelledby": m
|
|
26
|
+
}, x) {
|
|
27
|
+
const [s, w] = N({ top: 0, left: 0, width: 0 });
|
|
28
|
+
L(() => {
|
|
27
29
|
if (l.current) {
|
|
28
30
|
const t = l.current.getBoundingClientRect();
|
|
29
|
-
|
|
31
|
+
w({
|
|
30
32
|
top: t.bottom + window.scrollY + 4,
|
|
31
33
|
left: t.left + window.scrollX,
|
|
32
34
|
width: t.width
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
}, [l]);
|
|
36
|
-
const
|
|
38
|
+
const $ = /* @__PURE__ */ e(
|
|
37
39
|
"div",
|
|
38
40
|
{
|
|
39
|
-
ref:
|
|
41
|
+
ref: x,
|
|
42
|
+
id: r,
|
|
40
43
|
role: "listbox",
|
|
41
|
-
"aria-
|
|
42
|
-
"aria-
|
|
44
|
+
"aria-multiselectable": i ? !0 : void 0,
|
|
45
|
+
"aria-label": m ? void 0 : v,
|
|
46
|
+
"aria-labelledby": m,
|
|
43
47
|
className: `
|
|
44
48
|
absolute
|
|
45
|
-
${
|
|
49
|
+
${k("popover")}
|
|
46
50
|
bg-surface-overlay
|
|
47
|
-
${
|
|
48
|
-
${
|
|
51
|
+
${j("md")}
|
|
52
|
+
${P("lg")}
|
|
49
53
|
border
|
|
50
54
|
border-line-default
|
|
51
55
|
max-h-60
|
|
@@ -53,13 +57,15 @@ const k = $(
|
|
|
53
57
|
${o("xs", "py")}
|
|
54
58
|
`,
|
|
55
59
|
style: {
|
|
56
|
-
top: `${
|
|
57
|
-
left: `${
|
|
58
|
-
width: `${
|
|
60
|
+
top: `${s.top}px`,
|
|
61
|
+
left: `${s.left}px`,
|
|
62
|
+
width: `${s.width}px`
|
|
59
63
|
},
|
|
60
|
-
children:
|
|
64
|
+
children: f ? /* @__PURE__ */ e(
|
|
61
65
|
"div",
|
|
62
66
|
{
|
|
67
|
+
role: "status",
|
|
68
|
+
"aria-live": "polite",
|
|
63
69
|
className: `
|
|
64
70
|
${o("md", "p")}
|
|
65
71
|
text-sm
|
|
@@ -68,19 +74,21 @@ const k = $(
|
|
|
68
74
|
`,
|
|
69
75
|
children: "Loading..."
|
|
70
76
|
}
|
|
71
|
-
) :
|
|
77
|
+
) : a.length === 0 ? /* @__PURE__ */ e(
|
|
72
78
|
"div",
|
|
73
79
|
{
|
|
80
|
+
role: "status",
|
|
81
|
+
"aria-live": "polite",
|
|
74
82
|
className: `
|
|
75
83
|
${o("md", "p")}
|
|
76
84
|
text-sm
|
|
77
85
|
text-fg-tertiary
|
|
78
86
|
text-center
|
|
79
87
|
`,
|
|
80
|
-
children:
|
|
88
|
+
children: u
|
|
81
89
|
}
|
|
82
|
-
) : /* @__PURE__ */
|
|
83
|
-
|
|
90
|
+
) : /* @__PURE__ */ y(C, { children: [
|
|
91
|
+
b && /* @__PURE__ */ e(
|
|
84
92
|
"div",
|
|
85
93
|
{
|
|
86
94
|
className: `
|
|
@@ -93,27 +101,29 @@ const k = $(
|
|
|
93
101
|
border-b
|
|
94
102
|
border-line-default
|
|
95
103
|
`,
|
|
96
|
-
onClick:
|
|
97
|
-
children:
|
|
104
|
+
onClick: d ? h : g,
|
|
105
|
+
children: d ? "Deselect All" : "Select All"
|
|
98
106
|
}
|
|
99
107
|
),
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
a.map((t, n) => /* @__PURE__ */ e(
|
|
109
|
+
D,
|
|
102
110
|
{
|
|
111
|
+
id: r ? `${r}-option-${n}` : void 0,
|
|
103
112
|
option: t,
|
|
104
|
-
isHighlighted:
|
|
105
|
-
|
|
113
|
+
isHighlighted: n === c,
|
|
114
|
+
selected: i ? i.includes(t.value) : void 0,
|
|
115
|
+
onSelect: p
|
|
106
116
|
},
|
|
107
117
|
t.value
|
|
108
118
|
))
|
|
109
119
|
] })
|
|
110
120
|
}
|
|
111
121
|
);
|
|
112
|
-
return typeof window != "undefined" ?
|
|
122
|
+
return typeof window != "undefined" ? S($, document.body) : null;
|
|
113
123
|
}
|
|
114
124
|
);
|
|
115
|
-
|
|
125
|
+
E.displayName = "AutocompleteList";
|
|
116
126
|
export {
|
|
117
|
-
|
|
127
|
+
E as default
|
|
118
128
|
};
|
|
119
129
|
//# sourceMappingURL=AutocompleteList.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteList.js","sources":["../../../../../src/ui/components/Autocomplete/AutocompleteList.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { getRadiusClass } from \"../../tokens/radius\";\nimport { getShadowClass } from \"../../tokens/shadows\";\nimport { getZIndexClass } from \"../../tokens/z-index\";\nimport { getSpacingClass } from \"../../tokens/spacing\";\nimport AutocompleteOption from \"./AutocompleteOption\";\nimport type { AutocompleteOptionType } from \"./AutocompleteOption\";\n\nexport interface AutocompleteListProps {\n options: AutocompleteOptionType[];\n highlightedIndex: number;\n onSelect: (option: AutocompleteOptionType) => void;\n loading?: boolean;\n emptyMessage?: string;\n containerRef: React.RefObject<HTMLDivElement | null>;\n showSelectAll?: boolean;\n allSelected?: boolean;\n onSelectAll?: () => void;\n onDeselectAll?: () => void;\n /**\n * Accessible name for the listbox. axe `aria-input-field-name`\n * (serious) flags a `role=\"listbox\"` portal without `aria-label` /\n * `aria-labelledby` / `title`. The Autocomplete / MultiSelect parent\n * cascades whatever accessible-name source the consumer provided\n * (label string, aria-label, or external id) so the listbox inherits\n * the same name as the input it dropdowns from.\n */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n}\n\n/**\n * AutocompleteList Component\n *\n * The list container for autocomplete options.\n * Renders in a portal and positions itself below the input.\n */\nconst AutocompleteList = forwardRef<HTMLDivElement, AutocompleteListProps>(\n function AutocompleteList(\n {\n options,\n highlightedIndex,\n onSelect,\n loading = false,\n emptyMessage = \"No options found\",\n containerRef,\n showSelectAll = false,\n allSelected = false,\n onSelectAll,\n onDeselectAll,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n },\n ref,\n ) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n\n // Calculate position\n useEffect(() => {\n if (containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n setPosition({\n top: rect.bottom + window.scrollY + 4,\n left: rect.left + window.scrollX,\n width: rect.width,\n });\n }\n }, [containerRef]);\n\n const listContent = (\n <div\n ref={ref}\n role=\"listbox\"\n aria-label={ariaLabelledBy ? undefined : ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={`\n absolute\n ${getZIndexClass(\"popover\")}\n bg-surface-overlay\n ${getRadiusClass(\"md\")}\n ${getShadowClass(\"lg\")}\n border\n border-line-default\n max-h-60\n overflow-y-auto\n ${getSpacingClass(\"xs\", \"py\")}\n `}\n style={{\n top: `${position.top}px`,\n left: `${position.left}px`,\n width: `${position.width}px`,\n }}\n >\n {loading ? (\n <div\n className={`\n ${getSpacingClass(\"md\", \"p\")}\n text-sm\n text-fg-tertiary\n text-center\n `}\n >\n Loading...\n </div>\n ) : options.length === 0 ? (\n <div\n className={`\n ${getSpacingClass(\"md\", \"p\")}\n text-sm\n text-fg-tertiary\n text-center\n `}\n >\n {emptyMessage}\n </div>\n ) : (\n <>\n {showSelectAll && (\n <div\n className={`\n ${getSpacingClass(\"sm\", \"px\")}\n ${getSpacingClass(\"sm\", \"py\")}\n text-sm\n font-medium\n cursor-pointer\n hover:bg-surface-hover\n border-b\n border-line-default\n `}\n onClick={allSelected ? onDeselectAll : onSelectAll}\n >\n {allSelected ? \"Deselect All\" : \"Select All\"}\n </div>\n )}\n {options.map((option, index) => (\n <AutocompleteOption\n key={option.value}\n option={option}\n isHighlighted={index === highlightedIndex}\n onSelect={onSelect}\n />\n ))}\n </>\n )}\n </div>\n );\n\n return typeof window !== \"undefined\"\n ? createPortal(listContent, document.body)\n : null;\n },\n);\n\nAutocompleteList.displayName = \"AutocompleteList\";\n\nexport default AutocompleteList;\n"],"names":["AutocompleteList","forwardRef","options","highlightedIndex","onSelect","loading","emptyMessage","containerRef","showSelectAll","allSelected","onSelectAll","onDeselectAll","ariaLabel","ariaLabelledBy","ref","position","setPosition","useState","useEffect","rect","listContent","jsx","getZIndexClass","getRadiusClass","getShadowClass","getSpacingClass","jsxs","Fragment","option","index","AutocompleteOption","createPortal"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"AutocompleteList.js","sources":["../../../../../src/ui/components/Autocomplete/AutocompleteList.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { getRadiusClass } from \"../../tokens/radius\";\nimport { getShadowClass } from \"../../tokens/shadows\";\nimport { getZIndexClass } from \"../../tokens/z-index\";\nimport { getSpacingClass } from \"../../tokens/spacing\";\nimport AutocompleteOption from \"./AutocompleteOption\";\nimport type { AutocompleteOptionType } from \"./AutocompleteOption\";\n\nexport interface AutocompleteListProps {\n options: AutocompleteOptionType[];\n highlightedIndex: number;\n onSelect: (option: AutocompleteOptionType) => void;\n loading?: boolean;\n emptyMessage?: string;\n containerRef: React.RefObject<HTMLDivElement | null>;\n showSelectAll?: boolean;\n allSelected?: boolean;\n onSelectAll?: () => void;\n onDeselectAll?: () => void;\n /** listbox id; option ids are derived as `${id}-option-${index}`. */\n id?: string;\n /** Multi-select: values currently selected, to drive aria-selected. */\n selectedValues?: string[];\n /**\n * Accessible name for the listbox. axe `aria-input-field-name`\n * (serious) flags a `role=\"listbox\"` portal without `aria-label` /\n * `aria-labelledby` / `title`. The Autocomplete / MultiSelect parent\n * cascades whatever accessible-name source the consumer provided\n * (label string, aria-label, or external id) so the listbox inherits\n * the same name as the input it dropdowns from.\n */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n}\n\n/**\n * AutocompleteList Component\n *\n * The list container for autocomplete options.\n * Renders in a portal and positions itself below the input.\n */\nconst AutocompleteList = forwardRef<HTMLDivElement, AutocompleteListProps>(\n function AutocompleteList(\n {\n options,\n highlightedIndex,\n onSelect,\n loading = false,\n emptyMessage = \"No options found\",\n containerRef,\n showSelectAll = false,\n allSelected = false,\n onSelectAll,\n onDeselectAll,\n id,\n selectedValues,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n },\n ref,\n ) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n\n // Calculate position\n useEffect(() => {\n if (containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n setPosition({\n top: rect.bottom + window.scrollY + 4,\n left: rect.left + window.scrollX,\n width: rect.width,\n });\n }\n }, [containerRef]);\n\n const listContent = (\n <div\n ref={ref}\n id={id}\n role=\"listbox\"\n aria-multiselectable={selectedValues ? true : undefined}\n aria-label={ariaLabelledBy ? undefined : ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={`\n absolute\n ${getZIndexClass(\"popover\")}\n bg-surface-overlay\n ${getRadiusClass(\"md\")}\n ${getShadowClass(\"lg\")}\n border\n border-line-default\n max-h-60\n overflow-y-auto\n ${getSpacingClass(\"xs\", \"py\")}\n `}\n style={{\n top: `${position.top}px`,\n left: `${position.left}px`,\n width: `${position.width}px`,\n }}\n >\n {loading ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={`\n ${getSpacingClass(\"md\", \"p\")}\n text-sm\n text-fg-tertiary\n text-center\n `}\n >\n Loading...\n </div>\n ) : options.length === 0 ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={`\n ${getSpacingClass(\"md\", \"p\")}\n text-sm\n text-fg-tertiary\n text-center\n `}\n >\n {emptyMessage}\n </div>\n ) : (\n <>\n {showSelectAll && (\n <div\n className={`\n ${getSpacingClass(\"sm\", \"px\")}\n ${getSpacingClass(\"sm\", \"py\")}\n text-sm\n font-medium\n cursor-pointer\n hover:bg-surface-hover\n border-b\n border-line-default\n `}\n onClick={allSelected ? onDeselectAll : onSelectAll}\n >\n {allSelected ? \"Deselect All\" : \"Select All\"}\n </div>\n )}\n {options.map((option, index) => (\n <AutocompleteOption\n key={option.value}\n id={id ? `${id}-option-${index}` : undefined}\n option={option}\n isHighlighted={index === highlightedIndex}\n selected={\n selectedValues\n ? selectedValues.includes(option.value)\n : undefined\n }\n onSelect={onSelect}\n />\n ))}\n </>\n )}\n </div>\n );\n\n return typeof window !== \"undefined\"\n ? createPortal(listContent, document.body)\n : null;\n },\n);\n\nAutocompleteList.displayName = \"AutocompleteList\";\n\nexport default AutocompleteList;\n"],"names":["AutocompleteList","forwardRef","options","highlightedIndex","onSelect","loading","emptyMessage","containerRef","showSelectAll","allSelected","onSelectAll","onDeselectAll","id","selectedValues","ariaLabel","ariaLabelledBy","ref","position","setPosition","useState","useEffect","rect","listContent","jsx","getZIndexClass","getRadiusClass","getShadowClass","getSpacingClass","jsxs","Fragment","option","index","AutocompleteOption","createPortal"],"mappings":"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"}
|
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as
|
|
3
|
-
import { forwardRef as
|
|
4
|
-
import { getSpacingClass as
|
|
5
|
-
const
|
|
6
|
-
function({ option:
|
|
7
|
-
const
|
|
8
|
-
|
|
2
|
+
import { jsxs as n, jsx as l } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as m } from "react";
|
|
4
|
+
import { getSpacingClass as r } from "../../tokens/spacing.js";
|
|
5
|
+
const p = m(
|
|
6
|
+
function({ option: a, isHighlighted: s, onSelect: o, id: i, selected: e }, c) {
|
|
7
|
+
const t = () => {
|
|
8
|
+
a.disabled || o(a);
|
|
9
9
|
};
|
|
10
|
-
return /* @__PURE__ */
|
|
10
|
+
return /* @__PURE__ */ n(
|
|
11
11
|
"div",
|
|
12
12
|
{
|
|
13
13
|
ref: c,
|
|
14
|
+
id: i,
|
|
14
15
|
role: "option",
|
|
15
|
-
"aria-selected":
|
|
16
|
-
"aria-disabled":
|
|
17
|
-
onClick:
|
|
16
|
+
"aria-selected": e != null ? e : s,
|
|
17
|
+
"aria-disabled": a.disabled,
|
|
18
|
+
onClick: t,
|
|
18
19
|
className: `
|
|
19
20
|
flex
|
|
20
21
|
items-center
|
|
21
|
-
${
|
|
22
|
-
${
|
|
23
|
-
${
|
|
22
|
+
${r("sm", "gap")}
|
|
23
|
+
${r("sm", "px")}
|
|
24
|
+
${r("sm", "py")}
|
|
24
25
|
text-sm
|
|
25
26
|
cursor-pointer
|
|
26
27
|
transition-colors
|
|
27
|
-
${
|
|
28
|
-
${
|
|
28
|
+
${s ? "bg-surface-active" : ""}
|
|
29
|
+
${a.disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-surface-hover"}
|
|
29
30
|
`,
|
|
30
31
|
children: [
|
|
31
|
-
|
|
32
|
-
/* @__PURE__ */
|
|
32
|
+
a.icon && /* @__PURE__ */ l("span", { className: "flex-shrink-0", children: a.icon }),
|
|
33
|
+
/* @__PURE__ */ l("span", { className: "flex-1", children: a.label })
|
|
33
34
|
]
|
|
34
35
|
}
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
);
|
|
38
|
-
|
|
39
|
+
p.displayName = "AutocompleteOption";
|
|
39
40
|
export {
|
|
40
|
-
|
|
41
|
+
p as default
|
|
41
42
|
};
|
|
42
43
|
//# sourceMappingURL=AutocompleteOption.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteOption.js","sources":["../../../../../src/ui/components/Autocomplete/AutocompleteOption.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\nimport { getSpacingClass } from \"../../tokens/spacing\";\n\nexport interface AutocompleteOptionType {\n value: string;\n label: string;\n disabled?: boolean;\n icon?: ReactNode;\n group?: string;\n}\n\nexport interface AutocompleteOptionProps {\n option: AutocompleteOptionType;\n isHighlighted: boolean;\n onSelect: (option: AutocompleteOptionType) => void;\n}\n\n/**\n * AutocompleteOption Component\n *\n * A single option in the autocomplete list.\n */\nconst AutocompleteOption = forwardRef<HTMLDivElement, AutocompleteOptionProps>(\n function AutocompleteOption({ option, isHighlighted, onSelect
|
|
1
|
+
{"version":3,"file":"AutocompleteOption.js","sources":["../../../../../src/ui/components/Autocomplete/AutocompleteOption.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\nimport { getSpacingClass } from \"../../tokens/spacing\";\n\nexport interface AutocompleteOptionType {\n value: string;\n label: string;\n disabled?: boolean;\n icon?: ReactNode;\n group?: string;\n}\n\nexport interface AutocompleteOptionProps {\n option: AutocompleteOptionType;\n isHighlighted: boolean;\n onSelect: (option: AutocompleteOptionType) => void;\n /** Stable id so the combobox input can point aria-activedescendant here. */\n id?: string;\n /**\n * Selection state for multi-select usage. When provided it drives\n * aria-selected; otherwise aria-selected reflects the highlight (the\n * single-select activedescendant model).\n */\n selected?: boolean;\n}\n\n/**\n * AutocompleteOption Component\n *\n * A single option in the autocomplete list.\n */\nconst AutocompleteOption = forwardRef<HTMLDivElement, AutocompleteOptionProps>(\n function AutocompleteOption(\n { option, isHighlighted, onSelect, id, selected },\n ref,\n ) {\n const handleClick = () => {\n if (!option.disabled) {\n onSelect(option);\n }\n };\n\n return (\n <div\n ref={ref}\n id={id}\n role=\"option\"\n aria-selected={selected ?? isHighlighted}\n aria-disabled={option.disabled}\n onClick={handleClick}\n className={`\n flex\n items-center\n ${getSpacingClass(\"sm\", \"gap\")}\n ${getSpacingClass(\"sm\", \"px\")}\n ${getSpacingClass(\"sm\", \"py\")}\n text-sm\n cursor-pointer\n transition-colors\n ${isHighlighted ? \"bg-surface-active\" : \"\"}\n ${option.disabled ? \"opacity-50 cursor-not-allowed\" : \"hover:bg-surface-hover\"}\n `}\n >\n {option.icon && <span className=\"flex-shrink-0\">{option.icon}</span>}\n <span className=\"flex-1\">{option.label}</span>\n </div>\n );\n },\n);\n\nAutocompleteOption.displayName = \"AutocompleteOption\";\n\nexport default AutocompleteOption;\n"],"names":["AutocompleteOption","forwardRef","option","isHighlighted","onSelect","id","selected","ref","handleClick","jsxs","getSpacingClass","jsx"],"mappings":";;;;AAgCA,MAAMA,IAAqBC;AAAA,EACzB,SACE,EAAE,QAAAC,GAAQ,eAAAC,GAAe,UAAAC,GAAU,IAAAC,GAAI,UAAAC,EAAA,GACvCC,GACA;AACA,UAAMC,IAAc,MAAM;AACxB,MAAKN,EAAO,YACVE,EAASF,CAAM;AAAA,IAEnB;AAEA,WACE,gBAAAO;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAAF;AAAA,QACA,IAAAF;AAAA,QACA,MAAK;AAAA,QACL,iBAAeC,KAAA,OAAAA,IAAYH;AAAA,QAC3B,iBAAeD,EAAO;AAAA,QACtB,SAASM;AAAA,QACT,WAAW;AAAA;AAAA;AAAA,YAGPE,EAAgB,MAAM,KAAK,CAAC;AAAA,YAC5BA,EAAgB,MAAM,IAAI,CAAC;AAAA,YAC3BA,EAAgB,MAAM,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,YAI3BP,IAAgB,sBAAsB,EAAE;AAAA,YACxCD,EAAO,WAAW,kCAAkC,wBAAwB;AAAA;AAAA,QAG/E,UAAA;AAAA,UAAAA,EAAO,QAAQ,gBAAAS,EAAC,QAAA,EAAK,WAAU,iBAAiB,YAAO,MAAK;AAAA,UAC7D,gBAAAA,EAAC,QAAA,EAAK,WAAU,UAAU,YAAO,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAG7C;AACF;AAEAX,EAAmB,cAAc;"}
|