@neatui/vue 4.5.0 → 4.6.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.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "basic component ui",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -6,11 +6,14 @@
6
6
  v-if="state.view"
7
7
  :am-view="am"
8
8
  :data-key="id"
9
- :style="`${`${dark}` ? 'background-color: rgba(0, 0, 0, ' + dark + ');' : ''}${zIndex ? 'z-index:' + zIndex + ';' : ''}`"
9
+ :data-recess="state.recess || undefined"
10
+ :data-cover-dark="state.coverDark !== '' ? '' : undefined"
11
+ :style="layerStyle"
10
12
  :view="state.view"
11
13
  class="fekit-layer"
12
14
  >
13
15
  <div :ui-flex="`col ${flex}`" :style="wrapStyle" class="fekit-layer-wrap" @animationend="anim" @click="_sys_hide">
16
+ <div v-if="state.coverDark !== ''" class="fekit-layer-cover-dark"></div>
14
17
  <slot :param="_tasks[id]?.params"></slot>
15
18
  </div>
16
19
  </div>
@@ -18,7 +21,7 @@
18
21
  </div>
19
22
  </template>
20
23
  <script lang="ts" setup>
21
- import { onBeforeUnmount, reactive, watch } from 'vue';
24
+ import { computed, nextTick, onBeforeUnmount, reactive, watch } from 'vue';
22
25
  import { env, isFunction } from '@fekit/utils';
23
26
 
24
27
  // 弹层队列
@@ -60,6 +63,21 @@
60
63
  const SCROLL_LOCK_STATE = Symbol.for('@neatui/vue.Layer.scrollLock');
61
64
  const ESCAPE_STACK = Symbol.for('@neatui/vue.Layer.escapeStack');
62
65
  const ESCAPE_LISTENER = Symbol.for('@neatui/vue.Layer.escapeListener');
66
+ const AREA_GROUPS = Symbol.for('@neatui/vue.Layer.areaGroups');
67
+
68
+ type LayerMode = 'cover' | 'cascade' | 'displace' | 'replace' | 'queue';
69
+
70
+ type AreaGroup = {
71
+ dark: {
72
+ afterHide?: () => void;
73
+ el: HTMLElement | null;
74
+ listening: boolean;
75
+ owners: Set<string>;
76
+ view: number;
77
+ };
78
+ queue: Array<{ id: string; kind: 'open' | 'show'; params: any; then: any }>;
79
+ stack: string[];
80
+ };
63
81
 
64
82
  const getScrollLockState = (): ScrollLockState => {
65
83
  if (!_tasks[SCROLL_LOCK_STATE]) {
@@ -119,6 +137,199 @@
119
137
  });
120
138
  };
121
139
 
140
+ const getAreaGroups = (): Record<string, AreaGroup> => {
141
+ if (!_tasks[AREA_GROUPS]) {
142
+ Object.defineProperty(_tasks, AREA_GROUPS, {
143
+ configurable: true,
144
+ value: {},
145
+ });
146
+ }
147
+ return _tasks[AREA_GROUPS];
148
+ };
149
+
150
+ const getAreaGroup = (area: string): AreaGroup => {
151
+ const groups = getAreaGroups();
152
+ if (!groups[area]) {
153
+ groups[area] = {
154
+ dark: {
155
+ el: null,
156
+ listening: false,
157
+ owners: new Set<string>(),
158
+ view: 0,
159
+ },
160
+ queue: [],
161
+ stack: [],
162
+ };
163
+ }
164
+ return groups[area];
165
+ };
166
+
167
+ const resolveDarkOpacity = (value: string | number | undefined) => {
168
+ return `${value ?? ''}` === '' ? '0.75' : `${value}`;
169
+ };
170
+
171
+ const participatesInGroup = (dark: string | number | undefined) => `${dark ?? ''}` !== '0';
172
+ const DEFAULT_COVER_DARK = 0.32;
173
+
174
+ const resolveCoverDark = (mode: LayerMode, hasPrev: boolean, coverDark: unknown) => {
175
+ if (mode !== 'cover' || !hasPrev) return '';
176
+ if (coverDark === false || `${coverDark}` === '0') return '';
177
+ if (coverDark === undefined || coverDark === true || `${coverDark}` === '') return DEFAULT_COVER_DARK;
178
+ return coverDark as string | number;
179
+ };
180
+
181
+ const isBlockingView = (view: number) => view === 1 || view === 2 || view === 3;
182
+
183
+ const occupiesGlobalDark = (task: any) => {
184
+ if (!task?.participates) return false;
185
+ if (task.view === 2 || task.view === 3 || task.view === 4) return true;
186
+ return task.view === 1 && !!task.parkAfterHide;
187
+ };
188
+
189
+ const setGlobalDarkView = (group: AreaGroup, view: number) => {
190
+ group.dark.view = view;
191
+ if (group.dark.el) group.dark.el.setAttribute('view', `${view}`);
192
+ };
193
+
194
+ const ensureGlobalDarkEl = (area: string, group: AreaGroup) => {
195
+ if (!env.client) return null;
196
+ if (group.dark.el?.isConnected) return group.dark.el;
197
+ group.dark.el = null;
198
+ group.dark.listening = false;
199
+ const container = document.querySelector(area);
200
+ if (!container) return null;
201
+ let el = container.querySelector(':scope > .fekit-layer-dark') as HTMLElement | null;
202
+ if (!el) {
203
+ el = document.createElement('div');
204
+ el.className = 'fekit-layer-dark';
205
+ el.setAttribute('view', '0');
206
+ container.insertBefore(el, container.firstChild);
207
+ }
208
+ group.dark.el = el;
209
+ el.addEventListener('animationend', (ev) => {
210
+ if (ev.target !== el) return;
211
+ if (group.dark.view === 2) setGlobalDarkView(group, 3);
212
+ if (group.dark.view === 1) {
213
+ setGlobalDarkView(group, 0);
214
+ const afterHide = group.dark.afterHide;
215
+ group.dark.afterHide = undefined;
216
+ afterHide?.();
217
+ }
218
+ });
219
+ group.dark.listening = true;
220
+ return el;
221
+ };
222
+
223
+ const syncGlobalDarkOpacity = (area: string, group: AreaGroup) => {
224
+ let opacity = '0.75';
225
+ for (let index = group.stack.length - 1; index >= 0; index -= 1) {
226
+ const task = _tasks[group.stack[index]];
227
+ if (occupiesGlobalDark(task)) {
228
+ opacity = resolveDarkOpacity(task.dark);
229
+ break;
230
+ }
231
+ }
232
+ const el = group.dark.owners.size ? ensureGlobalDarkEl(area, group) : group.dark.el;
233
+ if (el) el.style.setProperty('--layer-dark', opacity);
234
+ };
235
+
236
+ const acquireGlobalDark = (area: string, group: AreaGroup, layerId: string, immediate = false) => {
237
+ if (!layerId) return;
238
+ if (group.dark.owners.has(layerId)) {
239
+ syncGlobalDarkOpacity(area, group);
240
+ return;
241
+ }
242
+ group.dark.owners.add(layerId);
243
+ const el = ensureGlobalDarkEl(area, group);
244
+ if (!el) return;
245
+ syncGlobalDarkOpacity(area, group);
246
+ if (group.dark.owners.size > 1) {
247
+ if (group.dark.view === 1) setGlobalDarkView(group, 3);
248
+ else if (group.dark.view === 0) setGlobalDarkView(group, immediate ? 3 : 2);
249
+ return;
250
+ }
251
+ if (immediate) {
252
+ setGlobalDarkView(group, 3);
253
+ return;
254
+ }
255
+ if (group.dark.view === 0) setGlobalDarkView(group, 2);
256
+ else if (group.dark.view === 1) setGlobalDarkView(group, 3);
257
+ };
258
+
259
+ const releaseGlobalDark = (area: string, group: AreaGroup, layerId: string, immediate = false) => {
260
+ if (!layerId || !group.dark.owners.delete(layerId)) {
261
+ syncGlobalDarkOpacity(area, group);
262
+ return;
263
+ }
264
+ syncGlobalDarkOpacity(area, group);
265
+ if (group.dark.owners.size > 0) return;
266
+ if (immediate || !group.dark.el) {
267
+ setGlobalDarkView(group, 0);
268
+ const afterHide = group.dark.afterHide;
269
+ group.dark.afterHide = undefined;
270
+ afterHide?.();
271
+ return;
272
+ }
273
+ if (group.dark.view === 0) return;
274
+ setGlobalDarkView(group, 1);
275
+ };
276
+
277
+ const visibleGroupIds = (group: AreaGroup) =>
278
+ group.stack.filter((layerId) => {
279
+ const view = _tasks[layerId]?.view;
280
+ return view === 2 || view === 3;
281
+ });
282
+
283
+ const groupHasBlocking = (group: AreaGroup, exceptId = '') =>
284
+ group.stack.some((layerId) => layerId !== exceptId && isBlockingView(_tasks[layerId]?.view));
285
+
286
+ const syncStackIndex = (group: AreaGroup) => {
287
+ group.stack.forEach((layerId, index) => {
288
+ _tasks[layerId]?.setStackIndex?.(index);
289
+ });
290
+ };
291
+
292
+ const recomputeRecess = (group: AreaGroup) => {
293
+ const visible = visibleGroupIds(group);
294
+ visible.forEach((layerId, index) => {
295
+ let depth = 0;
296
+ for (let next = index + 1; next < visible.length; next += 1) {
297
+ if (_tasks[visible[next]]?.openMode === 'cascade') depth += 1;
298
+ }
299
+ _tasks[layerId]?.setRecess?.(depth > 2 ? 2 : depth);
300
+ });
301
+ group.stack.forEach((layerId) => {
302
+ if (!visible.includes(layerId)) _tasks[layerId]?.setRecess?.(0);
303
+ });
304
+ };
305
+
306
+ const ensureOnStack = (group: AreaGroup, layerId: string) => {
307
+ const index = group.stack.indexOf(layerId);
308
+ if (index !== -1) group.stack.splice(index, 1);
309
+ group.stack.push(layerId);
310
+ syncStackIndex(group);
311
+ };
312
+
313
+ const removeFromStack = (group: AreaGroup, layerId: string) => {
314
+ const index = group.stack.indexOf(layerId);
315
+ if (index !== -1) group.stack.splice(index, 1);
316
+ syncStackIndex(group);
317
+ };
318
+
319
+ const removeFromQueue = (group: AreaGroup, layerId: string) => {
320
+ group.queue = group.queue.filter((job) => job.id !== layerId);
321
+ };
322
+
323
+ const getVisibleTop = (group: AreaGroup, exceptId = '') => {
324
+ for (let index = group.stack.length - 1; index >= 0; index -= 1) {
325
+ const layerId = group.stack[index];
326
+ if (layerId === exceptId) continue;
327
+ const task = _tasks[layerId];
328
+ if (task && (task.view === 2 || task.view === 3)) return task;
329
+ }
330
+ return undefined;
331
+ };
332
+
122
333
  const hasInlineProperty = (style: CSSStyleDeclaration, property: string) => {
123
334
  for (let index = 0; index < style.length; index += 1) {
124
335
  if (style.item(index) === property) return true;
@@ -307,25 +518,78 @@
307
518
 
308
519
  // 状态管理
309
520
  const state = reactive({
310
- view: 0,
521
+ coverDark: '' as string | number,
522
+ immediate: false,
523
+ openMode: 'cover' as LayerMode,
311
524
  params: {},
525
+ parkAfterHide: false,
526
+ recess: 0,
527
+ stackIndex: 0,
528
+ view: 0,
312
529
  });
313
530
  const scrollLockOwner = {};
314
531
 
532
+ const layerStyle = computed(() => {
533
+ let style = '';
534
+ if (`${props.dark}` !== '') style += `--layer-dark:${props.dark};`;
535
+ if (`${state.coverDark}` !== '') style += `--layer-cover-dark:${state.coverDark};`;
536
+ if (props.zIndex) style += `z-index:${props.zIndex};`;
537
+ else if (state.stackIndex > 0) style += `z-index:${50 + state.stackIndex};`;
538
+ return style;
539
+ });
540
+
541
+ const syncTaskView = (view: number) => {
542
+ if (!_tasks[props.id]) return;
543
+ _tasks[props.id].view = view;
544
+ _tasks[props.id].openMode = state.openMode;
545
+ _tasks[props.id].parkAfterHide = state.parkAfterHide;
546
+ _tasks[props.id].participates = participatesInGroup(props.dark);
547
+ _tasks[props.id].dark = props.dark;
548
+ };
549
+
550
+ const syncOwnDarkOccupancy = () => {
551
+ if (!env.client || !participatesInGroup(props.dark) || !props.id) return;
552
+ const group = getAreaGroup(props.area);
553
+ const immediate = state.immediate;
554
+ if (
555
+ occupiesGlobalDark({
556
+ dark: props.dark,
557
+ parkAfterHide: state.parkAfterHide,
558
+ participates: true,
559
+ view: state.view,
560
+ })
561
+ ) {
562
+ acquireGlobalDark(props.area, group, props.id, immediate);
563
+ } else {
564
+ releaseGlobalDark(props.area, group, props.id, immediate);
565
+ }
566
+ };
567
+
315
568
  watch(
316
569
  () => state.view,
317
570
  (view: any) => {
318
- if (_tasks[props.id]) {
319
- _tasks[props.id].view = view;
320
- }
571
+ syncTaskView(view);
321
572
  if (view === 2 || view === 3) pushEscapeStack(props.id);
322
573
  else removeEscapeStack(props.id);
323
574
  if (view > 0) acquireScrollLock(scrollLockOwner);
324
575
  else releaseScrollLock(scrollLockOwner);
576
+ syncOwnDarkOccupancy();
577
+ state.immediate = false;
578
+ if (participatesInGroup(props.dark) && props.id) {
579
+ nextTick(() => recomputeRecess(getAreaGroup(props.area)));
580
+ }
325
581
  },
326
582
  { deep: true, immediate: true },
327
583
  );
328
584
 
585
+ watch(
586
+ () => props.dark,
587
+ () => {
588
+ syncTaskView(state.view);
589
+ syncOwnDarkOccupancy();
590
+ },
591
+ );
592
+
329
593
  watch(
330
594
  () => props.escClose,
331
595
  (escClose) => {
@@ -350,6 +614,138 @@
350
614
  none: [],
351
615
  };
352
616
 
617
+ const resolveMode = (params: any): LayerMode => {
618
+ const mode = params?.mode;
619
+ if (mode === 'cascade' || mode === 'displace' || mode === 'replace' || mode === 'queue') return mode;
620
+ return 'cover';
621
+ };
622
+
623
+ const bindOpenCallbacks = (params: any = {}, then = () => {}) => {
624
+ if (params?.on?.show) on.show.push(params.on.show);
625
+ if (params?.on?.hide) on.hide.push(params.on.hide);
626
+ on.show.push(then);
627
+ };
628
+
629
+ const runCallbacks = (key: 'show' | 'hide' | 'none') => {
630
+ on[key].forEach((fun: any) => {
631
+ if (isFunction(fun)) fun();
632
+ });
633
+ on[key].length = 0;
634
+ };
635
+
636
+ const flushQueue = (group: AreaGroup) => {
637
+ if (groupHasBlocking(group) || !group.queue.length) return;
638
+ const job = group.queue.shift();
639
+ if (!job) return;
640
+ const task = _tasks[job.id];
641
+ if (!task) {
642
+ flushQueue(group);
643
+ return;
644
+ }
645
+ const playParams = { ...job.params };
646
+ if (playParams.mode === 'queue') delete playParams.mode;
647
+ if (job.kind === 'open') task.open(playParams, job.then);
648
+ else task.show(playParams, job.then);
649
+ };
650
+
651
+ const scheduleFlushQueue = (group: AreaGroup) => {
652
+ const flush = () => flushQueue(group);
653
+ if (group.dark.view === 1) {
654
+ const prev = group.dark.afterHide;
655
+ group.dark.afterHide = () => {
656
+ prev?.();
657
+ flush();
658
+ };
659
+ return;
660
+ }
661
+ nextTick(flush);
662
+ };
663
+
664
+ const onGroupLayerClosed = (layerId: string) => {
665
+ if (!participatesInGroup(props.dark)) return;
666
+ const group = getAreaGroup(props.area);
667
+ removeFromStack(group, layerId);
668
+ const topId = group.stack[group.stack.length - 1];
669
+ if (topId && _tasks[topId]?.view === 4) _tasks[topId].unpark();
670
+ recomputeRecess(group);
671
+ scheduleFlushQueue(group);
672
+ };
673
+
674
+ const closeReplacedLayers = (mode: LayerMode, params: any, immediate: boolean) => {
675
+ if (mode !== 'replace' || !participatesInGroup(props.dark) || !props.id) return;
676
+ const group = getAreaGroup(props.area);
677
+ const prev = getVisibleTop(group, props.id);
678
+ const targets = params?.replace === 'all' ? group.stack.filter((layerId) => layerId !== props.id) : prev ? [prev.id] : [];
679
+ targets.forEach((layerId) => {
680
+ const task = _tasks[layerId];
681
+ if (!task) return;
682
+ if (immediate) task.none();
683
+ else task.hide();
684
+ });
685
+ };
686
+
687
+ const applyIncomingRelation = (mode: LayerMode, params: any, immediate: boolean) => {
688
+ if (!participatesInGroup(props.dark) || !props.id) {
689
+ state.coverDark = '';
690
+ state.recess = 0;
691
+ return;
692
+ }
693
+ const group = getAreaGroup(props.area);
694
+ const prev = getVisibleTop(group, props.id);
695
+ state.coverDark = resolveCoverDark(mode, !!prev, params?.coverDark);
696
+ state.recess = 0;
697
+
698
+ if (mode === 'displace' && prev) prev.park({ immediate });
699
+ ensureOnStack(group, props.id);
700
+ };
701
+
702
+ const syncGroupPresentation = () => {
703
+ if (!participatesInGroup(props.dark) || !props.id) return;
704
+ recomputeRecess(getAreaGroup(props.area));
705
+ };
706
+
707
+ const tryEnqueue = (kind: 'open' | 'show', params: any, then: any) => {
708
+ if (!participatesInGroup(props.dark) || resolveMode(params) !== 'queue' || !props.id) return false;
709
+ const group = getAreaGroup(props.area);
710
+ if (!groupHasBlocking(group, props.id)) return false;
711
+ removeFromQueue(group, props.id);
712
+ group.queue.push({ id: props.id, kind, params, then });
713
+ return true;
714
+ };
715
+
716
+ const setRecess = (depth: number) => {
717
+ state.recess = depth;
718
+ };
719
+
720
+ const setStackIndex = (index: number) => {
721
+ state.stackIndex = index;
722
+ };
723
+
724
+ const park = (options: any = {}) => {
725
+ if (options.immediate) {
726
+ state.parkAfterHide = false;
727
+ state.immediate = true;
728
+ state.view = 4;
729
+ return;
730
+ }
731
+ if (state.view === 2 || state.view === 3) {
732
+ state.parkAfterHide = true;
733
+ syncTaskView(state.view);
734
+ state.view = 1;
735
+ }
736
+ };
737
+
738
+ const unpark = (options: any = {}) => {
739
+ if (state.view !== 4) return;
740
+ state.parkAfterHide = false;
741
+ if (options.immediate) {
742
+ state.immediate = true;
743
+ state.view = 3;
744
+ return;
745
+ }
746
+ state.view = 2;
747
+ };
748
+
353
749
  // 注入参数
354
750
  const init = (params: any = {}) => {
355
751
  _tasks[id].params = params;
@@ -364,68 +760,86 @@
364
760
 
365
761
  // 显示弹层
366
762
  const show = (params: any = {}, then = () => {}) => {
763
+ if (tryEnqueue('show', params, then)) return;
764
+ if (participatesInGroup(props.dark) && props.id) removeFromQueue(getAreaGroup(props.area), props.id);
765
+ bindOpenCallbacks(params, then);
766
+ state.openMode = resolveMode(params);
767
+ applyIncomingRelation(state.openMode, params, false);
768
+ state.parkAfterHide = false;
367
769
  state.view = 2;
368
- if (params?.on?.show) {
369
- on.show.push(params.on.show);
370
- }
371
- if (params?.on?.hide) {
372
- on.hide.push(params.on.hide);
373
- }
374
- on.show.push(then);
770
+ closeReplacedLayers(state.openMode, params, false);
771
+ syncGroupPresentation();
375
772
  };
376
773
 
377
774
  // 隐藏弹层
378
775
  const hide = (_params: any = {}, then = () => {}) => {
379
- if (state.view === 3) {
776
+ if (state.view === 4) {
777
+ on.hide.push(then);
778
+ state.parkAfterHide = false;
779
+ state.immediate = true;
780
+ state.view = 0;
781
+ runCallbacks('hide');
782
+ onGroupLayerClosed(id);
783
+ return;
784
+ }
785
+ if (state.view === 2 || state.view === 3) {
786
+ state.parkAfterHide = false;
787
+ syncTaskView(state.view);
380
788
  state.view = 1;
381
789
  on.hide.push(then);
382
790
  }
383
791
  };
384
792
 
385
793
  // 打开弹层(没有动画过程)
386
- const open = (_params: any = {}, then = () => {}) => {
794
+ const open = (params: any = {}, then = () => {}) => {
795
+ if (tryEnqueue('open', params, then)) return;
796
+ if (participatesInGroup(props.dark) && props.id) removeFromQueue(getAreaGroup(props.area), props.id);
797
+ bindOpenCallbacks(params, then);
798
+ state.openMode = resolveMode(params);
799
+ applyIncomingRelation(state.openMode, params, true);
800
+ state.parkAfterHide = false;
801
+ state.immediate = true;
387
802
  state.view = 3;
388
- on.show.push(then);
803
+ closeReplacedLayers(state.openMode, params, true);
804
+ syncGroupPresentation();
805
+ runCallbacks('show');
389
806
  };
390
807
  // 关闭弹层(没有动画过程)
391
808
  const none = (_params: any = {}, then = () => {}) => {
809
+ const wasOpen = state.view > 0;
810
+ if (participatesInGroup(props.dark) && props.id) removeFromQueue(getAreaGroup(props.area), props.id);
811
+ state.parkAfterHide = false;
812
+ state.immediate = true;
392
813
  state.view = 0;
393
814
  on.none.push(then);
394
815
  // 立即执行回调,因为没有动画
395
- on.none.forEach((fun: any) => {
396
- if (isFunction(fun)) {
397
- fun();
398
- }
399
- });
400
- on.none.length = 0;
816
+ runCallbacks('none');
817
+ if (wasOpen) onGroupLayerClosed(id);
401
818
  };
402
819
 
403
820
  // 点暗层关闭
404
821
  const _sys_hide = (ev: any) => {
405
822
  if (props.darkClickClose && ev.target.className === 'fekit-layer-wrap' && state.view === 3) {
406
- state.view = 1;
823
+ hide();
407
824
  }
408
825
  };
409
826
 
410
827
  // 监听动画
411
- const anim = () => {
828
+ const anim = (ev?: AnimationEvent) => {
829
+ if (ev && ev.target !== ev.currentTarget) return;
412
830
  if (state.view === 1) {
831
+ if (state.parkAfterHide) {
832
+ state.parkAfterHide = false;
833
+ state.view = 4;
834
+ return;
835
+ }
413
836
  state.view = 0;
414
- on.hide.forEach((fun: any) => {
415
- if (isFunction(fun)) {
416
- fun();
417
- }
418
- });
419
- on.hide.length = 0; // 清空回调数组
837
+ runCallbacks('hide');
838
+ onGroupLayerClosed(id);
420
839
  }
421
840
  if (state.view === 2) {
422
841
  state.view = 3;
423
- on.show.forEach((fun: any) => {
424
- if (isFunction(fun)) {
425
- fun();
426
- }
427
- });
428
- on.show.length = 0; // 清空回调数组
842
+ runCallbacks('show');
429
843
  }
430
844
  };
431
845
 
@@ -435,7 +849,25 @@
435
849
  console.error('Layer 有重复 id', id);
436
850
  }
437
851
  if (id) {
438
- _tasks[id] = { init, show, hide, open, none, view: state.view, escClose: props.escClose, darkClickClose: props.darkClickClose };
852
+ _tasks[id] = {
853
+ dark: props.dark,
854
+ darkClickClose: props.darkClickClose,
855
+ escClose: props.escClose,
856
+ hide,
857
+ id,
858
+ init,
859
+ none,
860
+ open,
861
+ openMode: state.openMode,
862
+ park,
863
+ parkAfterHide: state.parkAfterHide,
864
+ participates: participatesInGroup(props.dark),
865
+ setRecess,
866
+ setStackIndex,
867
+ show,
868
+ unpark,
869
+ view: state.view,
870
+ };
439
871
  ensureEscapeListener();
440
872
  } else {
441
873
  console.error('Layer 必须填写全局统一的 id');
@@ -443,6 +875,17 @@
443
875
 
444
876
  // 卸载弹层
445
877
  onBeforeUnmount(() => {
878
+ if (participatesInGroup(props.dark) && id) {
879
+ const group = getAreaGroup(props.area);
880
+ removeFromQueue(group, id);
881
+ if (state.view > 0) {
882
+ state.immediate = true;
883
+ state.view = 0;
884
+ onGroupLayerClosed(id);
885
+ } else {
886
+ removeFromStack(group, id);
887
+ }
888
+ }
446
889
  removeEscapeStack(id);
447
890
  releaseScrollLock(scrollLockOwner);
448
891
  delete _tasks[id];
@@ -453,24 +896,122 @@
453
896
  @use '@neatui/css' as neatui;
454
897
 
455
898
  // 弹层
899
+ @keyframes am-layer-dark-show {
900
+ 0% {
901
+ opacity: 0;
902
+ }
903
+ 100% {
904
+ opacity: 1;
905
+ }
906
+ }
907
+
908
+ @keyframes am-layer-dark-hide {
909
+ 0% {
910
+ opacity: 1;
911
+ }
912
+ 100% {
913
+ opacity: 0;
914
+ }
915
+ }
916
+
917
+ .fekit-layer-dark {
918
+ --layer-dark: 0.75;
919
+ position: fixed;
920
+ inset: 0;
921
+ z-index: 50;
922
+ pointer-events: none;
923
+ background-color: rgba(0, 0, 0, var(--layer-dark));
924
+ opacity: 0;
925
+ transition: background-color 180ms linear;
926
+
927
+ &[view='2'] {
928
+ animation: am-layer-dark-show 320ms cubic-bezier(0.4, 0, 0.2, 1) both;
929
+ }
930
+
931
+ &[view='3'] {
932
+ opacity: 1;
933
+ }
934
+
935
+ &[view='1'] {
936
+ animation: am-layer-dark-hide 260ms cubic-bezier(0.4, 0, 0.2, 1) both;
937
+ }
938
+
939
+ &[view='0'] {
940
+ opacity: 0;
941
+ }
942
+ }
943
+
456
944
  .fekit-layer {
945
+ --layer-dark: 0.75;
946
+ --layer-cover-dark: 0.32;
457
947
  position: fixed;
458
948
  width: 100%;
459
949
  height: 100%;
460
950
  left: 0;
461
951
  top: 0;
462
952
  z-index: 50;
463
- background-color: rgba(0, 0, 0, 0.75);
953
+ background-color: transparent;
464
954
  pointer-events: none;
465
955
 
956
+ .fekit-layer-cover-dark {
957
+ position: absolute;
958
+ inset: 0;
959
+ z-index: 0;
960
+ background-color: rgba(0, 0, 0, var(--layer-cover-dark));
961
+ opacity: 0;
962
+ pointer-events: none;
963
+ }
964
+
965
+ &[view='2'] .fekit-layer-cover-dark {
966
+ animation: am-layer-dark-show 320ms cubic-bezier(0.4, 0, 0.2, 1) both;
967
+ }
968
+
969
+ &[view='3'] .fekit-layer-cover-dark {
970
+ opacity: 1;
971
+ }
972
+
973
+ &[view='1'] .fekit-layer-cover-dark {
974
+ animation: am-layer-dark-hide 260ms cubic-bezier(0.4, 0, 0.2, 1) both;
975
+ }
976
+
977
+ &[view='4'] {
978
+ pointer-events: none;
979
+
980
+ .fekit-layer-wrap {
981
+ pointer-events: none;
982
+
983
+ & > * {
984
+ opacity: 0;
985
+ visibility: hidden;
986
+ }
987
+ }
988
+ }
989
+
990
+ &[view='3'][data-recess='1'] .fekit-layer-wrap > *:not(.fekit-layer-cover-dark) {
991
+ transform: scale(0.94);
992
+ opacity: 0.88;
993
+ transition:
994
+ transform 280ms cubic-bezier(0.4, 0, 0.2, 1),
995
+ opacity 280ms cubic-bezier(0.4, 0, 0.2, 1);
996
+ }
997
+
998
+ &[view='3'][data-recess='2'] .fekit-layer-wrap > *:not(.fekit-layer-cover-dark) {
999
+ transform: scale(0.88);
1000
+ opacity: 0.76;
1001
+ transition:
1002
+ transform 280ms cubic-bezier(0.4, 0, 0.2, 1),
1003
+ opacity 280ms cubic-bezier(0.4, 0, 0.2, 1);
1004
+ }
1005
+
466
1006
  &-wrap {
467
1007
  position: relative;
468
1008
  width: 100%;
469
1009
  height: 100%;
470
1010
  pointer-events: auto;
471
1011
 
472
- & > * {
1012
+ & > *:not(.fekit-layer-cover-dark) {
473
1013
  position: relative !important;
1014
+ z-index: 1;
474
1015
 
475
1016
  &.com-dialog {
476
1017
  max-width: 80%;