js-kompo-utils 1.0.9 → 1.0.11

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.
@@ -0,0 +1,265 @@
1
+ <template>
2
+ <div
3
+ :tabindex="0"
4
+ v-bind="$_attributes"
5
+ @mouseenter="onRootMouseEnter"
6
+ @mouseleave="onRootMouseLeave"
7
+ @keyup.space="toggleForceOpen"
8
+ ref="dropdownRoot"
9
+ >
10
+
11
+ <component
12
+ v-bind="$_togglerAttributes"
13
+ v-turbo-click="component.turbo"
14
+ @click="checkClickable">
15
+
16
+ <span class="flex items-center" v-html="$_fullLabel" />
17
+
18
+ </component>
19
+
20
+ <div class="vlDropdownMenu"
21
+ :class="menuClass"
22
+ :style="menuPositionStyle"
23
+ ref="dropdownMenu"
24
+ @mouseenter="onMenuMouseEnter"
25
+ @mouseleave="onMenuMouseLeave">
26
+ <component
27
+ v-for="(col,index) in elements"
28
+ :key="index"
29
+ v-on="{ ...col.on, click: () => $emit('click', col)}"
30
+ v-bind="$_defaultLayoutAttributes(col)"
31
+ />
32
+ </div>
33
+
34
+ </div>
35
+ </template>
36
+
37
+ <script>
38
+ import MenuItem from 'vue-kompo/js/menu/mixins/MenuItem'
39
+ import HasSubmenu from 'vue-kompo/js/menu/mixins/HasSubmenu'
40
+
41
+ export default {
42
+ mixins: [MenuItem, HasSubmenu],
43
+ data(){
44
+ return {
45
+ open: false,
46
+ forcedOpen: false,
47
+ isHoveringRoot: false,
48
+ isHoveringMenu: false,
49
+ menuTop: -9999,
50
+ menuLeft: -9999,
51
+ menuRight: null,
52
+ }
53
+ },
54
+ watch:{
55
+ 'vkompo.elements': function () {
56
+ this.elements = { ...this.vkompo.elements }
57
+ },
58
+ 'vkompo.label': function () {
59
+ this.component = { ...this.component, label: this.vkompo.label }
60
+ },
61
+ shouldShowMenu(visible) {
62
+ if (visible) {
63
+ this.updatePosition()
64
+ }
65
+ if (!visible && this.openOnClick) {
66
+ this.open = false
67
+ }
68
+ }
69
+ },
70
+ computed: {
71
+ shouldShowMenu() {
72
+ if (this.forcedOpen) return true
73
+ const hovering = this.isHoveringRoot || this.isHoveringMenu
74
+ if (this.openOnClick) return this.open && hovering
75
+ return hovering
76
+ },
77
+ dropdownPosition() {
78
+ return this.$_config('dropdownPosition') || ''
79
+ },
80
+ isRightAligned() {
81
+ return this.dropdownPosition.includes('Right')
82
+ },
83
+ isUpward() {
84
+ return this.dropdownPosition.includes('Up')
85
+ },
86
+ menuPositionStyle() {
87
+ const style = {
88
+ position: 'fixed',
89
+ top: this.menuTop + 'px',
90
+ bottom: 'auto',
91
+ zIndex: 9999,
92
+ transition: 'transform 0.2s ease-in-out, opacity 0.2s ease-in-out',
93
+ transformOrigin: (this.isUpward ? 'bottom' : 'top') + ' ' + (this.isRightAligned ? 'right' : 'left'),
94
+ }
95
+
96
+ if (this.menuRight !== null) {
97
+ style.left = 'auto'
98
+ style.right = this.menuRight + 'px'
99
+ } else {
100
+ style.left = this.menuLeft + 'px'
101
+ style.right = 'auto'
102
+ }
103
+
104
+ if (this.shouldShowMenu) {
105
+ style.transform = 'scale(1)'
106
+ style.opacity = '1'
107
+ style.pointerEvents = 'auto'
108
+ } else {
109
+ style.transform = 'scale(0)'
110
+ style.opacity = '0'
111
+ style.pointerEvents = 'none'
112
+ }
113
+
114
+ return style
115
+ },
116
+ $_customClassArray(){
117
+ return [
118
+ this.$_config('active'),
119
+ this.forcedOpen ? 'vlForcedOpen' : '',
120
+ ]
121
+ },
122
+ togglerClass(){
123
+ return this.$_classString([
124
+ 'vlDropdownToggler',
125
+ 'vlTogglerClosed',
126
+ this.$_config('togglerClass'),
127
+ ])
128
+ },
129
+ menuClass(){
130
+ return this.$_classString([
131
+ 'vlMenuClosed',
132
+ this.$_config('menuNoBorder') ? 'vlNoBorder' : '',
133
+ this.dropdownPosition,
134
+ ])
135
+ },
136
+ openOnClick(){
137
+ return this.$_config('openOnClick')
138
+ }
139
+ },
140
+ mounted() {
141
+ this.teleportMenu()
142
+ this.setupListeners()
143
+ },
144
+ beforeDestroy() {
145
+ this.cleanupMenu()
146
+ this.removeListeners()
147
+ },
148
+ methods:{
149
+ teleportMenu() {
150
+ const menu = this.$refs.dropdownMenu
151
+ const root = this.$refs.dropdownRoot
152
+ if (!menu) return
153
+
154
+ // Transfer scroll styles from root before teleporting
155
+ if (root && root.classList.contains('dropdown-with-scroll')) {
156
+ const maxHeight = getComputedStyle(root).getPropertyValue('--dropdown-max-height').trim()
157
+ if (maxHeight) {
158
+ menu.style.maxHeight = maxHeight
159
+ menu.style.overflowY = 'auto'
160
+ menu.classList.add('mini-scroll')
161
+ }
162
+ }
163
+
164
+ document.body.appendChild(menu)
165
+ },
166
+ cleanupMenu() {
167
+ const menu = this.$refs.dropdownMenu
168
+ if (menu && menu.parentNode === document.body) {
169
+ document.body.removeChild(menu)
170
+ }
171
+ if (this._closeTimeout) {
172
+ clearTimeout(this._closeTimeout)
173
+ }
174
+ },
175
+ setupListeners() {
176
+ this._scrollHandler = () => {
177
+ if (this.shouldShowMenu) {
178
+ requestAnimationFrame(() => this.updatePosition())
179
+ }
180
+ }
181
+ this._resizeHandler = () => {
182
+ if (this.shouldShowMenu) {
183
+ this.updatePosition()
184
+ }
185
+ }
186
+ window.addEventListener('scroll', this._scrollHandler, true)
187
+ window.addEventListener('resize', this._resizeHandler, { passive: true })
188
+ },
189
+ removeListeners() {
190
+ if (this._scrollHandler) {
191
+ window.removeEventListener('scroll', this._scrollHandler, true)
192
+ }
193
+ if (this._resizeHandler) {
194
+ window.removeEventListener('resize', this._resizeHandler)
195
+ }
196
+ },
197
+ updatePosition() {
198
+ const root = this.$refs.dropdownRoot
199
+ const menu = this.$refs.dropdownMenu
200
+ if (!root) return
201
+
202
+ const rect = root.getBoundingClientRect()
203
+
204
+ if (this.isUpward) {
205
+ const menuHeight = menu ? menu.offsetHeight : 0
206
+ this.menuTop = rect.top - menuHeight
207
+ } else {
208
+ this.menuTop = rect.bottom
209
+ }
210
+
211
+ if (this.isRightAligned) {
212
+ this.menuRight = window.innerWidth - rect.right
213
+ this.menuLeft = -9999
214
+ } else {
215
+ this.menuLeft = rect.left
216
+ this.menuRight = null
217
+ }
218
+ },
219
+ onRootMouseEnter() {
220
+ this.isHoveringRoot = true
221
+ this.clearCloseTimeout()
222
+ },
223
+ onRootMouseLeave() {
224
+ this.isHoveringRoot = false
225
+ this.scheduleClose()
226
+ },
227
+ onMenuMouseEnter() {
228
+ this.isHoveringMenu = true
229
+ this.clearCloseTimeout()
230
+ },
231
+ onMenuMouseLeave() {
232
+ this.isHoveringMenu = false
233
+ this.scheduleClose()
234
+ },
235
+ clearCloseTimeout() {
236
+ if (this._closeTimeout) {
237
+ clearTimeout(this._closeTimeout)
238
+ this._closeTimeout = null
239
+ }
240
+ },
241
+ scheduleClose() {
242
+ this.clearCloseTimeout()
243
+ this._closeTimeout = setTimeout(() => {
244
+ if (!this.isHoveringRoot && !this.isHoveringMenu) {
245
+ if (this.openOnClick) {
246
+ this.open = false
247
+ }
248
+ }
249
+ }, 80)
250
+ },
251
+ checkClickable(){
252
+ if(this.openOnClick){
253
+ this.open = true
254
+ }
255
+
256
+ this.forcedOpen = false
257
+
258
+ this.$_clickAction()
259
+ },
260
+ toggleForceOpen(){
261
+ this.forcedOpen = !this.forcedOpen
262
+ }
263
+ }
264
+ }
265
+ </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-kompo-utils",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -32,7 +32,7 @@ export class Bubble {
32
32
  this.closeBtn = document.createElement('button');
33
33
  this.closeBtn.className = 'tutorial-close-btn';
34
34
  this.closeBtn.setAttribute('aria-label', 'Close tutorial');
35
- this.closeBtn.textContent = '\u00D7';
35
+ this.closeBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="1" y1="1" x2="13" y2="13"/><line x1="13" y1="1" x2="1" y2="13"/></svg>';
36
36
  this.el.appendChild(this.closeBtn);
37
37
 
38
38
  this.arrowEl = document.createElement('div');
@@ -216,25 +216,25 @@
216
216
  // === Close button (top-right of bubble) ===
217
217
  .tutorial-close-btn {
218
218
  position: absolute;
219
- top: 8px;
220
- right: 8px;
221
- width: 28px;
222
- height: 28px;
219
+ top: 10px;
220
+ right: 10px;
221
+ width: 24px;
222
+ height: 24px;
223
223
  border: none;
224
224
  border-radius: 50%;
225
- background: rgba(0, 0, 0, 0.06);
226
- color: #999;
227
- font-size: 18px;
225
+ background: none;
226
+ color: #aaa;
227
+ font-size: 20px;
228
228
  line-height: 1;
229
229
  cursor: pointer;
230
230
  display: flex;
231
231
  align-items: center;
232
232
  justify-content: center;
233
- transition: background 0.15s, color 0.15s;
234
- z-index: 1;
233
+ transition: color 0.15s;
234
+ z-index: 2;
235
+ padding: 0;
235
236
 
236
237
  &:hover {
237
- background: rgba(0, 0, 0, 0.12);
238
238
  color: #333;
239
239
  }
240
240
  }