@ctrl-ui/react 0.8.6 → 0.8.8
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.d.ts +15 -7
- package/dist/action-bar.js +39 -19
- package/dist/activity.js +7 -15
- package/dist/audio-recorder.d.ts +14 -4
- package/dist/audio-recorder.js +82 -29
- package/dist/audio-visualizer.js +1 -1
- package/dist/blocks/coding-agent.js +54 -57
- 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 +21 -0
- package/dist/blocks/design-canvas.js +452 -0
- package/dist/blocks/discussion.d.ts +28 -0
- package/dist/blocks/discussion.js +67 -0
- package/dist/blocks/file-explorer-data.d.ts +1 -0
- package/dist/blocks/file-explorer-data.js +4 -1
- package/dist/blocks/file-explorer.d.ts +9 -1
- package/dist/blocks/file-explorer.js +461 -192
- package/dist/blocks/settings.d.ts +2 -1
- package/dist/blocks/settings.js +101 -79
- package/dist/blocks/team-chat.d.ts +12 -0
- package/dist/blocks/team-chat.js +26 -0
- package/dist/blocks/theme-toggle.js +23 -31
- package/dist/chat-composer-attachment.d.ts +12 -3
- package/dist/chat-composer-attachment.js +61 -19
- package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
- package/dist/chat-composer-editor/extensions/mention.js +58 -27
- package/dist/chat-composer-editor/ghost.js +2 -4
- package/dist/chat-composer-editor/types.d.ts +5 -0
- package/dist/chat-composer-editor.d.ts +1 -1
- package/dist/chat-composer-editor.js +71 -28
- package/dist/chat-composer.d.ts +13 -32
- package/dist/chat-composer.js +54 -13
- package/dist/chat-layout.d.ts +19 -0
- package/dist/chat-layout.js +96 -32
- package/dist/chat-message/social.d.ts +26 -0
- package/dist/chat-message/social.js +122 -0
- package/dist/chat-message.d.ts +28 -29
- package/dist/chat-message.js +106 -25
- package/dist/context.d.ts +2 -2
- package/dist/context.js +38 -23
- package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
- package/dist/dynamic-notification-canvas-loop.js +113 -0
- package/dist/dynamic-notification-glass.js +23 -114
- package/dist/dynamic-notification-liquid.js +14 -118
- package/dist/dynamic-notification.d.ts +16 -8
- package/dist/dynamic-notification.js +85 -22
- package/dist/email/email-brand.d.ts +7 -4
- package/dist/email/email-brand.js +9 -6
- package/dist/email/email-code.d.ts +3 -2
- package/dist/email/email-code.js +11 -9
- package/dist/email/email-footer.d.ts +5 -3
- package/dist/email/email-footer.js +11 -5
- package/dist/email/email.d.ts +31 -9
- package/dist/email/email.js +50 -26
- package/dist/email/templates.d.ts +10 -9
- package/dist/email/templates.js +102 -38
- package/dist/email/theme.js +1 -1
- package/dist/environment-variables.d.ts +4 -2
- package/dist/environment-variables.js +52 -32
- 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 +216 -0
- package/dist/filter-bar/model.d.ts +10 -0
- package/dist/filter-bar/model.js +57 -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 +244 -0
- package/dist/filter-bar/types.d.ts +54 -0
- package/dist/filter-bar/types.js +0 -0
- package/dist/filter-bar.d.ts +27 -0
- package/dist/filter-bar.js +310 -0
- package/dist/hooks/use-audio-recorder.js +12 -6
- package/dist/hooks/use-chat-composer.d.ts +21 -7
- package/dist/hooks/use-chat-composer.js +32 -5
- package/dist/hooks/use-chat-message.d.ts +28 -2
- package/dist/hooks/use-chat-message.js +41 -10
- package/dist/hooks/use-chat-thread-scroll.js +43 -7
- package/dist/hooks/use-color-area.d.ts +3 -3
- package/dist/hooks/use-color-area.js +13 -12
- package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
- package/dist/hooks/use-copy-to-clipboard.js +20 -20
- package/dist/hooks/use-dropzone.d.ts +7 -4
- package/dist/hooks/use-dropzone.js +34 -3
- package/dist/hooks/use-dynamic-notification.d.ts +6 -2
- package/dist/hooks/use-dynamic-notification.js +26 -10
- package/dist/hooks/use-mobile.d.ts +3 -2
- package/dist/hooks/use-mobile.js +8 -4
- package/dist/hooks/use-textarea-trigger-menu.js +2 -1
- package/dist/hooks/use-trigger-menu.d.ts +54 -0
- package/dist/hooks/use-trigger-menu.js +102 -47
- package/dist/hooks/use-user-ask.d.ts +2 -0
- package/dist/hooks/use-user-ask.js +35 -41
- package/dist/inline-attachment.d.ts +6 -2
- package/dist/inline-attachment.js +44 -29
- package/dist/inline-citation.d.ts +12 -5
- package/dist/inline-citation.js +25 -16
- package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
- package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
- package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
- package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
- package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
- package/dist/knob-contracts/avatar-knobs.js +6 -1
- package/dist/knob-contracts/badge-knobs.d.ts +1 -1
- package/dist/knob-contracts/badge-knobs.js +2 -1
- package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
- package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
- package/dist/knob-contracts/button-knobs.d.ts +1 -1
- package/dist/knob-contracts/button-knobs.js +1 -0
- 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/chat-message-knobs.d.ts +1 -1
- package/dist/knob-contracts/chat-message-knobs.js +8 -1
- package/dist/knob-contracts/choice-knobs.d.ts +1 -1
- package/dist/knob-contracts/choice-knobs.js +1 -0
- package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
- package/dist/knob-contracts/code-diff-knobs.js +2 -0
- package/dist/knob-contracts/code-knobs.d.ts +1 -1
- package/dist/knob-contracts/code-knobs.js +2 -1
- package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
- package/dist/knob-contracts/collapsible-knobs.js +1 -1
- package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
- package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
- package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
- package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
- package/dist/knob-contracts/field-knobs.d.ts +1 -1
- package/dist/knob-contracts/field-knobs.js +5 -1
- package/dist/knob-contracts/filter-bar-knobs.d.ts +4 -0
- package/dist/knob-contracts/filter-bar-knobs.js +15 -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/markdown-knobs.d.ts +1 -1
- package/dist/knob-contracts/markdown-knobs.js +3 -1
- package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/page-layout-knobs.js +4 -1
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +28 -0
- package/dist/knob-contracts/range-knobs.d.ts +1 -1
- package/dist/knob-contracts/range-knobs.js +1 -0
- package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
- package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
- package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
- package/dist/knob-contracts/sidebar-knobs.js +27 -2
- package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
- package/dist/knob-contracts/spinner-knobs.js +1 -1
- package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
- package/dist/knob-contracts/stepper-knobs.js +4 -1
- package/dist/knob-contracts/switch-knobs.d.ts +1 -1
- package/dist/knob-contracts/switch-knobs.js +5 -0
- package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
- package/dist/knob-contracts/table-of-contents-knobs.js +4 -5
- package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
- package/dist/knob-contracts/tabs-knobs.js +1 -0
- package/dist/knob-contracts/tree-knobs.d.ts +1 -1
- package/dist/knob-contracts/tree-knobs.js +2 -1
- package/dist/lib/cn.js +0 -4
- package/dist/lib/contrast.d.ts +1 -0
- package/dist/lib/contrast.js +17 -5
- package/dist/lib/dropzone-validation.js +41 -28
- package/dist/lib/env-file.d.ts +1 -1
- package/dist/lib/env-file.js +5 -5
- package/dist/lib/motion.d.ts +4 -0
- package/dist/lib/motion.js +15 -0
- package/dist/lib/trigger-detect.js +6 -8
- package/dist/markdown-block.js +13 -8
- package/dist/popup-parts.d.ts +8 -0
- package/dist/popup-parts.js +43 -0
- package/dist/scripts/contrast-eval.js +5 -4
- package/dist/source-badge.js +3 -1
- package/dist/styles/code.css +0 -2
- package/dist/styles/effects.css +6 -32
- package/dist/styles/index.css +14 -8
- package/dist/styles/recipes/accordion.css +9 -6
- package/dist/styles/recipes/action-bar.css +0 -15
- package/dist/styles/recipes/activity.css +5 -11
- package/dist/styles/recipes/alert.css +4 -4
- package/dist/styles/recipes/audio-recorder.css +15 -3
- package/dist/styles/recipes/audio-visualizer.css +13 -2
- package/dist/styles/recipes/avatar.css +78 -1
- package/dist/styles/recipes/badge.css +14 -4
- package/dist/styles/recipes/breadcrumb.css +77 -0
- package/dist/styles/recipes/button-group.css +6 -8
- package/dist/styles/recipes/button-states.css +66 -0
- package/dist/styles/recipes/button.css +26 -85
- package/dist/styles/recipes/calendar.css +17 -23
- package/dist/styles/recipes/card.css +2 -2
- package/dist/styles/recipes/chat-composer-attachment.css +9 -3
- package/dist/styles/recipes/chat-composer.css +21 -16
- package/dist/styles/recipes/chat-layout.css +64 -2
- package/dist/styles/recipes/chat-message-social.css +123 -0
- package/dist/styles/recipes/chat-message.css +157 -37
- package/dist/styles/recipes/choice.css +25 -21
- package/dist/styles/recipes/code-diff.css +51 -26
- package/dist/styles/recipes/code.css +44 -18
- package/dist/styles/recipes/collapsible.css +43 -1
- package/dist/styles/recipes/color-picker-details.css +3 -4
- package/dist/styles/recipes/color-picker-values.css +9 -8
- package/dist/styles/recipes/color-picker.css +23 -4
- package/dist/styles/recipes/context.css +3 -3
- package/dist/styles/recipes/dockable-panel.css +18 -7
- package/dist/styles/recipes/dropzone.css +7 -8
- package/dist/styles/recipes/dynamic-notification-input.css +8 -5
- package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
- package/dist/styles/recipes/dynamic-notification.css +40 -26
- package/dist/styles/recipes/emoji-picker.css +132 -0
- package/dist/styles/recipes/empty.css +5 -4
- package/dist/styles/recipes/environment-variables.css +4 -15
- package/dist/styles/recipes/field-parts.css +89 -21
- package/dist/styles/recipes/field.css +83 -26
- package/dist/styles/recipes/filter-bar.css +161 -0
- package/dist/styles/recipes/gradient-editor.css +18 -7
- package/dist/styles/recipes/infinite-canvas.css +51 -4
- package/dist/styles/recipes/inline-attachment.css +9 -4
- package/dist/styles/recipes/inline-citation.css +10 -4
- package/dist/styles/recipes/item.css +23 -9
- package/dist/styles/recipes/markdown-block.css +5 -4
- package/dist/styles/recipes/markdown.css +68 -26
- package/dist/styles/recipes/morphing-panel.css +8 -8
- package/dist/styles/recipes/page-layout.css +36 -10
- package/dist/styles/recipes/pagination.css +5 -6
- package/dist/styles/recipes/phone-input.css +12 -8
- package/dist/styles/recipes/popup-controls.css +175 -26
- package/dist/styles/recipes/popup-drawer.css +35 -44
- package/dist/styles/recipes/popup-lists.css +139 -29
- package/dist/styles/recipes/popup-motion.css +4 -45
- package/dist/styles/recipes/popup-toast.css +55 -26
- package/dist/styles/recipes/popup.css +149 -153
- package/dist/styles/recipes/range.css +55 -19
- package/dist/styles/recipes/resizable.css +36 -7
- package/dist/styles/recipes/rich-tooltip.css +24 -34
- package/dist/styles/recipes/scroll-area.css +8 -1
- package/dist/styles/recipes/sidebar-menu.css +149 -34
- package/dist/styles/recipes/sidebar-parts.css +154 -0
- package/dist/styles/recipes/sidebar.css +187 -128
- package/dist/styles/recipes/source-badge.css +1 -1
- package/dist/styles/recipes/spinner.css +12 -1
- package/dist/styles/recipes/stepper.css +31 -13
- package/dist/styles/recipes/switch.css +68 -23
- package/dist/styles/recipes/table-of-contents.css +49 -41
- package/dist/styles/recipes/table.css +14 -10
- package/dist/styles/recipes/tabs.css +38 -12
- package/dist/styles/recipes/task-list.css +7 -14
- package/dist/styles/recipes/thread-rail.css +42 -19
- package/dist/styles/recipes/timeline.css +2 -0
- package/dist/styles/recipes/toolbar.css +27 -25
- package/dist/styles/recipes/track-highlight.css +12 -8
- package/dist/styles/recipes/transcript-divider.css +2 -2
- package/dist/styles/recipes/tree.css +31 -4
- package/dist/styles/recipes/user-ask-details.css +0 -1
- package/dist/styles/recipes/user-ask.css +4 -4
- package/dist/styles/theme.css +287 -107
- package/dist/task-list.d.ts +4 -1
- package/dist/task-list.js +24 -8
- package/dist/thread-rail.d.ts +1 -1
- package/dist/thread-rail.js +61 -19
- package/dist/transcript-divider.js +2 -1
- package/dist/ui/alert.d.ts +3 -1
- package/dist/ui/alert.js +14 -8
- package/dist/ui/autocomplete.js +7 -1
- package/dist/ui/avatar.d.ts +8 -0
- package/dist/ui/avatar.js +23 -3
- package/dist/ui/badge.js +1 -1
- package/dist/ui/breadcrumb.d.ts +27 -0
- package/dist/ui/breadcrumb.js +88 -0
- package/dist/ui/button.d.ts +2 -1
- package/dist/ui/button.js +1 -1
- package/dist/ui/calendar.js +2 -2
- package/dist/ui/card.d.ts +3 -1
- package/dist/ui/card.js +12 -7
- package/dist/ui/checkbox-group.js +1 -1
- package/dist/ui/checkbox.d.ts +2 -0
- package/dist/ui/code-diff.d.ts +1 -1
- package/dist/ui/code-diff.js +71 -27
- package/dist/ui/code.d.ts +5 -3
- package/dist/ui/code.js +27 -28
- package/dist/ui/color-picker.d.ts +5 -2
- package/dist/ui/color-picker.js +84 -36
- package/dist/ui/combobox-disabled-selection.d.ts +1 -2
- package/dist/ui/combobox-disabled-selection.js +7 -8
- package/dist/ui/combobox.d.ts +5 -23
- package/dist/ui/combobox.js +36 -16
- package/dist/ui/command.d.ts +8 -2
- package/dist/ui/command.js +39 -15
- package/dist/ui/context-menu.js +3 -0
- package/dist/ui/dialog.d.ts +2 -1
- package/dist/ui/dialog.js +3 -22
- package/dist/ui/dockable-panel-geometry.d.ts +1 -0
- package/dist/ui/dockable-panel-geometry.js +7 -1
- package/dist/ui/dockable-panel.d.ts +9 -6
- package/dist/ui/dockable-panel.js +81 -33
- package/dist/ui/drawer.d.ts +7 -4
- package/dist/ui/drawer.js +35 -22
- package/dist/ui/dropdown-menu.js +4 -3
- package/dist/ui/dropzone.d.ts +5 -3
- package/dist/ui/dropzone.js +78 -17
- package/dist/ui/emoji-picker.d.ts +18 -0
- package/dist/ui/emoji-picker.js +136 -0
- package/dist/ui/empty.d.ts +3 -1
- package/dist/ui/empty.js +12 -7
- package/dist/ui/field.js +1 -1
- package/dist/ui/gradient-editor.d.ts +1 -1
- package/dist/ui/gradient-editor.js +37 -21
- package/dist/ui/infinite-canvas.d.ts +13 -1
- package/dist/ui/infinite-canvas.js +139 -20
- package/dist/ui/input-group.d.ts +2 -4
- package/dist/ui/input-group.js +13 -14
- package/dist/ui/input-otp.d.ts +1 -1
- package/dist/ui/input-otp.js +32 -15
- package/dist/ui/input.js +11 -2
- package/dist/ui/item.d.ts +2 -1
- package/dist/ui/item.js +11 -7
- package/dist/ui/live-status.d.ts +7 -0
- package/dist/ui/live-status.js +17 -0
- package/dist/ui/markdown-elements.d.ts +6 -0
- package/dist/ui/markdown-elements.js +42 -9
- package/dist/ui/menubar.js +2 -2
- package/dist/ui/morphing-panel.js +2 -1
- package/dist/ui/native-select.d.ts +1 -1
- package/dist/ui/native-select.js +24 -11
- package/dist/ui/number-field.d.ts +1 -1
- package/dist/ui/number-field.js +13 -2
- package/dist/ui/page-layout.d.ts +2 -2
- package/dist/ui/page-layout.js +2 -4
- package/dist/ui/pagination.d.ts +7 -5
- package/dist/ui/pagination.js +19 -13
- package/dist/ui/phone-input.d.ts +1 -0
- package/dist/ui/phone-input.js +56 -58
- package/dist/ui/radio-group.d.ts +3 -0
- package/dist/ui/radio-group.js +11 -1
- package/dist/ui/resizable.d.ts +20 -2
- package/dist/ui/resizable.js +130 -8
- package/dist/ui/responsive-dialog.d.ts +1 -1
- package/dist/ui/responsive-dialog.js +9 -23
- package/dist/ui/rich-tooltip.d.ts +10 -4
- package/dist/ui/rich-tooltip.js +81 -33
- package/dist/ui/scroll-area.d.ts +4 -1
- package/dist/ui/scroll-area.js +44 -28
- package/dist/ui/select.d.ts +2 -1
- package/dist/ui/select.js +57 -32
- package/dist/ui/sidebar-menu.d.ts +5 -1
- package/dist/ui/sidebar-menu.js +31 -33
- package/dist/ui/sidebar-provider.d.ts +10 -1
- package/dist/ui/sidebar-provider.js +38 -13
- package/dist/ui/sidebar-resize-rail.js +7 -3
- package/dist/ui/sidebar.d.ts +11 -6
- package/dist/ui/sidebar.js +46 -23
- package/dist/ui/slider.js +31 -22
- package/dist/ui/spinner.d.ts +3 -2
- package/dist/ui/spinner.js +3 -2
- package/dist/ui/stepper.d.ts +8 -1
- package/dist/ui/stepper.js +74 -25
- package/dist/ui/switch.d.ts +1 -0
- package/dist/ui/table-of-contents.d.ts +5 -4
- package/dist/ui/table-of-contents.js +143 -52
- package/dist/ui/table.js +15 -9
- package/dist/ui/textarea.d.ts +1 -1
- package/dist/ui/textarea.js +20 -9
- package/dist/ui/timeline.d.ts +3 -1
- package/dist/ui/timeline.js +17 -9
- package/dist/ui/toast.d.ts +11 -5
- package/dist/ui/toast.js +100 -54
- package/dist/ui/toggle.d.ts +1 -1
- package/dist/ui/toggle.js +2 -1
- package/dist/ui/toolbar.d.ts +3 -2
- package/dist/ui/toolbar.js +4 -2
- package/dist/ui/tooltip.js +3 -20
- package/dist/ui/tree.js +120 -80
- package/dist/ui/trigger-menu.js +7 -2
- package/dist/user-ask.d.ts +10 -5
- package/dist/user-ask.js +31 -11
- package/package.json +5 -1
- package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
- package/dist/hooks/use-keyboard-navigation.js +0 -22
|
@@ -3,9 +3,9 @@ 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 { InputGroup, InputGroupAddon } from "./ui/input-group.js";
|
|
6
7
|
import { Input } from "./ui/input.js";
|
|
7
|
-
import {
|
|
8
|
-
import { createContext, use, useId } from "react";
|
|
8
|
+
import { createContext, use, useId, useRef, useState } 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";
|
|
11
11
|
//#region src/environment-variables.tsx
|
|
@@ -15,18 +15,28 @@ function useEnvironmentVariablesContext(componentName) {
|
|
|
15
15
|
if (!context) throw new Error(`${componentName} must be used inside <EnvironmentVariables.Root>.`);
|
|
16
16
|
return context;
|
|
17
17
|
}
|
|
18
|
-
function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false, rowErrors, keyLabel = "Key", valueLabel = "Value", keyPlaceholder = "OPENAI_API_KEY", valuePlaceholder = "sk
|
|
19
|
-
|
|
18
|
+
function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false, rowErrors, keyLabel = "Key", valueLabel = "Value", keyPlaceholder = "OPENAI_API_KEY", valuePlaceholder = "sk-…", duplicateKeyMessage = DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE, revealValueLabel = (key) => `Show value for ${key}`, onSubmit, className, children, ...props }) {
|
|
19
|
+
const pendingRef = useRef(false);
|
|
20
|
+
const [pending, setPending] = useState(false);
|
|
21
|
+
async function handleSubmit(event) {
|
|
20
22
|
if (event.defaultPrevented || !onSubmit) return;
|
|
21
23
|
event.preventDefault();
|
|
22
|
-
if (editor.hasDuplicateKeys) return;
|
|
24
|
+
if (pendingRef.current || editor.hasDuplicateKeys) return;
|
|
23
25
|
const rows = editor.getRowsForSubmit();
|
|
24
|
-
|
|
26
|
+
const payload = {
|
|
25
27
|
rows,
|
|
26
28
|
variables: collectEnvironmentVariables(rows),
|
|
27
29
|
envFileText: rowsToEnvFileText(rows),
|
|
28
30
|
reset: () => editor.resetRows(rows)
|
|
29
|
-
}
|
|
31
|
+
};
|
|
32
|
+
pendingRef.current = true;
|
|
33
|
+
setPending(true);
|
|
34
|
+
try {
|
|
35
|
+
await onSubmit(payload);
|
|
36
|
+
} finally {
|
|
37
|
+
pendingRef.current = false;
|
|
38
|
+
setPending(false);
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
return /* @__PURE__ */ jsx(EnvironmentVariablesContext.Provider, {
|
|
32
42
|
value: {
|
|
@@ -38,7 +48,9 @@ function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false,
|
|
|
38
48
|
valueLabel,
|
|
39
49
|
keyPlaceholder,
|
|
40
50
|
valuePlaceholder,
|
|
41
|
-
duplicateKeyMessage
|
|
51
|
+
duplicateKeyMessage,
|
|
52
|
+
revealValueLabel,
|
|
53
|
+
pending
|
|
42
54
|
},
|
|
43
55
|
children: /* @__PURE__ */ jsx("form", {
|
|
44
56
|
"data-control-ui": "environment-variables",
|
|
@@ -47,6 +59,7 @@ function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false,
|
|
|
47
59
|
"data-surface": "panel",
|
|
48
60
|
"data-disabled": disabled ? "true" : void 0,
|
|
49
61
|
"data-readonly": readOnly ? "true" : void 0,
|
|
62
|
+
"aria-busy": pending || void 0,
|
|
50
63
|
className: cn("flex min-w-0 flex-col", className),
|
|
51
64
|
onSubmit: handleSubmit,
|
|
52
65
|
...props,
|
|
@@ -157,6 +170,7 @@ function EnvironmentVariablesUploadButton({ inputLabel = "Import .env file", chi
|
|
|
157
170
|
type: "file",
|
|
158
171
|
accept: ".env,text/plain",
|
|
159
172
|
className: "sr-only",
|
|
173
|
+
tabIndex: -1,
|
|
160
174
|
disabled: isDisabled,
|
|
161
175
|
"aria-label": inputLabel,
|
|
162
176
|
onChange: handleChange
|
|
@@ -203,7 +217,7 @@ function EnvironmentVariablesRows({ rowErrors, className, children, ...props })
|
|
|
203
217
|
});
|
|
204
218
|
}
|
|
205
219
|
function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props }) {
|
|
206
|
-
const { editor, disabled, readOnly, rowErrors: contextRowErrors, keyLabel, valueLabel, keyPlaceholder, valuePlaceholder, duplicateKeyMessage } = useEnvironmentVariablesContext("EnvironmentVariables.Row");
|
|
220
|
+
const { editor, disabled, readOnly, rowErrors: contextRowErrors, keyLabel, valueLabel, keyPlaceholder, valuePlaceholder, duplicateKeyMessage, revealValueLabel } = useEnvironmentVariablesContext("EnvironmentVariables.Row");
|
|
207
221
|
const resolvedRowErrors = rowErrors ?? contextRowErrors;
|
|
208
222
|
const keyError = resolvedRowErrors?.[index]?.key ?? (editor.rowHasDuplicateKey(index) ? duplicateKeyMessage : null);
|
|
209
223
|
const valueError = resolvedRowErrors?.[index]?.value;
|
|
@@ -211,8 +225,11 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
|
|
|
211
225
|
const rowId = editor.getRowId(index);
|
|
212
226
|
const keyInputId = `${rowId}-key`;
|
|
213
227
|
const valueInputId = `${rowId}-value`;
|
|
214
|
-
const
|
|
215
|
-
const
|
|
228
|
+
const keyErrorId = `${keyInputId}-error`;
|
|
229
|
+
const valueErrorId = `${valueInputId}-error`;
|
|
230
|
+
const rowName = row.key.trim() || `row ${index + 1}`;
|
|
231
|
+
const valueRevealed = editor.isValueRevealed(index);
|
|
232
|
+
const removeRowLabel = `Remove environment variable ${rowName}`;
|
|
216
233
|
function handlePaste(event) {
|
|
217
234
|
if (editor.handlePaste(index, event.clipboardData.getData("text"))) event.preventDefault();
|
|
218
235
|
}
|
|
@@ -235,19 +252,18 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
|
|
|
235
252
|
children: keyLabel
|
|
236
253
|
}),
|
|
237
254
|
/* @__PURE__ */ jsx(Input, {
|
|
238
|
-
"data-control-ui": "environment-variables",
|
|
239
|
-
"data-control-family": "environment-variables",
|
|
240
|
-
"data-slot": "key-input",
|
|
241
255
|
id: keyInputId,
|
|
242
256
|
size: "sm",
|
|
243
257
|
value: row.key,
|
|
244
258
|
placeholder: keyPlaceholder,
|
|
245
259
|
disabled: isDisabled,
|
|
246
260
|
"aria-invalid": Boolean(keyError),
|
|
261
|
+
"aria-describedby": keyError ? keyErrorId : void 0,
|
|
247
262
|
onChange: (event) => editor.updateRow(index, { key: event.target.value }),
|
|
248
263
|
onPaste: handlePaste
|
|
249
264
|
}),
|
|
250
265
|
keyError ? /* @__PURE__ */ jsx("p", {
|
|
266
|
+
id: keyErrorId,
|
|
251
267
|
"data-control-ui": "environment-variables",
|
|
252
268
|
"data-control-family": "environment-variables",
|
|
253
269
|
"data-slot": "field-error",
|
|
@@ -267,35 +283,30 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
|
|
|
267
283
|
children: valueLabel
|
|
268
284
|
}),
|
|
269
285
|
/* @__PURE__ */ jsxs(InputGroup, {
|
|
270
|
-
"data-control-ui": "environment-variables",
|
|
271
|
-
"data-control-family": "environment-variables",
|
|
272
|
-
"data-slot": "value-group",
|
|
273
|
-
"data-invalid": valueError ? "true" : void 0,
|
|
274
286
|
size: "sm",
|
|
275
|
-
children: [/* @__PURE__ */ jsx(
|
|
276
|
-
"data-control-ui": "environment-variables",
|
|
277
|
-
"data-control-family": "environment-variables",
|
|
278
|
-
"data-slot": "value-input",
|
|
287
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
279
288
|
id: valueInputId,
|
|
280
289
|
value: row.value,
|
|
281
290
|
placeholder: valuePlaceholder,
|
|
282
291
|
disabled: isDisabled,
|
|
283
292
|
"aria-invalid": Boolean(valueError),
|
|
284
|
-
|
|
293
|
+
"aria-describedby": valueError ? valueErrorId : void 0,
|
|
294
|
+
type: valueRevealed ? "text" : "password",
|
|
285
295
|
onChange: (event) => editor.updateRow(index, { value: event.target.value }),
|
|
286
296
|
onPaste: handlePaste
|
|
287
297
|
}), /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
288
|
-
className: "
|
|
298
|
+
className: "pe-1",
|
|
289
299
|
children: /* @__PURE__ */ jsx(Button, {
|
|
290
300
|
type: "button",
|
|
291
301
|
variant: "quiet",
|
|
292
302
|
size: "sm",
|
|
293
303
|
iconOnly: true,
|
|
294
304
|
disabled: isDisabled,
|
|
295
|
-
"aria-label": revealValueLabel,
|
|
296
|
-
|
|
305
|
+
"aria-label": revealValueLabel(rowName),
|
|
306
|
+
"aria-pressed": valueRevealed,
|
|
307
|
+
title: revealValueLabel(rowName),
|
|
297
308
|
onClick: () => editor.toggleValueVisibility(index),
|
|
298
|
-
children:
|
|
309
|
+
children: valueRevealed ? /* @__PURE__ */ jsx(EyeOffIcon, {
|
|
299
310
|
"aria-hidden": "true",
|
|
300
311
|
className: "size-3.5"
|
|
301
312
|
}) : /* @__PURE__ */ jsx(EyeIcon, {
|
|
@@ -306,6 +317,7 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
|
|
|
306
317
|
})]
|
|
307
318
|
}),
|
|
308
319
|
valueError ? /* @__PURE__ */ jsx("p", {
|
|
320
|
+
id: valueErrorId,
|
|
309
321
|
"data-control-ui": "environment-variables",
|
|
310
322
|
"data-control-family": "environment-variables",
|
|
311
323
|
"data-slot": "field-error",
|
|
@@ -322,7 +334,7 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
|
|
|
322
334
|
disabled,
|
|
323
335
|
"aria-label": removeRowLabel,
|
|
324
336
|
title: "Remove variable",
|
|
325
|
-
className: "absolute top-2
|
|
337
|
+
className: "absolute top-2 end-1 sm:static sm:justify-self-end sm:self-start",
|
|
326
338
|
onClick: () => editor.removeRow(index),
|
|
327
339
|
children: /* @__PURE__ */ jsx(Trash2Icon, {
|
|
328
340
|
"aria-hidden": "true",
|
|
@@ -371,14 +383,14 @@ function EnvironmentVariablesResetButton({ children = "Reset", variant = "quiet"
|
|
|
371
383
|
});
|
|
372
384
|
}
|
|
373
385
|
function EnvironmentVariablesSubmitButton({ children = "Save", variant = "solid", tone = "primary", size = "sm", disabled, ...props }) {
|
|
374
|
-
const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext("EnvironmentVariables.SubmitButton");
|
|
386
|
+
const { editor, disabled: contextDisabled, readOnly, pending } = useEnvironmentVariablesContext("EnvironmentVariables.SubmitButton");
|
|
375
387
|
if (readOnly) return null;
|
|
376
388
|
return /* @__PURE__ */ jsxs(Button, {
|
|
377
389
|
type: "submit",
|
|
378
390
|
variant,
|
|
379
391
|
tone,
|
|
380
392
|
size,
|
|
381
|
-
disabled: disabled ?? (contextDisabled || editor.hasDuplicateKeys),
|
|
393
|
+
disabled: disabled ?? (contextDisabled || pending || editor.hasDuplicateKeys),
|
|
382
394
|
...props,
|
|
383
395
|
children: [/* @__PURE__ */ jsx(SaveIcon, {
|
|
384
396
|
"aria-hidden": "true",
|
|
@@ -449,7 +461,7 @@ function EnvironmentVariablesReadOnlyList({ rows, emptyMessage = "No environment
|
|
|
449
461
|
}))
|
|
450
462
|
});
|
|
451
463
|
}
|
|
452
|
-
function EnvironmentVariablesReadOnlyItem({ name, value =
|
|
464
|
+
function EnvironmentVariablesReadOnlyItem({ name, value, revealed = false, hiddenValueLabel = "Hidden value", className, ...props }) {
|
|
453
465
|
return /* @__PURE__ */ jsxs("div", {
|
|
454
466
|
"data-control-ui": "environment-variables",
|
|
455
467
|
"data-control-family": "environment-variables",
|
|
@@ -461,13 +473,21 @@ function EnvironmentVariablesReadOnlyItem({ name, value = "********", revealed =
|
|
|
461
473
|
"data-control-family": "environment-variables",
|
|
462
474
|
"data-slot": "readonly-key",
|
|
463
475
|
className: "min-w-0 truncate",
|
|
476
|
+
title: typeof name === "string" ? name : void 0,
|
|
464
477
|
children: name
|
|
465
478
|
}), /* @__PURE__ */ jsx("span", {
|
|
466
479
|
"data-control-ui": "environment-variables",
|
|
467
480
|
"data-control-family": "environment-variables",
|
|
468
481
|
"data-slot": "readonly-value",
|
|
469
482
|
className: "min-w-0 truncate",
|
|
470
|
-
|
|
483
|
+
title: revealed && typeof value === "string" ? value : void 0,
|
|
484
|
+
children: revealed ? value : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
485
|
+
"aria-hidden": "true",
|
|
486
|
+
children: "••••••••"
|
|
487
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
488
|
+
className: "sr-only",
|
|
489
|
+
children: hiddenValueLabel
|
|
490
|
+
})] })
|
|
471
491
|
})]
|
|
472
492
|
});
|
|
473
493
|
}
|
|
@@ -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,216 @@
|
|
|
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
|
+
ariaLabel: operator.ariaLabel
|
|
41
|
+
}));
|
|
42
|
+
return valueChoices(field, draft.query);
|
|
43
|
+
}
|
|
44
|
+
function searchLabel(stage, field) {
|
|
45
|
+
if (stage === "field") return "Filter field";
|
|
46
|
+
if (stage === "operator") return "Filter operator";
|
|
47
|
+
return `${field?.label ?? "Filter"} value`;
|
|
48
|
+
}
|
|
49
|
+
function statusId(owner) {
|
|
50
|
+
return `${owner}-filter-status`;
|
|
51
|
+
}
|
|
52
|
+
function handleSelection(context, draft, choice) {
|
|
53
|
+
if (!choice || !draft) return;
|
|
54
|
+
const field = context.fields.find((candidate) => candidate.id === draft.fieldId);
|
|
55
|
+
if (draft.stage === "value" && (field?.loading || field?.error)) return;
|
|
56
|
+
if (Array.isArray(choice)) context.selectValues(choice.flatMap((selected) => selected.value === void 0 ? [] : [selected.value]));
|
|
57
|
+
else if (draft.stage === "field") context.selectField(choice.id);
|
|
58
|
+
else if (draft.stage === "operator") context.selectOperator(choice.id);
|
|
59
|
+
else if (choice.value !== void 0) context.commit(choice.value);
|
|
60
|
+
}
|
|
61
|
+
function handleEditorKeyDown(context, draft, event) {
|
|
62
|
+
if (event.nativeEvent.isComposing) return false;
|
|
63
|
+
const backwards = event.key === "Escape" || event.key === "Backspace" && draft?.query === "";
|
|
64
|
+
if (draft && backwards) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
event.stopPropagation();
|
|
67
|
+
context.back();
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const operator = context.operators.find((candidate) => candidate.id === draft?.operatorId);
|
|
71
|
+
const field = context.fields.find((candidate) => candidate.id === draft?.fieldId);
|
|
72
|
+
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;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
context.commit(draft.values);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function EditorOptions({ field, invalidNumber, owner }) {
|
|
78
|
+
if (field?.loading || field?.error) return /* @__PURE__ */ jsx("div", {
|
|
79
|
+
id: statusId(owner),
|
|
80
|
+
className: "p-3 text-caption",
|
|
81
|
+
children: field.error ?? "Loading values…"
|
|
82
|
+
});
|
|
83
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: invalidNumber ? "Enter a number, like 42 or 3.5." : "No matching options." }), /* @__PURE__ */ jsx(ComboboxList, { children: (choice) => /* @__PURE__ */ jsx(ComboboxItem, {
|
|
84
|
+
value: choice,
|
|
85
|
+
disabled: choice.disabled,
|
|
86
|
+
"aria-label": choice.ariaLabel,
|
|
87
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
88
|
+
className: "flex min-w-0 items-center gap-2",
|
|
89
|
+
children: [choice.icon, /* @__PURE__ */ jsx("span", {
|
|
90
|
+
className: "truncate",
|
|
91
|
+
title: choice.label,
|
|
92
|
+
children: choice.label
|
|
93
|
+
})]
|
|
94
|
+
})
|
|
95
|
+
}, choice.id) })] });
|
|
96
|
+
}
|
|
97
|
+
function handleOpenChange(context, draft, anchorRef, owner, open, eventDetails) {
|
|
98
|
+
if (open && !draft && anchorRef?.current && eventDetails.reason !== "input-change") context.start(owner, anchorRef.current);
|
|
99
|
+
if (open || !draft) return;
|
|
100
|
+
if (eventDetails.reason === "item-press" || eventDetails.reason === "escape-key") eventDetails.cancel();
|
|
101
|
+
else context.close(false);
|
|
102
|
+
}
|
|
103
|
+
function editorView(context, draft) {
|
|
104
|
+
const field = context.fields.find((candidate) => candidate.id === draft?.fieldId);
|
|
105
|
+
const operator = context.operators.find((candidate) => candidate.id === draft?.operatorId);
|
|
106
|
+
const stage = draft?.stage ?? "field";
|
|
107
|
+
const many = stage === "value" && operator?.arity === "many";
|
|
108
|
+
const query = draft?.query ?? "";
|
|
109
|
+
const choices = editorChoices(context, draft);
|
|
110
|
+
const selected = (draft?.values ?? []).map((value) => choices.find((choice) => choice.value === value) ?? valueChoice(value, field?.formatValue?.(value) ?? String(value)));
|
|
111
|
+
const customNumber = field?.type === "number" && allowsCustomValue(field);
|
|
112
|
+
return {
|
|
113
|
+
field,
|
|
114
|
+
stage,
|
|
115
|
+
many,
|
|
116
|
+
query,
|
|
117
|
+
choices,
|
|
118
|
+
selected,
|
|
119
|
+
invalidNumber: stage === "value" && Boolean(customNumber) && query.trim() !== "" && field !== void 0 && parseCustomValue(field, query) === void 0,
|
|
120
|
+
remote: stage === "value" && field?.optionsMode === "remote",
|
|
121
|
+
unavailable: stage === "value" && Boolean(field?.loading || field?.error),
|
|
122
|
+
label: searchLabel(stage, field),
|
|
123
|
+
described: stage === "value" && Boolean(field?.error)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function FilterBarEditor({ owner, mode, anchorRef, children }) {
|
|
127
|
+
const context = useFilterBarContext();
|
|
128
|
+
const searchRef = useRef(null);
|
|
129
|
+
const draft = context.draft?.owner === owner ? context.draft : null;
|
|
130
|
+
const { field, stage, many, query, choices, selected, invalidNumber, remote, unavailable, label, described } = editorView(context, draft);
|
|
131
|
+
const canCommitMany = selected.length > 0 && !unavailable && !context.disabled && !context.readOnly;
|
|
132
|
+
return /* @__PURE__ */ jsxs(Combobox, {
|
|
133
|
+
items: choices,
|
|
134
|
+
multiple: many,
|
|
135
|
+
value: many ? selected : null,
|
|
136
|
+
inputValue: query,
|
|
137
|
+
open: draft !== null,
|
|
138
|
+
disabled: context.disabled,
|
|
139
|
+
readOnly: context.readOnly,
|
|
140
|
+
modal: false,
|
|
141
|
+
isItemEqualToValue: sameChoice,
|
|
142
|
+
filter: remote ? null : void 0,
|
|
143
|
+
onValueChange: (choice) => handleSelection(context, draft, choice),
|
|
144
|
+
onInputValueChange: (nextQuery, eventDetails) => {
|
|
145
|
+
if (eventDetails.reason !== "input-change" && eventDetails.reason !== "input-clear") return;
|
|
146
|
+
if (draft) context.setQuery(nextQuery);
|
|
147
|
+
else if (anchorRef?.current) context.start(owner, anchorRef.current, "field", void 0, nextQuery);
|
|
148
|
+
},
|
|
149
|
+
onOpenChange: (open, eventDetails) => handleOpenChange(context, draft, anchorRef, owner, open, eventDetails),
|
|
150
|
+
children: [children, /* @__PURE__ */ jsxs(ComboboxContent, {
|
|
151
|
+
anchor: draft?.anchor,
|
|
152
|
+
initialFocus: mode === "input" ? false : searchRef,
|
|
153
|
+
finalFocus: false,
|
|
154
|
+
className: "w-72 max-h-(--available-height) overflow-y-auto",
|
|
155
|
+
onKeyDown: (event) => {
|
|
156
|
+
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
context.back();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
children: [
|
|
162
|
+
mode === "button" && /* @__PURE__ */ jsx("div", {
|
|
163
|
+
className: "p-2",
|
|
164
|
+
children: /* @__PURE__ */ jsx(ComboboxInput, {
|
|
165
|
+
ref: searchRef,
|
|
166
|
+
size: "sm",
|
|
167
|
+
"aria-label": label,
|
|
168
|
+
"aria-describedby": described ? statusId(owner) : void 0,
|
|
169
|
+
placeholder: label,
|
|
170
|
+
inputMode: field?.type === "number" && stage === "value" ? "decimal" : void 0,
|
|
171
|
+
onKeyDown: (event) => handleEditorKeyDown(context, draft, event)
|
|
172
|
+
})
|
|
173
|
+
}),
|
|
174
|
+
/* @__PURE__ */ jsx(EditorOptions, {
|
|
175
|
+
field: stage === "value" ? field : void 0,
|
|
176
|
+
invalidNumber,
|
|
177
|
+
owner
|
|
178
|
+
}),
|
|
179
|
+
many && /* @__PURE__ */ jsx("div", {
|
|
180
|
+
className: "flex justify-end p-2",
|
|
181
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
182
|
+
disabled: !canCommitMany,
|
|
183
|
+
onClick: () => context.commit(draft?.values ?? []),
|
|
184
|
+
children: "Apply"
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
]
|
|
188
|
+
})]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function FilterBarInlineInput({ owner, inputRef, className, ...props }) {
|
|
192
|
+
const context = useFilterBarContext();
|
|
193
|
+
const draft = context.draft?.owner === owner ? context.draft : null;
|
|
194
|
+
const { label, described } = editorView(context, draft);
|
|
195
|
+
function handleKeyDown(event) {
|
|
196
|
+
props.onKeyDown?.(event);
|
|
197
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
198
|
+
if (handleEditorKeyDown(context, draft, event) || event.currentTarget.value !== "") return;
|
|
199
|
+
const last = context.items.at(-1);
|
|
200
|
+
if (event.key === "Backspace" && !draft && last) {
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
context.remove(last);
|
|
203
|
+
} else context.navigate(event);
|
|
204
|
+
}
|
|
205
|
+
return /* @__PURE__ */ jsx(ComboboxInput, {
|
|
206
|
+
ref: inputRef,
|
|
207
|
+
size: "sm",
|
|
208
|
+
className,
|
|
209
|
+
...props,
|
|
210
|
+
"aria-label": draft ? label : props["aria-label"],
|
|
211
|
+
"aria-describedby": described ? statusId(owner) : props["aria-describedby"],
|
|
212
|
+
onKeyDown: handleKeyDown
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { FilterBarEditor, FilterBarInlineInput };
|
|
@@ -0,0 +1,10 @@
|
|
|
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 describeFilter(item: FilterBarItem, fields: readonly FilterBarField[], operators: readonly FilterBarOperator[]): string;
|
|
9
|
+
export declare function filterNavigationKeys(items: readonly FilterBarItem[]): string[];
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,57 @@
|
|
|
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 describeFilter(item, fields, operators) {
|
|
40
|
+
const field = fields.find((candidate) => candidate.id === item.fieldId);
|
|
41
|
+
const operator = operators.find((candidate) => candidate.id === item.operatorId);
|
|
42
|
+
return [
|
|
43
|
+
field?.label ?? item.fieldId,
|
|
44
|
+
operator?.ariaLabel ?? operator?.label,
|
|
45
|
+
formatFilterValue(item, field)
|
|
46
|
+
].filter(Boolean).join(" ");
|
|
47
|
+
}
|
|
48
|
+
function filterNavigationKeys(items) {
|
|
49
|
+
return items.flatMap((item) => [
|
|
50
|
+
"field",
|
|
51
|
+
"operator",
|
|
52
|
+
...item.value === null ? [] : ["value"],
|
|
53
|
+
"remove"
|
|
54
|
+
].map((segment) => `${item.id}:${segment}`));
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { allowsCustomValue, describeFilter, 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 { prefersReducedMotion, readDurationMs } from "../lib/motion.js";
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
//#region src/filter-bar/motion.ts
|
|
5
|
+
function filterMotionEnabled(element) {
|
|
6
|
+
return !prefersReducedMotion(element) && readDurationMs(element, "--duration-fast") > 0;
|
|
7
|
+
}
|
|
8
|
+
function animateMovement(element, before, after, duration, easing) {
|
|
9
|
+
const x = before.left - after.left;
|
|
10
|
+
const y = before.top - after.top;
|
|
11
|
+
if (Math.abs(x) < 1 && Math.abs(y) < 1) return null;
|
|
12
|
+
return element.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "translate(0, 0)" }], {
|
|
13
|
+
duration,
|
|
14
|
+
easing
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function animateLayout(elements, before, after, animations, duration, easing) {
|
|
18
|
+
for (const [key, bounds] of after) {
|
|
19
|
+
const origin = before.get(key);
|
|
20
|
+
const element = elements.get(key);
|
|
21
|
+
if (!origin || !element) continue;
|
|
22
|
+
const animation = animateMovement(element, origin, bounds, duration, easing);
|
|
23
|
+
if (animation) animations.set(key, animation);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function useFilterBarMotion() {
|
|
27
|
+
const rootRef = useRef(null);
|
|
28
|
+
const [motion] = useState(() => {
|
|
29
|
+
const elements = /* @__PURE__ */ new Map();
|
|
30
|
+
const animations = /* @__PURE__ */ new Map();
|
|
31
|
+
let captured = null;
|
|
32
|
+
function measure() {
|
|
33
|
+
return new Map([...elements].map(([key, element]) => [key, element.getBoundingClientRect()]));
|
|
34
|
+
}
|
|
35
|
+
function cancel() {
|
|
36
|
+
for (const animation of animations.values()) animation.cancel();
|
|
37
|
+
animations.clear();
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
register(key, element) {
|
|
41
|
+
if (element) elements.set(key, element);
|
|
42
|
+
else elements.delete(key);
|
|
43
|
+
},
|
|
44
|
+
capture() {
|
|
45
|
+
captured = measure();
|
|
46
|
+
},
|
|
47
|
+
cancel,
|
|
48
|
+
play() {
|
|
49
|
+
const root = rootRef.current;
|
|
50
|
+
if (!root || !filterMotionEnabled(root)) {
|
|
51
|
+
cancel();
|
|
52
|
+
captured = null;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!captured) return;
|
|
56
|
+
const origin = captured;
|
|
57
|
+
captured = null;
|
|
58
|
+
cancel();
|
|
59
|
+
const next = measure();
|
|
60
|
+
const duration = readDurationMs(root, "--duration-fast");
|
|
61
|
+
const easing = getComputedStyle(root).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 };
|