@mptw/skylens-ui 1.0.0

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 (143) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintrc.cjs +4 -0
  3. package/.nuxtrc +1 -0
  4. package/.playground/app.config.ts +5 -0
  5. package/.playground/nuxt.config.ts +3 -0
  6. package/README.md +73 -0
  7. package/app.config.ts +5 -0
  8. package/app.vue +3 -0
  9. package/assets/css/fonts/MEShPlus-font-icon.eot +0 -0
  10. package/assets/css/fonts/MEShPlus-font-icon.svg +186 -0
  11. package/assets/css/fonts/MEShPlus-font-icon.ttf +0 -0
  12. package/assets/css/fonts/MEShPlus-font-icon.woff +0 -0
  13. package/assets/css/icons.css +555 -0
  14. package/assets/css/layout.styl +98 -0
  15. package/assets/css/tailwind.css +3 -0
  16. package/assets/images/commons/app-logo-white.png +0 -0
  17. package/assets/images/commons/app-logo.png +0 -0
  18. package/assets/images/commons/empty-box.png +0 -0
  19. package/assets/images/commons/watermark.svg +15 -0
  20. package/components/SLAlertModal.vue +99 -0
  21. package/components/SLBreadcrumbs.vue +42 -0
  22. package/components/SLButton.vue +301 -0
  23. package/components/SLCalendarButton.vue +259 -0
  24. package/components/SLCalendarPopup.vue +832 -0
  25. package/components/SLCard.vue +118 -0
  26. package/components/SLChart.vue +181 -0
  27. package/components/SLCheckbox.vue +179 -0
  28. package/components/SLCircleButton.vue +95 -0
  29. package/components/SLCollapsableBlock.vue +116 -0
  30. package/components/SLCollapsableMulitPillSelect.vue +92 -0
  31. package/components/SLConfirmModal.vue +122 -0
  32. package/components/SLDateInput.vue +460 -0
  33. package/components/SLDatePicker.vue +143 -0
  34. package/components/SLDistributor.vue +30 -0
  35. package/components/SLDownloadButton.vue +410 -0
  36. package/components/SLDraggable.vue +151 -0
  37. package/components/SLDropdown.vue +277 -0
  38. package/components/SLDropdownItem.vue +31 -0
  39. package/components/SLElementWithTitle.vue +181 -0
  40. package/components/SLEyeCheckbox.vue +162 -0
  41. package/components/SLFileInput.vue +101 -0
  42. package/components/SLGenderAgeSelect.vue +279 -0
  43. package/components/SLHeatMap.vue +151 -0
  44. package/components/SLHourRangeInput.vue +211 -0
  45. package/components/SLIOSSwitch.vue +235 -0
  46. package/components/SLIcon.vue +35 -0
  47. package/components/SLIconButton.vue +130 -0
  48. package/components/SLInfoTip.vue +221 -0
  49. package/components/SLInsightSitePage.vue +27 -0
  50. package/components/SLLegend.vue +76 -0
  51. package/components/SLLinearProgressBar.vue +35 -0
  52. package/components/SLLink.vue +162 -0
  53. package/components/SLLoading.vue +127 -0
  54. package/components/SLMapChart.vue +151 -0
  55. package/components/SLModal.vue +377 -0
  56. package/components/SLMonthCalendarButton.vue +378 -0
  57. package/components/SLMonthPicker.vue +151 -0
  58. package/components/SLMultiSelect.vue +121 -0
  59. package/components/SLNoData.vue +43 -0
  60. package/components/SLPageModal.vue +64 -0
  61. package/components/SLPagination.vue +192 -0
  62. package/components/SLPillCheckbox.vue +207 -0
  63. package/components/SLPillLabel.vue +101 -0
  64. package/components/SLPopupMenuButton.vue +158 -0
  65. package/components/SLProfileButton.vue +186 -0
  66. package/components/SLRadioButton.vue +97 -0
  67. package/components/SLRadioButtonGroup.vue +113 -0
  68. package/components/SLRadioGroup.vue +201 -0
  69. package/components/SLRangeInput.vue +209 -0
  70. package/components/SLRegionsMapChart.vue +151 -0
  71. package/components/SLRightSider.vue +242 -0
  72. package/components/SLSearchInput.vue +87 -0
  73. package/components/SLSelect.vue +545 -0
  74. package/components/SLSelectAllToggle.vue +118 -0
  75. package/components/SLSiderbarNav.vue +394 -0
  76. package/components/SLSiderbarNavItem.vue +204 -0
  77. package/components/SLSitePage.vue +96 -0
  78. package/components/SLSkyInsightSitePage.vue +27 -0
  79. package/components/SLSortByDropdown.vue +189 -0
  80. package/components/SLSpinIcon.vue +24 -0
  81. package/components/SLStayRangeInput.vue +306 -0
  82. package/components/SLTab.vue +159 -0
  83. package/components/SLTable.vue +409 -0
  84. package/components/SLTableTooltip.vue +111 -0
  85. package/components/SLTabs.vue +388 -0
  86. package/components/SLTextInput.vue +362 -0
  87. package/components/SLTextarea.vue +134 -0
  88. package/components/SLToast.vue +71 -0
  89. package/components/SLToggleButton.vue +78 -0
  90. package/components/SLTwoLayerMultiSelect.vue +428 -0
  91. package/components/SLTwoLayerSelect.vue +265 -0
  92. package/components/SLWarnModal.vue +75 -0
  93. package/interface/IDateRange.ts +5 -0
  94. package/interface/IHourRange.ts +4 -0
  95. package/interface/IInputOption.ts +6 -0
  96. package/interface/ILegendItem.ts +7 -0
  97. package/interface/IPopupMenuButton.ts +5 -0
  98. package/interface/IStayRange.ts +15 -0
  99. package/interface/ITableField.ts +19 -0
  100. package/interface/IWeb.ts +5 -0
  101. package/interface/NavConfigType.ts +14 -0
  102. package/interface/commons.ts +7 -0
  103. package/models/DateRange.ts +21 -0
  104. package/nuxt.config.ts +22 -0
  105. package/package.json +52 -0
  106. package/plugins/googlemaps.ts +17 -0
  107. package/plugins/v-calendar.ts +5 -0
  108. package/public/assets/files/taiwan-regions.json +7 -0
  109. package/public/assets/files/taiwan.json +24 -0
  110. package/public/assets/images/commons/default-favico.png +0 -0
  111. package/public/assets/images/dashboard/closure-button-icon.png +0 -0
  112. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-disabled.png +0 -0
  113. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-poi.png +0 -0
  114. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-web.png +0 -0
  115. package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon-disabled.png +0 -0
  116. package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon.png +0 -0
  117. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-disabled.png +0 -0
  118. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-poi.png +0 -0
  119. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-web.png +0 -0
  120. package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-disabled.png +0 -0
  121. package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-web.png +0 -0
  122. package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-disabled.png +0 -0
  123. package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-poi.png +0 -0
  124. package/public/assets/images/dashboard/dashboard-widget-list-icon-disabled.png +0 -0
  125. package/public/assets/images/dashboard/dashboard-widget-list-icon-poi.png +0 -0
  126. package/public/assets/images/dashboard/dashboard-widget-list-icon-web.png +0 -0
  127. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-disabled.png +0 -0
  128. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-poi.png +0 -0
  129. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-web.png +0 -0
  130. package/public/assets/images/dashboard/drag-hint-icon.svg +3 -0
  131. package/public/assets/images/dashboard/extend-button-icon.png +0 -0
  132. package/public/assets/images/dashboard/map-list-chart-icon.svg +17 -0
  133. package/public/assets/images/dashboard/map-marker-chart-icon.svg +32 -0
  134. package/public/assets/images/dashboard/pie-chart-icon.svg +4 -0
  135. package/public/assets/images/dashboard/poi-gender-age-chart-icon.svg +20 -0
  136. package/public/assets/images/dashboard/poi-nav-icon.png +0 -0
  137. package/public/assets/images/dashboard/web-gender-age-chart-icon.svg +20 -0
  138. package/public/assets/images/dashboard/web-nav-icon.png +0 -0
  139. package/tailwind.config.js +300 -0
  140. package/tsconfig.json +8 -0
  141. package/utils/constants.ts +160 -0
  142. package/utils/generateSheet.ts +36 -0
  143. package/utils/index.ts +539 -0
@@ -0,0 +1,151 @@
1
+ <template lang="pug">
2
+ component(
3
+ :is="tag"
4
+ :class="class"
5
+ ref="draggableEl"
6
+ ) ddd
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import type { PropType } from "vue";
11
+ import Sortable from "sortablejs";
12
+
13
+ export default defineComponent({
14
+ name: "SLDraggable",
15
+ components: {},
16
+ props: {
17
+ tag: {
18
+ type: String,
19
+ default: "div",
20
+ },
21
+ class: {
22
+ type: String,
23
+ default: "",
24
+ },
25
+ modelValue: {
26
+ type: Array as PropType<any[]>,
27
+ default: () => [],
28
+ },
29
+ group: {
30
+ type: String,
31
+ default: "",
32
+ },
33
+ sort: {
34
+ type: Boolean,
35
+ default: true,
36
+ },
37
+ itemKey: {
38
+ type: String,
39
+ default: "id",
40
+ },
41
+ },
42
+ emits: ["update:modelValue"],
43
+ setup(props, { emit }) {
44
+ const { modelValue, group, sort } = toRefs(props);
45
+
46
+ const draggableEl = shallowRef<HTMLElement | null>(null);
47
+
48
+ let sortable: Sortable | null = null;
49
+
50
+ onMounted(() => {
51
+ if (draggableEl.value) {
52
+ const sortableOptions: Sortable.Options = {
53
+ sort: sort.value,
54
+ setData: (dataTransfer, dragEl) => {
55
+ if (!draggableEl.value) {
56
+ dataTransfer.clearData("element");
57
+ return;
58
+ }
59
+
60
+ const elIndex = Array.from(draggableEl.value.children).indexOf(
61
+ dragEl
62
+ );
63
+
64
+ if (elIndex === -1) {
65
+ dataTransfer.clearData("element");
66
+ return;
67
+ }
68
+
69
+ const element = modelValue.value[elIndex];
70
+
71
+ if (element === undefined || element === null) {
72
+ dataTransfer.clearData("element");
73
+ return;
74
+ }
75
+
76
+ dataTransfer.setData("widget", JSON.stringify(element));
77
+ },
78
+ onAdd: (e) => {
79
+ const elementIndex = e.newIndex;
80
+ const originalEvent = e["originalEvent"];
81
+ if (elementIndex === undefined || !originalEvent) return;
82
+
83
+ const elementJson = originalEvent.dataTransfer.getData("widget");
84
+ if (!elementJson) return;
85
+
86
+ const element = JSON.parse(elementJson);
87
+
88
+ if (modelValue.value.length === 0) {
89
+ emit("update:modelValue", [
90
+ ...modelValue.value.slice(0, elementIndex - 1),
91
+ element,
92
+ ...modelValue.value.slice(elementIndex - 1),
93
+ ]);
94
+ } else {
95
+ emit("update:modelValue", [
96
+ ...modelValue.value.slice(0, elementIndex),
97
+ element,
98
+ ...modelValue.value.slice(elementIndex),
99
+ ]);
100
+ }
101
+ },
102
+ onRemove: (e) => {
103
+ if (e.oldIndex === undefined) return;
104
+
105
+ emit("update:modelValue", [
106
+ ...modelValue.value.slice(0, e.oldIndex),
107
+ ...modelValue.value.slice(e.oldIndex + 1),
108
+ ]);
109
+ },
110
+ onEnd: (e) => {
111
+ if (
112
+ e.newIndex === undefined ||
113
+ e.oldIndex === undefined ||
114
+ e.newIndex === e.oldIndex
115
+ ) {
116
+ return;
117
+ }
118
+
119
+ const element = modelValue.value[e.oldIndex];
120
+ const newValue = [...modelValue.value];
121
+
122
+ newValue.splice(e.oldIndex, 1);
123
+ newValue.splice(e.newIndex, 0, element);
124
+ emit("update:modelValue", newValue);
125
+ },
126
+ };
127
+
128
+ if (group.value !== "") {
129
+ sortableOptions.group = group.value;
130
+ }
131
+
132
+ sortable = new Sortable(draggableEl.value, sortableOptions);
133
+ }
134
+ });
135
+
136
+ onBeforeUnmount(() => {
137
+ if (sortable) {
138
+ sortable.destroy();
139
+ }
140
+
141
+ sortable = null;
142
+ });
143
+
144
+ return {
145
+ draggableEl,
146
+ };
147
+ },
148
+ });
149
+ </script>
150
+
151
+ <style lang="stylus"></style>
@@ -0,0 +1,277 @@
1
+ <template lang="pug">
2
+ .dropdown(:class="{ active: visible }" ref="buttonEl")
3
+ a(
4
+ href="javascript:;"
5
+ @click="onClickedToggle"
6
+ ref="toggleEl"
7
+ ).dropdown-toggle
8
+ span.toggle-title {{ title }}
9
+ SLIcon(:icon="toggleIcon").toggle-icon
10
+ Teleport(to='body')
11
+ .dropdown-menu(:class='menuClass', ref="popupEl")
12
+ .dropdown-search(v-if="withSearch")
13
+ SLSearchInput(
14
+ placeholder="搜尋"
15
+ v-model="search"
16
+ )
17
+ .dropdown-list-wrapper.custom-scrollbar-y(:style='`min-width: ${listWidth}px`', ref="listWrapperEl")
18
+ .dropdown-list(ref='dropdownListEl')
19
+ slot
20
+ </template>
21
+
22
+ <script lang="ts">
23
+ import { fromEvent } from "rxjs";
24
+ import { createPopper } from "@popperjs/core";
25
+ import type { Placement } from "@popperjs/core";
26
+ import { gsap } from "gsap";
27
+ import { ScrollToPlugin } from "gsap/ScrollToPlugin";
28
+
29
+ gsap.registerPlugin(ScrollToPlugin);
30
+
31
+ export default defineComponent({
32
+ name: "SLDropdown",
33
+ components: {},
34
+ props: {
35
+ showDropdown: {
36
+ type: Boolean,
37
+ default: false,
38
+ },
39
+ withSearch: {
40
+ type: Boolean,
41
+ default: false,
42
+ },
43
+ title: {
44
+ type: String,
45
+ default: "",
46
+ },
47
+ placement: {
48
+ type: String,
49
+ default: "bottom-start",
50
+ },
51
+ disabled: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ toggleIcon: {
56
+ type: String,
57
+ default: "caret-down",
58
+ },
59
+ menuClass: {
60
+ type: [Object, Array, String] as PropType<
61
+ { [key: string]: boolean } | string[] | string
62
+ >,
63
+ default: () => [],
64
+ },
65
+ },
66
+ emits: ["update:showDropdown", "update:search"],
67
+ setup(props, { emit }) {
68
+ const { showDropdown, placement, disabled } = toRefs(props);
69
+
70
+ const toggleEl = shallowRef<HTMLElement | null>(null);
71
+ const popupEl = shallowRef<HTMLElement | null>(null);
72
+ const listWrapperEl = shallowRef<HTMLElement | null>(null);
73
+ const dropdownListEl = shallowRef<HTMLElement | null>(null);
74
+ const buttonEl = shallowRef<HTMLElement | null>(null);
75
+ const visible = ref(showDropdown.value);
76
+ const search = ref("");
77
+ const listWidth = ref(64);
78
+
79
+ let popperInstance: any = null;
80
+ let clickSubscriber: any = null;
81
+ let observer: MutationObserver | null = null;
82
+
83
+ function subscribeClick() {
84
+ clickSubscriber = fromEvent(window, "click").subscribe((e) => {
85
+ const { target } = e;
86
+ if (buttonEl.value && !buttonEl.value.contains(target as Node)) {
87
+ hide();
88
+ }
89
+ });
90
+ }
91
+
92
+ function unsubscribeClick() {
93
+ if (clickSubscriber) {
94
+ clickSubscriber.unsubscribe();
95
+ }
96
+ clickSubscriber = null;
97
+ }
98
+
99
+ function show() {
100
+ if (!popupEl.value) return;
101
+
102
+ if (popupEl.value.getAttribute("data-show") === "") return;
103
+
104
+ const body = document.querySelector("body");
105
+ if (body) {
106
+ body.classList.add("popup");
107
+ }
108
+
109
+ subscribeClick();
110
+ popupEl.value.setAttribute("data-show", "");
111
+
112
+ popperInstance.setOptions({
113
+ modifiers: [
114
+ {
115
+ name: "offset",
116
+ options: {
117
+ offset: [0, 4],
118
+ },
119
+ },
120
+ { name: "eventListeners", enabled: true },
121
+ ],
122
+ });
123
+
124
+ popperInstance.update();
125
+ visible.value = true;
126
+ }
127
+
128
+ function hide() {
129
+ const body = document.querySelector("body");
130
+ if (body) {
131
+ body.classList.remove("popup");
132
+ }
133
+
134
+ if (!popupEl.value || popupEl.value.getAttribute("data-show") === null)
135
+ return;
136
+ unsubscribeClick();
137
+ popupEl.value.removeAttribute("data-show");
138
+ popperInstance.setOptions({
139
+ modifiers: [{ name: "eventListeners", enabled: false }],
140
+ });
141
+ visible.value = false;
142
+ }
143
+
144
+ function onClickedToggle() {
145
+ if (disabled.value) return;
146
+
147
+ if (!popupEl.value || popupEl.value.getAttribute("data-show") !== null) {
148
+ hide();
149
+ return;
150
+ }
151
+ show();
152
+ }
153
+
154
+ function refreshScroll() {
155
+ if (listWrapperEl.value) {
156
+ gsap.to(listWrapperEl.value, { duration: 0.5, scrollTo: 0 });
157
+ }
158
+ }
159
+
160
+ function setupListWidth() {
161
+ nextTick(() => {
162
+ const lwidth = dropdownListEl.value?.getBoundingClientRect().width || 0;
163
+ const toggleWidth = toggleEl.value?.getBoundingClientRect().width || 0;
164
+ listWidth.value = Math.max(lwidth, toggleWidth);
165
+ });
166
+ }
167
+
168
+ watch(showDropdown, () => {
169
+ visible.value = showDropdown.value;
170
+ });
171
+ watch(
172
+ visible,
173
+ () => {
174
+ if (visible.value) {
175
+ show();
176
+ } else {
177
+ hide();
178
+ }
179
+ emit("update:showDropdown", visible.value);
180
+ },
181
+ { immediate: true }
182
+ );
183
+ watch(search, () => {
184
+ emit("update:search", search.value);
185
+ });
186
+
187
+ onMounted(() => {
188
+ if (toggleEl.value && popupEl.value) {
189
+ popperInstance = createPopper(toggleEl.value, popupEl.value, {
190
+ placement: placement.value as Placement,
191
+ modifiers: [{ name: "eventListeners", enabled: false }],
192
+ });
193
+ }
194
+
195
+ observer = new MutationObserver(() => {
196
+ refreshScroll();
197
+ setupListWidth();
198
+ });
199
+
200
+ if (dropdownListEl.value) {
201
+ observer.observe(dropdownListEl.value, {
202
+ attributes: false,
203
+ childList: true,
204
+ subtree: false,
205
+ });
206
+ }
207
+
208
+ setupListWidth();
209
+ });
210
+ onBeforeUnmount(() => {
211
+ const body = document.querySelector("body");
212
+ if (body) {
213
+ body.classList.remove("popup");
214
+ }
215
+
216
+ if (popperInstance) {
217
+ popperInstance.destroy();
218
+ }
219
+
220
+ if (observer) {
221
+ observer.disconnect();
222
+ }
223
+
224
+ popperInstance = null;
225
+ observer = null;
226
+ });
227
+
228
+ return {
229
+ toggleEl,
230
+ popupEl,
231
+ listWrapperEl,
232
+ dropdownListEl,
233
+ buttonEl,
234
+ visible,
235
+ search,
236
+ listWidth,
237
+ onClickedToggle,
238
+ };
239
+ },
240
+ });
241
+ </script>
242
+
243
+ <style lang="stylus">
244
+ .dropdown
245
+ @apply inline-block relative
246
+
247
+ &.active
248
+ .dropdown-toggle
249
+
250
+ .toggle-icon
251
+ @apply text-primary-hover rotate-180
252
+
253
+ &-toggle
254
+ @apply flex items-center s('p-1.75') border border-primary-light rounded bg-white space-x-2 text-base text-gray-2 whitespace-nowrap
255
+
256
+ .toggle-icon
257
+ @apply text-xs leading-none text-gray-2 transition-transform transform
258
+
259
+ &-menu
260
+ @apply z-50 flex flex-col max-h-48 border border-primary-light rounded bg-white shadow-md overflow-hidden invisible pointer-events-none
261
+
262
+ &[data-show]
263
+ @apply visible pointer-events-auto
264
+
265
+ .dropdown-list-wrapper
266
+ .dropdown-list
267
+ @apply relative
268
+
269
+ .dropdown-search
270
+ @apply flex-grow-0 flex-shrink-0 mb-1 p-1
271
+
272
+ .dropdown-list-wrapper
273
+ @apply flex-grow
274
+
275
+ .dropdown-list
276
+ @apply absolute
277
+ </style>
@@ -0,0 +1,31 @@
1
+ <template lang="pug">
2
+ a(href="javascript:;", :class='{ active }').dropdown-item
3
+ slot {{ title }}
4
+ </template>
5
+
6
+ <script lang="ts">
7
+ export default defineComponent({
8
+ name: "SLDropdownItem",
9
+ props: {
10
+ title: {
11
+ type: String,
12
+ default: "",
13
+ },
14
+ active: {
15
+ type: Boolean,
16
+ default: false,
17
+ },
18
+ },
19
+ setup() {
20
+ return {};
21
+ },
22
+ });
23
+ </script>
24
+
25
+ <style lang="stylus">
26
+ .dropdown-item
27
+ @apply block p-2 bg-white text-sm text-primary whitespace-nowrap s('hover:text-primary') s('hover:bg-bg-middle') s('focus:text-primary') s('focus:bg-bg-middle') s('active:text-primary') s('active:bg-bg-middle')
28
+
29
+ &.active
30
+ @apply text-primary bg-bg-middle
31
+ </style>
@@ -0,0 +1,181 @@
1
+ <template lang="pug">
2
+ .element-with-title(
3
+ @mouseenter="onMouseEnter"
4
+ @mouseleave="onMouseLeave"
5
+ @mousemove="onMouseMove"
6
+ ref="el"
7
+ )
8
+ slot
9
+ .element-with-title-tooltip(
10
+ v-if="title !== '' || $slots.title"
11
+ :style="`max-width: ${maxWidth}px`"
12
+ ref="tooltipEl"
13
+ )
14
+ slot(name="title") {{ title }}
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ import type { PropType } from "vue";
19
+ import { createPopper } from "@popperjs/core";
20
+ import type {
21
+ VirtualElement,
22
+ Placement,
23
+ Instance as PopperInstance,
24
+ } from "@popperjs/core";
25
+
26
+ export default defineComponent({
27
+ name: "SLElementWithTitle",
28
+ components: {},
29
+ props: {
30
+ title: {
31
+ type: String,
32
+ default: "",
33
+ },
34
+ placement: {
35
+ type: String as PropType<Placement>,
36
+ default: "bottom-start",
37
+ },
38
+ maxWidth: {
39
+ type: [Number, String],
40
+ default: "none",
41
+ },
42
+ },
43
+ setup(props) {
44
+ const { title, placement } = toRefs(props);
45
+
46
+ const el = shallowRef<HTMLElement | null>(null);
47
+ const tooltipEl = shallowRef<HTMLElement | null>(null);
48
+ const mouseX = ref(0);
49
+ const mouseY = ref(0);
50
+
51
+ let popperInstance: PopperInstance | null = null;
52
+
53
+ const virtualElement = computed(() => {
54
+ return {
55
+ getBoundingClientRect: () => {
56
+ return {
57
+ width: 0,
58
+ height: 0,
59
+ top: mouseY.value,
60
+ right: mouseX.value,
61
+ bottom: mouseY.value,
62
+ left: mouseX.value,
63
+ };
64
+ },
65
+ } as VirtualElement;
66
+ });
67
+
68
+ async function createTipPopper() {
69
+ await nextTick();
70
+ destroyPopper();
71
+ if (!virtualElement.value || !tooltipEl.value) return;
72
+
73
+ popperInstance = createPopper(virtualElement.value, tooltipEl.value, {
74
+ placement: placement.value,
75
+ strategy: "fixed",
76
+ modifiers: [{ name: "eventListeners", enabled: false }],
77
+ });
78
+ }
79
+
80
+ function destroyPopper() {
81
+ if (popperInstance) {
82
+ popperInstance.destroy();
83
+ }
84
+ popperInstance = null;
85
+ }
86
+
87
+ function show() {
88
+ if (!tooltipEl.value || !popperInstance) return;
89
+ const appWrapperEl = document.querySelector(".app-wrapper");
90
+
91
+ tooltipEl.value.setAttribute("data-show", "");
92
+ popperInstance.setOptions({
93
+ modifiers: [
94
+ {
95
+ name: "offset",
96
+ options: {
97
+ offset: placement.value === "bottom-start" ? [-8, 18] : [0, 0],
98
+ },
99
+ },
100
+ {
101
+ name: "preventOverflow",
102
+ options: {
103
+ boundary: appWrapperEl,
104
+ padding: 16,
105
+ },
106
+ },
107
+ { name: "eventListeners", enabled: true },
108
+ ],
109
+ });
110
+ popperInstance.update();
111
+ }
112
+
113
+ function hide() {
114
+ if (
115
+ !tooltipEl.value ||
116
+ tooltipEl.value.getAttribute("data-show") === null ||
117
+ !popperInstance
118
+ )
119
+ return;
120
+ tooltipEl.value.removeAttribute("data-show");
121
+ popperInstance.setOptions({
122
+ modifiers: [{ name: "eventListeners", enabled: false }],
123
+ });
124
+ }
125
+
126
+ function onMouseEnter(e: MouseEvent) {
127
+ show();
128
+ }
129
+
130
+ function onMouseLeave(e: MouseEvent) {
131
+ hide();
132
+ }
133
+
134
+ function onMouseMove(e: MouseEvent) {
135
+ mouseX.value = e.clientX;
136
+ mouseY.value = e.clientY;
137
+
138
+ nextTick(() => {
139
+ if (popperInstance) {
140
+ popperInstance.update();
141
+ }
142
+ });
143
+ }
144
+
145
+ watch(title, () => {
146
+ if (title.value) {
147
+ createTipPopper();
148
+ } else {
149
+ destroyPopper();
150
+ }
151
+ });
152
+
153
+ onMounted(() => {
154
+ createTipPopper();
155
+ });
156
+
157
+ onBeforeUnmount(() => {
158
+ destroyPopper();
159
+ });
160
+
161
+ return {
162
+ el,
163
+ tooltipEl,
164
+ onMouseEnter,
165
+ onMouseLeave,
166
+ onMouseMove,
167
+ };
168
+ },
169
+ });
170
+ </script>
171
+
172
+ <style lang="stylus">
173
+ .element-with-title
174
+ @apply inline-flex
175
+
176
+ &-tooltip
177
+ @apply s('z-[10]') p-2 s('bg-backdrop/70') rounded text-xs text-white whitespace-pre shadow invisible pointer-events-none
178
+
179
+ &[data-show]
180
+ @apply visible
181
+ </style>