goodteditor-ui 1.0.10 → 1.0.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.
- package/index.js +5 -1
- package/package.json +1 -1
- package/src/components/ui/Avatar.md +68 -0
- package/src/components/ui/Avatar.vue +177 -0
- package/src/components/ui/Popover.vue +79 -30
- package/src/components/ui/ResponsiveContainer.md +58 -0
- package/src/components/ui/ResponsiveContainer.vue +99 -0
- package/src/components/ui/Tooltip.md +62 -0
- package/src/components/ui/utils/Helpers.js +24 -1
- package/src/components/ui/utils/WithPopover.js +18 -0
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Avatar from './src/components/ui/Avatar.vue';
|
|
1
2
|
import Badge from './src/components/ui/Badge.vue';
|
|
2
3
|
import Collapse from './src/components/ui/Collapse.vue';
|
|
3
4
|
import ColorPicker from './src/components/ui/ColorPicker.vue';
|
|
@@ -16,12 +17,14 @@ import Pagination from './src/components/ui/Pagination.vue';
|
|
|
16
17
|
import Paginator from './src/components/ui/Paginator.vue';
|
|
17
18
|
import Popover from './src/components/ui/Popover.vue';
|
|
18
19
|
import Popup from './src/components/ui/Popup.vue';
|
|
20
|
+
import ResponsiveContainer from './src/components/ui/ResponsiveContainer.vue';
|
|
19
21
|
import Select from './src/components/ui/Select.vue';
|
|
20
22
|
import TimePicker from './src/components/ui/TimePicker.vue';
|
|
21
23
|
import Tooltip from './src/components/ui/Tooltip.vue';
|
|
22
24
|
import Grid from './src/components/ui/Grid.vue';
|
|
23
25
|
|
|
24
26
|
export {
|
|
27
|
+
Avatar,
|
|
25
28
|
Badge,
|
|
26
29
|
Collapse,
|
|
27
30
|
ColorPicker,
|
|
@@ -40,8 +43,9 @@ export {
|
|
|
40
43
|
Paginator,
|
|
41
44
|
Popover,
|
|
42
45
|
Popup,
|
|
46
|
+
ResponsiveContainer,
|
|
43
47
|
Select,
|
|
44
48
|
TimePicker,
|
|
45
49
|
Tooltip,
|
|
46
|
-
Grid
|
|
50
|
+
Grid,
|
|
47
51
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
```vue
|
|
2
|
+
<template>
|
|
3
|
+
<div class="pad-l5">
|
|
4
|
+
<div class="p">
|
|
5
|
+
<!-- default -->
|
|
6
|
+
<ui-avatar v-bind="props" @load="onLoad" @error="onError"></ui-avatar>
|
|
7
|
+
|
|
8
|
+
<!-- custom slots -->
|
|
9
|
+
<ui-avatar v-bind="props">
|
|
10
|
+
<template v-slot="{abbr}">
|
|
11
|
+
<code>{{abbr}}</code>
|
|
12
|
+
</template>
|
|
13
|
+
<template #loading>loading</template>
|
|
14
|
+
</ui-avatar>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="row">
|
|
17
|
+
<div class="col">
|
|
18
|
+
<template v-for="key in Object.keys(props)">
|
|
19
|
+
<label class="text-small " v-if="isBool(key)">
|
|
20
|
+
<input class="checkbox checkbox-small" type="checkbox" v-model="props[key]">
|
|
21
|
+
<span class="v-mid">{{key}}</span>
|
|
22
|
+
</label>
|
|
23
|
+
<div :key="key" v-else>
|
|
24
|
+
<label class="text-xsmall">{{key}}</label>
|
|
25
|
+
<input class="input input-small d-block" type="text" v-model="props[key]">
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="col">
|
|
30
|
+
<pre class="text-xsmall" style="max-height:10rem">{{logStr}}</pre>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
<script>
|
|
36
|
+
import UiAvatar from './Avatar.vue';
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
components: { UiAvatar },
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
props: {
|
|
43
|
+
round: true,
|
|
44
|
+
src:'https://picsum.photos/150/150',
|
|
45
|
+
size: '4rem',
|
|
46
|
+
alt: 'John Wick',
|
|
47
|
+
color: '#ffffff',
|
|
48
|
+
background:'#fca522',
|
|
49
|
+
},
|
|
50
|
+
log:[]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
computed: {
|
|
54
|
+
logStr() {
|
|
55
|
+
return this.log.reverse().join("\n")
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
methods: {
|
|
59
|
+
isBool(key) {
|
|
60
|
+
return key === 'round'
|
|
61
|
+
},
|
|
62
|
+
addLog(n,e) { this.log.push(`event: ${n}`) },
|
|
63
|
+
onLoad(e) { this.addLog('load') },
|
|
64
|
+
onError(e) { this.addLog('error') },
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
</script>
|
|
68
|
+
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ui-avatar" :style="cssStyle">
|
|
3
|
+
<img
|
|
4
|
+
class="ui-avatar__img"
|
|
5
|
+
:src="src"
|
|
6
|
+
:alt="isDoneState ? alt : ''"
|
|
7
|
+
@error="onImageError"
|
|
8
|
+
@load="onImageLoad"
|
|
9
|
+
v-if="isLoadingState || isDoneState"
|
|
10
|
+
/>
|
|
11
|
+
<div class="ui-avatar__alt" v-if="isErrorState">
|
|
12
|
+
<!--
|
|
13
|
+
@slot Alternative content slot (displayed if no image provided)
|
|
14
|
+
@binding {string} alt alternative text
|
|
15
|
+
@binding {string} abbr alternative text abbr
|
|
16
|
+
-->
|
|
17
|
+
<slot v-bind="{ alt, abbr }">
|
|
18
|
+
<b class="ui-avatar__abbr">{{ abbr }}</b>
|
|
19
|
+
</slot>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="ui-avatar__loading" v-else-if="isLoadingState">
|
|
22
|
+
<!--
|
|
23
|
+
@slot Loading content slot
|
|
24
|
+
-->
|
|
25
|
+
<slot name="loading">
|
|
26
|
+
<div class="preloader color-inherit"></div>
|
|
27
|
+
</slot>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
<style lang="less" scoped>
|
|
32
|
+
.ui-avatar {
|
|
33
|
+
--size: 2rem;
|
|
34
|
+
--alt-scale: 0.4;
|
|
35
|
+
position: relative;
|
|
36
|
+
width: var(--size);
|
|
37
|
+
height: var(--size);
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
&__abbr {
|
|
43
|
+
font-size: calc(var(--size) * var(--alt-scale));
|
|
44
|
+
line-height: 1;
|
|
45
|
+
}
|
|
46
|
+
&__img {
|
|
47
|
+
width: 100%;
|
|
48
|
+
height: 100%;
|
|
49
|
+
object-fit: cover;
|
|
50
|
+
}
|
|
51
|
+
&__alt,
|
|
52
|
+
&__loading {
|
|
53
|
+
position: absolute;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
57
|
+
<script>
|
|
58
|
+
const State = {
|
|
59
|
+
LOADING: 'loading',
|
|
60
|
+
ERROR: 'error',
|
|
61
|
+
DONE: 'done',
|
|
62
|
+
};
|
|
63
|
+
export default {
|
|
64
|
+
props: {
|
|
65
|
+
/**
|
|
66
|
+
* Avatar src url
|
|
67
|
+
*/
|
|
68
|
+
src: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: '',
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Alternative text
|
|
74
|
+
*/
|
|
75
|
+
alt: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: '',
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Color (any web color format)
|
|
81
|
+
*/
|
|
82
|
+
color: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: '#fff',
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Background color (any web color format)
|
|
88
|
+
*/
|
|
89
|
+
background: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: '#999',
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Whether the avatar should be around
|
|
95
|
+
*/
|
|
96
|
+
round: {
|
|
97
|
+
type: Boolean,
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* Defines avatar's size (any web size)
|
|
102
|
+
*/
|
|
103
|
+
size: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: '2rem',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
data: () => ({ state: State.LOADING }),
|
|
109
|
+
computed: {
|
|
110
|
+
/**
|
|
111
|
+
* @return {boolean}
|
|
112
|
+
*/
|
|
113
|
+
isLoadingState() {
|
|
114
|
+
return this.state === State.LOADING;
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* @return {boolean}
|
|
118
|
+
*/
|
|
119
|
+
isErrorState() {
|
|
120
|
+
return this.state === State.ERROR;
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* @return {boolean}
|
|
124
|
+
*/
|
|
125
|
+
isDoneState() {
|
|
126
|
+
return this.state === State.DONE;
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* @return {object}
|
|
130
|
+
*/
|
|
131
|
+
cssClass() {
|
|
132
|
+
return { 'ui-avatar--round': this.round };
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* @return {object}
|
|
136
|
+
*/
|
|
137
|
+
cssStyle() {
|
|
138
|
+
const { size, color, round, background } = this;
|
|
139
|
+
const borderRadius = round ? '50%' : 0;
|
|
140
|
+
return { '--size': size, height: size, color, background, borderRadius };
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* @return {string}
|
|
144
|
+
*/
|
|
145
|
+
abbr() {
|
|
146
|
+
const str = this.alt ?? '';
|
|
147
|
+
return str
|
|
148
|
+
.split(' ')
|
|
149
|
+
.map(s => (s.length ? s[0].toUpperCase() : ''))
|
|
150
|
+
.join('');
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
watch: {
|
|
154
|
+
src() {
|
|
155
|
+
this.state = State.LOADING;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
methods: {
|
|
159
|
+
onImageError(event) {
|
|
160
|
+
this.state = State.ERROR;
|
|
161
|
+
/**
|
|
162
|
+
* Error event
|
|
163
|
+
* @property {Event} event
|
|
164
|
+
*/
|
|
165
|
+
this.$emit('error', event);
|
|
166
|
+
},
|
|
167
|
+
onImageLoad(event) {
|
|
168
|
+
this.state = State.DONE;
|
|
169
|
+
/**
|
|
170
|
+
* Load event
|
|
171
|
+
* @property {Event} event
|
|
172
|
+
*/
|
|
173
|
+
this.$emit('load', event);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
</script>
|
|
@@ -19,10 +19,20 @@
|
|
|
19
19
|
</style>
|
|
20
20
|
<script>
|
|
21
21
|
import { createPopper } from '@popperjs/core';
|
|
22
|
-
import { Position } from './utils/Helpers';
|
|
22
|
+
import { Position, generateGetBoundingClientRect } from './utils/Helpers';
|
|
23
23
|
|
|
24
24
|
const isElem = el => el instanceof HTMLElement;
|
|
25
25
|
|
|
26
|
+
const Strategy = Object.freeze({
|
|
27
|
+
ABSOLUTE: 'absolute',
|
|
28
|
+
FIXED: 'fixed'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const Modifier = Object.freeze({
|
|
32
|
+
FLIP: 'flip',
|
|
33
|
+
OFFSET: 'offset'
|
|
34
|
+
});
|
|
35
|
+
|
|
26
36
|
export default {
|
|
27
37
|
directives: {
|
|
28
38
|
'append-body': {
|
|
@@ -96,6 +106,20 @@ export default {
|
|
|
96
106
|
type: Boolean,
|
|
97
107
|
default: false,
|
|
98
108
|
},
|
|
109
|
+
/**
|
|
110
|
+
* Should follow the cursor
|
|
111
|
+
*/
|
|
112
|
+
shouldFollowCursor: {
|
|
113
|
+
type: Boolean,
|
|
114
|
+
default: false
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* Cursor coordinates [ x, y ]
|
|
118
|
+
*/
|
|
119
|
+
cursorCoordinates: {
|
|
120
|
+
type: Array,
|
|
121
|
+
default: () => [0, 0]
|
|
122
|
+
}
|
|
99
123
|
},
|
|
100
124
|
data() {
|
|
101
125
|
return { cssStyle: {} };
|
|
@@ -114,39 +138,35 @@ export default {
|
|
|
114
138
|
}
|
|
115
139
|
return position;
|
|
116
140
|
},
|
|
141
|
+
options() {
|
|
142
|
+
const { appendToBody, placement, positionFlip, positionOffset } = this;
|
|
143
|
+
const strategy = appendToBody ? Strategy.FIXED : Strategy.ABSOLUTE;
|
|
144
|
+
const modifiers = [
|
|
145
|
+
{
|
|
146
|
+
name: Modifier.FLIP,
|
|
147
|
+
enabled: positionFlip,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: Modifier.OFFSET,
|
|
151
|
+
options: {
|
|
152
|
+
offset: positionOffset,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
]
|
|
156
|
+
return { placement, strategy, modifiers };
|
|
157
|
+
}
|
|
117
158
|
},
|
|
118
159
|
watch: {
|
|
119
160
|
show: {
|
|
120
161
|
handler(val) {
|
|
121
|
-
const { appendToBody, placement, positionFlip, positionOffset } = this;
|
|
122
|
-
const strategy = appendToBody ? 'fixed' : 'absolute';
|
|
123
162
|
this.$nextTick(() => {
|
|
124
|
-
if (val) {
|
|
163
|
+
if (val === true) {
|
|
125
164
|
this.addEventListeners();
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
strategy,
|
|
129
|
-
modifiers: [
|
|
130
|
-
{
|
|
131
|
-
name: 'flip',
|
|
132
|
-
enabled: positionFlip,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'offset',
|
|
136
|
-
options: {
|
|
137
|
-
offset: positionOffset,
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
});
|
|
142
|
-
this.$nextTick(() => {
|
|
143
|
-
this.calcStyle();
|
|
144
|
-
this.popper.update();
|
|
145
|
-
});
|
|
146
|
-
} else {
|
|
147
|
-
this.removeEventListeners();
|
|
148
|
-
this.destroyPopper();
|
|
165
|
+
this.createPopper();
|
|
166
|
+
return;
|
|
149
167
|
}
|
|
168
|
+
|
|
169
|
+
this.handleReset();
|
|
150
170
|
});
|
|
151
171
|
},
|
|
152
172
|
immediate: true,
|
|
@@ -156,8 +176,7 @@ export default {
|
|
|
156
176
|
this.popper = null;
|
|
157
177
|
},
|
|
158
178
|
destroyed() {
|
|
159
|
-
this.
|
|
160
|
-
this.destroyPopper();
|
|
179
|
+
this.handleReset();
|
|
161
180
|
},
|
|
162
181
|
methods: {
|
|
163
182
|
addEventListeners() {
|
|
@@ -185,10 +204,40 @@ export default {
|
|
|
185
204
|
setShow(val) {
|
|
186
205
|
this.$nextTick(() => this.$emit('update:show', val));
|
|
187
206
|
},
|
|
207
|
+
createPopper() {
|
|
208
|
+
const { shouldFollowCursor, options, $el: popper, cursorCoordinates } = this;
|
|
209
|
+
const target = this.getTarget();
|
|
210
|
+
|
|
211
|
+
if (shouldFollowCursor) {
|
|
212
|
+
const [x, y] = cursorCoordinates;
|
|
213
|
+
const virtualElem = {
|
|
214
|
+
getBoundingClientRect: generateGetBoundingClientRect(x, y),
|
|
215
|
+
contextElement: target
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.popper = createPopper(virtualElem, popper, options);
|
|
219
|
+
this.unwatchCursorCoordinates = this.$watch('cursorCoordinates', ([x, y]) => {
|
|
220
|
+
virtualElem.getBoundingClientRect = generateGetBoundingClientRect(x, y);
|
|
221
|
+
this.popper?.update();
|
|
222
|
+
})
|
|
223
|
+
} else {
|
|
224
|
+
this.popper = createPopper(target, popper, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.$nextTick(() => {
|
|
228
|
+
this.calcStyle();
|
|
229
|
+
this.popper.update();
|
|
230
|
+
});
|
|
231
|
+
},
|
|
188
232
|
destroyPopper() {
|
|
189
|
-
this.popper
|
|
233
|
+
this.popper?.destroy();
|
|
190
234
|
this.popper = null;
|
|
191
235
|
},
|
|
236
|
+
handleReset() {
|
|
237
|
+
this.unwatchCursorCoordinates?.();
|
|
238
|
+
this.removeEventListeners();
|
|
239
|
+
this.destroyPopper();
|
|
240
|
+
},
|
|
192
241
|
onWinBlur(e) {
|
|
193
242
|
if (this.show) {
|
|
194
243
|
this.setShow(false);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
```vue
|
|
2
|
+
<template>
|
|
3
|
+
<div class="pad-l5">
|
|
4
|
+
<div class="p">
|
|
5
|
+
<div class="form-label form-label-small">Tile width:</div>
|
|
6
|
+
<input class="input input-small" type="number" min="0" step="10" v-model.number="tileWidth">
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<ui-responsive-container v-bind="binds" v-slot="{ breakpoint, width, height }">
|
|
10
|
+
<!-- {demo-tile} -->
|
|
11
|
+
<div class="tile" :class="[`tile--${breakpoint}`]" :style="tileStyle">
|
|
12
|
+
<div class="tile-body">
|
|
13
|
+
<div class="row">
|
|
14
|
+
<div class="col col-auto tile__avatar">
|
|
15
|
+
<img class="avatar avatar-4 round" src="https://picsum.photos/150/150" >
|
|
16
|
+
</div>
|
|
17
|
+
<div class="col">
|
|
18
|
+
<div class="text-bold">John Wick</div>
|
|
19
|
+
<div class="text-xsmall">john.wick@site.com</div>
|
|
20
|
+
<code>{{breakpoint}}</code> | <code>{{width}}×{{height}}</code>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<!-- {/demo-tile} -->
|
|
25
|
+
</div>
|
|
26
|
+
</ui-responsive-container>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
<script>
|
|
30
|
+
import UiResponsiveContainer from './ResponsiveContainer.vue';
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
components: { UiResponsiveContainer },
|
|
34
|
+
data: () => ({
|
|
35
|
+
binds: {
|
|
36
|
+
breakpoints: {
|
|
37
|
+
's': 450,
|
|
38
|
+
'm': 550,
|
|
39
|
+
'l': 650,
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
tileWidth: ''
|
|
43
|
+
}),
|
|
44
|
+
computed: {
|
|
45
|
+
tileStyle() {
|
|
46
|
+
return { width: `${this.tileWidth}px` }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
51
|
+
<style scoped>
|
|
52
|
+
.tile { min-width: 150px; }
|
|
53
|
+
.tile--s { color: red; }
|
|
54
|
+
.tile--s .tile__avatar { display:none; }
|
|
55
|
+
.tile--m { color: orange; }
|
|
56
|
+
.tile--l { color: green; }
|
|
57
|
+
</style>
|
|
58
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { debounce } from './utils/Helpers';
|
|
3
|
+
|
|
4
|
+
const ObserverManager = {
|
|
5
|
+
observer: null,
|
|
6
|
+
records: new Map(),
|
|
7
|
+
register({ fn, el }) {
|
|
8
|
+
const { records } = this;
|
|
9
|
+
if (!records.size) {
|
|
10
|
+
this.createObserver();
|
|
11
|
+
}
|
|
12
|
+
records.set(el, fn);
|
|
13
|
+
this.observer.observe(el);
|
|
14
|
+
},
|
|
15
|
+
unregister(el) {
|
|
16
|
+
const { records } = this;
|
|
17
|
+
records.delete(el);
|
|
18
|
+
this.observer.unobserve(el);
|
|
19
|
+
if (!records.size) {
|
|
20
|
+
this.destroyObserver();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
createObserver() {
|
|
24
|
+
const { records } = this;
|
|
25
|
+
const callback = entries => {
|
|
26
|
+
entries.forEach(entry => {
|
|
27
|
+
const fn = records.get(entry.target);
|
|
28
|
+
fn && fn(entry);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
this.observer = new ResizeObserver(debounce(callback, 50));
|
|
32
|
+
},
|
|
33
|
+
destroyObserver() {
|
|
34
|
+
this.observer.disconnect();
|
|
35
|
+
this.observer = null;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
props: {
|
|
41
|
+
/**
|
|
42
|
+
* Breakpoints hash { '<name>': '<max-width-in-px>' }<br>
|
|
43
|
+
* example:
|
|
44
|
+
* <pre>{ 'mobile': 400, 'tablet': 800 }</pre>
|
|
45
|
+
*/
|
|
46
|
+
breakpoints: {
|
|
47
|
+
type: Object,
|
|
48
|
+
default: () => ({}),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
data: () => ({ width: 0, height: 0 }),
|
|
52
|
+
computed: {
|
|
53
|
+
/**
|
|
54
|
+
* @return {[string, number][]}
|
|
55
|
+
*/
|
|
56
|
+
breakpointEntries() {
|
|
57
|
+
return Object.entries(this.breakpoints).sort(([, aVal], [, bVal]) => aVal - bVal);
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* @return {string}
|
|
61
|
+
*/
|
|
62
|
+
breakpoint() {
|
|
63
|
+
const { breakpointEntries, width } = this;
|
|
64
|
+
if (width === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const match = breakpointEntries.find(([, value]) => width < value);
|
|
68
|
+
return match?.[0] ?? null;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
mounted() {
|
|
72
|
+
const { $el: el } = this;
|
|
73
|
+
const fn = ({ contentRect }) => {
|
|
74
|
+
this.width = contentRect.width | 0;
|
|
75
|
+
this.height = contentRect.height | 0;
|
|
76
|
+
};
|
|
77
|
+
ObserverManager.register({ fn, el });
|
|
78
|
+
},
|
|
79
|
+
beforeDestroy() {
|
|
80
|
+
ObserverManager.unregister(this.$el);
|
|
81
|
+
},
|
|
82
|
+
render(h) {
|
|
83
|
+
const { breakpoint, width, height } = this;
|
|
84
|
+
/*
|
|
85
|
+
@slot Page data slot
|
|
86
|
+
@binding {string} breakpoint breakpoint name
|
|
87
|
+
@binding {number} width current container width (px)
|
|
88
|
+
@binding {number} height current container height (px)
|
|
89
|
+
*/
|
|
90
|
+
const content = this.$scopedSlots.default
|
|
91
|
+
? this.$scopedSlots.default({ breakpoint, width, height })
|
|
92
|
+
: null;
|
|
93
|
+
if (content.length > 1) {
|
|
94
|
+
return h('div', 'Slot content should have one child');
|
|
95
|
+
}
|
|
96
|
+
return content;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
</script>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Simple example
|
|
2
|
+
|
|
1
3
|
```vue
|
|
2
4
|
<template>
|
|
3
5
|
<div class="pos-rel pad-l5">
|
|
@@ -50,3 +52,63 @@ export default {
|
|
|
50
52
|
};
|
|
51
53
|
</script>
|
|
52
54
|
```
|
|
55
|
+
|
|
56
|
+
Advanced example. Using cursor tracking
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<template>
|
|
60
|
+
<div class="pos-rel pad-l5">
|
|
61
|
+
<ui-tooltip v-bind="options">
|
|
62
|
+
<template #target="{ binds }">
|
|
63
|
+
<div
|
|
64
|
+
ref="target"
|
|
65
|
+
class="pad-l2 bg-green color-white radius text-center"
|
|
66
|
+
v-bind="binds"
|
|
67
|
+
>
|
|
68
|
+
hover me
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
<div>
|
|
72
|
+
I follow the cursor everywhere
|
|
73
|
+
</div>
|
|
74
|
+
</ui-tooltip>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
<script>
|
|
78
|
+
import UiTooltip from './Tooltip.vue';
|
|
79
|
+
export default {
|
|
80
|
+
components: { UiTooltip },
|
|
81
|
+
data() {
|
|
82
|
+
return {
|
|
83
|
+
options: {
|
|
84
|
+
show: false,
|
|
85
|
+
shouldFollowCursor: true,
|
|
86
|
+
cursorCoordinates: [0, 0]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
mounted() {
|
|
91
|
+
this.$refs.target.addEventListener('mouseover', this.showTooltip);
|
|
92
|
+
this.$refs.target.addEventListener('mousemove', this.changeTooltipCoords);
|
|
93
|
+
this.$refs.target.addEventListener('mouseleave', this.hideTooltip);
|
|
94
|
+
},
|
|
95
|
+
beforeDestroy() {
|
|
96
|
+
this.$refs.target.removeEventListener('mouseover', this.showTooltip);
|
|
97
|
+
this.$refs.target.removeEventListener('mousemove', this.changeTooltipCoords);
|
|
98
|
+
this.$refs.target.removeEventListener('mouseleave', this.hideTooltip);
|
|
99
|
+
|
|
100
|
+
},
|
|
101
|
+
methods: {
|
|
102
|
+
showTooltip() {
|
|
103
|
+
this.options.show = true;
|
|
104
|
+
},
|
|
105
|
+
hideTooltip() {
|
|
106
|
+
this.options.show = false;
|
|
107
|
+
},
|
|
108
|
+
changeTooltipCoords({ clientX: x, clientY: y }) {
|
|
109
|
+
this.options.cursorCoordinates = [x, y];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
@@ -58,4 +58,27 @@ const useIntersectionObserver = (target, callback, options) => {
|
|
|
58
58
|
return { observer, stop: () => observer.disconnect() };
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* @param {number} x - x-axis coordinate
|
|
63
|
+
* @param {number} y - y-axis coordinate
|
|
64
|
+
* @return {function: DOMRect}
|
|
65
|
+
*/
|
|
66
|
+
const generateGetBoundingClientRect = (x = 0, y = 0) => () => ({
|
|
67
|
+
width: 0,
|
|
68
|
+
height: 0,
|
|
69
|
+
top: y,
|
|
70
|
+
right: x,
|
|
71
|
+
bottom: y,
|
|
72
|
+
left: x,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
scrollIntoView,
|
|
77
|
+
isDateValid,
|
|
78
|
+
nextId,
|
|
79
|
+
Key,
|
|
80
|
+
Position,
|
|
81
|
+
debounce,
|
|
82
|
+
useIntersectionObserver,
|
|
83
|
+
generateGetBoundingClientRect,
|
|
84
|
+
};
|
|
@@ -43,6 +43,20 @@ export default {
|
|
|
43
43
|
type: Boolean,
|
|
44
44
|
default: false,
|
|
45
45
|
},
|
|
46
|
+
/**
|
|
47
|
+
* Should follow the cursor
|
|
48
|
+
*/
|
|
49
|
+
shouldFollowCursor: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Cursor coordinates [ x, y ]
|
|
55
|
+
*/
|
|
56
|
+
cursorCoordinates: {
|
|
57
|
+
type: Array,
|
|
58
|
+
default: () => [0, 0]
|
|
59
|
+
}
|
|
46
60
|
},
|
|
47
61
|
data() {
|
|
48
62
|
return {
|
|
@@ -62,6 +76,8 @@ export default {
|
|
|
62
76
|
positionOffset,
|
|
63
77
|
autoWidth,
|
|
64
78
|
popoverTarget: target,
|
|
79
|
+
shouldFollowCursor,
|
|
80
|
+
cursorCoordinates
|
|
65
81
|
} = this;
|
|
66
82
|
return {
|
|
67
83
|
zIndex,
|
|
@@ -70,6 +86,8 @@ export default {
|
|
|
70
86
|
positionOffset,
|
|
71
87
|
autoWidth,
|
|
72
88
|
target,
|
|
89
|
+
shouldFollowCursor,
|
|
90
|
+
cursorCoordinates
|
|
73
91
|
};
|
|
74
92
|
},
|
|
75
93
|
},
|