fantasia-ui 0.6.12 → 0.6.13

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.
@@ -8315,18 +8315,18 @@ to {
8315
8315
  font-size: 14px;
8316
8316
  color: #1e293b;
8317
8317
  line-height: 1;
8318
- }.f-dropdown-trigger-wrapper[data-v-4f7ae74f] {
8318
+ }.f-dropdown-trigger-wrapper[data-v-c46e9d1e] {
8319
8319
  display: contents;
8320
8320
  }
8321
- .f-dropdown-item[data-v-4f7ae74f] {
8321
+ .f-dropdown-item[data-v-c46e9d1e] {
8322
8322
  position: fixed;
8323
8323
  pointer-events: auto;
8324
8324
  }
8325
- .f-dropdown-bridge[data-v-4f7ae74f] {
8325
+ .f-dropdown-bridge[data-v-c46e9d1e] {
8326
8326
  position: absolute;
8327
8327
  background: transparent;
8328
8328
  }
8329
- .f-dropdown-content[data-v-4f7ae74f] {
8329
+ .f-dropdown-content[data-v-c46e9d1e] {
8330
8330
  background: rgba(255, 255, 255, 0.96);
8331
8331
  border-radius: 8px;
8332
8332
  border: 1px solid rgba(148, 163, 184, 0.2);
@@ -8336,7 +8336,7 @@ to {
8336
8336
  width: fit-content;
8337
8337
  backdrop-filter: blur(6px);
8338
8338
  }
8339
- .f-dropdown-content .f-dropdown-option[data-v-4f7ae74f] {
8339
+ .f-dropdown-content .f-dropdown-option[data-v-c46e9d1e] {
8340
8340
  display: flex;
8341
8341
  align-items: center;
8342
8342
  padding: 7px 10px;
@@ -8347,25 +8347,25 @@ to {
8347
8347
  font-size: 13px;
8348
8348
  transition: background 0.15s ease, color 0.15s ease;
8349
8349
  }
8350
- .f-dropdown-content .f-dropdown-option[data-v-4f7ae74f]:hover:not(.is-disabled) {
8350
+ .f-dropdown-content .f-dropdown-option[data-v-c46e9d1e]:hover:not(.is-disabled) {
8351
8351
  background: var(--surface3);
8352
8352
  color: var(--text);
8353
8353
  }
8354
- .f-dropdown-content .f-dropdown-option.is-disabled[data-v-4f7ae74f] {
8354
+ .f-dropdown-content .f-dropdown-option.is-disabled[data-v-c46e9d1e] {
8355
8355
  opacity: 0.35;
8356
8356
  cursor: not-allowed;
8357
8357
  }
8358
- .f-dropdown-content .f-dropdown-option.is-selected[data-v-4f7ae74f] {
8358
+ .f-dropdown-content .f-dropdown-option.is-selected[data-v-c46e9d1e] {
8359
8359
  color: var(--primary);
8360
8360
  }
8361
- .f-dropdown-content .f-dropdown-option.is-selected .opt-icon[data-v-4f7ae74f] {
8361
+ .f-dropdown-content .f-dropdown-option.is-selected .opt-icon[data-v-c46e9d1e] {
8362
8362
  color: var(--primary);
8363
8363
  }
8364
- .f-dropdown-content .f-dropdown-option .opt-checkbox[data-v-4f7ae74f] {
8364
+ .f-dropdown-content .f-dropdown-option .opt-checkbox[data-v-c46e9d1e] {
8365
8365
  flex-shrink: 0;
8366
8366
  pointer-events: none;
8367
8367
  }
8368
- .f-dropdown-content .f-dropdown-option .opt-icon[data-v-4f7ae74f] {
8368
+ .f-dropdown-content .f-dropdown-option .opt-icon[data-v-c46e9d1e] {
8369
8369
  display: inline-flex;
8370
8370
  align-items: center;
8371
8371
  width: 16px;
@@ -8373,20 +8373,20 @@ to {
8373
8373
  flex-shrink: 0;
8374
8374
  color: var(--text-dim);
8375
8375
  }
8376
- .f-dropdown-content .f-dropdown-option .opt-icon[data-v-4f7ae74f] svg {
8376
+ .f-dropdown-content .f-dropdown-option .opt-icon[data-v-c46e9d1e] svg {
8377
8377
  width: 100%;
8378
8378
  height: 100%;
8379
8379
  }
8380
- .f-dropdown-content .f-dropdown-option .opt-label[data-v-4f7ae74f] {
8380
+ .f-dropdown-content .f-dropdown-option .opt-label[data-v-c46e9d1e] {
8381
8381
  flex: 1;
8382
8382
  white-space: nowrap;
8383
8383
  }
8384
- .f-dropdown-motion-enter-active[data-v-4f7ae74f],
8385
- .f-dropdown-motion-leave-active[data-v-4f7ae74f] {
8384
+ .f-dropdown-motion-enter-active[data-v-c46e9d1e],
8385
+ .f-dropdown-motion-leave-active[data-v-c46e9d1e] {
8386
8386
  transition: opacity 0.18s ease, transform 0.2s cubic-bezier(0.23, 1, 0.32, 1);
8387
8387
  }
8388
- .f-dropdown-motion-enter-from[data-v-4f7ae74f],
8389
- .f-dropdown-motion-leave-to[data-v-4f7ae74f] {
8388
+ .f-dropdown-motion-enter-from[data-v-c46e9d1e],
8389
+ .f-dropdown-motion-leave-to[data-v-c46e9d1e] {
8390
8390
  opacity: 0;
8391
8391
  transform: scale(0.92);
8392
8392
  }.f-tag[data-v-19e20dd3] {
@@ -39,7 +39,7 @@ var __async = (__this, __arguments, generator) => {
39
39
  };
40
40
  import { defineComponent, computed, createElementBlock, openBlock, normalizeStyle, unref, renderSlot, normalizeClass, createCommentVNode, Fragment, renderList, createElementVNode, toDisplayString, ref, watch, createVNode, withDirectives, Transition, withCtx, vShow, mergeModels, useModel, useSlots, inject, createBlock, resolveDynamicComponent, withModifiers, nextTick, onMounted, onBeforeUnmount, onUnmounted, Teleport, createTextVNode, withKeys, isRef, vModelText, TransitionGroup, Comment as Comment$1, Text, provide, readonly, reactive, toRefs, h, render } from "vue";
41
41
  import { CloseCircleOutline, EyeOutline, EyeOffOutline, StarOutline, StarHalf, Star, CloseCircle, Close, ChevronDownOutline, CalendarOutline, ChevronBackOutline, ChevronForwardOutline, EllipsisHorizontal, Person, InformationCircleOutline, WarningOutline, AlertCircleOutline, CheckmarkCircleOutline, CloudUpload, CheckmarkCircle, AlertCircle, DocumentOutline, Reload, FileTrayOutline, RemoveOutline, AddOutline, ChevronUpOutline, InformationCircle, RefreshOutline, PauseOutline, PlayOutline, CloseOutline, Warning } from "@vicons/ionicons5";
42
- import { useEventListener } from "@vueuse/core";
42
+ import { useResizeObserver, useEventListener } from "@vueuse/core";
43
43
  const _sfc_main$Y = /* @__PURE__ */ defineComponent({
44
44
  __name: "FIcon",
45
45
  props: {
@@ -771,63 +771,78 @@ const _sfc_main$Q = /* @__PURE__ */ defineComponent({
771
771
  disabled: { type: Boolean, default: false },
772
772
  multiple: { type: Boolean, default: false },
773
773
  modelValue: { default: () => [] },
774
- maxHeight: { default: "300px" }
774
+ maxHeight: { default: "300px" },
775
+ show: { type: Boolean, default: void 0 },
776
+ closeOnClickOutside: { type: Boolean, default: false }
775
777
  },
776
- emits: ["select", "show", "hide", "update:modelValue"],
777
- setup(__props, { emit: __emit }) {
778
+ emits: ["select", "show", "hide", "update:modelValue", "update:show"],
779
+ setup(__props, { expose: __expose, emit: __emit }) {
778
780
  const props = __props;
779
781
  const emit = __emit;
780
782
  const selectedKeys = computed({
781
783
  get: () => props.modelValue || [],
782
784
  set: (val) => emit("update:modelValue", val)
783
785
  });
784
- const show = ref(false);
786
+ const innerVisible = ref(false);
787
+ const visible = computed(
788
+ () => props.show !== void 0 ? !!props.show : innerVisible.value
789
+ );
790
+ const setVisible = (v) => {
791
+ if (v && props.disabled) return;
792
+ if (v === visible.value) return;
793
+ innerVisible.value = v;
794
+ emit("update:show", v);
795
+ };
785
796
  const anchorRect = ref(null);
797
+ const virtualRect = ref(null);
786
798
  const triggerRef = ref(null);
787
799
  const dropdownRef = ref(null);
788
800
  const dropdownHeight = ref(0);
789
801
  const dropdownWidth = ref(0);
790
802
  const getAnchorRect = () => {
791
803
  var _a;
804
+ if (virtualRect.value) return virtualRect.value;
792
805
  const root = triggerRef.value;
793
806
  if (!root) return null;
794
807
  const el = (_a = root.firstElementChild) != null ? _a : root;
795
808
  return el.getBoundingClientRect();
796
809
  };
810
+ const measure = () => {
811
+ const el = dropdownRef.value;
812
+ if (!el) return;
813
+ dropdownWidth.value = el.offsetWidth;
814
+ dropdownHeight.value = el.offsetHeight;
815
+ };
816
+ const updatePosition = () => {
817
+ if (!visible.value) return;
818
+ anchorRect.value = getAnchorRect();
819
+ measure();
820
+ };
821
+ const normalizedOffset = computed(() => {
822
+ var _a, _b;
823
+ const o = props.offset;
824
+ return Array.isArray(o) ? { gap: (_a = o[0]) != null ? _a : 0, skid: (_b = o[1]) != null ? _b : 0 } : { gap: o, skid: 0 };
825
+ });
797
826
  const bridgeStyle = computed(() => {
798
827
  const [side] = props.placement.split("-");
799
- const gap = Array.isArray(props.offset) ? props.offset[0] : props.offset;
800
- const size = gap + 4;
828
+ const size = normalizedOffset.value.gap + 4;
801
829
  const styles = { position: "absolute" };
802
830
  if (side === "right") {
803
- styles.width = `${size}px`;
804
- styles.left = `-${size}px`;
805
- styles.top = 0;
806
- styles.bottom = 0;
831
+ Object.assign(styles, { width: `${size}px`, left: `-${size}px`, top: 0, bottom: 0 });
807
832
  } else if (side === "left") {
808
- styles.width = `${size}px`;
809
- styles.right = `-${size}px`;
810
- styles.top = 0;
811
- styles.bottom = 0;
833
+ Object.assign(styles, { width: `${size}px`, right: `-${size}px`, top: 0, bottom: 0 });
812
834
  } else if (side === "bottom") {
813
- styles.height = `${size}px`;
814
- styles.top = `-${size}px`;
815
- styles.left = 0;
816
- styles.right = 0;
835
+ Object.assign(styles, { height: `${size}px`, top: `-${size}px`, left: 0, right: 0 });
817
836
  } else if (side === "top") {
818
- styles.height = `${size}px`;
819
- styles.bottom = `-${size}px`;
820
- styles.left = 0;
821
- styles.right = 0;
837
+ Object.assign(styles, { height: `${size}px`, bottom: `-${size}px`, left: 0, right: 0 });
822
838
  }
823
839
  return styles;
824
840
  });
825
841
  const dropdownStyle = computed(() => {
826
842
  if (!anchorRect.value) return {};
827
843
  const rect = anchorRect.value;
828
- const { placement } = props;
829
- const gap = Array.isArray(props.offset) ? props.offset[0] : props.offset;
830
- const [side, align] = placement.split("-");
844
+ const { gap, skid } = normalizedOffset.value;
845
+ const [side, align] = props.placement.split("-");
831
846
  const vw = window.innerWidth;
832
847
  const vh = window.innerHeight;
833
848
  const w = dropdownWidth.value || 160;
@@ -864,19 +879,19 @@ const _sfc_main$Q = /* @__PURE__ */ defineComponent({
864
879
  if (align === "start") left = rect.left;
865
880
  else if (align === "end") left = rect.right - w;
866
881
  else left = rect.left + (rect.width - w) / 2;
867
- left = Math.max(8, Math.min(left, vw - w - 8));
882
+ left = Math.max(8, Math.min(left + skid, vw - w - 8));
868
883
  } else {
869
884
  if (align === "start") top = rect.top;
870
885
  else if (align === "end") top = rect.bottom - h2;
871
886
  else top = rect.top + (rect.height - h2) / 2;
872
- top = Math.max(8, Math.min(top, vh - h2 - 8));
887
+ top = Math.max(8, Math.min(top + skid, vh - h2 - 8));
873
888
  }
874
889
  const originX = resolvedSide === "right" ? "left" : resolvedSide === "left" ? "right" : align === "start" ? "left" : align === "end" ? "right" : "center";
875
890
  const originY = resolvedSide === "bottom" ? "top" : resolvedSide === "top" ? "bottom" : align === "start" ? "top" : align === "end" ? "bottom" : "center";
876
891
  return {
877
892
  top: `${top}px`,
878
893
  left: `${left}px`,
879
- transformOrigin: `${originY} ${originX}`
894
+ transformOrigin: `${originX} ${originY}`
880
895
  };
881
896
  });
882
897
  let leaveTimer = null;
@@ -887,77 +902,85 @@ const _sfc_main$Q = /* @__PURE__ */ defineComponent({
887
902
  leaveTimer = null;
888
903
  }
889
904
  };
890
- const handleTriggerClick = (e) => {
891
- if (props.trigger !== "click" || props.disabled) return;
892
- e.stopPropagation();
893
- if (!show.value) {
894
- anchorRect.value = getAnchorRect();
895
- show.value = true;
896
- } else {
897
- show.value = false;
905
+ const clearShowTimer = () => {
906
+ if (showTimer) {
907
+ clearTimeout(showTimer);
908
+ showTimer = null;
898
909
  }
899
910
  };
911
+ const handleTriggerClick = () => {
912
+ if (props.trigger !== "click") return;
913
+ virtualRect.value = null;
914
+ setVisible(!visible.value);
915
+ };
900
916
  const handleMouseEnter = () => {
901
917
  if (props.trigger !== "hover" || props.disabled) return;
902
918
  clearLeaveTimer();
903
- showTimer = setTimeout(() => {
904
- anchorRect.value = getAnchorRect();
905
- show.value = true;
906
- }, 400);
919
+ showTimer = setTimeout(() => setVisible(true), 400);
907
920
  };
908
921
  const handleMouseLeave = () => {
909
922
  if (props.trigger !== "hover") return;
910
- if (showTimer) {
911
- clearTimeout(showTimer);
912
- showTimer = null;
913
- }
914
- leaveTimer = setTimeout(() => {
915
- show.value = false;
916
- }, 400);
923
+ clearShowTimer();
924
+ leaveTimer = setTimeout(() => setVisible(false), 400);
917
925
  };
918
- const handleWindowClick = () => {
919
- if (props.trigger !== "click" || !show.value) return;
920
- show.value = false;
926
+ const handleDocumentClick = (e) => {
927
+ var _a, _b;
928
+ if (!visible.value) return;
929
+ const canClose = props.trigger === "click" || props.trigger === "manual" && props.closeOnClickOutside;
930
+ if (!canClose) return;
931
+ const target = e.target;
932
+ if (target && (((_a = triggerRef.value) == null ? void 0 : _a.contains(target)) || ((_b = dropdownRef.value) == null ? void 0 : _b.contains(target)))) return;
933
+ setVisible(false);
921
934
  };
922
935
  const handleOptionClick = (option) => {
923
936
  if (option.disabled) return;
924
937
  if (props.multiple) {
925
938
  const keys = [...selectedKeys.value];
926
939
  const index = keys.indexOf(option.key);
927
- if (index > -1) {
928
- keys.splice(index, 1);
929
- } else {
930
- keys.push(option.key);
931
- }
940
+ if (index > -1) keys.splice(index, 1);
941
+ else keys.push(option.key);
932
942
  selectedKeys.value = keys;
933
943
  }
934
944
  emit("select", option.key, option);
935
- if (!props.multiple && !option.children) {
936
- show.value = false;
937
- }
938
- };
939
- const isSelected = (key) => {
940
- return props.multiple && selectedKeys.value.includes(key);
945
+ if (!props.multiple && !option.children) setVisible(false);
941
946
  };
942
- watch(show, (val) => __async(null, null, function* () {
943
- var _a, _b;
947
+ const isSelected = (key) => props.multiple && selectedKeys.value.includes(key);
948
+ watch(visible, (val) => __async(null, null, function* () {
944
949
  if (val) {
945
950
  anchorRect.value = getAnchorRect();
946
951
  yield nextTick();
947
- dropdownHeight.value = ((_a = dropdownRef.value) == null ? void 0 : _a.offsetHeight) || 200;
948
- dropdownWidth.value = ((_b = dropdownRef.value) == null ? void 0 : _b.offsetWidth) || 160;
952
+ measure();
949
953
  emit("show");
950
954
  } else {
951
955
  dropdownHeight.value = 0;
952
956
  dropdownWidth.value = 0;
957
+ virtualRect.value = null;
953
958
  emit("hide");
954
959
  }
955
960
  }));
961
+ const onEnter = () => measure();
962
+ useResizeObserver(dropdownRef, () => {
963
+ if (visible.value) measure();
964
+ });
965
+ useEventListener(window, "scroll", updatePosition, { capture: true, passive: true });
966
+ useEventListener(window, "resize", updatePosition, { passive: true });
967
+ useEventListener(document, "click", handleDocumentClick);
956
968
  onUnmounted(() => {
957
969
  clearLeaveTimer();
958
- if (showTimer) clearTimeout(showTimer);
970
+ clearShowTimer();
959
971
  });
960
- useEventListener(window, "click", handleWindowClick);
972
+ const open = () => {
973
+ virtualRect.value = null;
974
+ setVisible(true);
975
+ };
976
+ const close = () => setVisible(false);
977
+ const toggle = () => setVisible(!visible.value);
978
+ const showAt = (x, y) => {
979
+ virtualRect.value = new DOMRect(x, y, 0, 0);
980
+ if (visible.value) updatePosition();
981
+ else setVisible(true);
982
+ };
983
+ __expose({ open, close, toggle, showAt, updatePosition, visible });
961
984
  return (_ctx, _cache) => {
962
985
  return openBlock(), createElementBlock("div", {
963
986
  ref_key: "triggerRef",
@@ -969,18 +992,19 @@ const _sfc_main$Q = /* @__PURE__ */ defineComponent({
969
992
  }, [
970
993
  renderSlot(_ctx.$slots, "default", {}, void 0, true),
971
994
  (openBlock(), createBlock(Teleport, { to: "body" }, [
972
- createVNode(Transition, { name: "f-dropdown-motion" }, {
995
+ createVNode(Transition, {
996
+ name: "f-dropdown-motion",
997
+ onEnter
998
+ }, {
973
999
  default: withCtx(() => [
974
- unref(show) ? (openBlock(), createElementBlock("div", {
1000
+ unref(visible) ? (openBlock(), createElementBlock("div", {
975
1001
  key: 0,
976
1002
  ref_key: "dropdownRef",
977
1003
  ref: dropdownRef,
978
1004
  class: "f-dropdown-item",
979
1005
  style: normalizeStyle([unref(dropdownStyle), { zIndex: _ctx.zIndex }]),
980
1006
  onMouseenter: clearLeaveTimer,
981
- onMouseleave: handleMouseLeave,
982
- onClick: _cache[0] || (_cache[0] = withModifiers(() => {
983
- }, ["stop"]))
1007
+ onMouseleave: handleMouseLeave
984
1008
  }, [
985
1009
  _ctx.trigger === "hover" ? (openBlock(), createElementBlock("div", {
986
1010
  key: 0,
@@ -1025,7 +1049,7 @@ const _sfc_main$Q = /* @__PURE__ */ defineComponent({
1025
1049
  };
1026
1050
  }
1027
1051
  });
1028
- const FDropdown = /* @__PURE__ */ _export_sfc(_sfc_main$Q, [["__scopeId", "data-v-4f7ae74f"]]);
1052
+ const FDropdown = /* @__PURE__ */ _export_sfc(_sfc_main$Q, [["__scopeId", "data-v-c46e9d1e"]]);
1029
1053
  const _hoisted_1$D = {
1030
1054
  key: 0,
1031
1055
  class: "f-tag__dot"
@@ -772,63 +772,78 @@ var __async = (__this, __arguments, generator) => {
772
772
  disabled: { type: Boolean, default: false },
773
773
  multiple: { type: Boolean, default: false },
774
774
  modelValue: { default: () => [] },
775
- maxHeight: { default: "300px" }
775
+ maxHeight: { default: "300px" },
776
+ show: { type: Boolean, default: void 0 },
777
+ closeOnClickOutside: { type: Boolean, default: false }
776
778
  },
777
- emits: ["select", "show", "hide", "update:modelValue"],
778
- setup(__props, { emit: __emit }) {
779
+ emits: ["select", "show", "hide", "update:modelValue", "update:show"],
780
+ setup(__props, { expose: __expose, emit: __emit }) {
779
781
  const props = __props;
780
782
  const emit = __emit;
781
783
  const selectedKeys = vue.computed({
782
784
  get: () => props.modelValue || [],
783
785
  set: (val) => emit("update:modelValue", val)
784
786
  });
785
- const show = vue.ref(false);
787
+ const innerVisible = vue.ref(false);
788
+ const visible = vue.computed(
789
+ () => props.show !== void 0 ? !!props.show : innerVisible.value
790
+ );
791
+ const setVisible = (v) => {
792
+ if (v && props.disabled) return;
793
+ if (v === visible.value) return;
794
+ innerVisible.value = v;
795
+ emit("update:show", v);
796
+ };
786
797
  const anchorRect = vue.ref(null);
798
+ const virtualRect = vue.ref(null);
787
799
  const triggerRef = vue.ref(null);
788
800
  const dropdownRef = vue.ref(null);
789
801
  const dropdownHeight = vue.ref(0);
790
802
  const dropdownWidth = vue.ref(0);
791
803
  const getAnchorRect = () => {
792
804
  var _a;
805
+ if (virtualRect.value) return virtualRect.value;
793
806
  const root = triggerRef.value;
794
807
  if (!root) return null;
795
808
  const el = (_a = root.firstElementChild) != null ? _a : root;
796
809
  return el.getBoundingClientRect();
797
810
  };
811
+ const measure = () => {
812
+ const el = dropdownRef.value;
813
+ if (!el) return;
814
+ dropdownWidth.value = el.offsetWidth;
815
+ dropdownHeight.value = el.offsetHeight;
816
+ };
817
+ const updatePosition = () => {
818
+ if (!visible.value) return;
819
+ anchorRect.value = getAnchorRect();
820
+ measure();
821
+ };
822
+ const normalizedOffset = vue.computed(() => {
823
+ var _a, _b;
824
+ const o = props.offset;
825
+ return Array.isArray(o) ? { gap: (_a = o[0]) != null ? _a : 0, skid: (_b = o[1]) != null ? _b : 0 } : { gap: o, skid: 0 };
826
+ });
798
827
  const bridgeStyle = vue.computed(() => {
799
828
  const [side] = props.placement.split("-");
800
- const gap = Array.isArray(props.offset) ? props.offset[0] : props.offset;
801
- const size = gap + 4;
829
+ const size = normalizedOffset.value.gap + 4;
802
830
  const styles = { position: "absolute" };
803
831
  if (side === "right") {
804
- styles.width = `${size}px`;
805
- styles.left = `-${size}px`;
806
- styles.top = 0;
807
- styles.bottom = 0;
832
+ Object.assign(styles, { width: `${size}px`, left: `-${size}px`, top: 0, bottom: 0 });
808
833
  } else if (side === "left") {
809
- styles.width = `${size}px`;
810
- styles.right = `-${size}px`;
811
- styles.top = 0;
812
- styles.bottom = 0;
834
+ Object.assign(styles, { width: `${size}px`, right: `-${size}px`, top: 0, bottom: 0 });
813
835
  } else if (side === "bottom") {
814
- styles.height = `${size}px`;
815
- styles.top = `-${size}px`;
816
- styles.left = 0;
817
- styles.right = 0;
836
+ Object.assign(styles, { height: `${size}px`, top: `-${size}px`, left: 0, right: 0 });
818
837
  } else if (side === "top") {
819
- styles.height = `${size}px`;
820
- styles.bottom = `-${size}px`;
821
- styles.left = 0;
822
- styles.right = 0;
838
+ Object.assign(styles, { height: `${size}px`, bottom: `-${size}px`, left: 0, right: 0 });
823
839
  }
824
840
  return styles;
825
841
  });
826
842
  const dropdownStyle = vue.computed(() => {
827
843
  if (!anchorRect.value) return {};
828
844
  const rect = anchorRect.value;
829
- const { placement } = props;
830
- const gap = Array.isArray(props.offset) ? props.offset[0] : props.offset;
831
- const [side, align] = placement.split("-");
845
+ const { gap, skid } = normalizedOffset.value;
846
+ const [side, align] = props.placement.split("-");
832
847
  const vw = window.innerWidth;
833
848
  const vh = window.innerHeight;
834
849
  const w = dropdownWidth.value || 160;
@@ -865,19 +880,19 @@ var __async = (__this, __arguments, generator) => {
865
880
  if (align === "start") left = rect.left;
866
881
  else if (align === "end") left = rect.right - w;
867
882
  else left = rect.left + (rect.width - w) / 2;
868
- left = Math.max(8, Math.min(left, vw - w - 8));
883
+ left = Math.max(8, Math.min(left + skid, vw - w - 8));
869
884
  } else {
870
885
  if (align === "start") top = rect.top;
871
886
  else if (align === "end") top = rect.bottom - h;
872
887
  else top = rect.top + (rect.height - h) / 2;
873
- top = Math.max(8, Math.min(top, vh - h - 8));
888
+ top = Math.max(8, Math.min(top + skid, vh - h - 8));
874
889
  }
875
890
  const originX = resolvedSide === "right" ? "left" : resolvedSide === "left" ? "right" : align === "start" ? "left" : align === "end" ? "right" : "center";
876
891
  const originY = resolvedSide === "bottom" ? "top" : resolvedSide === "top" ? "bottom" : align === "start" ? "top" : align === "end" ? "bottom" : "center";
877
892
  return {
878
893
  top: `${top}px`,
879
894
  left: `${left}px`,
880
- transformOrigin: `${originY} ${originX}`
895
+ transformOrigin: `${originX} ${originY}`
881
896
  };
882
897
  });
883
898
  let leaveTimer = null;
@@ -888,77 +903,85 @@ var __async = (__this, __arguments, generator) => {
888
903
  leaveTimer = null;
889
904
  }
890
905
  };
891
- const handleTriggerClick = (e) => {
892
- if (props.trigger !== "click" || props.disabled) return;
893
- e.stopPropagation();
894
- if (!show.value) {
895
- anchorRect.value = getAnchorRect();
896
- show.value = true;
897
- } else {
898
- show.value = false;
906
+ const clearShowTimer = () => {
907
+ if (showTimer) {
908
+ clearTimeout(showTimer);
909
+ showTimer = null;
899
910
  }
900
911
  };
912
+ const handleTriggerClick = () => {
913
+ if (props.trigger !== "click") return;
914
+ virtualRect.value = null;
915
+ setVisible(!visible.value);
916
+ };
901
917
  const handleMouseEnter = () => {
902
918
  if (props.trigger !== "hover" || props.disabled) return;
903
919
  clearLeaveTimer();
904
- showTimer = setTimeout(() => {
905
- anchorRect.value = getAnchorRect();
906
- show.value = true;
907
- }, 400);
920
+ showTimer = setTimeout(() => setVisible(true), 400);
908
921
  };
909
922
  const handleMouseLeave = () => {
910
923
  if (props.trigger !== "hover") return;
911
- if (showTimer) {
912
- clearTimeout(showTimer);
913
- showTimer = null;
914
- }
915
- leaveTimer = setTimeout(() => {
916
- show.value = false;
917
- }, 400);
924
+ clearShowTimer();
925
+ leaveTimer = setTimeout(() => setVisible(false), 400);
918
926
  };
919
- const handleWindowClick = () => {
920
- if (props.trigger !== "click" || !show.value) return;
921
- show.value = false;
927
+ const handleDocumentClick = (e) => {
928
+ var _a, _b;
929
+ if (!visible.value) return;
930
+ const canClose = props.trigger === "click" || props.trigger === "manual" && props.closeOnClickOutside;
931
+ if (!canClose) return;
932
+ const target = e.target;
933
+ if (target && (((_a = triggerRef.value) == null ? void 0 : _a.contains(target)) || ((_b = dropdownRef.value) == null ? void 0 : _b.contains(target)))) return;
934
+ setVisible(false);
922
935
  };
923
936
  const handleOptionClick = (option) => {
924
937
  if (option.disabled) return;
925
938
  if (props.multiple) {
926
939
  const keys = [...selectedKeys.value];
927
940
  const index = keys.indexOf(option.key);
928
- if (index > -1) {
929
- keys.splice(index, 1);
930
- } else {
931
- keys.push(option.key);
932
- }
941
+ if (index > -1) keys.splice(index, 1);
942
+ else keys.push(option.key);
933
943
  selectedKeys.value = keys;
934
944
  }
935
945
  emit("select", option.key, option);
936
- if (!props.multiple && !option.children) {
937
- show.value = false;
938
- }
939
- };
940
- const isSelected = (key) => {
941
- return props.multiple && selectedKeys.value.includes(key);
946
+ if (!props.multiple && !option.children) setVisible(false);
942
947
  };
943
- vue.watch(show, (val) => __async(null, null, function* () {
944
- var _a, _b;
948
+ const isSelected = (key) => props.multiple && selectedKeys.value.includes(key);
949
+ vue.watch(visible, (val) => __async(null, null, function* () {
945
950
  if (val) {
946
951
  anchorRect.value = getAnchorRect();
947
952
  yield vue.nextTick();
948
- dropdownHeight.value = ((_a = dropdownRef.value) == null ? void 0 : _a.offsetHeight) || 200;
949
- dropdownWidth.value = ((_b = dropdownRef.value) == null ? void 0 : _b.offsetWidth) || 160;
953
+ measure();
950
954
  emit("show");
951
955
  } else {
952
956
  dropdownHeight.value = 0;
953
957
  dropdownWidth.value = 0;
958
+ virtualRect.value = null;
954
959
  emit("hide");
955
960
  }
956
961
  }));
962
+ const onEnter = () => measure();
963
+ core.useResizeObserver(dropdownRef, () => {
964
+ if (visible.value) measure();
965
+ });
966
+ core.useEventListener(window, "scroll", updatePosition, { capture: true, passive: true });
967
+ core.useEventListener(window, "resize", updatePosition, { passive: true });
968
+ core.useEventListener(document, "click", handleDocumentClick);
957
969
  vue.onUnmounted(() => {
958
970
  clearLeaveTimer();
959
- if (showTimer) clearTimeout(showTimer);
971
+ clearShowTimer();
960
972
  });
961
- core.useEventListener(window, "click", handleWindowClick);
973
+ const open = () => {
974
+ virtualRect.value = null;
975
+ setVisible(true);
976
+ };
977
+ const close = () => setVisible(false);
978
+ const toggle = () => setVisible(!visible.value);
979
+ const showAt = (x, y) => {
980
+ virtualRect.value = new DOMRect(x, y, 0, 0);
981
+ if (visible.value) updatePosition();
982
+ else setVisible(true);
983
+ };
984
+ __expose({ open, close, toggle, showAt, updatePosition, visible });
962
985
  return (_ctx, _cache) => {
963
986
  return vue.openBlock(), vue.createElementBlock("div", {
964
987
  ref_key: "triggerRef",
@@ -970,18 +993,19 @@ var __async = (__this, __arguments, generator) => {
970
993
  }, [
971
994
  vue.renderSlot(_ctx.$slots, "default", {}, void 0, true),
972
995
  (vue.openBlock(), vue.createBlock(vue.Teleport, { to: "body" }, [
973
- vue.createVNode(vue.Transition, { name: "f-dropdown-motion" }, {
996
+ vue.createVNode(vue.Transition, {
997
+ name: "f-dropdown-motion",
998
+ onEnter
999
+ }, {
974
1000
  default: vue.withCtx(() => [
975
- vue.unref(show) ? (vue.openBlock(), vue.createElementBlock("div", {
1001
+ vue.unref(visible) ? (vue.openBlock(), vue.createElementBlock("div", {
976
1002
  key: 0,
977
1003
  ref_key: "dropdownRef",
978
1004
  ref: dropdownRef,
979
1005
  class: "f-dropdown-item",
980
1006
  style: vue.normalizeStyle([vue.unref(dropdownStyle), { zIndex: _ctx.zIndex }]),
981
1007
  onMouseenter: clearLeaveTimer,
982
- onMouseleave: handleMouseLeave,
983
- onClick: _cache[0] || (_cache[0] = vue.withModifiers(() => {
984
- }, ["stop"]))
1008
+ onMouseleave: handleMouseLeave
985
1009
  }, [
986
1010
  _ctx.trigger === "hover" ? (vue.openBlock(), vue.createElementBlock("div", {
987
1011
  key: 0,
@@ -1026,7 +1050,7 @@ var __async = (__this, __arguments, generator) => {
1026
1050
  };
1027
1051
  }
1028
1052
  });
1029
- const FDropdown = /* @__PURE__ */ _export_sfc(_sfc_main$Q, [["__scopeId", "data-v-4f7ae74f"]]);
1053
+ const FDropdown = /* @__PURE__ */ _export_sfc(_sfc_main$Q, [["__scopeId", "data-v-c46e9d1e"]]);
1030
1054
  const _hoisted_1$D = {
1031
1055
  key: 0,
1032
1056
  class: "f-tag__dot"
@@ -23,13 +23,13 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
23
23
  width: string | number;
24
24
  type: import("../../types/components").FDialogType;
25
25
  title: string;
26
+ closeOnClickOutside: boolean;
26
27
  content: string;
27
28
  confirmText: string;
28
29
  cancelText: string;
29
30
  showCancel: boolean;
30
31
  showClose: boolean;
31
32
  divider: boolean;
32
- closeOnClickOutside: boolean;
33
33
  closeOnEsc: boolean;
34
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
35
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -19,8 +19,8 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
19
19
  onClose?: () => any;
20
20
  }>, {
21
21
  placement: import("../../types/components").FDrawerPlacement;
22
- showClose: boolean;
23
22
  closeOnClickOutside: boolean;
23
+ showClose: boolean;
24
24
  lockScroll: boolean;
25
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
26
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -1,22 +1,31 @@
1
1
  import type { FDropdownProps, FDropdownOption } from '../../types';
2
- declare var __VLS_1: {}, __VLS_21: {}, __VLS_23: {};
2
+ declare var __VLS_1: {}, __VLS_25: {}, __VLS_27: {};
3
3
  type __VLS_Slots = {} & {
4
4
  default?: (props: typeof __VLS_1) => any;
5
5
  } & {
6
- content?: (props: typeof __VLS_21) => any;
6
+ content?: (props: typeof __VLS_25) => any;
7
7
  } & {
8
- footer?: (props: typeof __VLS_23) => any;
8
+ footer?: (props: typeof __VLS_27) => any;
9
9
  };
10
- declare const __VLS_component: import("vue").DefineComponent<FDropdownProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ declare const __VLS_component: import("vue").DefineComponent<FDropdownProps, {
11
+ open: () => void;
12
+ close: () => void;
13
+ toggle: () => void;
14
+ showAt: (x: number, y: number) => void;
15
+ updatePosition: () => void;
16
+ visible: globalThis.ComputedRef<boolean>;
17
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
18
  select: (key: string | number, option: FDropdownOption) => any;
12
19
  "update:modelValue": (value: (string | number)[]) => any;
13
20
  show: () => any;
14
21
  hide: () => any;
22
+ "update:show": (value: boolean) => any;
15
23
  }, string, import("vue").PublicProps, Readonly<FDropdownProps> & Readonly<{
16
24
  onSelect?: (key: string | number, option: FDropdownOption) => any;
17
25
  "onUpdate:modelValue"?: (value: (string | number)[]) => any;
18
26
  onShow?: () => any;
19
27
  onHide?: () => any;
28
+ "onUpdate:show"?: (value: boolean) => any;
20
29
  }>, {
21
30
  disabled: boolean;
22
31
  modelValue: (string | number)[];
@@ -24,9 +33,11 @@ declare const __VLS_component: import("vue").DefineComponent<FDropdownProps, {},
24
33
  placement: import("../../types").Placement;
25
34
  offset: number | [number, number];
26
35
  zIndex: number;
27
- trigger: "hover" | "click";
36
+ trigger: "hover" | "click" | "manual";
28
37
  options: FDropdownOption[];
29
38
  multiple: boolean;
39
+ show: boolean;
40
+ closeOnClickOutside: boolean;
30
41
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
31
42
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
32
43
  export default _default;
@@ -20,7 +20,7 @@ interface FDropdownProps {
20
20
  /** 下拉菜单层级 @default 2000 */
21
21
  zIndex?: number;
22
22
  /** 触发方式 @default 'hover' */
23
- trigger?: 'hover' | 'click';
23
+ trigger?: 'hover' | 'click' | 'manual';
24
24
  /** 下拉选项列表 */
25
25
  options?: FDropdownOption[];
26
26
  /** 是否禁用 @default false */
@@ -31,5 +31,7 @@ interface FDropdownProps {
31
31
  modelValue?: (string | number)[];
32
32
  /** 下拉菜单最大高度 @default '300px' */
33
33
  maxHeight?: string;
34
+ show?: boolean;
35
+ closeOnClickOutside?: boolean;
34
36
  }
35
37
  export type { FDropdownProps, FDropdownOption };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fantasia-ui",
3
3
  "private": false,
4
- "version": "0.6.12",
4
+ "version": "0.6.13",
5
5
  "type": "module",
6
6
  "main": "dist/fantasia-ui.umd.js",
7
7
  "module": "dist/fantasia-ui.es.js",