goodteditor-ui 1.0.11 → 1.0.14

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 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';
@@ -23,6 +24,7 @@ import Tooltip from './src/components/ui/Tooltip.vue';
23
24
  import Grid from './src/components/ui/Grid.vue';
24
25
 
25
26
  export {
27
+ Avatar,
26
28
  Badge,
27
29
  Collapse,
28
30
  ColorPicker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goodteditor-ui",
3
- "version": "1.0.11",
3
+ "version": "1.0.14",
4
4
  "main": "index.js",
5
5
  "homepage": "https://goodt-ui.netlify.app/",
6
6
  "scripts": {
@@ -10,8 +10,7 @@
10
10
  "dev": "vue-styleguidist server",
11
11
  "docs:build": "set NODE_ENV=development && vue-styleguidist build",
12
12
  "docs:deploy": "npx netlify deploy --dir=docs --prod",
13
- "notify": "node ./ci/teams-notify.js",
14
- "publish": "npm run docs:build && npm run docs:deploy && npm run notify"
13
+ "notify": "node ./ci/teams-notify.js"
15
14
  },
16
15
  "devDependencies": {
17
16
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
@@ -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,27 @@ 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
+ },
123
+ /**
124
+ * Whether handle events to control visibility
125
+ */
126
+ isManualControlMode: {
127
+ type: Boolean,
128
+ default: false
129
+ }
99
130
  },
100
131
  data() {
101
132
  return { cssStyle: {} };
@@ -114,39 +145,38 @@ export default {
114
145
  }
115
146
  return position;
116
147
  },
148
+ options() {
149
+ const { appendToBody, placement, positionFlip, positionOffset } = this;
150
+ const strategy = appendToBody ? Strategy.FIXED : Strategy.ABSOLUTE;
151
+ const modifiers = [
152
+ {
153
+ name: Modifier.FLIP,
154
+ enabled: positionFlip,
155
+ },
156
+ {
157
+ name: Modifier.OFFSET,
158
+ options: {
159
+ offset: positionOffset,
160
+ },
161
+ },
162
+ ]
163
+ return { placement, strategy, modifiers };
164
+ }
117
165
  },
118
166
  watch: {
119
167
  show: {
120
168
  handler(val) {
121
- const { appendToBody, placement, positionFlip, positionOffset } = this;
122
- const strategy = appendToBody ? 'fixed' : 'absolute';
123
169
  this.$nextTick(() => {
124
- if (val) {
170
+ if (val === false) {
171
+ this.handleReset();
172
+ return;
173
+ }
174
+
175
+ if (this.isManualControlMode === false) {
125
176
  this.addEventListeners();
126
- this.popper = createPopper(this.getTarget(), this.$el, {
127
- placement,
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();
149
177
  }
178
+
179
+ this.createPopper();
150
180
  });
151
181
  },
152
182
  immediate: true,
@@ -156,8 +186,7 @@ export default {
156
186
  this.popper = null;
157
187
  },
158
188
  destroyed() {
159
- this.removeEventListeners();
160
- this.destroyPopper();
189
+ this.handleReset();
161
190
  },
162
191
  methods: {
163
192
  addEventListeners() {
@@ -185,10 +214,42 @@ export default {
185
214
  setShow(val) {
186
215
  this.$nextTick(() => this.$emit('update:show', val));
187
216
  },
217
+ createPopper() {
218
+ const { shouldFollowCursor, options, $el: popper, cursorCoordinates } = this;
219
+ const target = this.getTarget();
220
+
221
+ if (shouldFollowCursor) {
222
+ const [x, y] = cursorCoordinates;
223
+ const virtualElem = {
224
+ getBoundingClientRect: generateGetBoundingClientRect(x, y),
225
+ contextElement: target
226
+ }
227
+
228
+ this.popper = createPopper(virtualElem, popper, options);
229
+ this.unwatchCursorCoordinates = this.$watch('cursorCoordinates', ([x, y]) => {
230
+ virtualElem.getBoundingClientRect = generateGetBoundingClientRect(x, y);
231
+ this.popper?.update();
232
+ })
233
+ } else {
234
+ this.popper = createPopper(target, popper, options);
235
+ }
236
+
237
+ this.$nextTick(() => {
238
+ this.calcStyle();
239
+ this.popper.update();
240
+ });
241
+ },
188
242
  destroyPopper() {
189
- this.popper && this.popper.destroy();
243
+ this.popper?.destroy();
190
244
  this.popper = null;
191
245
  },
246
+ handleReset() {
247
+ this.unwatchCursorCoordinates?.();
248
+ if (this.isManualControlMode === false) {
249
+ this.removeEventListeners();
250
+ }
251
+ this.destroyPopper();
252
+ },
192
253
  onWinBlur(e) {
193
254
  if (this.show) {
194
255
  this.setShow(false);
@@ -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
- export { scrollIntoView, isDateValid, nextId, Key, Position, debounce, useIntersectionObserver };
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
  },