@neatui/vue 4.6.0 → 4.7.1

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.6.0",
3
+ "version": "4.7.1",
4
4
  "description": "basic component ui",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -27,14 +27,14 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@fekit/floor": "^3.0.1",
30
- "@fekit/follow": "^1.7.0",
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",
34
34
  "@fekit/scrollto": "^3.0.3",
35
35
  "@fekit/toast": "^2.7.2",
36
36
  "@fekit/utils": "^6.0.0",
37
- "@neatui/css": "^4.4.1",
37
+ "@neatui/css": "4.7.1",
38
38
  "@rollup/plugin-commonjs": "^26.0.1",
39
39
  "@rollup/plugin-node-resolve": "^15.2.3",
40
40
  "@vue/compiler-sfc": "^3.4.30",
@@ -46,5 +46,8 @@
46
46
  "sortablejs": "^1.15.6",
47
47
  "swiper": "^11.0.4",
48
48
  "vue-cropper": "^0.6.4"
49
+ },
50
+ "resolutions": {
51
+ "@neatui/css": "4.7.1"
49
52
  }
50
53
  }
@@ -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
- @keyframes am-show-am {
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
- %am-show-am {
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: 0;
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-am;
170
- animation: am-show-am $time $delay both;
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-am;
182
- animation: am-hide-am $time $delay both;
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
- [am-view='am'] {
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="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: any = ref(null);
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
- nextTick(() => {
140
- if (props.autoHeight && el.value) {
141
- el.value.style.height = 'auto';
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>