@ctrl-ui/react 0.8.6 → 0.8.7
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/action-bar.js +1 -1
- package/dist/activity.js +1 -1
- package/dist/blocks/coding-agent.js +16 -19
- package/dist/blocks/design-canvas-data.d.ts +32 -0
- package/dist/blocks/design-canvas-data.js +100 -0
- package/dist/blocks/design-canvas-inspector.d.ts +8 -0
- package/dist/blocks/design-canvas-inspector.js +238 -0
- package/dist/blocks/design-canvas.d.ts +17 -0
- package/dist/blocks/design-canvas.js +346 -0
- package/dist/blocks/file-explorer.js +2 -2
- package/dist/blocks/settings.js +2 -2
- package/dist/chat-composer-attachment.d.ts +9 -3
- package/dist/chat-composer-attachment.js +27 -10
- package/dist/chat-composer-editor.js +20 -4
- package/dist/chat-composer.d.ts +1 -1
- package/dist/chat-composer.js +4 -3
- package/dist/chat-layout.js +32 -19
- package/dist/context.js +1 -1
- package/dist/dynamic-notification.js +1 -1
- package/dist/environment-variables.js +1 -1
- package/dist/filter-bar/context.d.ts +32 -0
- package/dist/filter-bar/context.js +11 -0
- package/dist/filter-bar/editor.d.ts +14 -0
- package/dist/filter-bar/editor.js +204 -0
- package/dist/filter-bar/model.d.ts +9 -0
- package/dist/filter-bar/model.js +48 -0
- package/dist/filter-bar/motion.d.ts +10 -0
- package/dist/filter-bar/motion.js +84 -0
- package/dist/filter-bar/parts.d.ts +19 -0
- package/dist/filter-bar/parts.js +233 -0
- package/dist/filter-bar/types.d.ts +53 -0
- package/dist/filter-bar/types.js +0 -0
- package/dist/filter-bar.d.ts +27 -0
- package/dist/filter-bar.js +304 -0
- package/dist/hooks/use-chat-composer.d.ts +3 -1
- package/dist/hooks/use-chat-composer.js +2 -2
- package/dist/hooks/use-dropzone.d.ts +7 -4
- package/dist/hooks/use-dropzone.js +34 -3
- package/dist/inline-attachment.js +8 -14
- package/dist/inline-citation.js +1 -1
- package/dist/knob-contracts/chat-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/chat-layout-knobs.js +2 -1
- package/dist/knob-contracts/choice-knobs.d.ts +1 -1
- package/dist/knob-contracts/choice-knobs.js +1 -0
- package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
- package/dist/knob-contracts/collapsible-knobs.js +1 -1
- package/dist/knob-contracts/filter-bar-knobs.d.ts +4 -0
- package/dist/knob-contracts/filter-bar-knobs.js +16 -0
- package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
- package/dist/knob-contracts/infinite-canvas-knobs.js +3 -0
- package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/page-layout-knobs.js +3 -1
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +1 -0
- package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
- package/dist/knob-contracts/table-of-contents-knobs.js +3 -5
- package/dist/lib/phone-input-format.js +1 -1
- package/dist/markdown-block.js +11 -7
- package/dist/scripts/contrast-eval.js +5 -4
- package/dist/styles/index.css +5 -4
- package/dist/styles/recipes/button-group.css +1 -1
- package/dist/styles/recipes/button.css +2 -2
- package/dist/styles/recipes/chat-composer.css +14 -6
- package/dist/styles/recipes/chat-layout.css +29 -2
- package/dist/styles/recipes/choice.css +10 -3
- package/dist/styles/recipes/code-diff.css +16 -4
- package/dist/styles/recipes/code.css +11 -5
- package/dist/styles/recipes/collapsible.css +37 -0
- package/dist/styles/recipes/color-picker.css +12 -2
- package/dist/styles/recipes/dockable-panel.css +11 -3
- package/dist/styles/recipes/field-parts.css +62 -0
- package/dist/styles/recipes/field.css +28 -6
- package/dist/styles/recipes/filter-bar.css +174 -0
- package/dist/styles/recipes/infinite-canvas.css +51 -3
- package/dist/styles/recipes/inline-attachment.css +6 -1
- package/dist/styles/recipes/page-layout.css +18 -0
- package/dist/styles/recipes/pagination.css +1 -1
- package/dist/styles/recipes/popup-controls.css +16 -0
- package/dist/styles/recipes/popup-drawer.css +34 -32
- package/dist/styles/recipes/popup-lists.css +11 -2
- package/dist/styles/recipes/popup.css +10 -12
- package/dist/styles/recipes/range.css +1 -1
- package/dist/styles/recipes/resizable.css +34 -0
- package/dist/styles/recipes/spinner.css +4 -0
- package/dist/styles/recipes/table-of-contents.css +42 -42
- package/dist/styles/recipes/tabs.css +21 -0
- package/dist/styles/recipes/track-highlight.css +2 -1
- package/dist/styles/recipes/transcript-divider.css +2 -2
- package/dist/styles/theme.css +250 -91
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/autocomplete.js +5 -1
- package/dist/ui/button.js +2 -2
- package/dist/ui/checkbox-group.js +1 -1
- package/dist/ui/code-diff.js +1 -1
- package/dist/ui/code.js +3 -3
- package/dist/ui/collapsible.js +1 -1
- package/dist/ui/color-picker.js +1 -1
- package/dist/ui/combobox-disabled-selection.d.ts +1 -1
- package/dist/ui/combobox-disabled-selection.js +2 -1
- package/dist/ui/combobox.d.ts +5 -23
- package/dist/ui/combobox.js +26 -14
- package/dist/ui/command.js +9 -1
- package/dist/ui/context-menu.js +1 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dockable-panel.js +18 -10
- package/dist/ui/drawer.d.ts +7 -4
- package/dist/ui/drawer.js +36 -23
- package/dist/ui/dropdown-menu.js +1 -1
- package/dist/ui/dropzone.d.ts +3 -1
- package/dist/ui/dropzone.js +3 -1
- package/dist/ui/field.js +1 -1
- package/dist/ui/hover-card.js +1 -1
- package/dist/ui/infinite-canvas.d.ts +11 -0
- package/dist/ui/infinite-canvas.js +82 -7
- package/dist/ui/input-group.d.ts +2 -1
- package/dist/ui/input-group.js +12 -3
- package/dist/ui/input.js +9 -0
- package/dist/ui/item.js +1 -1
- package/dist/ui/markdown-elements.js +11 -8
- package/dist/ui/menubar.js +1 -1
- package/dist/ui/morphing-panel.js +1 -1
- package/dist/ui/navigation-menu.js +1 -1
- package/dist/ui/number-field.d.ts +1 -1
- package/dist/ui/number-field.js +13 -2
- package/dist/ui/phone-input.js +2 -2
- package/dist/ui/popover.js +1 -1
- package/dist/ui/radio-group.d.ts +1 -0
- package/dist/ui/radio-group.js +11 -1
- package/dist/ui/resizable.d.ts +18 -1
- package/dist/ui/resizable.js +127 -7
- package/dist/ui/responsive-dialog.js +6 -3
- package/dist/ui/rich-tooltip.js +1 -1
- package/dist/ui/scroll-area.d.ts +4 -1
- package/dist/ui/scroll-area.js +2 -1
- package/dist/ui/select.d.ts +2 -1
- package/dist/ui/select.js +58 -33
- package/dist/ui/sheet.js +1 -1
- package/dist/ui/sidebar-menu.js +1 -1
- package/dist/ui/sidebar-provider.js +2 -2
- package/dist/ui/sidebar-resize-rail.js +1 -1
- package/dist/ui/sidebar.js +1 -1
- package/dist/ui/table-of-contents.d.ts +4 -3
- package/dist/ui/table-of-contents.js +126 -47
- package/dist/ui/table.js +11 -7
- package/dist/ui/toast.js +1 -1
- package/dist/ui/toggle.d.ts +1 -1
- package/dist/ui/toggle.js +2 -1
- package/dist/ui/tooltip.js +1 -1
- package/dist/ui/tree.js +1 -1
- package/dist/ui/trigger-menu.js +7 -3
- package/dist/user-ask.js +1 -1
- package/package.json +1 -1
package/dist/context.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "./lib/cn.js";
|
|
3
3
|
import { Button } from "./ui/button.js";
|
|
4
|
+
import { Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger } from "./ui/popover.js";
|
|
4
5
|
import { ScrollArea } from "./ui/scroll-area.js";
|
|
5
6
|
import { deriveContextModel } from "./context-model.js";
|
|
6
|
-
import { Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger } from "./ui/popover.js";
|
|
7
7
|
import { createContext, useContext } from "react";
|
|
8
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { X } from "lucide-react";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "./lib/cn.js";
|
|
3
3
|
import { Button } from "./ui/button.js";
|
|
4
|
-
import { createDynamicNotificationGlass } from "./dynamic-notification-glass.js";
|
|
5
4
|
import { createDynamicNotificationLiquid } from "./dynamic-notification-liquid.js";
|
|
5
|
+
import { createDynamicNotificationGlass } from "./dynamic-notification-glass.js";
|
|
6
6
|
import { useDynamicNotification } from "./hooks/use-dynamic-notification.js";
|
|
7
7
|
import { createContext, useContext, useEffect, useId, useMemo, useRef } from "react";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -3,8 +3,8 @@ import { cn } from "./lib/cn.js";
|
|
|
3
3
|
import { Button } from "./ui/button.js";
|
|
4
4
|
import { DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE, collectEnvironmentVariables, rowsToEnvFileText } from "./lib/env-file.js";
|
|
5
5
|
import { useEnvironmentVariables } from "./hooks/use-environment-variables.js";
|
|
6
|
-
import { Input } from "./ui/input.js";
|
|
7
6
|
import { InputGroup, InputGroupAddon, InputGroupInput } from "./ui/input-group.js";
|
|
7
|
+
import { Input } from "./ui/input.js";
|
|
8
8
|
import { createContext, use, useId } from "react";
|
|
9
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { AlertTriangleIcon, EyeIcon, EyeOffIcon, FileUpIcon, PlusIcon, RotateCcwIcon, SaveIcon, Trash2Icon } from "lucide-react";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { FilterBarDraft, FilterBarField, FilterBarItem, FilterBarOperator, FilterBarScalar, FilterBarStage, FilterBarValue } from "./types.js";
|
|
2
|
+
import { CSSProperties, KeyboardEvent } from "react";
|
|
3
|
+
//#region src/filter-bar/context.d.ts
|
|
4
|
+
export type FilterBarExit = {
|
|
5
|
+
item: FilterBarItem;
|
|
6
|
+
style: CSSProperties;
|
|
7
|
+
};
|
|
8
|
+
export type FilterBarContextValue = {
|
|
9
|
+
fields: readonly FilterBarField[];
|
|
10
|
+
operators: readonly FilterBarOperator[];
|
|
11
|
+
items: readonly FilterBarItem[];
|
|
12
|
+
draft: FilterBarDraft | null;
|
|
13
|
+
exits: FilterBarExit[];
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
readOnly: boolean;
|
|
16
|
+
register: (key: string, element: HTMLElement | null) => void;
|
|
17
|
+
start: (owner: string, anchor: HTMLElement, stage?: FilterBarStage, item?: FilterBarItem, query?: string) => void;
|
|
18
|
+
close: (restoreFocus?: boolean) => void;
|
|
19
|
+
back: () => void;
|
|
20
|
+
setQuery: (query: string) => void;
|
|
21
|
+
selectField: (fieldId: string) => void;
|
|
22
|
+
selectOperator: (operatorId: string) => void;
|
|
23
|
+
selectValues: (values: FilterBarScalar[]) => void;
|
|
24
|
+
commit: (value: FilterBarValue) => void;
|
|
25
|
+
remove: (item: FilterBarItem) => void;
|
|
26
|
+
clear: () => void;
|
|
27
|
+
releaseExit: (id: string) => void;
|
|
28
|
+
navigate: (event: KeyboardEvent<HTMLElement>, itemId?: string, part?: string) => void;
|
|
29
|
+
};
|
|
30
|
+
export declare const FilterBarContext: import("react").Context<FilterBarContextValue | null>;
|
|
31
|
+
export declare function useFilterBarContext(): FilterBarContextValue;
|
|
32
|
+
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/filter-bar/context.tsx
|
|
4
|
+
const FilterBarContext = createContext(null);
|
|
5
|
+
function useFilterBarContext() {
|
|
6
|
+
const context = use(FilterBarContext);
|
|
7
|
+
if (!context) throw new Error("FilterBar parts must be used inside FilterBar.Root.");
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { FilterBarContext, useFilterBarContext };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ComboboxInputProps } from "../ui/combobox.js";
|
|
2
|
+
import { ReactNode, RefObject } from "react";
|
|
3
|
+
//#region src/filter-bar/editor.d.ts
|
|
4
|
+
export declare function FilterBarEditor({ owner, mode, anchorRef, children }: {
|
|
5
|
+
owner: string;
|
|
6
|
+
mode: "input" | "button";
|
|
7
|
+
anchorRef?: RefObject<HTMLInputElement | null>;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export declare function FilterBarInlineInput({ owner, inputRef, className, ...props }: Omit<ComboboxInputProps, "ref"> & {
|
|
11
|
+
owner: string;
|
|
12
|
+
inputRef: RefObject<HTMLInputElement | null>;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
//#endregion
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "../ui/button.js";
|
|
3
|
+
import { useFilterBarContext } from "./context.js";
|
|
4
|
+
import { allowsCustomValue, fieldOperators, fieldOptions, parseCustomValue } from "./model.js";
|
|
5
|
+
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "../ui/combobox.js";
|
|
6
|
+
import { useRef } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/filter-bar/editor.tsx
|
|
9
|
+
function valueChoice(value, label) {
|
|
10
|
+
return {
|
|
11
|
+
id: `${typeof value}:${value}`,
|
|
12
|
+
value,
|
|
13
|
+
label
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function sameChoice(first, second) {
|
|
17
|
+
return first.id === second.id;
|
|
18
|
+
}
|
|
19
|
+
function valueChoices(field, query) {
|
|
20
|
+
const choices = fieldOptions(field).map((option) => ({
|
|
21
|
+
...valueChoice(option.value, option.label),
|
|
22
|
+
icon: option.icon,
|
|
23
|
+
disabled: option.disabled
|
|
24
|
+
}));
|
|
25
|
+
const customValue = allowsCustomValue(field) ? parseCustomValue(field, query) : void 0;
|
|
26
|
+
if (customValue !== void 0 && !choices.some((choice) => choice.value === customValue)) choices.push(valueChoice(customValue, `Use “${customValue}”`));
|
|
27
|
+
return choices;
|
|
28
|
+
}
|
|
29
|
+
function editorChoices(context, draft) {
|
|
30
|
+
if (!draft || draft.stage === "field") return context.fields.filter((candidate) => fieldOperators(candidate, context.operators).length > 0).map((candidate) => ({
|
|
31
|
+
id: candidate.id,
|
|
32
|
+
label: candidate.label,
|
|
33
|
+
icon: candidate.icon
|
|
34
|
+
}));
|
|
35
|
+
const field = context.fields.find((candidate) => candidate.id === draft.fieldId);
|
|
36
|
+
if (!field) return [];
|
|
37
|
+
if (draft.stage === "operator") return fieldOperators(field, context.operators).map((operator) => ({
|
|
38
|
+
id: operator.id,
|
|
39
|
+
label: operator.label
|
|
40
|
+
}));
|
|
41
|
+
return valueChoices(field, draft.query);
|
|
42
|
+
}
|
|
43
|
+
function searchLabel(stage, field) {
|
|
44
|
+
if (stage === "field") return "Filter field";
|
|
45
|
+
if (stage === "operator") return "Filter operator";
|
|
46
|
+
return `${field?.label ?? "Filter"} value`;
|
|
47
|
+
}
|
|
48
|
+
function handleSelection(context, draft, choice) {
|
|
49
|
+
if (!choice || !draft) return;
|
|
50
|
+
const field = context.fields.find((candidate) => candidate.id === draft.fieldId);
|
|
51
|
+
if (draft.stage === "value" && (field?.loading || field?.error)) return;
|
|
52
|
+
if (Array.isArray(choice)) context.selectValues(choice.flatMap((selected) => selected.value === void 0 ? [] : [selected.value]));
|
|
53
|
+
else if (draft.stage === "field") context.selectField(choice.id);
|
|
54
|
+
else if (draft.stage === "operator") context.selectOperator(choice.id);
|
|
55
|
+
else if (choice.value !== void 0) context.commit(choice.value);
|
|
56
|
+
}
|
|
57
|
+
function handleEditorKeyDown(context, draft, event) {
|
|
58
|
+
if (event.nativeEvent.isComposing) return false;
|
|
59
|
+
const backwards = event.key === "Escape" || event.key === "Backspace" && draft?.query === "";
|
|
60
|
+
if (draft && backwards) {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
context.back();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const operator = context.operators.find((candidate) => candidate.id === draft?.operatorId);
|
|
67
|
+
const field = context.fields.find((candidate) => candidate.id === draft?.fieldId);
|
|
68
|
+
if (!(draft?.stage === "value" && operator?.arity === "many" && event.key === "Enter" && (event.metaKey || event.ctrlKey)) || !draft || draft.values.length === 0 || field?.loading || field?.error) return false;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
context.commit(draft.values);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function EditorOptions({ field, invalidNumber }) {
|
|
74
|
+
if (field?.loading || field?.error) return /* @__PURE__ */ jsx("div", {
|
|
75
|
+
className: "p-3 text-caption",
|
|
76
|
+
role: field.error ? "alert" : "status",
|
|
77
|
+
children: field.error ?? "Loading values…"
|
|
78
|
+
});
|
|
79
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: invalidNumber ? "Enter a finite number." : "No matching options." }), /* @__PURE__ */ jsx(ComboboxList, { children: (choice) => /* @__PURE__ */ jsx(ComboboxItem, {
|
|
80
|
+
value: choice,
|
|
81
|
+
disabled: choice.disabled,
|
|
82
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
83
|
+
className: "flex min-w-0 items-center gap-2",
|
|
84
|
+
children: [choice.icon, /* @__PURE__ */ jsx("span", {
|
|
85
|
+
className: "truncate",
|
|
86
|
+
children: choice.label
|
|
87
|
+
})]
|
|
88
|
+
})
|
|
89
|
+
}, choice.id) })] });
|
|
90
|
+
}
|
|
91
|
+
function handleOpenChange(context, draft, anchorRef, owner, open, eventDetails) {
|
|
92
|
+
if (open && !draft && anchorRef?.current && eventDetails.reason !== "input-change") context.start(owner, anchorRef.current);
|
|
93
|
+
if (open || !draft) return;
|
|
94
|
+
if (eventDetails.reason === "item-press" || eventDetails.reason === "escape-key") eventDetails.cancel();
|
|
95
|
+
else context.close(false);
|
|
96
|
+
}
|
|
97
|
+
function editorView(context, draft) {
|
|
98
|
+
const field = context.fields.find((candidate) => candidate.id === draft?.fieldId);
|
|
99
|
+
const operator = context.operators.find((candidate) => candidate.id === draft?.operatorId);
|
|
100
|
+
const stage = draft?.stage ?? "field";
|
|
101
|
+
const many = stage === "value" && operator?.arity === "many";
|
|
102
|
+
const query = draft?.query ?? "";
|
|
103
|
+
const choices = editorChoices(context, draft);
|
|
104
|
+
const selected = (draft?.values ?? []).map((value) => choices.find((choice) => choice.value === value) ?? valueChoice(value, field?.formatValue?.(value) ?? String(value)));
|
|
105
|
+
const customNumber = field?.type === "number" && allowsCustomValue(field);
|
|
106
|
+
return {
|
|
107
|
+
field,
|
|
108
|
+
stage,
|
|
109
|
+
many,
|
|
110
|
+
query,
|
|
111
|
+
choices,
|
|
112
|
+
selected,
|
|
113
|
+
invalidNumber: stage === "value" && Boolean(customNumber) && query.trim() !== "" && field !== void 0 && parseCustomValue(field, query) === void 0,
|
|
114
|
+
remote: stage === "value" && field?.optionsMode === "remote",
|
|
115
|
+
unavailable: stage === "value" && Boolean(field?.loading || field?.error),
|
|
116
|
+
label: searchLabel(stage, field)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function FilterBarEditor({ owner, mode, anchorRef, children }) {
|
|
120
|
+
const context = useFilterBarContext();
|
|
121
|
+
const searchRef = useRef(null);
|
|
122
|
+
const draft = context.draft?.owner === owner ? context.draft : null;
|
|
123
|
+
const { field, stage, many, query, choices, selected, invalidNumber, remote, unavailable, label } = editorView(context, draft);
|
|
124
|
+
const canCommitMany = selected.length > 0 && !unavailable && !context.disabled && !context.readOnly;
|
|
125
|
+
return /* @__PURE__ */ jsxs(Combobox, {
|
|
126
|
+
items: choices,
|
|
127
|
+
multiple: many,
|
|
128
|
+
value: many ? selected : null,
|
|
129
|
+
inputValue: query,
|
|
130
|
+
open: draft !== null,
|
|
131
|
+
disabled: context.disabled,
|
|
132
|
+
readOnly: context.readOnly,
|
|
133
|
+
modal: false,
|
|
134
|
+
isItemEqualToValue: sameChoice,
|
|
135
|
+
filter: remote ? null : void 0,
|
|
136
|
+
onValueChange: (choice) => handleSelection(context, draft, choice),
|
|
137
|
+
onInputValueChange: (nextQuery, eventDetails) => {
|
|
138
|
+
if (eventDetails.reason !== "input-change" && eventDetails.reason !== "input-clear") return;
|
|
139
|
+
if (draft) context.setQuery(nextQuery);
|
|
140
|
+
else if (anchorRef?.current) context.start(owner, anchorRef.current, "field", void 0, nextQuery);
|
|
141
|
+
},
|
|
142
|
+
onOpenChange: (open, eventDetails) => handleOpenChange(context, draft, anchorRef, owner, open, eventDetails),
|
|
143
|
+
children: [children, /* @__PURE__ */ jsxs(ComboboxContent, {
|
|
144
|
+
anchor: draft?.anchor,
|
|
145
|
+
initialFocus: mode === "input" ? false : searchRef,
|
|
146
|
+
finalFocus: false,
|
|
147
|
+
className: "w-72 max-h-(--available-height) overflow-y-auto",
|
|
148
|
+
onKeyDown: (event) => {
|
|
149
|
+
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
context.back();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
mode === "button" && /* @__PURE__ */ jsx("div", {
|
|
156
|
+
className: "p-2",
|
|
157
|
+
children: /* @__PURE__ */ jsx(ComboboxInput, {
|
|
158
|
+
ref: searchRef,
|
|
159
|
+
size: "sm",
|
|
160
|
+
"aria-label": label,
|
|
161
|
+
placeholder: label,
|
|
162
|
+
inputMode: field?.type === "number" && stage === "value" ? "decimal" : void 0,
|
|
163
|
+
onKeyDown: (event) => handleEditorKeyDown(context, draft, event)
|
|
164
|
+
})
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ jsx(EditorOptions, {
|
|
167
|
+
field: stage === "value" ? field : void 0,
|
|
168
|
+
invalidNumber
|
|
169
|
+
}),
|
|
170
|
+
many && /* @__PURE__ */ jsx("div", {
|
|
171
|
+
className: "flex justify-end p-2",
|
|
172
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
173
|
+
disabled: !canCommitMany,
|
|
174
|
+
onClick: () => context.commit(draft?.values ?? []),
|
|
175
|
+
children: "Done"
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
})]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function FilterBarInlineInput({ owner, inputRef, className, ...props }) {
|
|
183
|
+
const context = useFilterBarContext();
|
|
184
|
+
const draft = context.draft?.owner === owner ? context.draft : null;
|
|
185
|
+
function handleKeyDown(event) {
|
|
186
|
+
props.onKeyDown?.(event);
|
|
187
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
188
|
+
if (handleEditorKeyDown(context, draft, event) || event.currentTarget.value !== "") return;
|
|
189
|
+
const last = context.items.at(-1);
|
|
190
|
+
if (event.key === "Backspace" && !draft && last) {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
context.remove(last);
|
|
193
|
+
} else context.navigate(event);
|
|
194
|
+
}
|
|
195
|
+
return /* @__PURE__ */ jsx(ComboboxInput, {
|
|
196
|
+
ref: inputRef,
|
|
197
|
+
size: "sm",
|
|
198
|
+
className,
|
|
199
|
+
...props,
|
|
200
|
+
onKeyDown: handleKeyDown
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { FilterBarEditor, FilterBarInlineInput };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FilterBarField, FilterBarItem, FilterBarOperator, FilterBarOption, FilterBarScalar } from "./types.js";
|
|
2
|
+
//#region src/filter-bar/model.d.ts
|
|
3
|
+
export declare function fieldOperators(field: FilterBarField, operators: readonly FilterBarOperator[]): FilterBarOperator[];
|
|
4
|
+
export declare function fieldOptions(field: FilterBarField): readonly FilterBarOption[];
|
|
5
|
+
export declare function parseCustomValue(field: FilterBarField, query: string): FilterBarScalar | undefined;
|
|
6
|
+
export declare function allowsCustomValue(field: FilterBarField): boolean;
|
|
7
|
+
export declare function formatFilterValue(item: FilterBarItem, field: FilterBarField | undefined): string;
|
|
8
|
+
export declare function filterNavigationKeys(items: readonly FilterBarItem[]): string[];
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/filter-bar/model.ts
|
|
2
|
+
const booleanOptions = [{
|
|
3
|
+
value: true,
|
|
4
|
+
label: "True"
|
|
5
|
+
}, {
|
|
6
|
+
value: false,
|
|
7
|
+
label: "False"
|
|
8
|
+
}];
|
|
9
|
+
function fieldOperators(field, operators) {
|
|
10
|
+
return operators.filter((operator) => !field.operators || field.operators.includes(operator.id));
|
|
11
|
+
}
|
|
12
|
+
function fieldOptions(field) {
|
|
13
|
+
return field.options ?? (field.type === "boolean" ? booleanOptions : []);
|
|
14
|
+
}
|
|
15
|
+
function parseCustomValue(field, query) {
|
|
16
|
+
if (!query.trim()) return void 0;
|
|
17
|
+
if (field.type === "number") {
|
|
18
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(query.trim())) return void 0;
|
|
19
|
+
const number = Number(query);
|
|
20
|
+
return Number.isFinite(number) ? number : void 0;
|
|
21
|
+
}
|
|
22
|
+
if (field.type === "boolean") {
|
|
23
|
+
if (query.toLowerCase() === "true") return true;
|
|
24
|
+
if (query.toLowerCase() === "false") return false;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
return query.trim();
|
|
28
|
+
}
|
|
29
|
+
function allowsCustomValue(field) {
|
|
30
|
+
return field.allowCustomValue ?? (field.options === void 0 && field.type !== "boolean" && field.optionsMode !== "remote");
|
|
31
|
+
}
|
|
32
|
+
function formatFilterValue(item, field) {
|
|
33
|
+
if (item.value === null) return "";
|
|
34
|
+
return (Array.isArray(item.value) ? item.value : [item.value]).map((value) => field?.formatValue?.(value) ?? fieldOptionsForLabel(field, value)).join(", ");
|
|
35
|
+
}
|
|
36
|
+
function fieldOptionsForLabel(field, value) {
|
|
37
|
+
return (field ? fieldOptions(field).find((option) => option.value === value)?.label : void 0) ?? String(value);
|
|
38
|
+
}
|
|
39
|
+
function filterNavigationKeys(items) {
|
|
40
|
+
return items.flatMap((item) => [
|
|
41
|
+
"field",
|
|
42
|
+
"operator",
|
|
43
|
+
...item.value === null ? [] : ["value"],
|
|
44
|
+
"remove"
|
|
45
|
+
].map((segment) => `${item.id}:${segment}`));
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { allowsCustomValue, fieldOperators, fieldOptions, filterNavigationKeys, formatFilterValue, parseCustomValue };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/filter-bar/motion.d.ts
|
|
2
|
+
export declare function filterMotionEnabled(element: HTMLElement): boolean;
|
|
3
|
+
export declare function useFilterBarMotion(): {
|
|
4
|
+
register(key: string, element: HTMLElement | null): void;
|
|
5
|
+
capture(): void;
|
|
6
|
+
cancel: () => void;
|
|
7
|
+
play(): void;
|
|
8
|
+
rootRef: import("react").RefObject<HTMLFieldSetElement | null>;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
//#region src/filter-bar/motion.ts
|
|
4
|
+
function filterMotionEnabled(element) {
|
|
5
|
+
return !window.matchMedia("(prefers-reduced-motion: reduce)").matches && Number.parseFloat(getComputedStyle(element).getPropertyValue("--duration-fast")) > 0;
|
|
6
|
+
}
|
|
7
|
+
function animateMovement(element, before, after, duration, easing) {
|
|
8
|
+
const x = before.left - after.left;
|
|
9
|
+
const y = before.top - after.top;
|
|
10
|
+
if (Math.abs(x) < 1 && Math.abs(y) < 1) return null;
|
|
11
|
+
return element.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "translate(0, 0)" }], {
|
|
12
|
+
duration,
|
|
13
|
+
easing
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function animateLayout(elements, before, after, animations, duration, easing) {
|
|
17
|
+
for (const [key, bounds] of after) {
|
|
18
|
+
const origin = before.get(key);
|
|
19
|
+
const element = elements.get(key);
|
|
20
|
+
if (!origin || !element) continue;
|
|
21
|
+
const animation = animateMovement(element, origin, bounds, duration, easing);
|
|
22
|
+
if (animation) animations.set(key, animation);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function useFilterBarMotion() {
|
|
26
|
+
const rootRef = useRef(null);
|
|
27
|
+
const [motion] = useState(() => {
|
|
28
|
+
const elements = /* @__PURE__ */ new Map();
|
|
29
|
+
const animations = /* @__PURE__ */ new Map();
|
|
30
|
+
let captured = null;
|
|
31
|
+
function measure() {
|
|
32
|
+
return new Map([...elements].map(([key, element]) => [key, element.getBoundingClientRect()]));
|
|
33
|
+
}
|
|
34
|
+
function cancel() {
|
|
35
|
+
for (const animation of animations.values()) animation.cancel();
|
|
36
|
+
animations.clear();
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
register(key, element) {
|
|
40
|
+
if (element) elements.set(key, element);
|
|
41
|
+
else elements.delete(key);
|
|
42
|
+
},
|
|
43
|
+
capture() {
|
|
44
|
+
captured = measure();
|
|
45
|
+
},
|
|
46
|
+
cancel,
|
|
47
|
+
play() {
|
|
48
|
+
const root = rootRef.current;
|
|
49
|
+
if (!root || !filterMotionEnabled(root)) {
|
|
50
|
+
cancel();
|
|
51
|
+
captured = null;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!captured) return;
|
|
55
|
+
const origin = captured;
|
|
56
|
+
captured = null;
|
|
57
|
+
cancel();
|
|
58
|
+
const next = measure();
|
|
59
|
+
const computed = getComputedStyle(root);
|
|
60
|
+
const duration = Number.parseFloat(computed.getPropertyValue("--duration-fast"));
|
|
61
|
+
const easing = computed.getPropertyValue("--ease-standard").trim();
|
|
62
|
+
animateLayout(elements, origin, next, animations, duration, easing);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
useLayoutEffect(() => motion.play());
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
69
|
+
function cancelReducedMotion() {
|
|
70
|
+
if (media.matches) motion.cancel();
|
|
71
|
+
}
|
|
72
|
+
media.addEventListener("change", cancelReducedMotion);
|
|
73
|
+
return () => {
|
|
74
|
+
media.removeEventListener("change", cancelReducedMotion);
|
|
75
|
+
motion.cancel();
|
|
76
|
+
};
|
|
77
|
+
}, [motion]);
|
|
78
|
+
return {
|
|
79
|
+
rootRef,
|
|
80
|
+
...motion
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { filterMotionEnabled, useFilterBarMotion };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ButtonProps } from "../ui/button.js";
|
|
2
|
+
import { FilterBarKnobStyle } from "../knob-contracts/filter-bar-knobs.js";
|
|
3
|
+
import { FilterBarInlineInput } from "./editor.js";
|
|
4
|
+
import { FilterBarItem } from "./types.js";
|
|
5
|
+
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
6
|
+
//#region src/filter-bar/parts.d.ts
|
|
7
|
+
export type FilterBarChipProps = ComponentProps<"div"> & {
|
|
8
|
+
item: FilterBarItem;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
style?: CSSProperties & FilterBarKnobStyle;
|
|
11
|
+
};
|
|
12
|
+
export declare function FilterBarChip(props: FilterBarChipProps): import("react").JSX.Element;
|
|
13
|
+
export declare function FilterBarChips({ renderChip }: {
|
|
14
|
+
renderChip?: (item: FilterBarItem) => ReactNode;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
export declare function FilterBarAddButton({ className, children, onClick, onKeyDown, ref, ...props }: ButtonProps): import("react").JSX.Element;
|
|
17
|
+
export declare function FilterBarInput({ className, ...props }: Omit<Parameters<typeof FilterBarInlineInput>[0], "owner" | "inputRef">): import("react").JSX.Element;
|
|
18
|
+
export declare function FilterBarClear({ children, onClick, ...props }: ButtonProps): import("react").JSX.Element | null;
|
|
19
|
+
//#endregion
|