goodteditor-ui 1.0.90 → 1.0.92
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.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/components/ui/EditableText.md +38 -1
- package/src/components/ui/EditableText.vue +175 -69
- package/src/components/ui/utils/Helpers.js +0 -13
package/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import Select from './src/components/ui/Select.vue';
|
|
|
22
22
|
import TimePicker from './src/components/ui/TimePicker.vue';
|
|
23
23
|
import Tooltip from './src/components/ui/Tooltip.vue';
|
|
24
24
|
import Grid from './src/components/ui/Grid.vue';
|
|
25
|
+
import EditableText from './src/components/ui/EditableText.vue';
|
|
25
26
|
import { WysiwygEditor, WysiwygConsts, WysiwygRenderMixins } from './src/components/ui/WysiwygEditor';
|
|
26
27
|
// utils stuff
|
|
27
28
|
import FormComponent from './src/components/ui/utils/FormComponent';
|
|
@@ -56,5 +57,6 @@ export {
|
|
|
56
57
|
TimePicker,
|
|
57
58
|
Tooltip,
|
|
58
59
|
Grid,
|
|
60
|
+
EditableText,
|
|
59
61
|
WysiwygEditor
|
|
60
62
|
};
|
package/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import Select from './src/components/ui/Select.vue';
|
|
|
22
22
|
import TimePicker from './src/components/ui/TimePicker.vue';
|
|
23
23
|
import Tooltip from './src/components/ui/Tooltip.vue';
|
|
24
24
|
import Grid from './src/components/ui/Grid.vue';
|
|
25
|
+
import EditableText from './src/components/ui/EditableText.vue';
|
|
25
26
|
import { WysiwygEditor, WysiwygConsts, WysiwygRenderMixins } from './src/components/ui/WysiwygEditor';
|
|
26
27
|
// utils stuff
|
|
27
28
|
import FormComponent from './src/components/ui/utils/FormComponent';
|
|
@@ -56,5 +57,6 @@ export {
|
|
|
56
57
|
TimePicker,
|
|
57
58
|
Tooltip,
|
|
58
59
|
Grid,
|
|
60
|
+
EditableText,
|
|
59
61
|
WysiwygEditor
|
|
60
62
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
|
+
Simple example
|
|
2
|
+
|
|
3
|
+
```vue
|
|
4
|
+
<template>
|
|
5
|
+
<div class="pad-l5">
|
|
6
|
+
<label>
|
|
7
|
+
<input id="editable-text" class="checkbox" type="checkbox" v-model="options.readonly">
|
|
8
|
+
<span class="mar-left-3 v-mid">readonly</span>
|
|
9
|
+
</label>
|
|
10
|
+
<p>model: {{ model }}</p>
|
|
11
|
+
<ui-editable-text v-model="model" v-bind="options"></ui-editable-text>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
<script>
|
|
15
|
+
import UiEditableText from './EditableText.vue';
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
components: { UiEditableText },
|
|
19
|
+
data: () => ({
|
|
20
|
+
model: 'hello world',
|
|
21
|
+
options: {
|
|
22
|
+
readonly: false
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Advanced example. Edit on double-click, large size & custom validation
|
|
30
|
+
|
|
1
31
|
```vue
|
|
2
32
|
<template>
|
|
3
33
|
<div class="pad-l5">
|
|
34
|
+
<label>
|
|
35
|
+
<input id="editable-text-2" class="checkbox" type="checkbox" v-model="options.readonly">
|
|
36
|
+
<span class="mar-left-3 v-mid">readonly</span>
|
|
37
|
+
</label>
|
|
4
38
|
<p>model: {{ model }}</p>
|
|
5
39
|
<ui-editable-text v-model="model" v-bind="options"></ui-editable-text>
|
|
6
40
|
</div>
|
|
@@ -13,7 +47,10 @@ export default {
|
|
|
13
47
|
data: () => ({
|
|
14
48
|
model: 'hello world',
|
|
15
49
|
options: {
|
|
16
|
-
size: 'large'
|
|
50
|
+
size: 'large',
|
|
51
|
+
validate: (value) => value !== '',
|
|
52
|
+
event: 'dblclick',
|
|
53
|
+
readonly: false
|
|
17
54
|
}
|
|
18
55
|
}),
|
|
19
56
|
};
|
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="ui-editable-text"
|
|
4
|
+
:contenteditable="isEditable ? 'plaintext-only' : null"
|
|
5
|
+
:class="cssClass"
|
|
6
|
+
tabindex="0"
|
|
7
|
+
@keydown.stop="onKeydown"
|
|
8
|
+
@keyup.stop="onKeyup"
|
|
9
|
+
@focus="onFocus"
|
|
10
|
+
@focusin.stop="onFocus"
|
|
11
|
+
@blur="onBlur"
|
|
12
|
+
@focusout.stop="onBlur"
|
|
13
|
+
@click.stop
|
|
14
|
+
v-on="{
|
|
15
|
+
[event]: onActivate
|
|
16
|
+
}">
|
|
17
|
+
<!--
|
|
18
|
+
@slot
|
|
19
|
+
@binding {string} value current value
|
|
20
|
+
-->
|
|
21
|
+
<slot v-bind="{ value }">{{ value }}</slot>
|
|
18
22
|
</div>
|
|
19
23
|
</template>
|
|
20
24
|
<script>
|
|
21
|
-
import { domEvent } from './utils/Helpers';
|
|
22
25
|
|
|
23
26
|
const Size = {
|
|
24
|
-
SMALL: { name: 'small',
|
|
25
|
-
NORMAL: { name: 'normal',
|
|
26
|
-
LARGE: { name: 'large',
|
|
27
|
+
SMALL: { name: 'small', class: ['text-small'] },
|
|
28
|
+
NORMAL: { name: 'normal', class: [] },
|
|
29
|
+
LARGE: { name: 'large', class: ['text-large'] }
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
export default {
|
|
@@ -33,9 +36,16 @@ export default {
|
|
|
33
36
|
* @example lorem ipsum sit dolor
|
|
34
37
|
*/
|
|
35
38
|
value: {
|
|
36
|
-
type: String,
|
|
39
|
+
type: [String, Number],
|
|
37
40
|
default: ''
|
|
38
41
|
},
|
|
42
|
+
/**
|
|
43
|
+
* supports v-sync
|
|
44
|
+
*/
|
|
45
|
+
edit: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false
|
|
48
|
+
},
|
|
39
49
|
/**
|
|
40
50
|
* @default normal
|
|
41
51
|
* @values small,normal,large
|
|
@@ -47,89 +57,185 @@ export default {
|
|
|
47
57
|
Object.values(Size)
|
|
48
58
|
.map(({ name }) => name)
|
|
49
59
|
.includes(val)
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* @default click
|
|
63
|
+
* @example click, dblclick
|
|
64
|
+
*/
|
|
65
|
+
event: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: 'click'
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* whether the text is readonly
|
|
71
|
+
*/
|
|
72
|
+
readonly: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* validation callback
|
|
78
|
+
*/
|
|
79
|
+
validate: {
|
|
80
|
+
type: Function,
|
|
81
|
+
default: () => true
|
|
50
82
|
}
|
|
51
83
|
},
|
|
52
84
|
data: () => ({
|
|
53
|
-
|
|
54
|
-
|
|
85
|
+
isEdited: false,
|
|
86
|
+
valueLast: '',
|
|
87
|
+
valueDirty: ''
|
|
55
88
|
}),
|
|
56
89
|
computed: {
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
isEditable() {
|
|
91
|
+
return this.readonly === false && this.isEdited;
|
|
92
|
+
},
|
|
93
|
+
cssClass() {
|
|
94
|
+
const { isEdited, size, readonly } = this;
|
|
59
95
|
const classes = [];
|
|
60
96
|
const sizeDef = Object.values(Size).find(({ name }) => name === size);
|
|
61
97
|
|
|
62
|
-
classes.push(...sizeDef.
|
|
98
|
+
classes.push(...sizeDef.class);
|
|
99
|
+
|
|
100
|
+
if (readonly) {
|
|
101
|
+
classes.push('readonly');
|
|
102
|
+
}
|
|
63
103
|
|
|
64
|
-
if (
|
|
65
|
-
classes.push('
|
|
104
|
+
if (isEdited) {
|
|
105
|
+
classes.push('is-edited');
|
|
66
106
|
}
|
|
67
107
|
|
|
68
108
|
return classes;
|
|
69
109
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return sizeDef.inputClass;
|
|
110
|
+
isValid() {
|
|
111
|
+
return this.validate(this.valueDirty);
|
|
73
112
|
}
|
|
74
113
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
114
|
+
watch: {
|
|
115
|
+
value: {
|
|
116
|
+
handler(value) {
|
|
117
|
+
this.valueLast = this.valueDirty = value;
|
|
118
|
+
},
|
|
119
|
+
immediate: true
|
|
120
|
+
},
|
|
121
|
+
edit(edit) {
|
|
122
|
+
if (edit) {
|
|
123
|
+
this.onActivate();
|
|
124
|
+
} else {
|
|
125
|
+
this.onBlur();
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
isValid(isValid) {
|
|
129
|
+
this.$emit('valid', isValid);
|
|
79
130
|
}
|
|
80
131
|
},
|
|
81
132
|
methods: {
|
|
82
|
-
|
|
83
|
-
|
|
133
|
+
/**
|
|
134
|
+
* @param {Event} event
|
|
135
|
+
*/
|
|
136
|
+
onActivate(event) {
|
|
137
|
+
event?.stopImmediatePropagation();
|
|
138
|
+
|
|
139
|
+
if (this.isEdited || this.readonly) {
|
|
84
140
|
return;
|
|
85
141
|
}
|
|
86
|
-
this.
|
|
87
|
-
this.$nextTick(() => {
|
|
88
|
-
const { input } = this.$refs;
|
|
89
|
-
input.select();
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
this.onDocClickDisposable = domEvent(document, 'click', this.onDocClick);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
142
|
+
this.setIsEdited(true);
|
|
94
143
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
144
|
+
/**
|
|
145
|
+
* @param {boolean} isEdited
|
|
146
|
+
*/
|
|
147
|
+
setIsEdited(isEdited) {
|
|
148
|
+
this.isEdited = isEdited;
|
|
149
|
+
// autofocus
|
|
150
|
+
if (isEdited) {
|
|
151
|
+
this.$nextTick(() => {
|
|
152
|
+
const range = document.createRange();
|
|
153
|
+
range.selectNodeContents(this.$el);
|
|
154
|
+
const selection = window.getSelection();
|
|
155
|
+
selection.removeAllRanges();
|
|
156
|
+
selection.addRange(range);
|
|
157
|
+
this.$el.focus();
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
this.$el.blur();
|
|
100
161
|
}
|
|
101
|
-
|
|
102
|
-
this
|
|
162
|
+
|
|
163
|
+
this.$emit('update:edit', isEdited);
|
|
103
164
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} value
|
|
167
|
+
*/
|
|
168
|
+
setValue(value) {
|
|
169
|
+
this.$el.textContent = value;
|
|
170
|
+
this.valueLast = value;
|
|
171
|
+
this.$emit('input', value);
|
|
172
|
+
this.$emit('change', value);
|
|
173
|
+
},
|
|
174
|
+
commitValue() {
|
|
175
|
+
this.setValue(this.isValid ? this.valueDirty : this.valueLast);
|
|
176
|
+
this.setIsEdited(false);
|
|
177
|
+
},
|
|
178
|
+
onFocus() {
|
|
179
|
+
this.$emit('focus');
|
|
180
|
+
},
|
|
181
|
+
onBlur() {
|
|
182
|
+
this.$emit('blur');
|
|
183
|
+
this.commitValue();
|
|
184
|
+
},
|
|
185
|
+
/**
|
|
186
|
+
* @param {KeyboardEvent} event
|
|
187
|
+
*/
|
|
188
|
+
onKeydown(event) {
|
|
189
|
+
if (event.code.toLocaleLowerCase() === 'enter') {
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
this.setIsEdited(!this.isEdited);
|
|
107
192
|
}
|
|
108
|
-
|
|
109
|
-
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* @param {KeyboardEvent} event
|
|
196
|
+
*/
|
|
197
|
+
onKeyup({ target }) {
|
|
198
|
+
this.valueDirty = target.textContent.trim();
|
|
110
199
|
}
|
|
111
200
|
}
|
|
112
201
|
};
|
|
113
202
|
</script>
|
|
114
|
-
<style
|
|
203
|
+
<style scoped>
|
|
115
204
|
.ui-editable-text {
|
|
116
205
|
position: relative;
|
|
117
206
|
display: inline-flex;
|
|
207
|
+
min-width: 1em;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
|
|
210
|
+
&:focus {
|
|
211
|
+
outline: var(--focus-outline-width) solid var(--color-focus);
|
|
212
|
+
outline-offset: 1px;
|
|
213
|
+
border-radius: var(--border-radius);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&:empty {
|
|
217
|
+
border-bottom: 2px dotted var(--color-primary);
|
|
218
|
+
&:before {
|
|
219
|
+
content: ' '
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
&:hover:not(.readonly, :focus) {
|
|
224
|
+
color: var(--color-primary);
|
|
225
|
+
}
|
|
118
226
|
|
|
119
|
-
|
|
227
|
+
&.readonly {
|
|
228
|
+
cursor: default;
|
|
120
229
|
&:hover {
|
|
121
|
-
color:
|
|
230
|
+
color: initial;
|
|
231
|
+
}
|
|
232
|
+
&:focus {
|
|
233
|
+
outline: none;
|
|
122
234
|
}
|
|
123
235
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
left: -0.5rem;
|
|
128
|
-
width: calc(100% + 1rem);
|
|
129
|
-
border: none;
|
|
130
|
-
padding-top: 0;
|
|
131
|
-
padding-bottom: 0;
|
|
132
|
-
min-height: auto;
|
|
236
|
+
|
|
237
|
+
&.is-edited {
|
|
238
|
+
white-space: pre-line;
|
|
133
239
|
}
|
|
134
240
|
}
|
|
135
241
|
</style>
|
|
@@ -77,18 +77,6 @@ const generateGetBoundingClientRect = (x = 0, y = 0) => () => ({
|
|
|
77
77
|
left: x,
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
/**
|
|
81
|
-
* @param {EventTarget} target
|
|
82
|
-
* @param {string} type
|
|
83
|
-
* @param {EventListenerOrEventListenerObject} listener
|
|
84
|
-
* @param {AddEventListenerOptions} options
|
|
85
|
-
* @returns
|
|
86
|
-
*/
|
|
87
|
-
const domEvent = (target, type, listener, options = undefined) => {
|
|
88
|
-
target.addEventListener(type, listener, options);
|
|
89
|
-
return () => target.removeEventListener(type, listener, options);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
80
|
export {
|
|
93
81
|
scrollIntoView,
|
|
94
82
|
isDateValid,
|
|
@@ -97,7 +85,6 @@ export {
|
|
|
97
85
|
Position,
|
|
98
86
|
TriggerOn,
|
|
99
87
|
debounce,
|
|
100
|
-
domEvent,
|
|
101
88
|
useIntersectionObserver,
|
|
102
89
|
generateGetBoundingClientRect,
|
|
103
90
|
};
|