@neatui/vue 4.6.0 → 4.7.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neatui/vue",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "basic component ui",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@fekit/floor": "^3.0.1",
|
|
30
|
-
"@fekit/follow": "^1.
|
|
30
|
+
"@fekit/follow": "^1.8.0",
|
|
31
31
|
"@fekit/itable": "^1.1.6",
|
|
32
32
|
"@fekit/pullload": "^1.3.5",
|
|
33
33
|
"@fekit/scale": "^2.0.1",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
clickTipHide?: boolean;
|
|
57
57
|
mobile?: boolean;
|
|
58
58
|
title?: string;
|
|
59
|
+
zIndex?: string | number | null;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
// 入参
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
clickTipHide: false,
|
|
74
75
|
mobile: true,
|
|
75
76
|
title: '',
|
|
77
|
+
zIndex: undefined,
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
const dom: any = ref(null);
|
|
@@ -214,13 +216,20 @@
|
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
},
|
|
217
|
-
{ hideOnScrollOut: false },
|
|
219
|
+
{ hideOnScrollOut: false, zIndex: props.zIndex },
|
|
218
220
|
);
|
|
219
221
|
} else {
|
|
220
222
|
ex.value = null;
|
|
221
223
|
}
|
|
222
224
|
});
|
|
223
225
|
|
|
226
|
+
watch(
|
|
227
|
+
() => props.zIndex,
|
|
228
|
+
(zIndex) => {
|
|
229
|
+
ex.value?.setOptions({ zIndex });
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
224
233
|
onUnmounted(() => {
|
|
225
234
|
document.removeEventListener('click', cancel, true);
|
|
226
235
|
// 移除触摸事件监听器
|
|
@@ -123,8 +123,9 @@
|
|
|
123
123
|
border-radius: 0.5em 0.5em 0 0;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
|
|
126
|
+
// Keep picker keyframes off the shared am-show-am / am-hide-am names
|
|
127
|
+
// so Layer's am="am" preset is not overwritten.
|
|
128
|
+
@keyframes am-picker-show {
|
|
128
129
|
0% {
|
|
129
130
|
opacity: 0;
|
|
130
131
|
transform: translate3d(0, 100%, 0) scale3d(1, 1, 1);
|
|
@@ -135,8 +136,7 @@
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
@keyframes am-hide-am {
|
|
139
|
+
@keyframes am-picker-hide {
|
|
140
140
|
0% {
|
|
141
141
|
opacity: 1;
|
|
142
142
|
transform: translate3d(0, 0%, 0) scale3d(1, 1, 1);
|
|
@@ -147,39 +147,34 @@
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
transform: translate3d(0, -150%, 0);
|
|
150
|
+
%am-picker-show {
|
|
151
|
+
transform: translate3d(0, 100%, 0);
|
|
153
152
|
opacity: 0;
|
|
154
153
|
transform-style: preserve-3d;
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
%am-hide-am {
|
|
156
|
+
%am-picker-hide {
|
|
159
157
|
transform: translate3d(0, 0, 0) scale3d(1, 1, 1);
|
|
160
|
-
opacity:
|
|
158
|
+
opacity: 1;
|
|
161
159
|
transform-style: preserve-3d;
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
// 传参调用
|
|
165
162
|
@mixin am-view-am($time: 450ms, $delay: 100ms) {
|
|
166
163
|
&.show,
|
|
167
164
|
&[view='2'] {
|
|
168
165
|
& > * {
|
|
169
|
-
@extend %am-show
|
|
170
|
-
animation: am-show
|
|
166
|
+
@extend %am-picker-show;
|
|
167
|
+
animation: am-picker-show $time $delay both;
|
|
171
168
|
}
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
&.hide,
|
|
175
172
|
&[view='1'] {
|
|
176
|
-
opacity: 0;
|
|
177
|
-
transition: ($time + $delay) * (4 * 0.1) ($time + $delay) * (6 * 0.1);
|
|
178
173
|
pointer-events: none;
|
|
179
174
|
|
|
180
175
|
& > * {
|
|
181
|
-
@extend %am-hide
|
|
182
|
-
animation: am-hide
|
|
176
|
+
@extend %am-picker-hide;
|
|
177
|
+
animation: am-picker-hide $time $delay both;
|
|
183
178
|
}
|
|
184
179
|
}
|
|
185
180
|
|
|
@@ -189,36 +184,11 @@
|
|
|
189
184
|
}
|
|
190
185
|
}
|
|
191
186
|
|
|
192
|
-
// 动画时间
|
|
193
187
|
$time: 400ms;
|
|
194
188
|
$delay: 100ms;
|
|
195
189
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
&.show,
|
|
199
|
-
&[view='2'] {
|
|
200
|
-
& > * {
|
|
201
|
-
@extend %am-show-am;
|
|
202
|
-
animation: am-show-am $time $delay both;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
&.hide,
|
|
207
|
-
&[view='1'] {
|
|
208
|
-
opacity: 0;
|
|
209
|
-
transition: ($time + $delay) * 0.4 ($time + $delay) * 0.6;
|
|
210
|
-
pointer-events: none;
|
|
211
|
-
|
|
212
|
-
& > * {
|
|
213
|
-
@extend %am-hide-am;
|
|
214
|
-
animation: am-hide-am $time $delay ease-in-out both;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
&.none,
|
|
219
|
-
&[view='0'] {
|
|
220
|
-
display: none;
|
|
221
|
-
}
|
|
190
|
+
.fekit-picker[am-view='am'] {
|
|
191
|
+
@include am-view-am($time, $delay);
|
|
222
192
|
}
|
|
223
193
|
|
|
224
194
|
@keyframes am-dark-show {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
:ui-form="`${theme} sz:${sz} ${state.error.co} type:textarea ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''}`"
|
|
3
|
+
:ui-form="`${theme} sz:${sz} ${state.error.co} type:textarea ${isAutoHeight ? 'auto-height' : ''} ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''}`"
|
|
4
4
|
@click="emits('click', $event)"
|
|
5
5
|
>
|
|
6
6
|
<div :ui-form-tips="state.error.ontips">{{ state.error.tips }}</div>
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
:placeholder="placeholder"
|
|
22
22
|
:disabled="disabled"
|
|
23
23
|
:readonly="readonly"
|
|
24
|
-
:rows="
|
|
25
|
-
ui-scroll=":y"
|
|
24
|
+
:rows="displayRows"
|
|
25
|
+
:ui-scroll="isAutoHeight && !isOverflowing ? undefined : ':y'"
|
|
26
26
|
dir="auto"
|
|
27
27
|
@focus="emits('focus', $event, state)"
|
|
28
28
|
@input="input($event)"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
</template>
|
|
46
46
|
|
|
47
47
|
<script setup lang="ts">
|
|
48
|
-
import { reactive, watch, ref, nextTick, onMounted } from 'vue';
|
|
48
|
+
import { reactive, watch, ref, nextTick, onMounted, onBeforeUnmount, computed } from 'vue';
|
|
49
49
|
import { isObject, isFunction } from '@fekit/utils';
|
|
50
50
|
|
|
51
51
|
const emits = defineEmits(['update:modelValue', 'click', 'blur', 'focus', 'change', 'input', 'clear']);
|
|
@@ -71,8 +71,10 @@
|
|
|
71
71
|
* prefix 前缀内容
|
|
72
72
|
* suffix 后缀内容
|
|
73
73
|
* interacted 用户是否交互
|
|
74
|
-
* autoHeight
|
|
75
|
-
* rows
|
|
74
|
+
* autoHeight 是否自动调整高度;同时传入 minRows 与 maxRows 时默认开启;单独开启时无限增高
|
|
75
|
+
* rows 初始行数(未开启自动高度,或开启但未同时设 min/max 时)
|
|
76
|
+
* minRows 与 maxRows 同时传入时生效的最小行数
|
|
77
|
+
* maxRows 与 minRows 同时传入时生效的最大行数,超出后出现滚动条
|
|
76
78
|
*/
|
|
77
79
|
|
|
78
80
|
// 类型
|
|
@@ -95,8 +97,10 @@
|
|
|
95
97
|
prefix?: Array<any>;
|
|
96
98
|
suffix?: Array<any>;
|
|
97
99
|
interacted?: boolean | number | null;
|
|
98
|
-
autoHeight?: boolean;
|
|
100
|
+
autoHeight?: boolean | null;
|
|
99
101
|
rows?: number;
|
|
102
|
+
minRows?: number;
|
|
103
|
+
maxRows?: number;
|
|
100
104
|
}
|
|
101
105
|
const props: any = withDefaults(defineProps<Props>(), {
|
|
102
106
|
modelValue: '',
|
|
@@ -117,8 +121,9 @@
|
|
|
117
121
|
prefix: () => [],
|
|
118
122
|
suffix: () => [],
|
|
119
123
|
interacted: null,
|
|
124
|
+
// Boolean 未传时 Vue 会落到 false,无法与「显式关闭」区分
|
|
125
|
+
autoHeight: null,
|
|
120
126
|
// textarea 特有属性默认值
|
|
121
|
-
autoHeight: false,
|
|
122
127
|
rows: 3,
|
|
123
128
|
});
|
|
124
129
|
|
|
@@ -132,18 +137,81 @@
|
|
|
132
137
|
});
|
|
133
138
|
|
|
134
139
|
// textarea 元素引用
|
|
135
|
-
const el
|
|
140
|
+
const el = ref<HTMLTextAreaElement | null>(null);
|
|
141
|
+
const isOverflowing = ref(false);
|
|
142
|
+
|
|
143
|
+
const fRowLimit = (value: unknown, fallback: number) => {
|
|
144
|
+
const n = Number(value);
|
|
145
|
+
return Number.isFinite(n) && n > 0 ? Math.max(1, Math.floor(n)) : fallback;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const hasRowRange = computed(() => props.minRows != null && props.maxRows != null);
|
|
149
|
+
const isAutoHeight = computed(() => (props.autoHeight === false ? false : props.autoHeight === true ? true : hasRowRange.value));
|
|
150
|
+
const resolvedMinRows = computed(() => fRowLimit(props.minRows, 1));
|
|
151
|
+
const resolvedMaxRows = computed(() => Math.max(resolvedMinRows.value, fRowLimit(props.maxRows, 10)));
|
|
152
|
+
// 有 min/max 时从最小行起算;仅 autoHeight 时保留 rows,并无限增高
|
|
153
|
+
const displayRows = computed(() => (isAutoHeight.value && hasRowRange.value ? resolvedMinRows.value : props.rows));
|
|
154
|
+
|
|
155
|
+
let measurementQueued = false;
|
|
156
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
157
|
+
let observedWidth = 0;
|
|
158
|
+
|
|
159
|
+
const fMeasureHeight = () => {
|
|
160
|
+
measurementQueued = false;
|
|
161
|
+
const node = el.value;
|
|
162
|
+
if (!node) return;
|
|
163
|
+
|
|
164
|
+
if (!isAutoHeight.value) {
|
|
165
|
+
node.style.height = '';
|
|
166
|
+
node.style.overflowY = '';
|
|
167
|
+
isOverflowing.value = false;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 未同时设 min/max:跟内容无限增高,不封顶
|
|
172
|
+
if (!hasRowRange.value) {
|
|
173
|
+
node.style.height = 'auto';
|
|
174
|
+
node.style.overflowY = 'hidden';
|
|
175
|
+
node.style.height = `${node.scrollHeight + 1}px`;
|
|
176
|
+
isOverflowing.value = false;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
node.style.height = '0px';
|
|
181
|
+
node.style.overflowY = 'hidden';
|
|
182
|
+
|
|
183
|
+
const style = getComputedStyle(node);
|
|
184
|
+
let lineHeight = parseFloat(style.lineHeight);
|
|
185
|
+
if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
|
|
186
|
+
lineHeight = (parseFloat(style.fontSize) || 14) * 1.5;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const paddingBlock = (parseFloat(style.paddingTop) || 0) + (parseFloat(style.paddingBottom) || 0);
|
|
190
|
+
const minHeight = resolvedMinRows.value * lineHeight + paddingBlock;
|
|
191
|
+
const maxHeight = resolvedMaxRows.value * lineHeight + paddingBlock;
|
|
192
|
+
const contentHeight = node.scrollHeight;
|
|
193
|
+
const overflowing = contentHeight > maxHeight + 1;
|
|
194
|
+
const measuredHeight = contentHeight <= minHeight + 1 ? minHeight : contentHeight;
|
|
195
|
+
const targetHeight = Math.min(Math.max(measuredHeight, minHeight), maxHeight);
|
|
196
|
+
|
|
197
|
+
node.style.height = `${Math.ceil(targetHeight)}px`;
|
|
198
|
+
node.style.overflowY = overflowing ? 'auto' : 'hidden';
|
|
199
|
+
isOverflowing.value = overflowing;
|
|
200
|
+
};
|
|
136
201
|
|
|
137
|
-
// 自动高度功能
|
|
138
202
|
const fAutoHeight = () => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
el.value.style.height = el.value.scrollHeight + 1 + 'px';
|
|
143
|
-
}
|
|
144
|
-
});
|
|
203
|
+
if (measurementQueued) return;
|
|
204
|
+
measurementQueued = true;
|
|
205
|
+
nextTick(fMeasureHeight);
|
|
145
206
|
};
|
|
146
207
|
|
|
208
|
+
watch(
|
|
209
|
+
[() => isAutoHeight.value, () => resolvedMinRows.value, () => resolvedMaxRows.value, () => props.modelValue, () => props.rows, () => props.sz, () => props.theme],
|
|
210
|
+
() => {
|
|
211
|
+
fAutoHeight();
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
147
215
|
// 外部传入交互标识,主要用于外部没有输入但点击了提交
|
|
148
216
|
watch(
|
|
149
217
|
() => props.interacted,
|
|
@@ -267,7 +335,23 @@
|
|
|
267
335
|
|
|
268
336
|
// 组件挂载后初始化自动高度
|
|
269
337
|
onMounted(() => {
|
|
270
|
-
|
|
338
|
+
const node = el.value;
|
|
339
|
+
if (node && typeof ResizeObserver !== 'undefined') {
|
|
340
|
+
observedWidth = node.getBoundingClientRect().width;
|
|
341
|
+
resizeObserver = new ResizeObserver(() => {
|
|
342
|
+
const width = node.getBoundingClientRect().width;
|
|
343
|
+
if (Math.abs(width - observedWidth) > 0.5) {
|
|
344
|
+
observedWidth = width;
|
|
345
|
+
fAutoHeight();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
resizeObserver.observe(node);
|
|
349
|
+
}
|
|
271
350
|
fAutoHeight();
|
|
272
351
|
});
|
|
352
|
+
|
|
353
|
+
onBeforeUnmount(() => {
|
|
354
|
+
resizeObserver?.disconnect();
|
|
355
|
+
resizeObserver = undefined;
|
|
356
|
+
});
|
|
273
357
|
</script>
|