layplux 1.0.1 → 2.0.1

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.
Files changed (91) hide show
  1. package/dist/cjs/components/center-view/index.cjs +18 -28
  2. package/dist/cjs/components/corner-glow/index.cjs +11 -28
  3. package/dist/cjs/components/dropdown/index.cjs +136 -130
  4. package/dist/cjs/components/icon/index.cjs +29 -51
  5. package/dist/cjs/components/index.cjs +24 -25
  6. package/dist/cjs/components/panel-view/index.cjs +111 -114
  7. package/dist/cjs/components/popup/index.cjs +166 -151
  8. package/dist/cjs/components/title/index.cjs +34 -47
  9. package/dist/cjs/components/tooltip/index.cjs +70 -61
  10. package/dist/cjs/components/widget/index.cjs +52 -72
  11. package/dist/cjs/index.cjs +13 -40
  12. package/dist/cjs/layout/glass-overlay.cjs +15 -28
  13. package/dist/cjs/layout/layered-manager.cjs +20 -29
  14. package/dist/cjs/layout/layplux.cjs +19 -32
  15. package/dist/cjs/layout/root-pane.cjs +20 -38
  16. package/dist/cjs/layout/skeleton/bottom-area.cjs +26 -43
  17. package/dist/cjs/layout/skeleton/bottom-left-area.cjs +12 -29
  18. package/dist/cjs/layout/skeleton/bottom-right-area.cjs +11 -28
  19. package/dist/cjs/layout/skeleton/center-area.cjs +278 -371
  20. package/dist/cjs/layout/skeleton/index.cjs +7 -24
  21. package/dist/cjs/layout/skeleton/left-bottom-area.cjs +12 -29
  22. package/dist/cjs/layout/skeleton/left-top-area.cjs +12 -29
  23. package/dist/cjs/layout/skeleton/right-bottom-area.cjs +11 -28
  24. package/dist/cjs/layout/skeleton/right-top-area.cjs +11 -28
  25. package/dist/cjs/layout/skeleton/skeleton.cjs +55 -60
  26. package/dist/cjs/layout/skeleton/top-area.cjs +26 -43
  27. package/dist/cjs/locales/en-US.cjs +11 -30
  28. package/dist/cjs/locales/index.cjs +12 -30
  29. package/dist/cjs/locales/zh-CN.cjs +11 -30
  30. package/dist/cjs/managers/area.cjs +12 -25
  31. package/dist/cjs/managers/index.cjs +12 -20
  32. package/dist/cjs/managers/pane.cjs +12 -26
  33. package/dist/cjs/managers/skeleton.cjs +112 -124
  34. package/dist/cjs/managers/theme.cjs +8 -29
  35. package/dist/cjs/managers/widget-container.cjs +31 -31
  36. package/dist/cjs/managers/widget.cjs +63 -50
  37. package/dist/cjs/types/config.cjs +2 -16
  38. package/dist/cjs/types/index.cjs +2 -18
  39. package/dist/cjs/types/locale.cjs +2 -16
  40. package/dist/cjs/utils/event-bus.cjs +53 -49
  41. package/dist/cjs/utils/focus-tracker.cjs +66 -42
  42. package/dist/cjs/utils/index.cjs +23 -31
  43. package/dist/cjs/utils/unique-id.cjs +5 -24
  44. package/dist/cjs/utils/vue.cjs +20 -30
  45. package/dist/esm/components/center-view/index.mjs +15 -7
  46. package/dist/esm/components/corner-glow/index.mjs +8 -7
  47. package/dist/esm/components/dropdown/index.mjs +117 -101
  48. package/dist/esm/components/icon/index.mjs +24 -30
  49. package/dist/esm/components/index.mjs +7 -8
  50. package/dist/esm/components/panel-view/index.mjs +107 -98
  51. package/dist/esm/components/popup/index.mjs +155 -130
  52. package/dist/esm/components/title/index.mjs +29 -24
  53. package/dist/esm/components/tooltip/index.mjs +67 -40
  54. package/dist/esm/components/widget/index.mjs +45 -48
  55. package/dist/esm/index.mjs +4 -10
  56. package/dist/esm/layout/glass-overlay.mjs +12 -7
  57. package/dist/esm/layout/layered-manager.mjs +17 -8
  58. package/dist/esm/layout/layplux.mjs +14 -11
  59. package/dist/esm/layout/root-pane.mjs +16 -16
  60. package/dist/esm/layout/skeleton/bottom-area.mjs +23 -22
  61. package/dist/esm/layout/skeleton/bottom-left-area.mjs +9 -8
  62. package/dist/esm/layout/skeleton/bottom-right-area.mjs +8 -7
  63. package/dist/esm/layout/skeleton/center-area.mjs +251 -333
  64. package/dist/esm/layout/skeleton/index.mjs +1 -4
  65. package/dist/esm/layout/skeleton/left-bottom-area.mjs +9 -8
  66. package/dist/esm/layout/skeleton/left-top-area.mjs +9 -8
  67. package/dist/esm/layout/skeleton/right-bottom-area.mjs +8 -7
  68. package/dist/esm/layout/skeleton/right-top-area.mjs +8 -7
  69. package/dist/esm/layout/skeleton/skeleton.mjs +52 -39
  70. package/dist/esm/layout/skeleton/top-area.mjs +23 -22
  71. package/dist/esm/locales/en-US.mjs +9 -10
  72. package/dist/esm/locales/index.mjs +7 -9
  73. package/dist/esm/locales/zh-CN.mjs +9 -10
  74. package/dist/esm/managers/area.mjs +10 -5
  75. package/dist/esm/managers/index.mjs +3 -3
  76. package/dist/esm/managers/pane.mjs +9 -5
  77. package/dist/esm/managers/skeleton.mjs +97 -95
  78. package/dist/esm/managers/theme.mjs +6 -9
  79. package/dist/esm/managers/widget-container.mjs +28 -10
  80. package/dist/esm/managers/widget.mjs +55 -25
  81. package/dist/esm/types/config.mjs +1 -0
  82. package/dist/esm/types/index.mjs +1 -1
  83. package/dist/esm/types/locale.mjs +1 -0
  84. package/dist/esm/utils/event-bus.mjs +46 -17
  85. package/dist/esm/utils/focus-tracker.mjs +63 -23
  86. package/dist/esm/utils/index.mjs +7 -10
  87. package/dist/esm/utils/unique-id.mjs +3 -4
  88. package/dist/esm/utils/vue.mjs +13 -5
  89. package/dist/types/managers/skeleton.d.ts.map +1 -1
  90. package/dist/umd/index.js +2 -30
  91. package/package.json +18 -14
@@ -1,16 +1,13 @@
1
- import { jsx, jsxs } from "vue/jsx-runtime";
2
- import { defineComponent, ref, inject } from "vue";
3
- import {
4
- Dropdown,
5
- DropdownMenu,
6
- DropdownItem,
7
- DropdownDivider,
8
- DropdownSubmenu
9
- } from "../dropdown/index.mjs";
10
- import { MoreIcon, MinimizeIcon } from "../icon/index.mjs";
11
- import { createContent } from "../../utils/index.mjs";
12
- import { getBuiltInLocale } from "../../locales/index.mjs";
13
- const viewModeKeys = /* @__PURE__ */ new Set(["DockPinned", "DockUnpinned", "Undock"]);
1
+ import { defineComponent, ref, inject, createVNode, createTextVNode, isVNode } from 'vue';
2
+ import { Dropdown, DropdownMenu, DropdownDivider, DropdownSubmenu, DropdownItem } from '../dropdown/index.mjs';
3
+ import { MoreIcon, MinimizeIcon } from '../icon/index.mjs';
4
+ import { getBuiltInLocale } from '../../locales/index.mjs';
5
+ import { createContent } from '../../utils/vue.mjs';
6
+
7
+ function _isSlot(s) {
8
+ return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s);
9
+ }
10
+ const viewModeKeys = new Set(['DockPinned', 'DockUnpinned', 'Undock']);
14
11
  function findItem(items, key) {
15
12
  if (!items) return;
16
13
  for (const item of items) {
@@ -22,20 +19,25 @@ function findItem(items, key) {
22
19
  }
23
20
  }
24
21
  const PanelView = defineComponent({
25
- name: "PanelView",
22
+ name: 'PanelView',
26
23
  props: {
27
24
  anchor: String,
28
25
  title: String,
29
26
  widget: Object,
30
27
  menuItems: Array
31
28
  },
32
- setup(props, { slots }) {
29
+ setup(props, {
30
+ slots
31
+ }) {
33
32
  const panelRef = ref();
34
- const defaultLocale = ref(getBuiltInLocale("zh-CN"));
35
- const locale = inject("layplux-locale", defaultLocale);
36
- const handleClick = (key) => {
33
+ const defaultLocale = ref(getBuiltInLocale('zh-CN'));
34
+ const locale = inject('layplux-locale', defaultLocale);
35
+ const handleClick = key => {
37
36
  const widget = props.widget;
38
- widget?.event?.emitGlobal(`panel:${widget.name}:menu-click`, { widget, key });
37
+ widget?.event?.emitGlobal(`panel:${widget.name}:menu-click`, {
38
+ widget,
39
+ key
40
+ });
39
41
  const widgetProps = widget?.config.props;
40
42
  const panelItems = widgetProps?.panelMenuItems;
41
43
  const panelItem = findItem(panelItems, key);
@@ -45,7 +47,7 @@ const PanelView = defineComponent({
45
47
  }
46
48
  if (viewModeKeys.has(key)) {
47
49
  widget?.pane.setViewMode(key);
48
- } else if (key === "help") {
50
+ } else if (key === 'help') {
49
51
  widgetProps?.onHelpClick?.();
50
52
  }
51
53
  };
@@ -53,29 +55,32 @@ const PanelView = defineComponent({
53
55
  props.widget?.focusable.active();
54
56
  }
55
57
  function renderItems(items, currentMode) {
56
- return items.map((item) => {
57
- if (item.type === "divider") {
58
- return /* @__PURE__ */ jsx(DropdownDivider, {}, item.key ?? "divider");
58
+ return items.map(item => {
59
+ if (item.type === 'divider') {
60
+ return createVNode(DropdownDivider, {
61
+ "key": item.key ?? 'divider'
62
+ }, null);
59
63
  }
60
- const k = item.key ?? "";
64
+ const k = item.key ?? '';
61
65
  if (item.children?.length) {
62
- return /* @__PURE__ */ jsx(
63
- DropdownSubmenu,
64
- {
65
- title: item.label,
66
- icon: item.icon,
67
- getContainer: () => panelRef.value,
68
- children: renderItems(item.children, currentMode)
69
- },
70
- k
71
- );
66
+ let _slot;
67
+ return createVNode(DropdownSubmenu, {
68
+ "key": k,
69
+ "title": item.label,
70
+ "icon": item.icon,
71
+ "getContainer": () => panelRef.value
72
+ }, _isSlot(_slot = renderItems(item.children, currentMode)) ? _slot : {
73
+ default: () => [_slot]
74
+ });
72
75
  }
73
- const disabled = currentMode !== void 0 && viewModeKeys.has(k) && currentMode === k;
74
- return /* @__PURE__ */ jsxs(DropdownItem, { eventKey: k, disabled, children: [
75
- item.icon,
76
- " ",
77
- item.label
78
- ] }, k);
76
+ const disabled = currentMode !== undefined && viewModeKeys.has(k) && currentMode === k;
77
+ return createVNode(DropdownItem, {
78
+ "key": k,
79
+ "eventKey": k,
80
+ "disabled": disabled
81
+ }, {
82
+ default: () => [item.icon, createTextVNode(" "), item.label]
83
+ });
79
84
  });
80
85
  }
81
86
  return () => {
@@ -87,66 +92,70 @@ const PanelView = defineComponent({
87
92
  const hasPanelMenuItems = panelMenuItems && panelMenuItems.length > 0;
88
93
  const showHelp = widgetProps?.showHelp !== false;
89
94
  const loc = locale.value.panel;
90
- const finalInnerItems = [
91
- {
92
- key: "viewMode",
93
- label: loc.viewMode,
94
- children: [
95
- { key: "DockPinned", label: loc.dockPinned },
96
- { key: "DockUnpinned", label: loc.dockUnpinned },
97
- { key: "Undock", label: loc.undock }
98
- ]
99
- },
100
- { type: "divider" },
101
- ...showHelp ? [{ key: "help", label: loc.help }] : []
102
- ];
95
+ const finalInnerItems = [{
96
+ key: 'viewMode',
97
+ label: loc.viewMode,
98
+ children: [{
99
+ key: 'DockPinned',
100
+ label: loc.dockPinned
101
+ }, {
102
+ key: 'DockUnpinned',
103
+ label: loc.dockUnpinned
104
+ }, {
105
+ key: 'Undock',
106
+ label: loc.undock
107
+ }]
108
+ }, {
109
+ type: 'divider'
110
+ }, ...(showHelp ? [{
111
+ key: 'help',
112
+ label: loc.help
113
+ }] : [])];
103
114
  const panelTitleExtra = widgetProps?.panelTitleExtra;
104
115
  const panelActionsExtra = widgetProps?.panelActionsExtra;
105
- return /* @__PURE__ */ jsxs("div", { ref: panelRef, id: widget?.id, class: "layplux-panel", onClick: handlePanelClick, children: [
106
- /* @__PURE__ */ jsxs("div", { class: "layplux-panel__header", children: [
107
- /* @__PURE__ */ jsx("span", { class: "layplux-panel__title", children: props.title ?? widget?.name }),
108
- panelTitleExtra && createContent(panelTitleExtra),
109
- /* @__PURE__ */ jsxs("div", { class: "layplux-panel__actions", children: [
110
- panelActionsExtra && createContent(panelActionsExtra),
111
- slots.actionsExtra?.(),
112
- /* @__PURE__ */ jsx(
113
- Dropdown,
114
- {
115
- trigger: "click",
116
- placement: "bottom-start",
117
- onClick: handleClick,
118
- getContainer: () => panelRef.value,
119
- children: {
120
- default: () => /* @__PURE__ */ jsx("button", { class: "layplux-panel__action-btn", title: loc.more, children: /* @__PURE__ */ jsx(MoreIcon, { size: 16 }) }),
121
- overlay: () => /* @__PURE__ */ jsxs(DropdownMenu, { children: [
122
- hasPanelMenuItems && renderItems(panelMenuItems),
123
- hasPanelMenuItems && hasCustomItems && /* @__PURE__ */ jsx(DropdownDivider, {}),
124
- hasCustomItems && renderItems(props.menuItems),
125
- (hasPanelMenuItems || hasCustomItems) && /* @__PURE__ */ jsx(DropdownDivider, {}),
126
- renderItems(finalInnerItems, currentMode)
127
- ] })
128
- }
129
- }
130
- ),
131
- /* @__PURE__ */ jsx(
132
- "button",
133
- {
134
- class: "layplux-panel__action-btn",
135
- title: loc.minimize,
136
- onClick: () => {
137
- widget?.event?.emitGlobal(`panel:${widget.name}:minimize`, { widget });
138
- widget?.container?.deactivate();
139
- },
140
- children: /* @__PURE__ */ jsx(MinimizeIcon, { size: 16 })
141
- }
142
- )
143
- ] })
144
- ] }),
145
- /* @__PURE__ */ jsx("div", { id: props.anchor, class: "layplux-panel__body" })
146
- ] });
116
+ return createVNode("div", {
117
+ "ref": panelRef,
118
+ "id": widget?.id,
119
+ "class": "layplux-panel",
120
+ "onClick": handlePanelClick
121
+ }, [createVNode("div", {
122
+ "class": "layplux-panel__header"
123
+ }, [createVNode("span", {
124
+ "class": "layplux-panel__title"
125
+ }, [props.title ?? widget?.name]), panelTitleExtra && createContent(panelTitleExtra), createVNode("div", {
126
+ "class": "layplux-panel__actions"
127
+ }, [panelActionsExtra && createContent(panelActionsExtra), slots.actionsExtra?.(), createVNode(Dropdown, {
128
+ "trigger": "click",
129
+ "placement": "bottom-start",
130
+ "onClick": handleClick,
131
+ "getContainer": () => panelRef.value
132
+ }, {
133
+ default: () => createVNode("button", {
134
+ "class": "layplux-panel__action-btn",
135
+ "title": loc.more
136
+ }, [createVNode(MoreIcon, {
137
+ "size": 16
138
+ }, null)]),
139
+ overlay: () => createVNode(DropdownMenu, null, {
140
+ default: () => [hasPanelMenuItems && renderItems(panelMenuItems), hasPanelMenuItems && hasCustomItems && createVNode(DropdownDivider, null, null), hasCustomItems && renderItems(props.menuItems), (hasPanelMenuItems || hasCustomItems) && createVNode(DropdownDivider, null, null), renderItems(finalInnerItems, currentMode)]
141
+ })
142
+ }), createVNode("button", {
143
+ "class": "layplux-panel__action-btn",
144
+ "title": loc.minimize,
145
+ "onClick": () => {
146
+ widget?.event?.emitGlobal(`panel:${widget.name}:minimize`, {
147
+ widget
148
+ });
149
+ widget?.container?.deactivate();
150
+ }
151
+ }, [createVNode(MinimizeIcon, {
152
+ "size": 16
153
+ }, null)])])]), createVNode("div", {
154
+ "id": props.anchor,
155
+ "class": "layplux-panel__body"
156
+ }, null)]);
147
157
  };
148
158
  }
149
159
  });
150
- export {
151
- PanelView
152
- };
160
+
161
+ export { PanelView };
@@ -1,81 +1,115 @@
1
- import { Fragment, jsx, jsxs } from "vue/jsx-runtime";
2
- import {
3
- defineComponent,
4
- ref,
5
- watch,
6
- nextTick,
7
- onMounted,
8
- onUnmounted,
9
- Teleport
10
- } from "vue";
1
+ import { defineComponent, ref, watch, onMounted, onUnmounted, createVNode, Fragment, Teleport, nextTick } from 'vue';
2
+
11
3
  function computePosition(triggerRect, popupW, popupH, placement, offsetX, offsetY) {
12
4
  const vw = window.innerWidth;
13
5
  const vh = window.innerHeight;
14
6
  const PADDING = 8;
15
7
  let top = 0;
16
8
  let left = 0;
17
- const [main, align] = placement.split("-");
18
- if (main === "bottom") {
9
+ const [main, align] = placement.split('-');
10
+
11
+ // Vertical placements
12
+ if (main === 'bottom') {
19
13
  top = triggerRect.bottom + offsetY;
20
- if (align === "start") left = triggerRect.left + offsetX;
21
- else if (align === "end") left = triggerRect.right - popupW + offsetX;
22
- else left = triggerRect.left + triggerRect.width / 2 - popupW / 2 + offsetX;
14
+ if (align === 'start') left = triggerRect.left + offsetX;else if (align === 'end') left = triggerRect.right - popupW + offsetX;else left = triggerRect.left + triggerRect.width / 2 - popupW / 2 + offsetX;
15
+
16
+ // Flip to top if not enough space
23
17
  if (top + popupH > vh - PADDING) {
24
18
  top = triggerRect.top - popupH - offsetY;
25
- placement = align ? `top-${align}` : "top";
19
+ placement = align ? `top-${align}` : 'top';
26
20
  }
27
- } else if (main === "top") {
21
+ } else if (main === 'top') {
28
22
  top = triggerRect.top - popupH - offsetY;
29
- if (align === "start") left = triggerRect.left + offsetX;
30
- else if (align === "end") left = triggerRect.right - popupW + offsetX;
31
- else left = triggerRect.left + triggerRect.width / 2 - popupW / 2 + offsetX;
23
+ if (align === 'start') left = triggerRect.left + offsetX;else if (align === 'end') left = triggerRect.right - popupW + offsetX;else left = triggerRect.left + triggerRect.width / 2 - popupW / 2 + offsetX;
24
+
25
+ // Flip to bottom if not enough space
32
26
  if (top < PADDING) {
33
27
  top = triggerRect.bottom + offsetY;
34
- placement = align ? `bottom-${align}` : "bottom";
28
+ placement = align ? `bottom-${align}` : 'bottom';
35
29
  }
36
- } else if (main === "right") {
30
+ }
31
+ // Horizontal placements
32
+ else if (main === 'right') {
37
33
  left = triggerRect.right + offsetX;
38
- if (align === "start") top = triggerRect.top + offsetY;
39
- else if (align === "end") top = triggerRect.bottom - popupH + offsetY;
40
- else top = triggerRect.top + triggerRect.height / 2 - popupH / 2 + offsetY;
34
+ if (align === 'start') top = triggerRect.top + offsetY;else if (align === 'end') top = triggerRect.bottom - popupH + offsetY;else top = triggerRect.top + triggerRect.height / 2 - popupH / 2 + offsetY;
41
35
  if (left + popupW > vw - PADDING) {
42
36
  left = triggerRect.left - popupW - offsetX;
43
- placement = align ? `left-${align}` : "left";
37
+ placement = align ? `left-${align}` : 'left';
44
38
  }
45
- } else if (main === "left") {
39
+ } else if (main === 'left') {
46
40
  left = triggerRect.left - popupW - offsetX;
47
- if (align === "start") top = triggerRect.top + offsetY;
48
- else if (align === "end") top = triggerRect.bottom - popupH + offsetY;
49
- else top = triggerRect.top + triggerRect.height / 2 - popupH / 2 + offsetY;
41
+ if (align === 'start') top = triggerRect.top + offsetY;else if (align === 'end') top = triggerRect.bottom - popupH + offsetY;else top = triggerRect.top + triggerRect.height / 2 - popupH / 2 + offsetY;
50
42
  if (left < PADDING) {
51
43
  left = triggerRect.right + offsetX;
52
- placement = align ? `right-${align}` : "right";
44
+ placement = align ? `right-${align}` : 'right';
53
45
  }
54
46
  }
47
+
48
+ // Clamp to viewport
55
49
  top = Math.max(PADDING, Math.min(top, vh - popupH - PADDING));
56
50
  left = Math.max(PADDING, Math.min(left, vw - popupW - PADDING));
57
- return { position: { top, left }, placement };
51
+ return {
52
+ position: {
53
+ top,
54
+ left
55
+ },
56
+ placement
57
+ };
58
58
  }
59
59
  const Popup = defineComponent({
60
- name: "LaypluxPopup",
60
+ name: 'LaypluxPopup',
61
61
  props: {
62
62
  visible: Boolean,
63
- trigger: { type: String, default: "hover" },
64
- placement: { type: String, default: "bottom" },
65
- offset: { type: Object, default: () => ({ y: 4 }) },
66
- mouseEnterDelay: { type: Number, default: 100 },
67
- mouseLeaveDelay: { type: Number, default: 100 },
68
- disabled: { type: Boolean, default: false },
69
- destroyOnClose: { type: Boolean, default: true },
70
- getContainer: { type: Function, default: () => document.body }
63
+ trigger: {
64
+ type: String,
65
+ default: 'hover'
66
+ },
67
+ placement: {
68
+ type: String,
69
+ default: 'bottom'
70
+ },
71
+ offset: {
72
+ type: Object,
73
+ default: () => ({
74
+ y: 4
75
+ })
76
+ },
77
+ mouseEnterDelay: {
78
+ type: Number,
79
+ default: 100
80
+ },
81
+ mouseLeaveDelay: {
82
+ type: Number,
83
+ default: 100
84
+ },
85
+ disabled: {
86
+ type: Boolean,
87
+ default: false
88
+ },
89
+ destroyOnClose: {
90
+ type: Boolean,
91
+ default: true
92
+ },
93
+ getContainer: {
94
+ type: Function,
95
+ default: () => document.body
96
+ }
71
97
  },
72
- emits: ["update:visible"],
73
- setup(props, { emit, slots }) {
98
+ emits: ['update:visible'],
99
+ setup(props, {
100
+ emit,
101
+ slots
102
+ }) {
74
103
  const triggerRef = ref();
75
104
  const popupRef = ref();
76
- const position = ref({ top: 0, left: 0 });
105
+ const position = ref({
106
+ top: 0,
107
+ left: 0
108
+ });
77
109
  const currentPlacement = ref(props.placement);
110
+ // Whether the popup DOM should exist
78
111
  const mounted = ref(false);
112
+ // Whether the popup is in its visible animation state
79
113
  const animatingIn = ref(false);
80
114
  let enterTimer = null;
81
115
  let leaveTimer = null;
@@ -98,15 +132,11 @@ const Popup = defineComponent({
98
132
  if (!triggerRef.value || !popupRef.value) return;
99
133
  const triggerRect = triggerRef.value.getBoundingClientRect();
100
134
  const popupRect = popupRef.value.getBoundingClientRect();
101
- const { x: ox = 0, y: oy = 0 } = props.offset;
102
- const result = computePosition(
103
- triggerRect,
104
- popupRect.width,
105
- popupRect.height,
106
- props.placement,
107
- ox,
108
- oy
109
- );
135
+ const {
136
+ x: ox = 0,
137
+ y: oy = 0
138
+ } = props.offset;
139
+ const result = computePosition(triggerRect, popupRect.width, popupRect.height, props.placement, ox, oy);
110
140
  position.value = result.position;
111
141
  currentPlacement.value = result.placement;
112
142
  };
@@ -115,9 +145,10 @@ const Popup = defineComponent({
115
145
  clearTimers();
116
146
  enterTimer = setTimeout(() => {
117
147
  mounted.value = true;
118
- emit("update:visible", true);
148
+ emit('update:visible', true);
119
149
  void nextTick(() => {
120
150
  updatePosition();
151
+ // Trigger enter animation
121
152
  requestAnimationFrame(() => {
122
153
  animatingIn.value = true;
123
154
  });
@@ -129,10 +160,12 @@ const Popup = defineComponent({
129
160
  const delay = immediate ? 0 : props.mouseLeaveDelay;
130
161
  leaveTimer = setTimeout(() => {
131
162
  animatingIn.value = false;
163
+
164
+ // Wait for CSS transition to finish before removing DOM
132
165
  leaveAnimationTimer = setTimeout(() => {
133
166
  mounted.value = false;
134
- emit("update:visible", false);
135
- }, 200);
167
+ emit('update:visible', false);
168
+ }, 200); // Match CSS transition duration
136
169
  }, delay);
137
170
  };
138
171
  const toggle = () => {
@@ -143,75 +176,80 @@ const Popup = defineComponent({
143
176
  show();
144
177
  }
145
178
  };
146
- watch(
147
- () => props.visible,
148
- (v) => {
149
- if (v && !mounted.value) show();
150
- else if (!v && mounted.value) hide(true);
151
- }
152
- );
153
- watch(
154
- () => props.placement,
155
- (p) => {
156
- currentPlacement.value = p;
157
- }
158
- );
179
+
180
+ // Sync external visible changes
181
+ watch(() => props.visible, v => {
182
+ if (v && !mounted.value) show();else if (!v && mounted.value) hide(true);
183
+ });
184
+
185
+ // Sync external placement changes
186
+ watch(() => props.placement, p => {
187
+ currentPlacement.value = p;
188
+ });
159
189
  const onResize = () => {
160
190
  if (mounted.value && animatingIn.value) updatePosition();
161
191
  };
162
192
  onMounted(() => {
163
- window.addEventListener("resize", onResize);
164
- window.addEventListener("scroll", onResize, true);
165
- document.addEventListener("mousedown", onClickOutside, true);
166
- document.addEventListener("keydown", onEsc);
193
+ window.addEventListener('resize', onResize);
194
+ window.addEventListener('scroll', onResize, true);
195
+ document.addEventListener('mousedown', onClickOutside, true);
196
+ document.addEventListener('keydown', onEsc);
167
197
  });
168
198
  onUnmounted(() => {
169
199
  clearTimers();
170
- window.removeEventListener("resize", onResize);
171
- window.removeEventListener("scroll", onResize, true);
172
- document.removeEventListener("mousedown", onClickOutside, true);
173
- document.removeEventListener("keydown", onEsc);
200
+ window.removeEventListener('resize', onResize);
201
+ window.removeEventListener('scroll', onResize, true);
202
+ document.removeEventListener('mousedown', onClickOutside, true);
203
+ document.removeEventListener('keydown', onEsc);
174
204
  });
175
- const onClickOutside = (e) => {
205
+
206
+ // Click outside
207
+ const onClickOutside = e => {
176
208
  if (!mounted.value) return;
177
209
  const target = e.target;
178
210
  const trigger = triggerRef.value;
179
211
  const popup = popupRef.value;
180
212
  if (trigger && trigger.contains(target)) return;
181
213
  if (popup && popup.contains(target)) return;
182
- if (props.trigger === "click" || props.trigger === "contextmenu") {
214
+ if (props.trigger === 'click' || props.trigger === 'contextmenu') {
183
215
  hide(true);
184
216
  }
185
217
  };
186
- const onEsc = (e) => {
187
- if (e.key === "Escape" && mounted.value) {
218
+
219
+ // ESC key
220
+ const onEsc = e => {
221
+ if (e.key === 'Escape' && mounted.value) {
188
222
  hide(true);
189
223
  }
190
224
  };
225
+
226
+ // Trigger event handlers
191
227
  const onTriggerMouseEnter = () => {
192
- if (props.trigger === "hover") show();
228
+ if (props.trigger === 'hover') show();
193
229
  };
194
230
  const onTriggerMouseLeave = () => {
195
- if (props.trigger === "hover") hide();
231
+ if (props.trigger === 'hover') hide();
196
232
  };
197
- const onTriggerClick = (e) => {
198
- if (props.trigger === "click") {
233
+ const onTriggerClick = e => {
234
+ if (props.trigger === 'click') {
199
235
  e.stopPropagation();
200
236
  toggle();
201
237
  }
202
238
  };
203
239
  const onTriggerFocus = () => {
204
- if (props.trigger === "focus") show();
240
+ if (props.trigger === 'focus') show();
205
241
  };
206
242
  const onTriggerBlur = () => {
207
- if (props.trigger === "focus") hide(true);
243
+ if (props.trigger === 'focus') hide(true);
208
244
  };
209
- const onTriggerContextmenu = (e) => {
210
- if (props.trigger === "contextmenu") {
245
+ const onTriggerContextmenu = e => {
246
+ if (props.trigger === 'contextmenu') {
211
247
  e.preventDefault();
212
248
  show();
213
249
  }
214
250
  };
251
+
252
+ // Popup hover (for safe triangle)
215
253
  const onPopupMouseEnter = () => {
216
254
  if (leaveTimer) {
217
255
  clearTimeout(leaveTimer);
@@ -219,50 +257,37 @@ const Popup = defineComponent({
219
257
  }
220
258
  };
221
259
  const onPopupMouseLeave = () => {
222
- if (props.trigger === "hover") hide();
260
+ if (props.trigger === 'hover') hide();
223
261
  };
224
262
  return () => {
225
263
  const container = props.getContainer();
226
- return /* @__PURE__ */ jsxs(Fragment, { children: [
227
- /* @__PURE__ */ jsx(
228
- "span",
229
- {
230
- ref: triggerRef,
231
- class: "layplux-popup-trigger",
232
- onMouseenter: onTriggerMouseEnter,
233
- onMouseleave: onTriggerMouseLeave,
234
- onClick: onTriggerClick,
235
- onFocus: onTriggerFocus,
236
- onBlur: onTriggerBlur,
237
- onContextmenu: onTriggerContextmenu,
238
- children: slots.default?.()
239
- }
240
- ),
241
- (mounted.value || !props.destroyOnClose) && /* @__PURE__ */ jsx(Teleport, { to: container, children: /* @__PURE__ */ jsx(
242
- "div",
243
- {
244
- ref: popupRef,
245
- class: [
246
- "layplux-portal",
247
- "layplux-popup",
248
- currentPlacement.value,
249
- animatingIn.value && "layplux-popup--visible"
250
- ],
251
- style: {
252
- position: "fixed",
253
- top: `${position.value.top}px`,
254
- left: `${position.value.left}px`,
255
- zIndex: "var(--layplux-popup-z-index, 2000)"
256
- },
257
- onMouseenter: onPopupMouseEnter,
258
- onMouseleave: onPopupMouseLeave,
259
- children: slots.content?.()
260
- }
261
- ) })
262
- ] });
264
+ return createVNode(Fragment, null, [createVNode("span", {
265
+ "ref": triggerRef,
266
+ "class": "layplux-popup-trigger",
267
+ "onMouseenter": onTriggerMouseEnter,
268
+ "onMouseleave": onTriggerMouseLeave,
269
+ "onClick": onTriggerClick,
270
+ "onFocus": onTriggerFocus,
271
+ "onBlur": onTriggerBlur,
272
+ "onContextmenu": onTriggerContextmenu
273
+ }, [slots.default?.()]), (mounted.value || !props.destroyOnClose) && createVNode(Teleport, {
274
+ "to": container
275
+ }, {
276
+ default: () => [createVNode("div", {
277
+ "ref": popupRef,
278
+ "class": ['layplux-portal', 'layplux-popup', currentPlacement.value, animatingIn.value && 'layplux-popup--visible'],
279
+ "style": {
280
+ position: 'fixed',
281
+ top: `${position.value.top}px`,
282
+ left: `${position.value.left}px`,
283
+ zIndex: 'var(--layplux-popup-z-index, 2000)'
284
+ },
285
+ "onMouseenter": onPopupMouseEnter,
286
+ "onMouseleave": onPopupMouseLeave
287
+ }, [slots.content?.()])]
288
+ })]);
263
289
  };
264
290
  }
265
291
  });
266
- export {
267
- Popup
268
- };
292
+
293
+ export { Popup };