@thinkpixellab-public/px-vue 3.0.93 → 3.0.95
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/components/PxBalancedText.vue +7 -2
- package/components/PxTextbox.vue +143 -43
- package/components/PxTransition.vue +2 -26
- package/package.json +1 -1
- package/utils/bem.js +5 -2
- package/utils/caretPosition.js +75 -0
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
<!-- slot -->
|
|
17
17
|
<component v-else :class="[bem('inner'), innerClass]" :is="innerTag" ref="text">
|
|
18
|
-
<slot
|
|
18
|
+
<slot>
|
|
19
|
+
{{ text }}
|
|
20
|
+
</slot>
|
|
19
21
|
</component>
|
|
20
22
|
</component>
|
|
21
23
|
</template>
|
|
@@ -41,6 +43,9 @@ export default {
|
|
|
41
43
|
// html that gets set on the inner tag element
|
|
42
44
|
html: { type: String, default: null },
|
|
43
45
|
|
|
46
|
+
// text that gets set on the inner tag element
|
|
47
|
+
text: { type: String, default: null },
|
|
48
|
+
|
|
44
49
|
// set to false to disable the balancing alorithm
|
|
45
50
|
enabled: { type: Boolean, default: true },
|
|
46
51
|
|
|
@@ -59,7 +64,7 @@ export default {
|
|
|
59
64
|
},
|
|
60
65
|
computed: {
|
|
61
66
|
hasHtml() {
|
|
62
|
-
return !!this.htmlValue;
|
|
67
|
+
return !!this.htmlValue && !this.text;
|
|
63
68
|
},
|
|
64
69
|
|
|
65
70
|
htmlValue() {
|
package/components/PxTextbox.vue
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Describe this component...
|
|
3
|
-
-->
|
|
4
1
|
<template>
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
<div
|
|
3
|
+
:class="
|
|
4
|
+
bem({
|
|
5
|
+
placeholder: placeholderVisible,
|
|
6
|
+
multiline: multiline,
|
|
7
|
+
...variantsMap,
|
|
8
|
+
})
|
|
9
|
+
"
|
|
10
|
+
ref="input"
|
|
11
|
+
@input="onInput"
|
|
12
|
+
@blur="onBlur"
|
|
13
|
+
@keydown="onKeydown"
|
|
14
|
+
:contenteditable="enabled ? 'plaintext-only' : false"
|
|
15
|
+
:placeholder="placeholder"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
></div>
|
|
21
18
|
</template>
|
|
22
19
|
|
|
23
20
|
<script>
|
|
24
21
|
import PxBase from './PxBase.vue';
|
|
25
22
|
import PxBaseVariants from './PxBaseVariants.vue';
|
|
23
|
+
import { getCaretPosition, setCaretPosition } from '../utils/caretPosition.js';
|
|
26
24
|
|
|
27
25
|
export default {
|
|
28
|
-
model: {
|
|
29
|
-
prop: 'value',
|
|
30
|
-
event: 'update:value',
|
|
31
|
-
},
|
|
32
26
|
name: 'px-textbox',
|
|
33
27
|
mixins: [PxBase, PxBaseVariants],
|
|
34
28
|
props: {
|
|
@@ -38,28 +32,117 @@ export default {
|
|
|
38
32
|
// the value of the textbox
|
|
39
33
|
value: { type: String, default: null },
|
|
40
34
|
|
|
41
|
-
// whether to show the resize element in the corner of multiline textbox
|
|
42
|
-
showResize: { type: Boolean, default: false },
|
|
43
|
-
|
|
44
35
|
// whether to fire an update / v-model event on every character change ('input') or when the textbox loses focus ('change')
|
|
45
36
|
updateMode: {
|
|
46
37
|
type: String,
|
|
47
38
|
default: 'input',
|
|
48
39
|
validator: val => ['input', 'change'].includes(val),
|
|
49
40
|
},
|
|
41
|
+
|
|
42
|
+
disabled: { type: Boolean, default: false },
|
|
43
|
+
|
|
44
|
+
placeholder: { type: String, default: '' },
|
|
45
|
+
|
|
46
|
+
lineBreakReplacement: { type: String, default: ' ' }, // the character to replace line breaks with when multiline is false
|
|
47
|
+
},
|
|
48
|
+
data() {
|
|
49
|
+
return {
|
|
50
|
+
minHeight: 300,
|
|
51
|
+
placeholderVisible: false,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
computed: {
|
|
55
|
+
enabled() {
|
|
56
|
+
return !this.disabled;
|
|
57
|
+
},
|
|
50
58
|
},
|
|
51
|
-
|
|
59
|
+
|
|
60
|
+
watch: {
|
|
61
|
+
value(nv) {
|
|
62
|
+
this.setValue(nv);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
mounted() {
|
|
66
|
+
this.setValue(this.value);
|
|
67
|
+
},
|
|
68
|
+
|
|
52
69
|
methods: {
|
|
53
|
-
|
|
70
|
+
setValue(val) {
|
|
71
|
+
const nbsp = '\u202f';
|
|
72
|
+
const startPos = getCaretPosition(this.$refs.input);
|
|
73
|
+
const prevValue = this.$refs.input.textContent;
|
|
74
|
+
|
|
75
|
+
// if the first character is nbps, we remove it (assuming we had added it to begin with)
|
|
76
|
+
if (val.startsWith(nbsp)) {
|
|
77
|
+
val = val.slice(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// remove line breaks if we are not in multiline mode
|
|
81
|
+
if (!this.multiline) {
|
|
82
|
+
val = val.replace(/(\r\n|\n|\r)/gm, this.lineBreakReplacement);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// remove leading and trailing whitespace / new lines
|
|
86
|
+
const trimmed = val.trim().replace(/^\s*\n|\n\s*$/g, '');
|
|
87
|
+
|
|
88
|
+
// if the trimmed value is empty, we set it to a single non-breaking space (this eliminates weird UI behaviors when the textbox is empty)
|
|
89
|
+
if (trimmed.length == 0) {
|
|
90
|
+
this.placeholderVisible = true;
|
|
91
|
+
val = nbsp;
|
|
92
|
+
} else {
|
|
93
|
+
this.placeholderVisible = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// set the text content
|
|
97
|
+
this.$refs.input.textContent = val;
|
|
98
|
+
|
|
99
|
+
// raise an update event
|
|
100
|
+
this.$emit('update:value', val);
|
|
101
|
+
|
|
102
|
+
// update the caret position
|
|
103
|
+
const newPos = startPos + (val.length - prevValue.length);
|
|
104
|
+
|
|
105
|
+
if (newPos > 0) {
|
|
106
|
+
setCaretPosition(this.$refs.input, newPos);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
onInput(e) {
|
|
110
|
+
const value = e.target.textContent;
|
|
54
111
|
if (this.updateMode == 'input') {
|
|
55
|
-
this
|
|
112
|
+
this.setValue(value);
|
|
56
113
|
}
|
|
114
|
+
this.$emit('input', value);
|
|
57
115
|
},
|
|
58
|
-
|
|
116
|
+
|
|
117
|
+
onBlur(e) {
|
|
118
|
+
const value = e.target.textContent;
|
|
59
119
|
if (this.updateMode == 'change') {
|
|
60
|
-
this
|
|
120
|
+
this.setValue(value);
|
|
121
|
+
}
|
|
122
|
+
this.$emit('change', value);
|
|
123
|
+
this.$emit('blue');
|
|
124
|
+
},
|
|
125
|
+
onKeydown(e) {
|
|
126
|
+
if (!this.multiline) {
|
|
127
|
+
if (e.key == 'Enter') {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.$emit('keydown', e);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
focus(cursorPosition = 'end') {
|
|
135
|
+
if (this.$refs?.input) {
|
|
136
|
+
this.$refs.input.focus();
|
|
137
|
+
|
|
138
|
+
if (cursorPosition == 'end') {
|
|
139
|
+
setCaretPosition(this.$refs.input, this.$refs.input.textContent.length);
|
|
140
|
+
} else {
|
|
141
|
+
setCaretPosition(this.$refs.input, 0);
|
|
142
|
+
}
|
|
61
143
|
}
|
|
62
144
|
},
|
|
145
|
+
|
|
63
146
|
selectAll() {
|
|
64
147
|
this.$el.select();
|
|
65
148
|
},
|
|
@@ -69,22 +152,39 @@ export default {
|
|
|
69
152
|
|
|
70
153
|
<style lang="scss">
|
|
71
154
|
@use '../styles/px.scss' as *;
|
|
155
|
+
@use '@thinkpixellab-public/px-styles/src/modules/controls' as *;
|
|
72
156
|
|
|
73
|
-
|
|
157
|
+
.px-textbox {
|
|
158
|
+
@include control-reset();
|
|
74
159
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
text-align: left;
|
|
163
|
+
display: inline-flex;
|
|
78
164
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
165
|
+
&--placeholder {
|
|
166
|
+
&:after {
|
|
167
|
+
content: attr(placeholder);
|
|
168
|
+
font-size: inherit;
|
|
169
|
+
font-family: inherit;
|
|
170
|
+
opacity: 0.5;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
82
174
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
175
|
+
&--default {
|
|
176
|
+
@include textbox(
|
|
177
|
+
(
|
|
178
|
+
display: inline-flex,
|
|
179
|
+
text-align: left,
|
|
180
|
+
align-items: center,
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
86
184
|
|
|
87
|
-
|
|
88
|
-
|
|
185
|
+
&--multiline {
|
|
186
|
+
white-space: normal;
|
|
187
|
+
overflow-y: auto;
|
|
188
|
+
}
|
|
89
189
|
}
|
|
90
190
|
</style>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<script>
|
|
12
12
|
import gsap from 'gsap';
|
|
13
|
+
import utils from '../utils/utils.js';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
// components: { Component },
|
|
@@ -63,10 +64,6 @@ export default {
|
|
|
63
64
|
type: String,
|
|
64
65
|
default: null,
|
|
65
66
|
},
|
|
66
|
-
fixedLeave: {
|
|
67
|
-
type: Boolean,
|
|
68
|
-
default: false,
|
|
69
|
-
},
|
|
70
67
|
},
|
|
71
68
|
// data() { return { b: 0 }; },f
|
|
72
69
|
computed: {
|
|
@@ -185,28 +182,7 @@ export default {
|
|
|
185
182
|
...params.to,
|
|
186
183
|
duration: this.hideDur,
|
|
187
184
|
ease: this.hideEase,
|
|
188
|
-
|
|
189
|
-
if (this.fixedLeave && element) {
|
|
190
|
-
const rect = element.getBoundingClientRect();
|
|
191
|
-
element.style.width = `${rect.width}px`;
|
|
192
|
-
element.style.height = `${rect.height}px`;
|
|
193
|
-
element.style.top = `${rect.top}px`;
|
|
194
|
-
element.style.left = `${rect.left}px`;
|
|
195
|
-
element.style.position = 'fixed';
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
onComplete: () => {
|
|
199
|
-
if (this.fixedLeave && element) {
|
|
200
|
-
if (element) {
|
|
201
|
-
element.style.width = '';
|
|
202
|
-
element.style.height = '';
|
|
203
|
-
element.style.position = '';
|
|
204
|
-
element.style.top = '';
|
|
205
|
-
element.style.left = '';
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
done();
|
|
209
|
-
},
|
|
185
|
+
onComplete: done,
|
|
210
186
|
});
|
|
211
187
|
}
|
|
212
188
|
},
|
package/package.json
CHANGED
package/utils/bem.js
CHANGED
|
@@ -32,7 +32,10 @@ function hyphenate(str, shouldHyphenate = true) {
|
|
|
32
32
|
// Get block name
|
|
33
33
|
function getBlockName(component) {
|
|
34
34
|
if (!component.$options._blockName) {
|
|
35
|
-
let block =
|
|
35
|
+
let block =
|
|
36
|
+
'bemBlockName' in component && typeof component?.bemBlockName === 'function'
|
|
37
|
+
? component.bemBlockName()
|
|
38
|
+
: null;
|
|
36
39
|
block = block || component.$options.name || component.$options._componentTag;
|
|
37
40
|
if (block && block.indexOf('/') > -1) {
|
|
38
41
|
block = block.split('/').pop().split('.')[0];
|
|
@@ -43,7 +46,7 @@ function getBlockName(component) {
|
|
|
43
46
|
return component.$options._blockName;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
//
|
|
49
|
+
// Calculate the bem classes for a given block, element and set of modifiers
|
|
47
50
|
function getBemClasses(blockName, elementName, modifiers, options) {
|
|
48
51
|
const block = hyphenate(blockName, options.hyphenate.block);
|
|
49
52
|
const element = elementName
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function createRange(node, endPos) {
|
|
2
|
+
let range = document.createRange();
|
|
3
|
+
range.selectNode(node);
|
|
4
|
+
range.setStart(node, 1);
|
|
5
|
+
|
|
6
|
+
let pos = 0;
|
|
7
|
+
const stack = [node];
|
|
8
|
+
while (stack.length > 0) {
|
|
9
|
+
const current = stack.pop();
|
|
10
|
+
|
|
11
|
+
if (current.nodeType === Node.TEXT_NODE) {
|
|
12
|
+
const len = current.textContent.length;
|
|
13
|
+
if (pos + len >= endPos) {
|
|
14
|
+
range.setEnd(current, endPos - pos);
|
|
15
|
+
return range;
|
|
16
|
+
}
|
|
17
|
+
pos += len;
|
|
18
|
+
} else if (current.childNodes && current.childNodes.length > 0) {
|
|
19
|
+
for (let i = current.childNodes.length - 1; i >= 0; i--) {
|
|
20
|
+
stack.push(current.childNodes[i]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The target position is greater than the
|
|
26
|
+
// length of the contenteditable element.
|
|
27
|
+
range.setEnd(node, node.childNodes.length);
|
|
28
|
+
return range;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function canProceed(element) {
|
|
32
|
+
if (
|
|
33
|
+
!element ||
|
|
34
|
+
!window ||
|
|
35
|
+
typeof window == undefined ||
|
|
36
|
+
!document ||
|
|
37
|
+
typeof document == undefined ||
|
|
38
|
+
!element.contains(document.activeElement)
|
|
39
|
+
) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getCaretPosition(element) {
|
|
47
|
+
if (!canProceed(element)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const selection = window.getSelection();
|
|
52
|
+
|
|
53
|
+
if (selection.rangeCount === 0) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const range = selection.getRangeAt(0);
|
|
58
|
+
const clonedRange = range.cloneRange();
|
|
59
|
+
clonedRange.selectNodeContents(element);
|
|
60
|
+
clonedRange.setEnd(range.startContainer, range.startOffset);
|
|
61
|
+
return clonedRange.toString().length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function setCaretPosition(element, position) {
|
|
65
|
+
if (!canProceed(element)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const range = createRange(element, position);
|
|
70
|
+
const selection = window.getSelection();
|
|
71
|
+
selection.removeAllRanges();
|
|
72
|
+
selection.addRange(range);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { getCaretPosition, setCaretPosition };
|