@mieweb/ui 0.8.0-dev.23 → 0.8.0-dev.24

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.
@@ -1,9 +1,9 @@
1
1
  import { cn } from './chunk-TQONQSJO.js';
2
2
  import { useAnchoredPosition } from './chunk-WSNMQQ4P.js';
3
- import * as React2 from 'react';
3
+ import * as React3 from 'react';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
- import { createPortal } from 'react-dom';
6
5
  import { cva } from 'class-variance-authority';
6
+ import { createPortal } from 'react-dom';
7
7
 
8
8
  function getFileType(mimeType) {
9
9
  if (mimeType.startsWith("image/")) return "image";
@@ -249,7 +249,7 @@ function AttachmentPreviewItem({
249
249
  );
250
250
  }
251
251
  AttachmentPreviewItem.displayName = "AttachmentPreviewItem";
252
- var AttachmentPicker = React2.forwardRef(
252
+ var AttachmentPicker = React3.forwardRef(
253
253
  ({
254
254
  onFilesSelected,
255
255
  acceptedTypes = ["image/*", "video/*", ".pdf", ".doc", ".docx"],
@@ -262,8 +262,8 @@ var AttachmentPicker = React2.forwardRef(
262
262
  className,
263
263
  children
264
264
  }, ref) => {
265
- const inputRef = React2.useRef(null);
266
- React2.useImperativeHandle(ref, () => inputRef.current);
265
+ const inputRef = React3.useRef(null);
266
+ React3.useImperativeHandle(ref, () => inputRef.current);
267
267
  const handleClick = () => {
268
268
  inputRef.current?.click();
269
269
  };
@@ -352,11 +352,12 @@ function DragDropZone({
352
352
  maxFiles = 10,
353
353
  disabled = false,
354
354
  onError,
355
+ overlayLabel = "Drop files here",
355
356
  children,
356
357
  className
357
358
  }) {
358
- const [isDragging, setIsDragging] = React2.useState(false);
359
- const dragCounterRef = React2.useRef(0);
359
+ const [isDragging, setIsDragging] = React3.useState(false);
360
+ const dragCounterRef = React3.useRef(0);
360
361
  const handleDragEnter = (event) => {
361
362
  event.preventDefault();
362
363
  event.stopPropagation();
@@ -443,7 +444,7 @@ function DragDropZone({
443
444
  )
444
445
  }
445
446
  ),
446
- /* @__PURE__ */ jsx("p", { className: "text-primary-700 dark:text-primary-300 mt-2 text-sm font-medium", children: "Drop files here" })
447
+ /* @__PURE__ */ jsx("p", { className: "text-primary-700 dark:text-primary-300 mt-2 text-sm font-medium", children: overlayLabel })
447
448
  ] })
448
449
  }
449
450
  )
@@ -458,7 +459,7 @@ function CameraButton({
458
459
  disabled = false,
459
460
  className
460
461
  }) {
461
- const inputRef = React2.useRef(null);
462
+ const inputRef = React3.useRef(null);
462
463
  const handleClick = () => {
463
464
  inputRef.current?.click();
464
465
  };
@@ -543,6 +544,162 @@ function activeMentionQuery(value, caret) {
543
544
  const query = match[2];
544
545
  return { query, start: caret - query.length - 1 };
545
546
  }
547
+ function useMentionAutocomplete({
548
+ options,
549
+ value,
550
+ setValue,
551
+ textareaRef
552
+ }) {
553
+ const enabled = !!options && options.length > 0;
554
+ const [mention, setMention] = React3.useState(null);
555
+ const [highlight, setHighlight] = React3.useState(0);
556
+ const listId = React3.useId();
557
+ const optionId = React3.useCallback(
558
+ (index) => `${listId}-option-${index}`,
559
+ [listId]
560
+ );
561
+ const suggestions = React3.useMemo(() => {
562
+ if (!mention || !options) return [];
563
+ const q = mention.query.toLowerCase();
564
+ return options.filter((o) => o.label.toLowerCase().includes(q));
565
+ }, [mention, options]);
566
+ const menuOpen = enabled && mention !== null && suggestions.length > 0;
567
+ const {
568
+ anchorRef,
569
+ floatingRef,
570
+ style: menuStyle
571
+ } = useAnchoredPosition({
572
+ open: menuOpen,
573
+ placement: "top-start",
574
+ maxHeight: 224
575
+ });
576
+ const clampedHighlight = menuOpen ? Math.min(highlight, suggestions.length - 1) : -1;
577
+ const activeOptionId = menuOpen ? optionId(clampedHighlight) : void 0;
578
+ const sync = React3.useCallback(
579
+ (nextValue, caret) => {
580
+ if (!enabled) return;
581
+ setMention(activeMentionQuery(nextValue, caret));
582
+ setHighlight(0);
583
+ },
584
+ [enabled]
585
+ );
586
+ const insert = (option) => {
587
+ if (!mention) return;
588
+ const insertValue = option.value ?? option.label.split(" ")[0];
589
+ const before = value.slice(0, mention.start);
590
+ const after = value.slice(mention.start + 1 + mention.query.length);
591
+ const insertText = `@${insertValue} `;
592
+ setValue(before + insertText + after);
593
+ setMention(null);
594
+ const caret = before.length + insertText.length;
595
+ requestAnimationFrame(() => {
596
+ const el = textareaRef.current;
597
+ if (el) {
598
+ el.focus();
599
+ el.setSelectionRange(caret, caret);
600
+ }
601
+ });
602
+ };
603
+ const handleKeyDown = (event) => {
604
+ if (!menuOpen) return false;
605
+ if (event.key === "ArrowDown") {
606
+ event.preventDefault();
607
+ setHighlight(
608
+ (h) => (Math.min(h, suggestions.length - 1) + 1) % suggestions.length
609
+ );
610
+ return true;
611
+ }
612
+ if (event.key === "ArrowUp") {
613
+ event.preventDefault();
614
+ setHighlight(
615
+ (h) => (Math.min(h, suggestions.length - 1) - 1 + suggestions.length) % suggestions.length
616
+ );
617
+ return true;
618
+ }
619
+ if (event.key === "Enter" || event.key === "Tab") {
620
+ event.preventDefault();
621
+ const chosen = suggestions[clampedHighlight];
622
+ if (chosen) insert(chosen);
623
+ return true;
624
+ }
625
+ if (event.key === "Escape") {
626
+ event.preventDefault();
627
+ setMention(null);
628
+ return true;
629
+ }
630
+ return false;
631
+ };
632
+ const inputProps = enabled ? {
633
+ "aria-controls": menuOpen ? listId : void 0,
634
+ "aria-activedescendant": activeOptionId,
635
+ "aria-autocomplete": "list"
636
+ } : {};
637
+ return {
638
+ enabled,
639
+ menuOpen,
640
+ suggestions,
641
+ highlight: clampedHighlight,
642
+ listId,
643
+ optionId,
644
+ anchorRef,
645
+ floatingRef,
646
+ menuStyle,
647
+ sync,
648
+ insert,
649
+ setHighlight,
650
+ handleKeyDown,
651
+ inputProps
652
+ };
653
+ }
654
+ function MentionMenu({
655
+ mention,
656
+ label = "Mention",
657
+ dataSlot = "composer-mention-list"
658
+ }) {
659
+ if (!mention.menuOpen) return null;
660
+ return createPortal(
661
+ /* @__PURE__ */ jsx(
662
+ "ul",
663
+ {
664
+ ref: mention.floatingRef,
665
+ style: mention.menuStyle,
666
+ id: mention.listId,
667
+ role: "listbox",
668
+ "aria-label": label,
669
+ "data-slot": dataSlot,
670
+ className: "w-64 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800",
671
+ children: mention.suggestions.map((option, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
672
+ "button",
673
+ {
674
+ type: "button",
675
+ id: mention.optionId(i),
676
+ role: "option",
677
+ "aria-selected": i === mention.highlight,
678
+ onMouseDown: (e) => {
679
+ e.preventDefault();
680
+ mention.insert(option);
681
+ },
682
+ onMouseEnter: () => mention.setHighlight(i),
683
+ className: cn(
684
+ "flex w-full items-center gap-2 px-3 py-1.5 text-start text-sm",
685
+ i === mention.highlight ? "bg-primary-100 text-primary-900 dark:bg-primary-900/40 dark:text-primary-100" : "text-neutral-700 dark:text-neutral-200"
686
+ ),
687
+ children: [
688
+ option.icon,
689
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate", children: [
690
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: option.label }),
691
+ option.description && /* @__PURE__ */ jsx("span", { className: "ms-1 text-xs text-neutral-400", children: option.description })
692
+ ] }),
693
+ option.meta && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-400", children: option.meta })
694
+ ]
695
+ }
696
+ ) }, option.id))
697
+ }
698
+ ),
699
+ document.body
700
+ );
701
+ }
702
+ MentionMenu.displayName = "MentionMenu";
546
703
  function isFileAccepted(file, accepted) {
547
704
  if (!accepted || accepted.length === 0) return true;
548
705
  const mime = file.type;
@@ -614,7 +771,7 @@ var sendButtonVariants = cva(
614
771
  }
615
772
  }
616
773
  );
617
- var SendButton = React2.forwardRef(
774
+ var SendButton = React3.forwardRef(
618
775
  ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
619
776
  return /* @__PURE__ */ jsx(
620
777
  "button",
@@ -679,7 +836,7 @@ var SendButton = React2.forwardRef(
679
836
  }
680
837
  );
681
838
  SendButton.displayName = "SendButton";
682
- var MessageComposer = React2.forwardRef(
839
+ var MessageComposer = React3.forwardRef(
683
840
  ({
684
841
  onSend,
685
842
  onTypingStart,
@@ -705,11 +862,11 @@ var MessageComposer = React2.forwardRef(
705
862
  mentionOptions,
706
863
  className
707
864
  }, ref) => {
708
- const textareaRef = React2.useRef(null);
709
- const [internalContent, setInternalContent] = React2.useState("");
865
+ const textareaRef = React3.useRef(null);
866
+ const [internalContent, setInternalContent] = React3.useState("");
710
867
  const isControlled = controlledValue !== void 0;
711
868
  const content = isControlled ? controlledValue : internalContent;
712
- const setContent = React2.useCallback(
869
+ const setContent = React3.useCallback(
713
870
  (val) => {
714
871
  if (isControlled) {
715
872
  const newVal = typeof val === "function" ? val(controlledValue) : val;
@@ -720,20 +877,20 @@ var MessageComposer = React2.forwardRef(
720
877
  },
721
878
  [isControlled, controlledValue, onValueChange]
722
879
  );
723
- const [attachments, setAttachments] = React2.useState(
880
+ const [attachments, setAttachments] = React3.useState(
724
881
  []
725
882
  );
726
- const [isTyping, setIsTyping] = React2.useState(false);
727
- const typingTimeoutRef = React2.useRef(null);
728
- React2.useImperativeHandle(ref, () => textareaRef.current);
729
- React2.useEffect(() => {
883
+ const [isTyping, setIsTyping] = React3.useState(false);
884
+ const typingTimeoutRef = React3.useRef(null);
885
+ React3.useImperativeHandle(ref, () => textareaRef.current);
886
+ React3.useEffect(() => {
730
887
  const textarea = textareaRef.current;
731
888
  if (textarea) {
732
889
  textarea.style.height = "auto";
733
890
  textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
734
891
  }
735
892
  }, [content]);
736
- React2.useEffect(() => {
893
+ React3.useEffect(() => {
737
894
  if (content.length > 0 && !isTyping) {
738
895
  setIsTyping(true);
739
896
  onTypingStart?.();
@@ -753,63 +910,22 @@ var MessageComposer = React2.forwardRef(
753
910
  }
754
911
  };
755
912
  }, [content, isTyping, onTypingStart, onTypingStop]);
756
- React2.useEffect(() => {
913
+ React3.useEffect(() => {
757
914
  if (autoFocus) {
758
915
  textareaRef.current?.focus();
759
916
  }
760
917
  }, [autoFocus]);
761
- React2.useEffect(() => {
918
+ React3.useEffect(() => {
762
919
  if (replyTo) {
763
920
  textareaRef.current?.focus();
764
921
  }
765
922
  }, [replyTo]);
766
- const mentionsEnabled = !!mentionOptions && mentionOptions.length > 0;
767
- const [mention, setMention] = React2.useState(null);
768
- const [mentionHighlight, setMentionHighlight] = React2.useState(0);
769
- const mentionListId = React2.useId();
770
- const mentionOptionId = (i) => `${mentionListId}-option-${i}`;
771
- const mentionSuggestions = React2.useMemo(() => {
772
- if (!mention || !mentionOptions) return [];
773
- const q = mention.query.toLowerCase();
774
- return mentionOptions.filter((o) => o.label.toLowerCase().includes(q));
775
- }, [mention, mentionOptions]);
776
- const mentionMenuOpen = mentionsEnabled && mention !== null && mentionSuggestions.length > 0;
777
- const {
778
- anchorRef: mentionAnchorRef,
779
- floatingRef: mentionFloatingRef,
780
- style: mentionStyle
781
- } = useAnchoredPosition({
782
- open: mentionMenuOpen,
783
- placement: "top-start",
784
- maxHeight: 224
923
+ const mention = useMentionAutocomplete({
924
+ options: mentionOptions,
925
+ value: content,
926
+ setValue: setContent,
927
+ textareaRef
785
928
  });
786
- const clampedMentionHighlight = mentionMenuOpen ? Math.min(mentionHighlight, mentionSuggestions.length - 1) : -1;
787
- const activeMentionOptionId = mentionMenuOpen ? mentionOptionId(clampedMentionHighlight) : void 0;
788
- const syncMention = React2.useCallback(
789
- (value, caret) => {
790
- if (!mentionsEnabled) return;
791
- setMention(activeMentionQuery(value, caret));
792
- setMentionHighlight(0);
793
- },
794
- [mentionsEnabled]
795
- );
796
- const insertMention = (option) => {
797
- if (!mention) return;
798
- const insertValue = option.value ?? option.label.split(" ")[0];
799
- const before = content.slice(0, mention.start);
800
- const after = content.slice(mention.start + 1 + mention.query.length);
801
- const insert = `@${insertValue} `;
802
- setContent(before + insert + after);
803
- setMention(null);
804
- const caret = before.length + insert.length;
805
- requestAnimationFrame(() => {
806
- const el = textareaRef.current;
807
- if (el) {
808
- el.focus();
809
- el.setSelectionRange(caret, caret);
810
- }
811
- });
812
- };
813
929
  const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
814
930
  const handleSubmit = async (event) => {
815
931
  event.preventDefault();
@@ -831,31 +947,7 @@ var MessageComposer = React2.forwardRef(
831
947
  }
832
948
  };
833
949
  const handleKeyDown = (event) => {
834
- if (mentionMenuOpen) {
835
- if (event.key === "ArrowDown") {
836
- event.preventDefault();
837
- setMentionHighlight((h) => (h + 1) % mentionSuggestions.length);
838
- return;
839
- }
840
- if (event.key === "ArrowUp") {
841
- event.preventDefault();
842
- setMentionHighlight(
843
- (h) => (h - 1 + mentionSuggestions.length) % mentionSuggestions.length
844
- );
845
- return;
846
- }
847
- if (event.key === "Enter" || event.key === "Tab") {
848
- event.preventDefault();
849
- const chosen = mentionSuggestions[mentionHighlight] ?? mentionSuggestions[0];
850
- if (chosen) insertMention(chosen);
851
- return;
852
- }
853
- if (event.key === "Escape") {
854
- event.preventDefault();
855
- setMention(null);
856
- return;
857
- }
858
- }
950
+ if (mention.handleKeyDown(event)) return;
859
951
  if (event.key === "Enter" && !event.shiftKey) {
860
952
  event.preventDefault();
861
953
  if (canSend) {
@@ -901,7 +993,7 @@ var MessageComposer = React2.forwardRef(
901
993
  return prev.filter((a) => a.id !== attachmentId);
902
994
  });
903
995
  };
904
- React2.useEffect(() => {
996
+ React3.useEffect(() => {
905
997
  const currentAttachments = attachments;
906
998
  return () => {
907
999
  currentAttachments.forEach((a) => {
@@ -1032,49 +1124,9 @@ var MessageComposer = React2.forwardRef(
1032
1124
  {
1033
1125
  "data-slot": "composer-input-wrapper",
1034
1126
  className: "relative flex-1",
1035
- ref: mentionAnchorRef,
1127
+ ref: mention.anchorRef,
1036
1128
  children: [
1037
- mentionMenuOpen && createPortal(
1038
- /* @__PURE__ */ jsx(
1039
- "ul",
1040
- {
1041
- ref: mentionFloatingRef,
1042
- style: mentionStyle,
1043
- id: mentionListId,
1044
- role: "listbox",
1045
- "aria-label": "Mention",
1046
- "data-slot": "composer-mention-list",
1047
- className: "w-64 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800",
1048
- children: mentionSuggestions.map((option, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
1049
- "button",
1050
- {
1051
- type: "button",
1052
- id: mentionOptionId(i),
1053
- role: "option",
1054
- "aria-selected": i === clampedMentionHighlight,
1055
- onMouseDown: (e) => {
1056
- e.preventDefault();
1057
- insertMention(option);
1058
- },
1059
- onMouseEnter: () => setMentionHighlight(i),
1060
- className: cn(
1061
- "flex w-full items-center gap-2 px-3 py-1.5 text-start text-sm",
1062
- i === clampedMentionHighlight ? "bg-primary-100 text-primary-900 dark:bg-primary-900/40 dark:text-primary-100" : "text-neutral-700 dark:text-neutral-200"
1063
- ),
1064
- children: [
1065
- option.icon,
1066
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate", children: [
1067
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: option.label }),
1068
- option.description && /* @__PURE__ */ jsx("span", { className: "ms-1 text-xs text-neutral-400", children: option.description })
1069
- ] }),
1070
- option.meta && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-400", children: option.meta })
1071
- ]
1072
- }
1073
- ) }, option.id))
1074
- }
1075
- ),
1076
- document.body
1077
- ),
1129
+ /* @__PURE__ */ jsx(MentionMenu, { mention }),
1078
1130
  /* @__PURE__ */ jsx(
1079
1131
  "textarea",
1080
1132
  {
@@ -1083,14 +1135,14 @@ var MessageComposer = React2.forwardRef(
1083
1135
  value: content,
1084
1136
  onChange: (e) => {
1085
1137
  setContent(e.target.value);
1086
- syncMention(
1138
+ mention.sync(
1087
1139
  e.target.value,
1088
1140
  e.target.selectionStart ?? e.target.value.length
1089
1141
  );
1090
1142
  },
1091
1143
  onClick: (e) => {
1092
1144
  const el = e.currentTarget;
1093
- syncMention(el.value, el.selectionStart ?? el.value.length);
1145
+ mention.sync(el.value, el.selectionStart ?? el.value.length);
1094
1146
  },
1095
1147
  onKeyDown: handleKeyDown,
1096
1148
  onPaste: handlePaste,
@@ -1110,11 +1162,7 @@ var MessageComposer = React2.forwardRef(
1110
1162
  ),
1111
1163
  "aria-label": "Message",
1112
1164
  "aria-describedby": showCharacterCount ? "char-count" : void 0,
1113
- ...mentionsEnabled ? {
1114
- "aria-controls": mentionMenuOpen ? mentionListId : void 0,
1115
- "aria-activedescendant": activeMentionOptionId,
1116
- "aria-autocomplete": "list"
1117
- } : {}
1165
+ ...mention.inputProps
1118
1166
  }
1119
1167
  ),
1120
1168
  inputTrailing && /* @__PURE__ */ jsx(
@@ -1157,6 +1205,6 @@ var MessageComposer = React2.forwardRef(
1157
1205
  );
1158
1206
  MessageComposer.displayName = "MessageComposer";
1159
1207
 
1160
- export { AttachmentPicker, AttachmentPreviewItem, CameraButton, CharacterCounter, DragDropZone, MessageComposer, SendButton, generateAttachmentId, getFileType, sendButtonVariants, validateFile };
1161
- //# sourceMappingURL=chunk-AZLDTPYK.js.map
1162
- //# sourceMappingURL=chunk-AZLDTPYK.js.map
1208
+ export { AttachmentPicker, AttachmentPreviewItem, CameraButton, CharacterCounter, DragDropZone, MentionMenu, MessageComposer, SendButton, generateAttachmentId, getFileType, sendButtonVariants, useMentionAutocomplete, validateFile };
1209
+ //# sourceMappingURL=chunk-O6GJ4IYS.js.map
1210
+ //# sourceMappingURL=chunk-O6GJ4IYS.js.map