@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
|
@@ -20,7 +20,7 @@ function formatAudioInputDeviceLabel(device, fallback = "Microphone") {
|
|
|
20
20
|
return `${label.replace(deviceIdRegex, "").trim() || fallback} (${id})`;
|
|
21
21
|
}
|
|
22
22
|
async function enumerateAudioInputDevices() {
|
|
23
|
-
if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) throw
|
|
23
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) throw unsupportedRecordingError();
|
|
24
24
|
return (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === "audioinput");
|
|
25
25
|
}
|
|
26
26
|
function useAudioInputDevices() {
|
|
@@ -43,7 +43,7 @@ function useAudioInputDevices() {
|
|
|
43
43
|
}
|
|
44
44
|
async function requestPermission() {
|
|
45
45
|
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
46
|
-
setError(
|
|
46
|
+
setError(unsupportedRecordingError());
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
setLoading(true);
|
|
@@ -96,10 +96,16 @@ function browserEnvironment() {
|
|
|
96
96
|
function asError(error) {
|
|
97
97
|
return error instanceof Error ? error : new Error(String(error));
|
|
98
98
|
}
|
|
99
|
+
function namedError(name, message) {
|
|
100
|
+
return Object.assign(new Error(message), { name });
|
|
101
|
+
}
|
|
102
|
+
function unsupportedRecordingError() {
|
|
103
|
+
return namedError("NotSupportedError", "This browser can't record audio. Try a current version of Chrome, Safari or Firefox.");
|
|
104
|
+
}
|
|
99
105
|
function microphoneError(error) {
|
|
100
106
|
const nextError = asError(error);
|
|
101
|
-
if (nextError.name === "NotAllowedError" || nextError.name === "SecurityError") return
|
|
102
|
-
if (nextError.name === "NotFoundError" || nextError.name === "OverconstrainedError") return
|
|
107
|
+
if (nextError.name === "NotAllowedError" || nextError.name === "SecurityError") return namedError("NotAllowedError", "Microphone access is blocked. Allow it in your browser's site settings, then try again.");
|
|
108
|
+
if (nextError.name === "NotFoundError" || nextError.name === "OverconstrainedError") return namedError("NotFoundError", "Selected microphone isn't connected. Plug it in or choose another microphone.");
|
|
103
109
|
return nextError;
|
|
104
110
|
}
|
|
105
111
|
function clampLevel(value) {
|
|
@@ -351,7 +357,7 @@ function createControllerForEnvironment(initialOptions, environment) {
|
|
|
351
357
|
}
|
|
352
358
|
function beginRecording(nextStream) {
|
|
353
359
|
const MediaRecorderImpl = environment.MediaRecorder;
|
|
354
|
-
if (!MediaRecorderImpl) throw
|
|
360
|
+
if (!MediaRecorderImpl) throw unsupportedRecordingError();
|
|
355
361
|
selectedMimeType = preferredAudioMimeType(MediaRecorderImpl) ?? "";
|
|
356
362
|
const nextRecorder = new MediaRecorderImpl(nextStream, selectedMimeType ? { mimeType: selectedMimeType } : void 0);
|
|
357
363
|
chunks = [];
|
|
@@ -388,7 +394,7 @@ function createControllerForEnvironment(initialOptions, environment) {
|
|
|
388
394
|
if (!environment.mediaDevices?.getUserMedia || !environment.MediaRecorder) {
|
|
389
395
|
setSnapshot({
|
|
390
396
|
state: "error",
|
|
391
|
-
error:
|
|
397
|
+
error: unsupportedRecordingError()
|
|
392
398
|
});
|
|
393
399
|
return false;
|
|
394
400
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FormSubmitEvent } from "../control-props.js";
|
|
2
|
-
import { ChatComposerKnobStyle } from "../knob-contracts/chat-composer-knobs.js";
|
|
3
2
|
import { ChatDensity } from "./use-chat-message.js";
|
|
3
|
+
import { ChatComposerKnobStyle } from "../knob-contracts/chat-composer-knobs.js";
|
|
4
4
|
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
5
5
|
//#region src/hooks/use-chat-composer.d.ts
|
|
6
6
|
export type ChatComposerSubmitPayload = {
|
|
@@ -8,6 +8,7 @@ export type ChatComposerSubmitPayload = {
|
|
|
8
8
|
clear: () => void;
|
|
9
9
|
mentions?: MentionItem[];
|
|
10
10
|
};
|
|
11
|
+
export type ChatComposerSubmitKey = "enter" | "mod-enter";
|
|
11
12
|
export type ChatComposerProps = Omit<ComponentProps<"form">, "onSubmit" | "style"> & {
|
|
12
13
|
children?: ReactNode;
|
|
13
14
|
value?: string;
|
|
@@ -17,6 +18,9 @@ export type ChatComposerProps = Omit<ComponentProps<"form">, "onSubmit" | "style
|
|
|
17
18
|
state?: "idle" | "submitting" | "disabled";
|
|
18
19
|
density?: ChatDensity;
|
|
19
20
|
disabled?: boolean;
|
|
21
|
+
/** Let an empty message send, for composers where an attachment or recording carries the turn. */
|
|
22
|
+
allowEmptySubmit?: boolean;
|
|
23
|
+
submitKey?: ChatComposerSubmitKey;
|
|
20
24
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
21
25
|
};
|
|
22
26
|
export type MentionItem = {
|
|
@@ -27,23 +31,27 @@ export type MentionItem = {
|
|
|
27
31
|
type ComposerKeyEvent = {
|
|
28
32
|
key: string;
|
|
29
33
|
shiftKey: boolean;
|
|
34
|
+
metaKey: boolean;
|
|
35
|
+
ctrlKey: boolean;
|
|
30
36
|
defaultPrevented: boolean;
|
|
31
37
|
nativeEvent: {
|
|
32
38
|
isComposing: boolean;
|
|
39
|
+
keyCode: number;
|
|
33
40
|
};
|
|
34
41
|
preventDefault: () => void;
|
|
35
42
|
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}): {
|
|
43
|
+
type InsertText = (text: string) => void;
|
|
44
|
+
export type RegisterInsertionTarget = (insert: InsertText) => () => void;
|
|
45
|
+
export type ChatComposerController = {
|
|
40
46
|
value: string;
|
|
41
|
-
setValue: (
|
|
47
|
+
setValue: (value: string) => void;
|
|
42
48
|
normalizedValue: string;
|
|
43
|
-
state: "
|
|
49
|
+
state: NonNullable<ChatComposerProps["state"]>;
|
|
44
50
|
density: ChatDensity;
|
|
51
|
+
submitKey: ChatComposerSubmitKey;
|
|
45
52
|
isCompact: boolean;
|
|
46
53
|
isDisabled: boolean;
|
|
54
|
+
isLocked: boolean;
|
|
47
55
|
canSubmit: boolean;
|
|
48
56
|
rows: number;
|
|
49
57
|
sendCount: number;
|
|
@@ -51,5 +59,11 @@ export declare function useChatComposer({ value, defaultValue, onValueChange, on
|
|
|
51
59
|
submit: (extra?: Partial<ChatComposerSubmitPayload>) => void;
|
|
52
60
|
handleSubmit: (event: FormSubmitEvent) => void;
|
|
53
61
|
handleKeyDown: (event: ComposerKeyEvent) => void;
|
|
62
|
+
insertText: InsertText;
|
|
63
|
+
registerInsertionTarget: RegisterInsertionTarget;
|
|
54
64
|
};
|
|
65
|
+
export declare function useChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, allowEmptySubmit, submitKey, trackSends }: Pick<ChatComposerProps, "value" | "defaultValue" | "onValueChange" | "onSubmit" | "state" | "density" | "disabled" | "allowEmptySubmit" | "submitKey"> & {
|
|
66
|
+
/** Count successful submits — only enabled when something reads counter (send-layer anchor), so idle apps pay no extra state update. */
|
|
67
|
+
trackSends?: boolean;
|
|
68
|
+
}): ChatComposerController;
|
|
55
69
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isComposingKey } from "./use-trigger-menu.js";
|
|
1
2
|
import { useState } from "react";
|
|
2
3
|
//#region src/hooks/use-chat-composer.ts
|
|
3
4
|
function useControllableText({ value, defaultValue = "", onValueChange }) {
|
|
@@ -10,16 +11,30 @@ function useControllableText({ value, defaultValue = "", onValueChange }) {
|
|
|
10
11
|
}
|
|
11
12
|
return [currentValue, setValue];
|
|
12
13
|
}
|
|
13
|
-
function useChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, trackSends = false }) {
|
|
14
|
+
function useChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, submitKey = "enter", trackSends = false }) {
|
|
14
15
|
const [inputValue, setInputValue] = useControllableText({
|
|
15
16
|
value,
|
|
16
17
|
defaultValue,
|
|
17
18
|
onValueChange
|
|
18
19
|
});
|
|
19
20
|
const [sendCount, setSendCount] = useState(0);
|
|
21
|
+
const [insertion] = useState(() => {
|
|
22
|
+
let target = null;
|
|
23
|
+
const register = (insert) => {
|
|
24
|
+
target = insert;
|
|
25
|
+
return () => {
|
|
26
|
+
if (target === insert) target = null;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
register,
|
|
31
|
+
current: () => target
|
|
32
|
+
};
|
|
33
|
+
});
|
|
20
34
|
const normalizedValue = inputValue.trim();
|
|
21
|
-
const isDisabled = disabled || state === "disabled"
|
|
22
|
-
const
|
|
35
|
+
const isDisabled = disabled || state === "disabled";
|
|
36
|
+
const isLocked = state === "submitting";
|
|
37
|
+
const canSubmit = (normalizedValue.length > 0 || allowEmptySubmit) && !isDisabled && !isLocked;
|
|
23
38
|
const isCompact = density === "compact";
|
|
24
39
|
function clear() {
|
|
25
40
|
setInputValue("");
|
|
@@ -38,25 +53,37 @@ function useChatComposer({ value, defaultValue, onValueChange, onSubmit, state =
|
|
|
38
53
|
submit();
|
|
39
54
|
}
|
|
40
55
|
function handleKeyDown(event) {
|
|
41
|
-
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.nativeEvent
|
|
56
|
+
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || isComposingKey(event.nativeEvent)) return;
|
|
57
|
+
const modifierHeld = event.metaKey || event.ctrlKey;
|
|
58
|
+
if (submitKey === "mod-enter" && !modifierHeld) return;
|
|
42
59
|
event.preventDefault();
|
|
43
60
|
submit();
|
|
44
61
|
}
|
|
62
|
+
function insertText(text) {
|
|
63
|
+
if (isDisabled || isLocked) return;
|
|
64
|
+
const insertAtCaret = insertion.current();
|
|
65
|
+
if (insertAtCaret) insertAtCaret(text);
|
|
66
|
+
else setInputValue(`${inputValue}${text}`);
|
|
67
|
+
}
|
|
45
68
|
return {
|
|
46
69
|
value: inputValue,
|
|
47
70
|
setValue: setInputValue,
|
|
48
71
|
normalizedValue,
|
|
49
72
|
state,
|
|
50
73
|
density,
|
|
74
|
+
submitKey,
|
|
51
75
|
isCompact,
|
|
52
76
|
isDisabled,
|
|
77
|
+
isLocked,
|
|
53
78
|
canSubmit,
|
|
54
79
|
rows: isCompact ? 2 : 4,
|
|
55
80
|
sendCount,
|
|
56
81
|
clear,
|
|
57
82
|
submit,
|
|
58
83
|
handleSubmit,
|
|
59
|
-
handleKeyDown
|
|
84
|
+
handleKeyDown,
|
|
85
|
+
insertText,
|
|
86
|
+
registerInsertionTarget: insertion.register
|
|
60
87
|
};
|
|
61
88
|
}
|
|
62
89
|
//#endregion
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { ChatMessageKnobStyle } from "../knob-contracts/chat-message-knobs.js";
|
|
2
2
|
import { CSSProperties, ComponentProps } from "react";
|
|
3
3
|
//#region src/hooks/use-chat-message.d.ts
|
|
4
|
-
export type ChatRole = "user" | "assistant" | "system" | "tool";
|
|
4
|
+
export type ChatRole = "user" | "assistant" | "participant" | "system" | "tool";
|
|
5
5
|
export type ChatDensity = "compact" | "comfortable";
|
|
6
6
|
export type ChatState = "idle" | "streaming" | "pending" | "error";
|
|
7
|
+
export type ChatMessageLayout = "bubble" | "flat";
|
|
8
|
+
/** Transition announcements a message publishes to its thread. */
|
|
9
|
+
export type ChatMessageStatusLabels = {
|
|
10
|
+
pending: string;
|
|
11
|
+
replied: string;
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
7
14
|
export type ChatMessageProps = ComponentProps<"article"> & {
|
|
8
15
|
from: ChatRole;
|
|
9
16
|
state?: ChatState;
|
|
10
17
|
density?: ChatDensity;
|
|
18
|
+
layout?: ChatMessageLayout;
|
|
19
|
+
continuation?: boolean;
|
|
20
|
+
/** Accessible name of the message, so the speaker isn't conveyed by alignment and colour alone. */
|
|
21
|
+
authorLabel?: string;
|
|
22
|
+
statusLabels?: Partial<ChatMessageStatusLabels>;
|
|
11
23
|
} & {
|
|
12
24
|
style?: CSSProperties & ChatMessageKnobStyle;
|
|
13
25
|
};
|
|
@@ -15,14 +27,28 @@ export type ChatMessageContext = {
|
|
|
15
27
|
from: ChatRole;
|
|
16
28
|
state: ChatState;
|
|
17
29
|
density: ChatDensity;
|
|
30
|
+
layout: ChatMessageLayout;
|
|
31
|
+
continuation: boolean;
|
|
18
32
|
isUser: boolean;
|
|
19
33
|
isAssistant: boolean;
|
|
34
|
+
isParticipant: boolean;
|
|
20
35
|
isSystem: boolean;
|
|
21
36
|
isTool: boolean;
|
|
22
37
|
isCompact: boolean;
|
|
23
38
|
isStreaming: boolean;
|
|
24
39
|
isPending: boolean;
|
|
25
40
|
isError: boolean;
|
|
41
|
+
isEndAligned: boolean;
|
|
42
|
+
isBubble: boolean;
|
|
26
43
|
};
|
|
27
|
-
export declare
|
|
44
|
+
export declare const chatAuthorLabels: Record<ChatRole, string>;
|
|
45
|
+
export declare const chatMessageStatusLabels: ChatMessageStatusLabels;
|
|
46
|
+
export declare const ChatThreadAnnounceContext: import("react").Context<(message: string) => void>;
|
|
47
|
+
/** Publishes a state transition (never streamed text) to the thread's one polite status region; a no-op outside `ChatThread`. */
|
|
48
|
+
export declare function useChatThreadAnnounce(): (message: string) => void;
|
|
49
|
+
export declare const ChatMessageStateProvider: import("react").Provider<ChatMessageContext | null>;
|
|
50
|
+
export declare function useChatMessageContext(): ChatMessageContext;
|
|
51
|
+
/** The announcement for a state change, or null when the change isn't worth announcing. */
|
|
52
|
+
export declare function chatMessageTransition(previous: ChatState, next: ChatState, labels: ChatMessageStatusLabels): string | null;
|
|
53
|
+
export declare function useChatMessage({ from, state, density, layout, continuation }: Pick<ChatMessageProps, "from" | "state" | "density" | "layout" | "continuation">): ChatMessageContext;
|
|
28
54
|
//#endregion
|
|
@@ -1,25 +1,56 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
1
2
|
//#region src/hooks/use-chat-message.ts
|
|
2
|
-
|
|
3
|
+
const chatAuthorLabels = {
|
|
4
|
+
user: "You",
|
|
5
|
+
assistant: "Assistant",
|
|
6
|
+
participant: "Participant",
|
|
7
|
+
system: "System",
|
|
8
|
+
tool: "Tool"
|
|
9
|
+
};
|
|
10
|
+
const chatMessageStatusLabels = {
|
|
11
|
+
pending: "Assistant is replying",
|
|
12
|
+
replied: "Assistant replied",
|
|
13
|
+
error: "Reply failed"
|
|
14
|
+
};
|
|
15
|
+
const ChatThreadAnnounceContext = createContext(() => {});
|
|
16
|
+
/** Publishes a state transition (never streamed text) to the thread's one polite status region; a no-op outside `ChatThread`. */
|
|
17
|
+
function useChatThreadAnnounce() {
|
|
18
|
+
return useContext(ChatThreadAnnounceContext);
|
|
19
|
+
}
|
|
20
|
+
const ChatMessageStateContext = createContext(null);
|
|
21
|
+
const ChatMessageStateProvider = ChatMessageStateContext.Provider;
|
|
22
|
+
function useChatMessageContext() {
|
|
23
|
+
const context = useContext(ChatMessageStateContext);
|
|
24
|
+
if (!context) throw new Error("ChatMessage compound components must be rendered inside <ChatMessage>.");
|
|
25
|
+
return context;
|
|
26
|
+
}
|
|
27
|
+
/** The announcement for a state change, or null when the change isn't worth announcing. */
|
|
28
|
+
function chatMessageTransition(previous, next, labels) {
|
|
29
|
+
if (previous === next) return null;
|
|
30
|
+
if (next === "pending") return labels.pending;
|
|
31
|
+
if (next === "error") return labels.error;
|
|
32
|
+
if (next === "idle" && (previous === "pending" || previous === "streaming")) return labels.replied;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function useChatMessage({ from, state = "idle", density = "comfortable", layout = "bubble", continuation = false }) {
|
|
3
36
|
return {
|
|
4
37
|
from,
|
|
5
38
|
state,
|
|
6
39
|
density,
|
|
40
|
+
layout,
|
|
41
|
+
continuation,
|
|
7
42
|
isUser: from === "user",
|
|
8
43
|
isAssistant: from === "assistant",
|
|
44
|
+
isParticipant: from === "participant",
|
|
9
45
|
isSystem: from === "system",
|
|
10
46
|
isTool: from === "tool",
|
|
11
47
|
isCompact: density === "compact",
|
|
12
48
|
isStreaming: state === "streaming",
|
|
13
49
|
isPending: state === "pending",
|
|
14
|
-
isError: state === "error"
|
|
50
|
+
isError: state === "error",
|
|
51
|
+
isEndAligned: from === "user" && layout === "bubble",
|
|
52
|
+
isBubble: layout === "bubble" && (from === "user" || from === "participant")
|
|
15
53
|
};
|
|
16
54
|
}
|
|
17
|
-
function useChatMessage({ from, state = "idle", density = "comfortable" }) {
|
|
18
|
-
return getChatMessageContext({
|
|
19
|
-
from,
|
|
20
|
-
state,
|
|
21
|
-
density
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
55
|
//#endregion
|
|
25
|
-
export { useChatMessage };
|
|
56
|
+
export { ChatMessageStateProvider, ChatThreadAnnounceContext, chatAuthorLabels, chatMessageStatusLabels, chatMessageTransition, useChatMessage, useChatMessageContext, useChatThreadAnnounce };
|
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import { prefersReducedMotion } from "../lib/motion.js";
|
|
1
2
|
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
//#region src/hooks/use-chat-thread-scroll.ts
|
|
3
4
|
const BOTTOM_THRESHOLD = 24;
|
|
4
5
|
const SETTLE_TIMEOUT = 700;
|
|
6
|
+
function firstVisibleChild(list, viewportTop) {
|
|
7
|
+
const children = list.children;
|
|
8
|
+
let low = 0;
|
|
9
|
+
let high = children.length - 1;
|
|
10
|
+
let found;
|
|
11
|
+
while (low <= high) {
|
|
12
|
+
const middle = low + high >> 1;
|
|
13
|
+
if (children[middle].getBoundingClientRect().bottom > viewportTop) {
|
|
14
|
+
found = children[middle];
|
|
15
|
+
high = middle - 1;
|
|
16
|
+
} else low = middle + 1;
|
|
17
|
+
}
|
|
18
|
+
return found;
|
|
19
|
+
}
|
|
5
20
|
function useChatThreadScroll() {
|
|
6
21
|
const viewportRef = useRef(null);
|
|
7
22
|
const contentRef = useRef(null);
|
|
8
23
|
const pinnedRef = useRef(true);
|
|
9
24
|
const [atBottom, setAtBottom] = useState(true);
|
|
10
25
|
const settlingRef = useRef(void 0);
|
|
11
|
-
function scrollToBottom(behavior
|
|
26
|
+
function scrollToBottom(behavior) {
|
|
12
27
|
const viewport = viewportRef.current;
|
|
13
28
|
if (!viewport) return;
|
|
14
29
|
pinnedRef.current = true;
|
|
@@ -19,13 +34,29 @@ function useChatThreadScroll() {
|
|
|
19
34
|
}, SETTLE_TIMEOUT);
|
|
20
35
|
viewport.scrollTo({
|
|
21
36
|
top: viewport.scrollHeight,
|
|
22
|
-
behavior
|
|
37
|
+
behavior: behavior ?? (prefersReducedMotion(viewport) ? "auto" : "smooth")
|
|
23
38
|
});
|
|
24
39
|
}
|
|
25
40
|
useEffect(() => {
|
|
26
41
|
const viewport = viewportRef.current;
|
|
27
42
|
const content = contentRef.current;
|
|
28
|
-
|
|
43
|
+
const messageList = content?.firstElementChild;
|
|
44
|
+
if (!viewport || !content || !messageList) return;
|
|
45
|
+
let anchor;
|
|
46
|
+
function rememberAnchor() {
|
|
47
|
+
if (!viewport || !messageList) return;
|
|
48
|
+
const viewportTop = viewport.getBoundingClientRect().top;
|
|
49
|
+
const element = firstVisibleChild(messageList, viewportTop);
|
|
50
|
+
anchor = element ? {
|
|
51
|
+
element,
|
|
52
|
+
offset: element.getBoundingClientRect().top - viewportTop
|
|
53
|
+
} : void 0;
|
|
54
|
+
}
|
|
55
|
+
function keepAnchorInPlace() {
|
|
56
|
+
if (!viewport || !anchor?.element.isConnected) return;
|
|
57
|
+
const drift = anchor.element.getBoundingClientRect().top - viewport.getBoundingClientRect().top - anchor.offset;
|
|
58
|
+
if (drift !== 0) viewport.scrollTop += drift;
|
|
59
|
+
}
|
|
29
60
|
function settle() {
|
|
30
61
|
clearTimeout(settlingRef.current);
|
|
31
62
|
settlingRef.current = void 0;
|
|
@@ -37,21 +68,26 @@ function useChatThreadScroll() {
|
|
|
37
68
|
pinnedRef.current = distance <= BOTTOM_THRESHOLD;
|
|
38
69
|
setAtBottom(pinnedRef.current);
|
|
39
70
|
}
|
|
71
|
+
function handleScroll() {
|
|
72
|
+
rememberAnchor();
|
|
73
|
+
readPosition();
|
|
74
|
+
}
|
|
40
75
|
const observer = new ResizeObserver(() => {
|
|
41
76
|
if (!viewport) return;
|
|
42
77
|
if (pinnedRef.current) viewport.scrollTop = viewport.scrollHeight;
|
|
43
|
-
else
|
|
78
|
+
else keepAnchorInPlace();
|
|
79
|
+
handleScroll();
|
|
44
80
|
});
|
|
45
81
|
observer.observe(content);
|
|
46
82
|
observer.observe(viewport);
|
|
47
|
-
viewport.addEventListener("scroll",
|
|
83
|
+
viewport.addEventListener("scroll", handleScroll, { passive: true });
|
|
48
84
|
viewport.addEventListener("scrollend", settle);
|
|
49
85
|
viewport.scrollTop = viewport.scrollHeight;
|
|
50
|
-
|
|
86
|
+
handleScroll();
|
|
51
87
|
return () => {
|
|
52
88
|
observer.disconnect();
|
|
53
89
|
clearTimeout(settlingRef.current);
|
|
54
|
-
viewport.removeEventListener("scroll",
|
|
90
|
+
viewport.removeEventListener("scroll", handleScroll);
|
|
55
91
|
viewport.removeEventListener("scrollend", settle);
|
|
56
92
|
};
|
|
57
93
|
}, []);
|
|
@@ -8,10 +8,10 @@ export type ColorAreaRect = {
|
|
|
8
8
|
width: number;
|
|
9
9
|
height: number;
|
|
10
10
|
};
|
|
11
|
-
export type ColorAreaPointer = {
|
|
12
|
-
areaRef: RefObject<
|
|
11
|
+
export type ColorAreaPointer<Element extends HTMLElement> = {
|
|
12
|
+
areaRef: RefObject<Element | null>;
|
|
13
13
|
dragging: boolean;
|
|
14
14
|
onPointerDown: (event: PointerEvent) => void;
|
|
15
15
|
};
|
|
16
|
-
export declare function useColorArea(onChange: (offset: ColorAreaOffset, rect: ColorAreaRect) => void): ColorAreaPointer
|
|
16
|
+
export declare function useColorArea<Element extends HTMLElement = HTMLDivElement>(onChange: (offset: ColorAreaOffset, rect: ColorAreaRect) => void, targetRef?: RefObject<Element | null>): ColorAreaPointer<Element>;
|
|
17
17
|
//#endregion
|
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
|
3
3
|
//#region src/hooks/use-color-area.ts
|
|
4
|
-
function useColorArea(onChange) {
|
|
5
|
-
const
|
|
4
|
+
function useColorArea(onChange, targetRef) {
|
|
5
|
+
const ownRef = useRef(null);
|
|
6
|
+
const areaRef = targetRef ?? ownRef;
|
|
6
7
|
const [dragging, setDragging] = useState(false);
|
|
7
8
|
const emitDragChange = useEffectEvent(onChange);
|
|
8
9
|
useEffect(() => {
|
|
9
10
|
if (!dragging) return;
|
|
10
|
-
function
|
|
11
|
+
function onMove(event) {
|
|
11
12
|
const el = areaRef.current;
|
|
12
13
|
if (!el) return;
|
|
13
14
|
const rect = el.getBoundingClientRect();
|
|
14
15
|
emitDragChange({
|
|
15
|
-
x: clientX - rect.left,
|
|
16
|
-
y: clientY - rect.top
|
|
16
|
+
x: event.clientX - rect.left,
|
|
17
|
+
y: event.clientY - rect.top
|
|
17
18
|
}, {
|
|
18
19
|
width: rect.width,
|
|
19
20
|
height: rect.height
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
-
emit(event.clientX, event.clientY);
|
|
24
|
-
}
|
|
25
|
-
function onUp() {
|
|
23
|
+
function onEnd() {
|
|
26
24
|
setDragging(false);
|
|
27
25
|
}
|
|
28
26
|
const previousSelect = document.body.style.userSelect;
|
|
29
27
|
document.body.style.userSelect = "none";
|
|
30
28
|
window.addEventListener("pointermove", onMove);
|
|
31
|
-
window.addEventListener("pointerup",
|
|
29
|
+
window.addEventListener("pointerup", onEnd);
|
|
30
|
+
window.addEventListener("pointercancel", onEnd);
|
|
32
31
|
return () => {
|
|
33
32
|
window.removeEventListener("pointermove", onMove);
|
|
34
|
-
window.removeEventListener("pointerup",
|
|
33
|
+
window.removeEventListener("pointerup", onEnd);
|
|
34
|
+
window.removeEventListener("pointercancel", onEnd);
|
|
35
35
|
document.body.style.userSelect = previousSelect;
|
|
36
36
|
};
|
|
37
|
-
}, [dragging]);
|
|
37
|
+
}, [dragging, areaRef]);
|
|
38
38
|
function onPointerDown(event) {
|
|
39
|
+
if (event.button !== 0) return;
|
|
39
40
|
const el = areaRef.current;
|
|
40
41
|
if (el) {
|
|
41
42
|
const rect = el.getBoundingClientRect();
|
|
@@ -5,8 +5,9 @@ export type UseCopyToClipboardOptions = {
|
|
|
5
5
|
onCopy?: (value: string) => void;
|
|
6
6
|
onCopyError?: (error: unknown) => void;
|
|
7
7
|
};
|
|
8
|
+
export type CopyStatus = "idle" | "copied" | "failed";
|
|
8
9
|
export type UseCopyToClipboardResult = {
|
|
9
|
-
|
|
10
|
+
status: CopyStatus;
|
|
10
11
|
copyToClipboard: (value: string) => Promise<boolean>;
|
|
11
12
|
resetCopied: () => void;
|
|
12
13
|
};
|
|
@@ -17,15 +17,15 @@ function copyViaSelection(value) {
|
|
|
17
17
|
textarea.setAttribute("readonly", "");
|
|
18
18
|
textarea.setAttribute("aria-hidden", "true");
|
|
19
19
|
textarea.style.position = "fixed";
|
|
20
|
-
textarea.style.
|
|
21
|
-
textarea.style.
|
|
20
|
+
textarea.style.insetBlockStart = "0";
|
|
21
|
+
textarea.style.insetInlineStart = "0";
|
|
22
22
|
textarea.style.width = "1px";
|
|
23
23
|
textarea.style.height = "1px";
|
|
24
24
|
textarea.style.opacity = "0";
|
|
25
25
|
textarea.style.pointerEvents = "none";
|
|
26
26
|
document.body.appendChild(textarea);
|
|
27
27
|
try {
|
|
28
|
-
textarea.focus();
|
|
28
|
+
textarea.focus({ preventScroll: true });
|
|
29
29
|
textarea.select();
|
|
30
30
|
textarea.setSelectionRange(0, value.length);
|
|
31
31
|
return document.execCommand("copy");
|
|
@@ -41,7 +41,7 @@ async function copyTextToClipboard(value) {
|
|
|
41
41
|
return copyViaSelection(value);
|
|
42
42
|
}
|
|
43
43
|
function useCopyToClipboard({ text, copiedDuration = 1200, onCopy, onCopyError } = {}) {
|
|
44
|
-
const [
|
|
44
|
+
const [status, setStatus] = useState("idle");
|
|
45
45
|
const resetTimeout = useRef(null);
|
|
46
46
|
useEffect(() => {
|
|
47
47
|
const timeoutRef = resetTimeout;
|
|
@@ -52,38 +52,38 @@ function useCopyToClipboard({ text, copiedDuration = 1200, onCopy, onCopyError }
|
|
|
52
52
|
function resetCopied() {
|
|
53
53
|
if (resetTimeout.current) window.clearTimeout(resetTimeout.current);
|
|
54
54
|
resetTimeout.current = null;
|
|
55
|
-
|
|
55
|
+
setStatus("idle");
|
|
56
|
+
}
|
|
57
|
+
function settle(next) {
|
|
58
|
+
setStatus(next);
|
|
59
|
+
if (resetTimeout.current) window.clearTimeout(resetTimeout.current);
|
|
60
|
+
resetTimeout.current = next === "copied" ? window.setTimeout(() => setStatus("idle"), copiedDuration) : null;
|
|
61
|
+
return next === "copied";
|
|
56
62
|
}
|
|
57
63
|
async function copyToClipboard(value) {
|
|
58
64
|
if (!await copyTextToClipboard(value)) {
|
|
59
65
|
onCopyError?.(/* @__PURE__ */ new Error("Unable to copy text"));
|
|
60
|
-
|
|
61
|
-
return false;
|
|
66
|
+
return settle("failed");
|
|
62
67
|
}
|
|
63
68
|
try {
|
|
64
69
|
onCopy?.(value);
|
|
65
70
|
} catch (error) {
|
|
66
71
|
onCopyError?.(error);
|
|
67
|
-
|
|
68
|
-
return false;
|
|
72
|
+
return settle("failed");
|
|
69
73
|
}
|
|
70
|
-
|
|
71
|
-
if (resetTimeout.current) window.clearTimeout(resetTimeout.current);
|
|
72
|
-
resetTimeout.current = window.setTimeout(() => setIsCopied(false), copiedDuration);
|
|
73
|
-
return true;
|
|
74
|
+
return settle("copied");
|
|
74
75
|
}
|
|
75
76
|
function handleCopy() {
|
|
76
77
|
return text !== void 0 ? copyToClipboard(text) : Promise.resolve(false);
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const state = {
|
|
80
|
+
status,
|
|
80
81
|
copyToClipboard,
|
|
81
82
|
resetCopied
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
handleCopy
|
|
86
|
-
resetCopied
|
|
83
|
+
};
|
|
84
|
+
return text === void 0 ? state : {
|
|
85
|
+
...state,
|
|
86
|
+
handleCopy
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
//#endregion
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DropzoneFileRejection, DropzonePolicy } from "../lib/dropzone-validation.js";
|
|
2
|
-
import { ChangeEvent, ComponentProps, DragEvent, RefObject } from "react";
|
|
2
|
+
import { ChangeEvent, ClipboardEvent, ComponentProps, DragEvent, RefObject } from "react";
|
|
3
3
|
//#region src/hooks/use-dropzone.d.ts
|
|
4
4
|
export type DropzoneVisualState = "idle" | "accept" | "reject" | "unknown" | "processing";
|
|
5
|
-
export type DropzoneValueChangeReason = "drop" | "input" | "remove" | "clear";
|
|
6
|
-
export type DropzoneEvent = DragEvent<HTMLElement> | ChangeEvent<HTMLInputElement>;
|
|
5
|
+
export type DropzoneValueChangeReason = "drop" | "input" | "paste" | "remove" | "clear";
|
|
6
|
+
export type DropzoneEvent = DragEvent<HTMLElement> | ClipboardEvent<HTMLElement> | ChangeEvent<HTMLInputElement>;
|
|
7
7
|
export type DropzoneGetFilesFromEvent = (event: DropzoneEvent, signal: AbortSignal) => readonly File[] | Promise<readonly File[]>;
|
|
8
8
|
export type DropzoneValueChangeDetails = {
|
|
9
9
|
reason: DropzoneValueChangeReason;
|
|
@@ -22,6 +22,7 @@ export type UseDropzoneOptions = {
|
|
|
22
22
|
policy?: DropzonePolicy;
|
|
23
23
|
disabled?: boolean;
|
|
24
24
|
drag?: boolean;
|
|
25
|
+
paste?: boolean;
|
|
25
26
|
preventDropOnDocument?: boolean;
|
|
26
27
|
trackDocumentDrag?: boolean;
|
|
27
28
|
getFilesFromEvent?: DropzoneGetFilesFromEvent;
|
|
@@ -35,6 +36,7 @@ export type UseDropzoneReturn = {
|
|
|
35
36
|
multiple: boolean;
|
|
36
37
|
disabled: boolean;
|
|
37
38
|
dragEnabled: boolean;
|
|
39
|
+
pasteEnabled: boolean;
|
|
38
40
|
visualState: DropzoneVisualState;
|
|
39
41
|
isDragActive: boolean;
|
|
40
42
|
isDragAccept: boolean;
|
|
@@ -48,11 +50,12 @@ export type UseDropzoneReturn = {
|
|
|
48
50
|
open: () => void;
|
|
49
51
|
removeFile: (file: File) => void;
|
|
50
52
|
clearFiles: () => void;
|
|
53
|
+
removeRejection: (file: File) => void;
|
|
51
54
|
clearRejections: () => void;
|
|
52
55
|
reset: () => void;
|
|
53
56
|
getRootProps: (props?: ComponentProps<"div">) => ComponentProps<"div">;
|
|
54
57
|
getInputProps: (props?: ComponentProps<"input">) => ComponentProps<"input">;
|
|
55
58
|
};
|
|
56
|
-
export declare function useDropzone({ value: controlledValue, defaultValue, onValueChange, policy, disabled, drag, preventDropOnDocument, trackDocumentDrag, getFilesFromEvent, onDrop, onError }?: UseDropzoneOptions): UseDropzoneReturn;
|
|
59
|
+
export declare function useDropzone({ value: controlledValue, defaultValue, onValueChange, policy, disabled, drag, paste, preventDropOnDocument, trackDocumentDrag, getFilesFromEvent, onDrop, onError }?: UseDropzoneOptions): UseDropzoneReturn;
|
|
57
60
|
export declare function defaultGetFilesFromEvent(event: DropzoneEvent): readonly File[];
|
|
58
61
|
//#endregion
|