@neatui/vue 5.2.1 → 5.3.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": "5.2.1",
3
+ "version": "5.3.0",
4
4
  "description": "basic component ui",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "@fekit/scrollto": "^3.0.3",
37
37
  "@fekit/toast": "^2.7.2",
38
38
  "@fekit/utils": "^6.0.0",
39
- "@neatui/css": "^5.2.1",
39
+ "@neatui/css": "^5.3.0",
40
40
  "@rollup/plugin-commonjs": "^26.0.1",
41
41
  "@rollup/plugin-node-resolve": "^15.2.3",
42
42
  "@vue/compiler-sfc": "^3.4.30",
@@ -67,6 +67,8 @@
67
67
  mobile?: boolean;
68
68
  title?: string;
69
69
  zIndex?: string | number | null;
70
+ /** 桌面端是否放入浏览器 top layer(Popover API),盖住整页任意层级;移动端底部面板始终放入。 */
71
+ topLayer?: boolean;
70
72
  }
71
73
 
72
74
  // 入参
@@ -87,6 +89,7 @@
87
89
  mobile: true,
88
90
  title: '',
89
91
  zIndex: undefined,
92
+ topLayer: true,
90
93
  });
91
94
 
92
95
  const dom: any = ref(null);
@@ -168,12 +171,14 @@
168
171
  },
169
172
  );
170
173
 
171
- // 移动端底部面板放入浏览器 top layer(Popover API),无需比较 z-index 即可盖住整页;
172
- // 桌面端不加 popover 属性,仍按 Follow 的祖先层级定位。不支持的浏览器退回普通 fixed。
174
+ // 弹层放入浏览器 top layer(Popover API),无需比较 z-index 即可盖住整页:
175
+ // 移动端底部面板始终放入;桌面端由 topLayer 控制(默认开启),关闭时按 Follow 的祖先层级定位。
176
+ // 不支持的浏览器退回普通 fixed。
173
177
  const SHEET_LEAVE_MS = 500;
174
178
  let sheetTimer: ReturnType<typeof setTimeout> | undefined;
175
179
  const supportsPopover = () => typeof HTMLElement !== 'undefined' && 'showPopover' in HTMLElement.prototype;
176
180
  const isSheet = () => props.mobile && window.matchMedia('(max-width: 799px)').matches;
181
+ const useTopLayer = () => props.topLayer || isSheet();
177
182
  const releaseSheet = () => {
178
183
  clearTimeout(sheetTimer);
179
184
  const el = tip.value;
@@ -183,6 +188,38 @@
183
188
  } catch {}
184
189
  el.removeAttribute('popover');
185
190
  };
191
+ const openSheet = () => {
192
+ const el = tip.value;
193
+ if (!el || !supportsPopover()) return;
194
+ clearTimeout(sheetTimer);
195
+ // 不需要 top layer 时主动清理(如关闭动画期间禁用 topLayer 后快速重开)。
196
+ if (!useTopLayer()) return releaseSheet();
197
+ el.setAttribute('popover', 'manual');
198
+ try {
199
+ // 已打开时先关再开,使其移到 top layer 最上方。
200
+ if (el.matches(':popover-open')) el.hidePopover();
201
+ el.showPopover();
202
+ } catch {
203
+ el.removeAttribute('popover');
204
+ }
205
+ };
206
+ // 打开状态下跨越断点(如桌面端拖窄窗口)时,同步进入/离开 top layer。
207
+ let sheetMql: MediaQueryList | null = null;
208
+ const onSheetMediaChange = () => {
209
+ if (!state.show) return;
210
+ if (!useTopLayer()) return releaseSheet();
211
+ // 已在 top layer 时保持原顺序,避免父面板被重排到子面板之上。
212
+ if (!tip.value?.matches(':popover-open')) openSheet();
213
+ };
214
+ onMounted(() => {
215
+ sheetMql = window.matchMedia('(max-width: 799px)');
216
+ sheetMql.addEventListener('change', onSheetMediaChange);
217
+ });
218
+ onUnmounted(() => {
219
+ sheetMql?.removeEventListener('change', onSheetMediaChange);
220
+ });
221
+ // 展开期间动态切换 topLayer 时同步。
222
+ watch(() => props.topLayer, onSheetMediaChange);
186
223
  watch(
187
224
  () => state.show,
188
225
  (show) => {
@@ -190,15 +227,7 @@
190
227
  if (!el || !supportsPopover()) return;
191
228
  clearTimeout(sheetTimer);
192
229
  if (show) {
193
- if (!isSheet()) return;
194
- el.setAttribute('popover', 'manual');
195
- try {
196
- // 已打开时先关再开,使其移到 top layer 最上方。
197
- if (el.matches(':popover-open')) el.hidePopover();
198
- el.showPopover();
199
- } catch {
200
- el.removeAttribute('popover');
201
- }
230
+ openSheet();
202
231
  } else if (el.hasAttribute('popover')) {
203
232
  // 等滑出动画结束再离开 top layer。
204
233
  sheetTimer = setTimeout(() => {
@@ -378,8 +407,11 @@
378
407
  },
379
408
  { hideOnScrollOut: false, zIndex: props.zIndex },
380
409
  );
381
- // 初始即显示(如 modelValue=1)时 state.show 不会再变化,需要立即启动定位。
382
- if (state.show) ex.value.listen();
410
+ // 初始即显示(如 modelValue=1)时 state.show 不会再变化,需要立即启动定位并同步 top layer。
411
+ if (state.show) {
412
+ ex.value.listen();
413
+ openSheet();
414
+ }
383
415
  } else {
384
416
  ex.value = null;
385
417
  }
@@ -433,8 +465,11 @@
433
465
  </script>
434
466
  <style lang="scss">
435
467
  @use '@neatui/css/query' as neatui;
436
- // 抵消 [popover] 的 UA 默认样式;放在媒体查询外,面板打开时切到桌面宽度也不会走样。
437
- .fekit-follow-mobile[popover] {
468
+ // 抵消 [popover] 的 UA 默认样式(桌面与移动端共用);放在媒体查询外,跨断点时不会走样。
469
+ [ui-tips-view][popover] {
470
+ inset: auto;
471
+ width: auto;
472
+ height: auto;
438
473
  margin: 0;
439
474
  padding: 0;
440
475
  border: 0;
@@ -4,7 +4,7 @@
4
4
  ref="ifollow"
5
5
  :tipBoxStyle="`${state.flatOpts.length && state.flatOpts[0]?.length > 0 ? 'min-width: 10em;' : 'min-width: 100%'}`"
6
6
  pos="bl"
7
- tipBoxClass="ny-sm nx-no"
7
+ tipBoxClass="ny-sm nx-ss"
8
8
  >
9
9
  <Input v-model="state.selectd.label" :clearable="false" :disabled="disabled" readonly v-bind="bindProps">
10
10
  <template #suffix><i ui-form-select=""></i></template>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select ${sz ? 'sz:' + sz : ''}`">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%">
4
4
  <slot>
5
5
  <Input
6
6
  v-bind="{ placeholder, rules, theme, sz, co, block, ready: true, type: 'text', tips, disabled, readonly, clearable: false, verify, prefix, suffix, interacted }"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : 'hide'">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" v-model="state.show" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" v-model="state.show" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
4
4
  <Input
5
5
  v-bind="{ placeholder, rules, theme, sz, co, block, ready, type: 'text', tips, disabled, readonly: true, clearable: false, verify, prefix, suffix, interacted }"
6
6
  :modelValue="state.label"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select ${sz ? 'sz:' + sz : ''}`" :ui-form-open="state.show ? 'show' : ''" ref="dom">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%">
4
4
  <slot>
5
5
  <Input v-bind="$props" :clearable="false" readonly v-model="state.selectd">
6
6
  <template #suffix><i ui-form-select=""></i></template>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : ''" ref="dom">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%">
4
4
  <Input v-bind="$props" :clearable="false" readonly v-model="state.selectd.label" :interacted="interacted">
5
5
  <template #suffix><i ui-form-select=""></i></template>
6
6
  </Input>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : ''">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
4
4
  <Input v-bind="$props" :clearable="false" readonly v-model="state.selectd" @click="toggle" :interacted="interacted">
5
5
  <template #suffix><i ui-form-select=""></i></template>
6
6
  </Input>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : ''">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
4
4
  <Input
5
5
  v-bind="{ placeholder, rules, theme, sz, co, block, ready, type, tips, disabled, readonly: true, clearable: false, verify, prefix, suffix, interacted }"
6
6
  v-model="state.selectd.label"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : ''">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
4
4
  <Input
5
5
  v-bind="{ placeholder, rules, theme, sz, co, block, ready, type, tips, disabled, readonly: true, clearable: false, verify, prefix, suffix, interacted }"
6
6
  v-model="state.displayLabel"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :ui-form="`${theme} type:select sz:${sz}`" :ui-form-open="state.show ? 'show' : 'hide'">
3
- <IFollowView :ui-tips="uiTips" ref="ifollow" v-model="state.show" tipBoxClass="ny-sm nx-no" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
3
+ <IFollowView :ui-tips="uiTips" ref="ifollow" v-model="state.show" tipBoxClass="ny-sm nx-ss" tipBoxStyle="min-width: 100%" @onshow="onshow" @onhide="onhide">
4
4
  <Input
5
5
  v-bind="{ placeholder, rules, theme, sz, co, block, ready, type, tips, disabled, readonly: true, clearable: false, verify, prefix, suffix, interacted }"
6
6
  v-model="state.label"