@mendylanda/ui 0.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +5 -0
  3. package/README.md +49 -0
  4. package/THIRD_PARTY_LICENSES/shadcn-ui.txt +21 -0
  5. package/THIRD_PARTY_LICENSES/startercn.txt +21 -0
  6. package/dist/class-names.d.ts +1 -0
  7. package/dist/class-names.js +1 -0
  8. package/dist/customization.d.ts +31 -0
  9. package/dist/customization.js +42 -0
  10. package/dist/filters/core.d.ts +3 -0
  11. package/dist/filters/core.js +3 -0
  12. package/dist/filters/filter-bar.d.ts +49 -0
  13. package/dist/filters/filter-bar.js +435 -0
  14. package/dist/filters/filter-date-editor.d.ts +10 -0
  15. package/dist/filters/filter-date-editor.js +31 -0
  16. package/dist/filters/filter-definition.d.ts +220 -0
  17. package/dist/filters/filter-definition.js +227 -0
  18. package/dist/filters/filter-menu-panel.d.ts +21 -0
  19. package/dist/filters/filter-menu-panel.js +164 -0
  20. package/dist/filters/filter-select-editor.d.ts +25 -0
  21. package/dist/filters/filter-select-editor.js +46 -0
  22. package/dist/filters/filter-state.d.ts +34 -0
  23. package/dist/filters/filter-state.js +107 -0
  24. package/dist/filters/filter-text-editor.d.ts +11 -0
  25. package/dist/filters/filter-text-editor.js +34 -0
  26. package/dist/filters/filter-utils.d.ts +1 -0
  27. package/dist/filters/filter-utils.js +8 -0
  28. package/dist/filters/filters.d.ts +38 -0
  29. package/dist/filters/filters.js +94 -0
  30. package/dist/filters/index.d.ts +10 -0
  31. package/dist/filters/index.js +10 -0
  32. package/dist/filters/nuqs.d.ts +1 -0
  33. package/dist/filters/nuqs.js +2 -0
  34. package/dist/filters/use-filter-options.d.ts +24 -0
  35. package/dist/filters/use-filter-options.js +221 -0
  36. package/dist/filters/use-filters.d.ts +78 -0
  37. package/dist/filters/use-filters.js +158 -0
  38. package/dist/filters/use-url-filters.d.ts +41 -0
  39. package/dist/filters/use-url-filters.js +197 -0
  40. package/dist/filters/use-value-draft.d.ts +2 -0
  41. package/dist/filters/use-value-draft.js +16 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +1 -0
  44. package/dist/primitives/button.d.ts +10 -0
  45. package/dist/primitives/button.js +36 -0
  46. package/dist/primitives/calendar.d.ts +8 -0
  47. package/dist/primitives/calendar.js +75 -0
  48. package/dist/primitives/checkbox.d.ts +4 -0
  49. package/dist/primitives/checkbox.js +10 -0
  50. package/dist/primitives/dropdown-menu.d.ts +25 -0
  51. package/dist/primitives/dropdown-menu.js +54 -0
  52. package/dist/primitives/index.d.ts +8 -0
  53. package/dist/primitives/index.js +8 -0
  54. package/dist/primitives/input.d.ts +3 -0
  55. package/dist/primitives/input.js +7 -0
  56. package/dist/primitives/label.d.ts +4 -0
  57. package/dist/primitives/label.js +9 -0
  58. package/dist/primitives/textarea.d.ts +3 -0
  59. package/dist/primitives/textarea.js +7 -0
  60. package/dist/styles.css +1775 -0
  61. package/dist/styles.css.d.ts +1 -0
  62. package/dist/utils.d.ts +3 -0
  63. package/dist/utils.js +19 -0
  64. package/package.json +98 -0
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { Input } from "../customization.js";
5
+ import { Label } from "../customization.js";
6
+ import { Search } from "lucide-react";
7
+ import { DropdownMenuRadioGroup, DropdownMenuRadioItem } from "../primitives/dropdown-menu.js";
8
+ import { FilterCheckboxItem } from "./filters.js";
9
+ function handleSearchKey(event) {
10
+ if (event.key === "ArrowDown") {
11
+ event.preventDefault();
12
+ event.currentTarget
13
+ .closest("[data-slot=filter-options]")
14
+ ?.querySelector('[role^="menuitem"]:not([data-disabled])')
15
+ ?.focus();
16
+ }
17
+ if (event.key !== "Escape" && event.key !== "Tab")
18
+ event.stopPropagation();
19
+ }
20
+ function SearchableOptions({ label, options, searchable, searchPlaceholder, emptyMessage = "No options found.", children, }) {
21
+ const [query, setQuery] = useState("");
22
+ const inputRef = useRef(null);
23
+ useEffect(() => {
24
+ if (!searchable)
25
+ return;
26
+ const frame = requestAnimationFrame(() => inputRef.current?.focus());
27
+ return () => cancelAnimationFrame(frame);
28
+ }, [searchable]);
29
+ const id = useId();
30
+ const filtered = options.filter((option) => option.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
31
+ return (_jsxs("div", { className: "mui-72b37c1a040d mui-81b198a2db84", "data-mendy-ui": "", "data-slot": "filter-options", onKeyDown: (event) => {
32
+ if (event.key === "Tab")
33
+ event.stopPropagation();
34
+ }, children: [searchable && (_jsxs("div", { "data-mendy-ui": "", className: "mui-d2d9e1f13413 mui-bbe39cfb5cc6 mui-b97db4a9f432", children: [_jsx(Search, { className: "mui-33228ff7d583 mui-747355bdc2a2 mui-866c339ea145 mui-4e45f4841abb mui-5dde0fd996f0 mui-100c22d5776b mui-0b5a4d1257ab", strokeWidth: 1.5, "aria-hidden": "true" }), _jsx(Label, { htmlFor: id, className: "mui-32fb090591d9", children: label }), _jsx(Input, { ref: inputRef, id: id, type: "search", className: "mui-75b27e813dcc mui-4f05a238500e", placeholder: searchPlaceholder ?? label, value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: handleSearchKey })] })), filtered.length ? (_jsx("div", { role: "menu", "aria-label": label, className: "mui-1dee6e3ec67d", children: children(filtered) })) : (_jsx("p", { role: "status", className: "mui-094f5333853b mui-c74ab393b96d mui-35f35c41d134", children: emptyMessage }))] }));
35
+ }
36
+ /** Single selection applies immediately and closes the dropdown. */
37
+ export function FilterSelectEditor({ value, onValueChange, ...props }) {
38
+ return (_jsx(SearchableOptions, { ...props, children: (options) => (_jsx(DropdownMenuRadioGroup, { value: value, onValueChange: onValueChange, "aria-label": props.label, children: options.map((option) => (_jsx(DropdownMenuRadioItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))) })) }));
39
+ }
40
+ /** Multiple selections apply immediately and keep the editor open. */
41
+ export function FilterMultiSelectEditor({ values, onValuesChange, ...props }) {
42
+ const selected = new Set(values);
43
+ return (_jsx(SearchableOptions, { ...props, children: (options) => (_jsx("div", { role: "group", "aria-label": props.label, children: options.map((option) => (_jsx(FilterCheckboxItem, { disabled: option.disabled, checked: selected.has(option.value), onCheckedChange: (checked) => onValuesChange(checked
44
+ ? [...new Set([...values, option.value])]
45
+ : values.filter((value) => value !== option.value)), children: option.label }, option.value))) })) }));
46
+ }
@@ -0,0 +1,34 @@
1
+ import type { FilterDefinitions, RuntimeField } from "./filter-definition.js";
2
+ export interface FilterEntry {
3
+ id: string;
4
+ field: RuntimeField;
5
+ value: unknown;
6
+ }
7
+ export interface PasteCandidate {
8
+ id: string;
9
+ label: string;
10
+ value: unknown;
11
+ }
12
+ export interface PasteAmbiguity {
13
+ token: string;
14
+ searchRange?: {
15
+ start: number;
16
+ end: number;
17
+ };
18
+ candidates: PasteCandidate[];
19
+ }
20
+ export interface PasteResult {
21
+ changes: Record<string, unknown>;
22
+ unmatched: string[];
23
+ ambiguous: PasteAmbiguity[];
24
+ }
25
+ export declare function classifyPaste(text: string, entries: readonly FilterEntry[]): PasteResult;
26
+ export declare function initialValues(definitions: FilterDefinitions): Record<string, unknown>;
27
+ export declare function decodeFilters(definitions: FilterDefinitions, params: URLSearchParams): Record<string, unknown>;
28
+ export declare function encodeFilters(definitions: FilterDefinitions, values: Record<string, unknown>, base?: URLSearchParams): URLSearchParams;
29
+ export declare function validateDefinitions(definitions: FilterDefinitions, searchKey: string, markerKey: string): void;
30
+ /** Remove only the occurrence inserted by this paste, retaining pre-existing search text. */
31
+ export declare function resolvePasteAmbiguity(search: string, item: PasteAmbiguity, all: PasteAmbiguity[]): {
32
+ search: string;
33
+ remaining: PasteAmbiguity[];
34
+ };
@@ -0,0 +1,107 @@
1
+ import { equalValues } from "./filter-definition.js";
2
+ export function classifyPaste(text, entries) {
3
+ const result = { changes: {}, unmatched: [], ambiguous: [] };
4
+ const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
5
+ for (const token of new Set(text
6
+ .split(/[\r\n\t,]+/)
7
+ .map((part) => part.trim())
8
+ .filter(Boolean))) {
9
+ const matches = [];
10
+ for (const { id, field } of entries) {
11
+ if (!field.recognize || field.disabled || field.hidden)
12
+ continue;
13
+ try {
14
+ const recognized = field.recognize(token);
15
+ if (recognized === undefined)
16
+ continue;
17
+ const value = field.normalize(recognized);
18
+ if (field.validate(value) || !field.isActive(value))
19
+ continue;
20
+ matches.push({ id, label: field.label, value, priority: field.pastePriority });
21
+ }
22
+ catch {
23
+ /* A rejected token stays in search; recognition is not validation of a request. */
24
+ }
25
+ }
26
+ const priority = Math.max(...matches.map((match) => match.priority));
27
+ const best = matches.filter((match) => match.priority === priority);
28
+ if (best.length === 0)
29
+ result.unmatched.push(token);
30
+ else if (best.length > 1)
31
+ result.ambiguous.push({ token, candidates: best });
32
+ else {
33
+ const match = best[0];
34
+ const entry = entriesById.get(match.id);
35
+ result.changes[match.id] = entry.field.merge(result.changes[match.id] ?? entry.value, match.value);
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ export function initialValues(definitions) {
41
+ return Object.fromEntries(Object.entries(definitions).map(([key, field]) => [key, field.defaultValue]));
42
+ }
43
+ export function decodeFilters(definitions, params) {
44
+ return Object.fromEntries(Object.entries(definitions).map(([key, field]) => {
45
+ const raw = params.get(field.urlKey ?? key);
46
+ if (raw === null)
47
+ return [key, field.defaultValue];
48
+ // Explicit null is distinct from an absent key when a field has an applied default.
49
+ if (raw === "~null")
50
+ return [key, null];
51
+ try {
52
+ const decoded = field.codec.parse(raw.startsWith("~~") ? raw.slice(1) : raw);
53
+ if (decoded === null || field.validate(decoded))
54
+ return [key, field.defaultValue];
55
+ return [key, decoded];
56
+ }
57
+ catch {
58
+ return [key, field.defaultValue];
59
+ }
60
+ }));
61
+ }
62
+ export function encodeFilters(definitions, values, base = new URLSearchParams()) {
63
+ const params = new URLSearchParams(base);
64
+ for (const [key, field] of Object.entries(definitions)) {
65
+ const value = values[key] === undefined ? field.defaultValue : values[key];
66
+ const urlKey = field.urlKey ?? key;
67
+ if (equalValues(value, field.defaultValue))
68
+ params.delete(urlKey);
69
+ else {
70
+ const raw = value === null ? "~null" : field.codec.serialize(value);
71
+ params.set(urlKey, value !== null && raw.startsWith("~") ? `~${raw}` : raw);
72
+ }
73
+ }
74
+ return params;
75
+ }
76
+ export function validateDefinitions(definitions, searchKey, markerKey) {
77
+ const keys = new Set([searchKey, markerKey]);
78
+ if (searchKey === markerKey)
79
+ throw new Error("Search and overflow keys must differ.");
80
+ for (const [id, field] of Object.entries(definitions)) {
81
+ const key = field.urlKey ?? id;
82
+ if (keys.has(key))
83
+ throw new Error(`Duplicate filter URL key: ${key}`);
84
+ keys.add(key);
85
+ }
86
+ }
87
+ /** Remove only the occurrence inserted by this paste, retaining pre-existing search text. */
88
+ export function resolvePasteAmbiguity(search, item, all) {
89
+ const range = item.searchRange;
90
+ const remaining = all.filter((other) => other !== item);
91
+ if (!range || search.slice(range.start, range.end) !== item.token)
92
+ return { search, remaining };
93
+ const start = range.start > 0 && search[range.start - 1] === " " ? range.start - 1 : range.start;
94
+ const end = start === 0 && search[range.end] === " " ? range.end + 1 : range.end;
95
+ return {
96
+ search: search.slice(0, start) + search.slice(end),
97
+ remaining: remaining.map((other) => other.searchRange && other.searchRange.start >= end
98
+ ? {
99
+ ...other,
100
+ searchRange: {
101
+ start: other.searchRange.start - (end - start),
102
+ end: other.searchRange.end - (end - start),
103
+ },
104
+ }
105
+ : other),
106
+ };
107
+ }
@@ -0,0 +1,11 @@
1
+ export interface FilterTextEditorProps {
2
+ label: string;
3
+ defaultValue: string;
4
+ applyLabel?: string;
5
+ commitMode?: "enter" | "apply";
6
+ placeholder?: string;
7
+ onApply: (value: string) => void;
8
+ validate?: (value: string) => string | undefined;
9
+ }
10
+ /** A draft is local to the open editor. Enter commits it by default; explicit button submission is optional. */
11
+ export declare function FilterTextEditor({ label, defaultValue, onApply, validate, applyLabel, commitMode, placeholder, }: FilterTextEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { Button } from "../customization.js";
5
+ import { Label } from "../customization.js";
6
+ import { Textarea } from "../customization.js";
7
+ /** A draft is local to the open editor. Enter commits it by default; explicit button submission is optional. */
8
+ export function FilterTextEditor({ label, defaultValue, onApply, validate, applyLabel = "Apply", commitMode = "enter", placeholder, }) {
9
+ const id = useId();
10
+ const inputRef = useRef(null);
11
+ const [draft, setDraft] = useState(defaultValue);
12
+ const error = validate?.(draft);
13
+ useEffect(() => {
14
+ // Let a parent submenu finish setting up its focus scope first.
15
+ const frame = requestAnimationFrame(() => inputRef.current?.focus());
16
+ return () => cancelAnimationFrame(frame);
17
+ }, []);
18
+ return (_jsxs("div", { "data-mendy-ui": "", className: "mui-37535002f0f7 mui-81b198a2db84 mui-267171770524 mui-094f5333853b", onKeyDown: (event) => {
19
+ if (event.key === "Tab")
20
+ event.stopPropagation();
21
+ }, children: [_jsx(Label, { htmlFor: id, className: "mui-c74ab393b96d mui-daaac3fbf55e", children: label }), _jsx(Textarea, { ref: inputRef, id: id, value: draft, placeholder: placeholder, "aria-invalid": Boolean(error), "aria-describedby": error ? `${id}-error` : undefined, onChange: (event) => setDraft(event.target.value), onKeyDown: (event) => {
22
+ if (commitMode === "enter" &&
23
+ event.key === "Enter" &&
24
+ !event.shiftKey &&
25
+ !event.nativeEvent.isComposing) {
26
+ event.preventDefault();
27
+ if (!error)
28
+ onApply(draft);
29
+ }
30
+ // Let the textarea handle typing and cursor movement, not menu typeahead.
31
+ if (event.key !== "Escape" && event.key !== "Tab")
32
+ event.stopPropagation();
33
+ } }), error && (_jsx("p", { id: `${id}-error`, className: "mui-c74ab393b96d mui-887b9502d5c7", children: error })), commitMode === "apply" ? (_jsx(Button, { type: "button", size: "sm", disabled: Boolean(error), onClick: () => onApply(draft), children: applyLabel })) : (_jsx("p", { className: "mui-3992de70b033 mui-35f35c41d134", children: "Enter to save. Shift+Enter for a new line." }))] }));
34
+ }
@@ -0,0 +1 @@
1
+ export declare function parseFilterValues(value: string): string[];
@@ -0,0 +1,8 @@
1
+ export function parseFilterValues(value) {
2
+ return [
3
+ ...new Set(value
4
+ .split(/[,\n\t]+/)
5
+ .map((part) => part.trim())
6
+ .filter(Boolean)),
7
+ ];
8
+ }
@@ -0,0 +1,38 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuTrigger } from "../primitives/dropdown-menu.js";
3
+ export type FilterEditorProps = ComponentProps<typeof DropdownMenu>;
4
+ export declare function FilterEditor(props: FilterEditorProps): import("react/jsx-runtime").JSX.Element;
5
+ export type FilterChipProps = ComponentProps<"div">;
6
+ export declare function FilterChip({ className, ...props }: FilterChipProps): import("react/jsx-runtime").JSX.Element;
7
+ export type FilterEditorTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;
8
+ export declare function FilterEditorTrigger({ className, asChild, ...props }: FilterEditorTriggerProps): import("react/jsx-runtime").JSX.Element;
9
+ export type FilterEditorContentProps = ComponentProps<typeof DropdownMenuContent>;
10
+ export declare function FilterEditorContent({ className, onClick, ...props }: FilterEditorContentProps): import("react/jsx-runtime").JSX.Element;
11
+ export type FilterRemoveProps = ComponentProps<"button"> & {
12
+ "aria-label": string;
13
+ };
14
+ export declare function FilterRemove({ className, children, ...props }: FilterRemoveProps): import("react/jsx-runtime").JSX.Element;
15
+ export type AppliedFilterProps = FilterChipProps & {
16
+ label: string;
17
+ editor?: ReactNode;
18
+ onRemove?: () => void;
19
+ disabled?: boolean;
20
+ open?: boolean;
21
+ onOpenChange?: (open: boolean) => void;
22
+ editLabel?: string;
23
+ removeLabel?: string;
24
+ contentProps?: FilterEditorContentProps;
25
+ triggerProps?: FilterEditorTriggerProps;
26
+ removeProps?: Omit<FilterRemoveProps, "aria-label" | "onClick">;
27
+ };
28
+ /** Editing and removal are independent. Values remain owned by the caller. */
29
+ export declare function AppliedFilter({ label, editor, onRemove, disabled, open, onOpenChange, editLabel, removeLabel, contentProps, triggerProps, removeProps, children, ...props }: AppliedFilterProps): import("react/jsx-runtime").JSX.Element;
30
+ export type FilterMenuItemProps = ComponentProps<typeof DropdownMenuSub> & {
31
+ label: ReactNode;
32
+ icon?: ReactNode;
33
+ contentProps?: ComponentProps<typeof DropdownMenuSubContent>;
34
+ };
35
+ export declare function FilterMenuItem({ label, icon, children, contentProps, open, defaultOpen, onOpenChange, ...props }: FilterMenuItemProps): import("react/jsx-runtime").JSX.Element;
36
+ export type FilterCheckboxItemProps = ComponentProps<typeof DropdownMenuCheckboxItem>;
37
+ /** Keep the editor open after pointer or keyboard selection. */
38
+ export declare function FilterCheckboxItem({ onSelect, ...props }: FilterCheckboxItemProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { XIcon } from "lucide-react";
5
+ import { Button } from "../customization.js";
6
+ import { cn } from "../utils.js";
7
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "../primitives/dropdown-menu.js";
8
+ export function FilterEditor(props) {
9
+ return _jsx(DropdownMenu, { modal: false, ...props });
10
+ }
11
+ export function FilterChip({ className, ...props }) {
12
+ return (_jsx("div", { "data-mendy-ui": "", "data-slot": "filter-chip", className: cn("mui-123306850fe1 mui-539ae144f076 mui-81b198a2db84 mui-d5111d0e9f48 mui-3fa8c572949b mui-71556df3b421 mui-d191cb569aac mui-c74ab393b96d mui-35f35c41d134 mui-5e49f8678d00 mui-3bc474178684 motion-safe:slide-in-from-bottom-2.5 mui-b225f0361d28 mui-bc94a66158a3", className), ...props }));
13
+ }
14
+ export function FilterEditorTrigger({ className, asChild, ...props }) {
15
+ return (_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { asChild: asChild, variant: "ghost", "data-mendy-ui": "", "data-slot": "filter-editor-trigger", "aria-haspopup": "dialog", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-66b490dd6005 mui-52417259a4f5 mui-52101fc7d8bb mui-184ddc11e5f9 mui-71556df3b421 mui-70007b876865 mui-e7e01cc7f4df mui-1a5d10977b93 mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6 mui-4027d9c589b6 mui-6f73a280432e", className), ...props }) }));
16
+ }
17
+ export function FilterEditorContent({ className, onClick, ...props }) {
18
+ return (_jsx(DropdownMenuContent, { "data-mendy-ui": "", "data-slot": "filter-editor-content", role: "dialog", "aria-orientation": undefined, align: "start", sideOffset: 6, loop: true, onClick: (event) => {
19
+ event.stopPropagation();
20
+ onClick?.(event);
21
+ }, className: cn("mui-dcd942c18ddc mui-34055e6f8c1b mui-2368e909f3a5 mui-04760bcd507f mui-37d8a3f448ff", className), ...props }));
22
+ }
23
+ export function FilterRemove({ className, children, ...props }) {
24
+ return (_jsx(Button, { variant: "ghost", type: "button", "data-mendy-ui": "", "data-slot": "filter-remove", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-52417259a4f5 mui-04760bcd507f mui-8867d85ed290 mui-27ead27a81df mui-71556df3b421 mui-a503dd374cca mui-8d940fccb093 mui-163a623b3716 mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6", className), ...props, children: children ?? _jsx(XIcon, { className: "mui-61c000c8a98a", "aria-hidden": "true" }) }));
25
+ }
26
+ /** Editing and removal are independent. Values remain owned by the caller. */
27
+ export function AppliedFilter({ label, editor, onRemove, disabled, open, onOpenChange, editLabel = `Edit ${label} filter`, removeLabel = `Remove ${label} filter`, contentProps, triggerProps, removeProps, children, ...props }) {
28
+ return (_jsx(FilterEditor, { open: open, onOpenChange: onOpenChange, children: _jsxs(FilterChip, { ...props, children: [editor ? (_jsx(FilterEditorTrigger, { disabled: disabled, "aria-label": editLabel, ...triggerProps, onClick: (event) => {
29
+ event.stopPropagation();
30
+ triggerProps?.onClick?.(event);
31
+ }, children: children })) : (_jsx("span", { className: "mui-184ddc11e5f9 mui-e7e01cc7f4df", children: children })), onRemove && (_jsx(FilterRemove, { ...removeProps, disabled: disabled, "aria-label": removeLabel, onClick: (event) => {
32
+ event.stopPropagation();
33
+ onRemove();
34
+ } })), editor && (_jsx(FilterEditorContent, { "aria-label": `${label} filter`, ...contentProps, children: editor }))] }) }));
35
+ }
36
+ export function FilterMenuItem({ label, icon, children, contentProps, open, defaultOpen, onOpenChange, ...props }) {
37
+ const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);
38
+ const triggerRef = useRef(null);
39
+ const [sideOffset, setSideOffset] = useState(14);
40
+ const isOpen = open ?? internalOpen;
41
+ useEffect(() => {
42
+ const trigger = triggerRef.current;
43
+ if (!trigger)
44
+ return;
45
+ const update = () => {
46
+ // Overlap the parent menu when there isn't room for two panels side by side.
47
+ const contentId = trigger.getAttribute("aria-controls");
48
+ const content = contentId ? document.getElementById(contentId) : null;
49
+ if (!content)
50
+ return;
51
+ const bounds = trigger.getBoundingClientRect();
52
+ const available = Math.max(bounds.left, window.innerWidth - bounds.right);
53
+ setSideOffset(available < content.offsetWidth + 14 ? -bounds.width : 14);
54
+ };
55
+ const observer = new ResizeObserver(update);
56
+ observer.observe(trigger);
57
+ window.addEventListener("resize", update);
58
+ update();
59
+ return () => {
60
+ observer.disconnect();
61
+ window.removeEventListener("resize", update);
62
+ };
63
+ }, [isOpen]);
64
+ return (_jsxs(DropdownMenuSub, { ...props, open: isOpen, onOpenChange: (nextOpen) => {
65
+ setInternalOpen(nextOpen);
66
+ onOpenChange?.(nextOpen);
67
+ }, children: [_jsxs(DropdownMenuSubTrigger, { ref: triggerRef, "aria-haspopup": contentProps?.role === "dialog" ? "dialog" : "menu", onKeyDown: (event) => {
68
+ const forward = getComputedStyle(event.currentTarget).direction === "rtl" ? "ArrowLeft" : "ArrowRight";
69
+ if (!isOpen && [forward, "Enter", " "].includes(event.key)) {
70
+ const contentId = event.currentTarget.getAttribute("aria-controls");
71
+ // A submenu reopened during its exit animation is still inert until
72
+ // React commits the open state. Restore keyboard entry after that commit.
73
+ requestAnimationFrame(() => {
74
+ const content = contentId ? document.getElementById(contentId) : null;
75
+ const input = content?.querySelector('input[type="search"], textarea');
76
+ (input ?? content)?.focus();
77
+ });
78
+ }
79
+ }, children: [icon, _jsx("span", { children: label })] }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { loop: true, sideOffset: sideOffset, alignOffset: -4, ...contentProps, onFocusOutside: (event) => {
80
+ contentProps?.onFocusOutside?.(event);
81
+ // On narrow screens, leaving the row can focus its parent menu while
82
+ // the pointer crosses into the overlapping submenu.
83
+ if (event.target === triggerRef.current?.closest("[data-radix-menu-content]")) {
84
+ event.preventDefault();
85
+ }
86
+ }, inert: !isOpen || contentProps?.inert, "aria-hidden": !isOpen || contentProps?.["aria-hidden"], ...(contentProps?.["aria-label"] ? { "aria-labelledby": undefined } : {}), className: cn("mui-dcd942c18ddc mui-34055e6f8c1b mui-2368e909f3a5 mui-04760bcd507f mui-237747164b60", contentProps?.className), children: children }) })] }));
87
+ }
88
+ /** Keep the editor open after pointer or keyboard selection. */
89
+ export function FilterCheckboxItem({ onSelect, ...props }) {
90
+ return (_jsx(DropdownMenuCheckboxItem, { ...props, onSelect: (event) => {
91
+ onSelect?.(event);
92
+ event.preventDefault();
93
+ } }));
94
+ }
@@ -0,0 +1,10 @@
1
+ export * from "./filter-definition.js";
2
+ export * from "./filter-state.js";
3
+ export * from "./filter-utils.js";
4
+ export * from "./use-filters.js";
5
+ export * from "./use-filter-options.js";
6
+ export * from "./filter-bar.js";
7
+ export * from "./filters.js";
8
+ export * from "./filter-text-editor.js";
9
+ export * from "./filter-select-editor.js";
10
+ export * from "../customization.js";
@@ -0,0 +1,10 @@
1
+ export * from "./filter-definition.js";
2
+ export * from "./filter-state.js";
3
+ export * from "./filter-utils.js";
4
+ export * from "./use-filters.js";
5
+ export * from "./use-filter-options.js";
6
+ export * from "./filter-bar.js";
7
+ export * from "./filters.js";
8
+ export * from "./filter-text-editor.js";
9
+ export * from "./filter-select-editor.js";
10
+ export * from "../customization.js";
@@ -0,0 +1 @@
1
+ export * from "./use-url-filters.js";
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export * from "./use-url-filters.js";
@@ -0,0 +1,24 @@
1
+ import type { Choice, OptionPage, RuntimeField } from "./filter-definition.js";
2
+ interface Request {
3
+ promise: Promise<OptionPage<Choice>>;
4
+ abort: AbortController;
5
+ users: number;
6
+ settled: boolean;
7
+ created: number;
8
+ }
9
+ export type OptionCache = Map<string, Request>;
10
+ export declare const FilterOptionCache: import("react").Context<OptionCache | null>;
11
+ export declare function selectedIds(value: unknown): string[];
12
+ export declare function useFilterOptions(id: string, field: RuntimeField, value: unknown, enabled: boolean): {
13
+ query: string;
14
+ setQuery(next: string): void;
15
+ items: readonly Choice[];
16
+ selected: Choice[];
17
+ loading: boolean;
18
+ resolving: boolean;
19
+ error: string | null | undefined;
20
+ retry(): void;
21
+ hasMore: boolean;
22
+ loadMore(): void;
23
+ };
24
+ export {};