fantasia-ui 0.6.11 → 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.
@@ -4196,13 +4196,18 @@
4196
4196
 
4197
4197
  .f-empty {
4198
4198
  display: flex;
4199
+ flex-direction: column;
4199
4200
  align-items: center;
4200
4201
  justify-content: center;
4201
- color: var(--f-empty-image-color);
4202
- flex-direction: column;
4203
4202
  padding: 32px 0;
4204
4203
  }
4205
- .f-empty-img {
4204
+ .f-empty__image {
4205
+ display: flex;
4206
+ align-items: center;
4207
+ justify-content: center;
4208
+ color: var(--f-empty-image-color);
4209
+ }
4210
+ .f-empty__image-img {
4206
4211
  width: 100%;
4207
4212
  height: 100%;
4208
4213
  object-fit: contain;
@@ -8310,18 +8315,18 @@ to {
8310
8315
  font-size: 14px;
8311
8316
  color: #1e293b;
8312
8317
  line-height: 1;
8313
- }.f-dropdown-trigger-wrapper[data-v-4f7ae74f] {
8318
+ }.f-dropdown-trigger-wrapper[data-v-c46e9d1e] {
8314
8319
  display: contents;
8315
8320
  }
8316
- .f-dropdown-item[data-v-4f7ae74f] {
8321
+ .f-dropdown-item[data-v-c46e9d1e] {
8317
8322
  position: fixed;
8318
8323
  pointer-events: auto;
8319
8324
  }
8320
- .f-dropdown-bridge[data-v-4f7ae74f] {
8325
+ .f-dropdown-bridge[data-v-c46e9d1e] {
8321
8326
  position: absolute;
8322
8327
  background: transparent;
8323
8328
  }
8324
- .f-dropdown-content[data-v-4f7ae74f] {
8329
+ .f-dropdown-content[data-v-c46e9d1e] {
8325
8330
  background: rgba(255, 255, 255, 0.96);
8326
8331
  border-radius: 8px;
8327
8332
  border: 1px solid rgba(148, 163, 184, 0.2);
@@ -8331,7 +8336,7 @@ to {
8331
8336
  width: fit-content;
8332
8337
  backdrop-filter: blur(6px);
8333
8338
  }
8334
- .f-dropdown-content .f-dropdown-option[data-v-4f7ae74f] {
8339
+ .f-dropdown-content .f-dropdown-option[data-v-c46e9d1e] {
8335
8340
  display: flex;
8336
8341
  align-items: center;
8337
8342
  padding: 7px 10px;
@@ -8342,25 +8347,25 @@ to {
8342
8347
  font-size: 13px;
8343
8348
  transition: background 0.15s ease, color 0.15s ease;
8344
8349
  }
8345
- .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) {
8346
8351
  background: var(--surface3);
8347
8352
  color: var(--text);
8348
8353
  }
8349
- .f-dropdown-content .f-dropdown-option.is-disabled[data-v-4f7ae74f] {
8354
+ .f-dropdown-content .f-dropdown-option.is-disabled[data-v-c46e9d1e] {
8350
8355
  opacity: 0.35;
8351
8356
  cursor: not-allowed;
8352
8357
  }
8353
- .f-dropdown-content .f-dropdown-option.is-selected[data-v-4f7ae74f] {
8358
+ .f-dropdown-content .f-dropdown-option.is-selected[data-v-c46e9d1e] {
8354
8359
  color: var(--primary);
8355
8360
  }
8356
- .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] {
8357
8362
  color: var(--primary);
8358
8363
  }
8359
- .f-dropdown-content .f-dropdown-option .opt-checkbox[data-v-4f7ae74f] {
8364
+ .f-dropdown-content .f-dropdown-option .opt-checkbox[data-v-c46e9d1e] {
8360
8365
  flex-shrink: 0;
8361
8366
  pointer-events: none;
8362
8367
  }
8363
- .f-dropdown-content .f-dropdown-option .opt-icon[data-v-4f7ae74f] {
8368
+ .f-dropdown-content .f-dropdown-option .opt-icon[data-v-c46e9d1e] {
8364
8369
  display: inline-flex;
8365
8370
  align-items: center;
8366
8371
  width: 16px;
@@ -8368,20 +8373,20 @@ to {
8368
8373
  flex-shrink: 0;
8369
8374
  color: var(--text-dim);
8370
8375
  }
8371
- .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 {
8372
8377
  width: 100%;
8373
8378
  height: 100%;
8374
8379
  }
8375
- .f-dropdown-content .f-dropdown-option .opt-label[data-v-4f7ae74f] {
8380
+ .f-dropdown-content .f-dropdown-option .opt-label[data-v-c46e9d1e] {
8376
8381
  flex: 1;
8377
8382
  white-space: nowrap;
8378
8383
  }
8379
- .f-dropdown-motion-enter-active[data-v-4f7ae74f],
8380
- .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] {
8381
8386
  transition: opacity 0.18s ease, transform 0.2s cubic-bezier(0.23, 1, 0.32, 1);
8382
8387
  }
8383
- .f-dropdown-motion-enter-from[data-v-4f7ae74f],
8384
- .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] {
8385
8390
  opacity: 0;
8386
8391
  transform: scale(0.92);
8387
8392
  }.f-tag[data-v-19e20dd3] {
@@ -13422,13 +13427,18 @@ to {
13422
13427
  }
13423
13428
  .f-empty[data-v-af34030d] {
13424
13429
  display: flex;
13430
+ flex-direction: column;
13425
13431
  align-items: center;
13426
13432
  justify-content: center;
13427
- color: var(--f-empty-image-color);
13428
- flex-direction: column;
13429
13433
  padding: 32px 0;
13430
13434
  }
13431
- .f-empty-img[data-v-af34030d] {
13435
+ .f-empty__image[data-v-af34030d] {
13436
+ display: flex;
13437
+ align-items: center;
13438
+ justify-content: center;
13439
+ color: var(--f-empty-image-color);
13440
+ }
13441
+ .f-empty__image-img[data-v-af34030d] {
13432
13442
  width: 100%;
13433
13443
  height: 100%;
13434
13444
  object-fit: contain;
@@ -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.11",
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",