@greypan/web-ui 4.0.0 → 5.0.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/README.CN.md +248 -139
- package/README.md +255 -139
- package/dist/assets/glass.js +1 -1
- package/dist/assets/glass.js.map +1 -1
- package/dist/assets/menu-portal.js +1 -1
- package/dist/assets/menu-portal.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +8 -0
- package/dist/components/autocomplete/index.js +108 -50
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js +1 -1
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/back-top/index.js +1 -1
- package/dist/components/back-top/index.js.map +1 -1
- package/dist/components/back-top/style.js +1 -1
- package/dist/components/back-top/style.js.map +1 -1
- package/dist/components/button/index.js +33 -32
- package/dist/components/button/index.js.map +1 -1
- package/dist/components/button/style.js +1 -1
- package/dist/components/button/style.js.map +1 -1
- package/dist/components/button-group/index.js +14 -12
- package/dist/components/button-group/index.js.map +1 -1
- package/dist/components/button-group/style.js +1 -1
- package/dist/components/button-group/style.js.map +1 -1
- package/dist/components/checkbox/index.js +23 -16
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox/style.js +1 -1
- package/dist/components/checkbox/style.js.map +1 -1
- package/dist/components/collapse/index.d.ts +66 -0
- package/dist/components/collapse/index.js +159 -0
- package/dist/components/collapse/index.js.map +1 -0
- package/dist/components/collapse/style.js +6 -0
- package/dist/components/collapse/style.js.map +1 -0
- package/dist/components/context-menu/index.d.ts +29 -0
- package/dist/components/context-menu/index.js +167 -51
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/dialog/index.d.ts +6 -0
- package/dist/components/dialog/index.js +28 -7
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +1 -1
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.d.ts +32 -2
- package/dist/components/drawer/index.js +229 -39
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/style.js +1 -1
- package/dist/components/drawer/style.js.map +1 -1
- package/dist/components/dropdown/index.d.ts +2 -0
- package/dist/components/dropdown/index.js +23 -13
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown-item/style.js +1 -1
- package/dist/components/dropdown-item/style.js.map +1 -1
- package/dist/components/empty/index.d.ts +1 -0
- package/dist/components/empty/index.js +23 -21
- package/dist/components/empty/index.js.map +1 -1
- package/dist/components/empty/style.js +1 -1
- package/dist/components/empty/style.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +28 -27
- package/dist/components/input/style.js +1 -1
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/input-number/style.js +1 -1
- package/dist/components/input-number/style.js.map +1 -1
- package/dist/components/layout/index.d.ts +23 -0
- package/dist/components/layout/index.js +134 -30
- package/dist/components/layout/index.js.map +1 -1
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/option/style.js +1 -1
- package/dist/components/option/style.js.map +1 -1
- package/dist/components/popover/index.d.ts +2 -1
- package/dist/components/popover/index.js +8 -4
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/style.js +1 -1
- package/dist/components/popover/style.js.map +1 -1
- package/dist/components/radio/index.js +18 -15
- package/dist/components/radio/index.js.map +1 -1
- package/dist/components/radio/style.js +1 -1
- package/dist/components/radio/style.js.map +1 -1
- package/dist/components/segmented/index.d.ts +8 -1
- package/dist/components/segmented/index.js +87 -21
- package/dist/components/segmented/index.js.map +1 -1
- package/dist/components/segmented/style.js +1 -1
- package/dist/components/segmented/style.js.map +1 -1
- package/dist/components/segmented-trigger/index.js +17 -14
- package/dist/components/segmented-trigger/index.js.map +1 -1
- package/dist/components/segmented-trigger/style.js +1 -1
- package/dist/components/segmented-trigger/style.js.map +1 -1
- package/dist/components/select/index.js +32 -28
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/select/style.js +1 -1
- package/dist/components/select/style.js.map +1 -1
- package/dist/components/slider/index.d.ts +3 -2
- package/dist/components/slider/index.js +65 -47
- package/dist/components/slider/index.js.map +1 -1
- package/dist/components/slider/style.js +1 -1
- package/dist/components/slider/style.js.map +1 -1
- package/dist/components/spinner/index.js.map +1 -1
- package/dist/components/switch/index.d.ts +8 -0
- package/dist/components/switch/index.js +72 -25
- package/dist/components/switch/index.js.map +1 -1
- package/dist/components/switch/style.js +1 -1
- package/dist/components/switch/style.js.map +1 -1
- package/dist/components/textarea/index.js +1 -4
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/style.js +1 -1
- package/dist/components/textarea/style.js.map +1 -1
- package/dist/components/theme/style.js +1 -1
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/toast/style.js +1 -1
- package/dist/components/toast/style.js.map +1 -1
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/tooltip/index.js +2 -2
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/style.js +1 -1
- package/dist/components/tooltip/style.js.map +1 -1
- package/dist/icons/generated/heroicons-arrow-down-16-solid.d.ts +6 -0
- package/dist/icons/generated/heroicons-arrow-up-16-solid.d.ts +6 -0
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.d.ts +6 -0
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.js +10 -0
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.js.map +1 -0
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.d.ts +6 -0
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.js +10 -0
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.js.map +1 -0
- package/dist/icons/generated/lucide-code.d.ts +6 -0
- package/dist/icons/generated/lucide-code.js +10 -0
- package/dist/icons/generated/lucide-code.js.map +1 -0
- package/dist/icons/generated/lucide-ellipsis-vertical.d.ts +6 -0
- package/dist/icons/generated/lucide-ellipsis-vertical.js +10 -0
- package/dist/icons/generated/lucide-ellipsis-vertical.js.map +1 -0
- package/dist/icons/generated/lucide-list-filter.d.ts +6 -0
- package/dist/icons/generated/lucide-list-filter.js +10 -0
- package/dist/icons/generated/lucide-list-filter.js.map +1 -0
- package/dist/icons/generated/lucide-list-restart.d.ts +6 -0
- package/dist/icons/generated/lucide-list-restart.js +10 -0
- package/dist/icons/generated/lucide-list-restart.js.map +1 -0
- package/dist/icons/generated/lucide-tag.d.ts +6 -0
- package/dist/icons/generated/lucide-tag.js +10 -0
- package/dist/icons/generated/lucide-tag.js.map +1 -0
- package/dist/icons/generated/tabler-sort-ascending-letters.d.ts +6 -0
- package/dist/icons/generated/tabler-sort-ascending-letters.js +10 -0
- package/dist/icons/generated/tabler-sort-ascending-letters.js.map +1 -0
- package/dist/icons/index.d.ts +8 -0
- package/dist/icons/index.js +18 -10
- package/dist/shared/gesture/drag-gesture.js +133 -0
- package/dist/shared/gesture/drag-gesture.js.map +1 -0
- package/dist/shared/gesture/physics.js +45 -0
- package/dist/shared/gesture/physics.js.map +1 -0
- package/dist/shared/group-management/index.js +49 -47
- package/dist/shared/group-management/index.js.map +1 -1
- package/dist/shared/menu-portal/menu-portal.js +22 -10
- package/dist/shared/menu-portal/menu-portal.js.map +1 -1
- package/dist/shared/menu-portal/menu-tree.js +4 -2
- package/dist/shared/menu-portal/menu-tree.js.map +1 -1
- package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
- package/dist/shared/overlay/nested-drawer-layers.js +55 -0
- package/dist/shared/overlay/nested-drawer-layers.js.map +1 -0
- package/dist/shared/overlay/portal.js +44 -13
- package/dist/shared/overlay/portal.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
function e(e, t) {
|
|
2
|
+
let n = typeof PointerEvent < "u" && e instanceof PointerEvent, r = n ? e.currentTarget || e.target : e, { axis: i = "both", threshold: a = 0, onStart: o, onMove: s, onEnd: c, onTap: l, onCancel: u } = t, d = null, f = 0, p = 0, m = a <= 0, h = [], g = null;
|
|
3
|
+
function _() {
|
|
4
|
+
if (h.length < 2) return {
|
|
5
|
+
vx: 0,
|
|
6
|
+
vy: 0,
|
|
7
|
+
v: 0
|
|
8
|
+
};
|
|
9
|
+
let e = h[0], t = h[h.length - 1], n = t.t - e.t;
|
|
10
|
+
if (n < 8) return {
|
|
11
|
+
vx: 0,
|
|
12
|
+
vy: 0,
|
|
13
|
+
v: 0
|
|
14
|
+
};
|
|
15
|
+
let r = (t.x - e.x) / n * 1e3, a = (t.y - e.y) / n * 1e3, o = 0;
|
|
16
|
+
return o = i === "x" ? r : i === "y" ? a : Math.hypot(r, a), {
|
|
17
|
+
vx: r,
|
|
18
|
+
vy: a,
|
|
19
|
+
v: o
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function v() {
|
|
23
|
+
window.removeEventListener("pointermove", w, !0), window.removeEventListener("pointerup", T, !0), window.removeEventListener("pointercancel", E, !0), window.removeEventListener("lostpointercapture", E, !0);
|
|
24
|
+
}
|
|
25
|
+
function y() {
|
|
26
|
+
window.addEventListener("pointermove", w, !0), window.addEventListener("pointerup", T, !0), window.addEventListener("pointercancel", E, !0), window.addEventListener("lostpointercapture", E, !0);
|
|
27
|
+
}
|
|
28
|
+
function b() {
|
|
29
|
+
r && (r.removeEventListener("pointermove", w), r.removeEventListener("pointerup", T), r.removeEventListener("pointercancel", E), r.removeEventListener("lostpointercapture", E));
|
|
30
|
+
}
|
|
31
|
+
function x() {
|
|
32
|
+
r && (r.addEventListener("pointermove", w), r.addEventListener("pointerup", T), r.addEventListener("pointercancel", E), r.addEventListener("lostpointercapture", E));
|
|
33
|
+
}
|
|
34
|
+
function S() {
|
|
35
|
+
if (d !== null && r && typeof r.hasPointerCapture == "function") try {
|
|
36
|
+
r.hasPointerCapture(d) && r.releasePointerCapture(d);
|
|
37
|
+
} catch {}
|
|
38
|
+
d = null, m = a <= 0, h = [], g = null, v(), b();
|
|
39
|
+
}
|
|
40
|
+
function C(e) {
|
|
41
|
+
if (d !== null || e.pointerType === "touch" && e.isPrimary === !1) return;
|
|
42
|
+
let t = {
|
|
43
|
+
pointerId: e.pointerId,
|
|
44
|
+
clientX: e.clientX,
|
|
45
|
+
clientY: e.clientY,
|
|
46
|
+
timeStamp: e.timeStamp
|
|
47
|
+
};
|
|
48
|
+
if (o?.(t, e) !== !1 && (d = e.pointerId, f = e.clientX, p = e.clientY, m = a <= 0, h = [{
|
|
49
|
+
t: e.timeStamp,
|
|
50
|
+
x: e.clientX,
|
|
51
|
+
y: e.clientY
|
|
52
|
+
}], g = null, y(), x(), m)) try {
|
|
53
|
+
r?.setPointerCapture?.(e.pointerId);
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
function w(e) {
|
|
57
|
+
if (d !== e.pointerId || g === e) return;
|
|
58
|
+
g = e;
|
|
59
|
+
let t = e.clientX - f, n = e.clientY - p;
|
|
60
|
+
if (!m) {
|
|
61
|
+
if ((i === "x" ? Math.abs(t) : i === "y" ? Math.abs(n) : Math.hypot(t, n)) < a) return;
|
|
62
|
+
if (i === "x" && Math.abs(n) > Math.abs(t)) {
|
|
63
|
+
D();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (i === "y" && Math.abs(t) > Math.abs(n)) {
|
|
67
|
+
D();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
m = !0;
|
|
71
|
+
try {
|
|
72
|
+
r?.setPointerCapture?.(e.pointerId);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
e.cancelable && e.preventDefault();
|
|
76
|
+
let o = e.timeStamp;
|
|
77
|
+
for (h.push({
|
|
78
|
+
t: o,
|
|
79
|
+
x: e.clientX,
|
|
80
|
+
y: e.clientY
|
|
81
|
+
}); h.length > 2 && o - h[0].t > 100;) h.shift();
|
|
82
|
+
let { vx: c, vy: l, v: u } = _(), v = {
|
|
83
|
+
pointerId: e.pointerId,
|
|
84
|
+
clientX: e.clientX,
|
|
85
|
+
clientY: e.clientY,
|
|
86
|
+
deltaX: t,
|
|
87
|
+
deltaY: n,
|
|
88
|
+
velocityX: c,
|
|
89
|
+
velocityY: l,
|
|
90
|
+
velocity: u,
|
|
91
|
+
timeStamp: o
|
|
92
|
+
};
|
|
93
|
+
s?.(v, e);
|
|
94
|
+
}
|
|
95
|
+
function T(e) {
|
|
96
|
+
if (d !== e.pointerId) return;
|
|
97
|
+
let t = e.timeStamp;
|
|
98
|
+
for (h.push({
|
|
99
|
+
t,
|
|
100
|
+
x: e.clientX,
|
|
101
|
+
y: e.clientY
|
|
102
|
+
}); h.length > 2 && t - h[0].t > 100;) h.shift();
|
|
103
|
+
let n = e.clientX - f, r = e.clientY - p, { vx: i, vy: a, v: o } = _(), s = {
|
|
104
|
+
pointerId: e.pointerId,
|
|
105
|
+
clientX: e.clientX,
|
|
106
|
+
clientY: e.clientY,
|
|
107
|
+
deltaX: n,
|
|
108
|
+
deltaY: r,
|
|
109
|
+
velocityX: i,
|
|
110
|
+
velocityY: a,
|
|
111
|
+
velocity: o,
|
|
112
|
+
timeStamp: e.timeStamp
|
|
113
|
+
}, g = m;
|
|
114
|
+
S(), g ? c?.(s, e) : (l?.(s, e), u?.(e));
|
|
115
|
+
}
|
|
116
|
+
function E(e) {
|
|
117
|
+
d === e.pointerId && (S(), u?.(e));
|
|
118
|
+
}
|
|
119
|
+
function D() {
|
|
120
|
+
d !== null && (S(), u?.());
|
|
121
|
+
}
|
|
122
|
+
return n ? C(e) : r && (r.addEventListener("pointerdown", C), r.addEventListener("pointermove", w), r.addEventListener("pointerup", T), r.addEventListener("pointercancel", E)), {
|
|
123
|
+
isDragging: () => d !== null,
|
|
124
|
+
cancel: D,
|
|
125
|
+
destroy: () => {
|
|
126
|
+
D(), !n && r && (r.removeEventListener("pointerdown", C), r.removeEventListener("pointermove", w), r.removeEventListener("pointerup", T), r.removeEventListener("pointercancel", E));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { e as attachDragGesture };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=drag-gesture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-gesture.js","names":[],"sources":["../../../src/shared/gesture/drag-gesture.ts"],"sourcesContent":["/**\n * 跨平台/跨浏览器 Pointer 拖拽手势生命周期管理器\n *\n * 封装:\n * 1. setPointerCapture 安全捕获与 window 兜底监听(防 pointer 丢失/离开视口悬挂)\n * 2. 多点触控副指针与未聚焦指针过滤\n * 3. 意图死区(threshold)与滑动轴向判定\n * 4. 100ms 滑动窗口平滑速度估算(Velocity px/s)\n * 5. Window / Handle 跨层事件去重(防重复采样)\n */\n\nconst VELOCITY_WINDOW_MS = 100\n\nexport interface DragSample {\n t: number\n x: number\n y: number\n}\n\nexport interface DragStartInfo {\n pointerId: number\n clientX: number\n clientY: number\n timeStamp: number\n}\n\nexport interface DragMoveInfo {\n pointerId: number\n clientX: number\n clientY: number\n deltaX: number\n deltaY: number\n velocityX: number\n velocityY: number\n velocity: number // 沿配置主轴(若为 both 则为欧氏速度)\n timeStamp: number\n}\n\nexport interface DragEndInfo {\n pointerId: number\n clientX: number\n clientY: number\n deltaX: number\n deltaY: number\n velocityX: number\n velocityY: number\n velocity: number\n timeStamp: number\n}\n\nexport interface DragGestureOptions {\n /** 允许拖拽的轴向,默认 'both' */\n axis?: 'x' | 'y' | 'both'\n /** 拖拽启动的意图死区阈值(像素)。默认 0(即时跟手) */\n threshold?: number\n /** 手势启动回调。返回 false 可取消本次拖拽 */\n onStart?: (info: DragStartInfo, event: PointerEvent) => boolean | void\n /** 拖拽移动回调 */\n onMove?: (info: DragMoveInfo, event: PointerEvent) => void\n /** 正常松手回调 */\n onEnd?: (info: DragEndInfo, event: PointerEvent) => void\n /** 未通过意图阈值的 pointerup 回调,供点击定位类控件提交一次 tap */\n onTap?: (info: DragEndInfo, event: PointerEvent) => void\n /** 手势取消/被打断回调(如 pointercancel、外部强行 cancel) */\n onCancel?: (event?: PointerEvent) => void\n}\n\nexport interface DragGestureHandle {\n /** 当前是否有正在进行的拖拽手势 */\n isDragging: () => boolean\n /** 外部强制取消当前拖拽(恢复初态并触发 onCancel) */\n cancel: () => void\n /** 彻底销毁手势监听器与解绑 DOM */\n destroy: () => void\n}\n\n/**\n * 为指定元素挂载统一的 Pointer 拖拽手势,或在已有的 pointerdown 事件中启动手势\n */\nexport function attachDragGesture(\n targetOrEvent: HTMLElement | PointerEvent,\n options: DragGestureOptions\n): DragGestureHandle {\n const isEvent = typeof PointerEvent !== 'undefined' && targetOrEvent instanceof PointerEvent\n const target: HTMLElement | null = isEvent\n ? (targetOrEvent.currentTarget as HTMLElement | null) || (targetOrEvent.target as HTMLElement | null)\n : (targetOrEvent as HTMLElement)\n\n const { axis = 'both', threshold = 0, onStart, onMove, onEnd, onTap, onCancel } = options\n\n let activePointerId: number | null = null\n let startX = 0\n let startY = 0\n let isThresholdPassed = threshold <= 0\n let samples: DragSample[] = []\n let handledMoveEvent: PointerEvent | null = null\n\n function estimateVelocity(): { vx: number; vy: number; v: number } {\n if (samples.length < 2) return { vx: 0, vy: 0, v: 0 }\n const first = samples[0]\n const last = samples[samples.length - 1]\n const dt = last.t - first.t\n if (dt < 8) return { vx: 0, vy: 0, v: 0 } // 采样跨度不足 8ms 时不可信\n const vx = ((last.x - first.x) / dt) * 1000\n const vy = ((last.y - first.y) / dt) * 1000\n let v = 0\n if (axis === 'x') v = vx\n else if (axis === 'y') v = vy\n else v = Math.hypot(vx, vy)\n return { vx, vy, v }\n }\n\n function detachWindowListeners() {\n window.removeEventListener('pointermove', handlePointerMove, true)\n window.removeEventListener('pointerup', handlePointerUp, true)\n window.removeEventListener('pointercancel', handlePointerCancel, true)\n window.removeEventListener('lostpointercapture', handlePointerCancel, true)\n }\n\n function attachWindowListeners() {\n window.addEventListener('pointermove', handlePointerMove, true)\n window.addEventListener('pointerup', handlePointerUp, true)\n window.addEventListener('pointercancel', handlePointerCancel, true)\n window.addEventListener('lostpointercapture', handlePointerCancel, true)\n }\n\n function detachTargetListeners() {\n if (!target) return\n target.removeEventListener('pointermove', handlePointerMove)\n target.removeEventListener('pointerup', handlePointerUp)\n target.removeEventListener('pointercancel', handlePointerCancel)\n target.removeEventListener('lostpointercapture', handlePointerCancel)\n }\n\n function attachTargetListeners() {\n if (!target) return\n target.addEventListener('pointermove', handlePointerMove)\n target.addEventListener('pointerup', handlePointerUp)\n target.addEventListener('pointercancel', handlePointerCancel)\n target.addEventListener('lostpointercapture', handlePointerCancel)\n }\n\n function resetState() {\n if (activePointerId !== null && target && typeof target.hasPointerCapture === 'function') {\n try {\n if (target.hasPointerCapture(activePointerId)) {\n target.releasePointerCapture(activePointerId)\n }\n } catch {\n // ignore\n }\n }\n activePointerId = null\n isThresholdPassed = threshold <= 0\n samples = []\n handledMoveEvent = null\n detachWindowListeners()\n detachTargetListeners()\n }\n\n function handlePointerDown(e: PointerEvent) {\n if (activePointerId !== null) return\n // 真实触控环境下的多点触控副指针不启动手势(合成事件或鼠标环境缺省时不拦截)\n if (e.pointerType === 'touch' && e.isPrimary === false) return\n\n const startInfo: DragStartInfo = {\n pointerId: e.pointerId,\n clientX: e.clientX,\n clientY: e.clientY,\n timeStamp: e.timeStamp\n }\n\n if (onStart?.(startInfo, e) === false) return\n\n activePointerId = e.pointerId\n startX = e.clientX\n startY = e.clientY\n isThresholdPassed = threshold <= 0\n samples = [{ t: e.timeStamp, x: e.clientX, y: e.clientY }]\n handledMoveEvent = null\n\n attachWindowListeners()\n attachTargetListeners()\n\n // 仅在无意图死区时立即捕获指针;若存在死区,延迟至确认拖拽后捕获,避免干扰子元素原生点击\n if (isThresholdPassed) {\n try {\n target?.setPointerCapture?.(e.pointerId)\n } catch {\n // 忽略合成指针或不可捕获上下文(如测试环境)\n }\n }\n }\n\n function handlePointerMove(e: PointerEvent) {\n if (activePointerId !== e.pointerId) return\n if (handledMoveEvent === e) return\n handledMoveEvent = e\n\n const deltaX = e.clientX - startX\n const deltaY = e.clientY - startY\n\n if (!isThresholdPassed) {\n const distance = axis === 'x' ? Math.abs(deltaX) : axis === 'y' ? Math.abs(deltaY) : Math.hypot(deltaX, deltaY)\n if (distance < threshold) return\n\n // 若限定了 X 轴且 Y 轴位移明显更大,视为滚动意图而非拖拽\n if (axis === 'x' && Math.abs(deltaY) > Math.abs(deltaX)) {\n cancel()\n return\n }\n // 若限定了 Y 轴且 X 轴位移明显更大,放弃拖拽\n if (axis === 'y' && Math.abs(deltaX) > Math.abs(deltaY)) {\n cancel()\n return\n }\n isThresholdPassed = true\n try {\n target?.setPointerCapture?.(e.pointerId)\n } catch {\n // 忽略合成指针\n }\n }\n\n // 确认拖拽后阻止默认滚动/文字选中\n if (e.cancelable) e.preventDefault()\n\n const now = e.timeStamp\n samples.push({ t: now, x: e.clientX, y: e.clientY })\n while (samples.length > 2 && now - samples[0].t > VELOCITY_WINDOW_MS) {\n samples.shift()\n }\n\n const { vx, vy, v } = estimateVelocity()\n\n const moveInfo: DragMoveInfo = {\n pointerId: e.pointerId,\n clientX: e.clientX,\n clientY: e.clientY,\n deltaX,\n deltaY,\n velocityX: vx,\n velocityY: vy,\n velocity: v,\n timeStamp: now\n }\n\n onMove?.(moveInfo, e)\n }\n\n function handlePointerUp(e: PointerEvent) {\n if (activePointerId !== e.pointerId) return\n\n const now = e.timeStamp\n samples.push({ t: now, x: e.clientX, y: e.clientY })\n while (samples.length > 2 && now - samples[0].t > VELOCITY_WINDOW_MS) {\n samples.shift()\n }\n\n const deltaX = e.clientX - startX\n const deltaY = e.clientY - startY\n const { vx, vy, v } = estimateVelocity()\n\n const endInfo: DragEndInfo = {\n pointerId: e.pointerId,\n clientX: e.clientX,\n clientY: e.clientY,\n deltaX,\n deltaY,\n velocityX: vx,\n velocityY: vy,\n velocity: v,\n timeStamp: e.timeStamp\n }\n\n const hadPassedThreshold = isThresholdPassed\n resetState()\n\n if (hadPassedThreshold) {\n onEnd?.(endInfo, e)\n } else {\n onTap?.(endInfo, e)\n onCancel?.(e)\n }\n }\n\n function handlePointerCancel(e: PointerEvent) {\n if (activePointerId !== e.pointerId) return\n resetState()\n onCancel?.(e)\n }\n\n function cancel() {\n if (activePointerId === null) return\n resetState()\n onCancel?.()\n }\n\n if (isEvent) {\n handlePointerDown(targetOrEvent)\n } else if (target) {\n target.addEventListener('pointerdown', handlePointerDown)\n target.addEventListener('pointermove', handlePointerMove)\n target.addEventListener('pointerup', handlePointerUp)\n target.addEventListener('pointercancel', handlePointerCancel)\n }\n\n return {\n isDragging: () => activePointerId !== null,\n cancel,\n destroy: () => {\n cancel()\n if (!isEvent && target) {\n target.removeEventListener('pointerdown', handlePointerDown)\n target.removeEventListener('pointermove', handlePointerMove)\n target.removeEventListener('pointerup', handlePointerUp)\n target.removeEventListener('pointercancel', handlePointerCancel)\n }\n }\n }\n}\n"],"mappings":"AA+EA,SAAgB,EACd,GACA,GACmB;CACnB,IAAM,IAAU,OAAO,eAAiB,OAAe,aAAyB,cAC1E,IAA6B,IAC9B,EAAc,iBAAyC,EAAc,SACrE,GAEC,EAAE,UAAO,QAAQ,eAAY,GAAG,YAAS,WAAQ,UAAO,UAAO,gBAAa,GAE9E,IAAiC,MACjC,IAAS,GACT,IAAS,GACT,IAAoB,KAAa,GACjC,IAAwB,CAAC,GACzB,IAAwC;CAE5C,SAAS,IAA0D;EACjE,IAAI,EAAQ,SAAS,GAAG,OAAO;GAAE,IAAI;GAAG,IAAI;GAAG,GAAG;EAAE;EACpD,IAAM,IAAQ,EAAQ,IAChB,IAAO,EAAQ,EAAQ,SAAS,IAChC,IAAK,EAAK,IAAI,EAAM;EAC1B,IAAI,IAAK,GAAG,OAAO;GAAE,IAAI;GAAG,IAAI;GAAG,GAAG;EAAE;EACxC,IAAM,KAAO,EAAK,IAAI,EAAM,KAAK,IAAM,KACjC,KAAO,EAAK,IAAI,EAAM,KAAK,IAAM,KACnC,IAAI;EAIR,OAHA,AAEK,IAFD,MAAS,MAAS,IACb,MAAS,MAAS,IAClB,KAAK,MAAM,GAAI,CAAE,GACnB;GAAE;GAAI;GAAI;EAAE;CACrB;CAEA,SAAS,IAAwB;EAI/B,AAHA,OAAO,oBAAoB,eAAe,GAAmB,EAAI,GACjE,OAAO,oBAAoB,aAAa,GAAiB,EAAI,GAC7D,OAAO,oBAAoB,iBAAiB,GAAqB,EAAI,GACrE,OAAO,oBAAoB,sBAAsB,GAAqB,EAAI;CAC5E;CAEA,SAAS,IAAwB;EAI/B,AAHA,OAAO,iBAAiB,eAAe,GAAmB,EAAI,GAC9D,OAAO,iBAAiB,aAAa,GAAiB,EAAI,GAC1D,OAAO,iBAAiB,iBAAiB,GAAqB,EAAI,GAClE,OAAO,iBAAiB,sBAAsB,GAAqB,EAAI;CACzE;CAEA,SAAS,IAAwB;EAC1B,MACL,EAAO,oBAAoB,eAAe,CAAiB,GAC3D,EAAO,oBAAoB,aAAa,CAAe,GACvD,EAAO,oBAAoB,iBAAiB,CAAmB,GAC/D,EAAO,oBAAoB,sBAAsB,CAAmB;CACtE;CAEA,SAAS,IAAwB;EAC1B,MACL,EAAO,iBAAiB,eAAe,CAAiB,GACxD,EAAO,iBAAiB,aAAa,CAAe,GACpD,EAAO,iBAAiB,iBAAiB,CAAmB,GAC5D,EAAO,iBAAiB,sBAAsB,CAAmB;CACnE;CAEA,SAAS,IAAa;EACpB,IAAI,MAAoB,QAAQ,KAAU,OAAO,EAAO,qBAAsB,YAC5E,IAAI;GACF,AAAI,EAAO,kBAAkB,CAAe,KAC1C,EAAO,sBAAsB,CAAe;EAEhD,QAAQ,CAER;EAOF,AALA,IAAkB,MAClB,IAAoB,KAAa,GACjC,IAAU,CAAC,GACX,IAAmB,MACnB,EAAsB,GACtB,EAAsB;CACxB;CAEA,SAAS,EAAkB,GAAiB;EAG1C,IAFI,MAAoB,QAEpB,EAAE,gBAAgB,WAAW,EAAE,cAAc,IAAO;EAExD,IAAM,IAA2B;GAC/B,WAAW,EAAE;GACb,SAAS,EAAE;GACX,SAAS,EAAE;GACX,WAAW,EAAE;EACf;EAEI,QAAU,GAAW,CAAC,MAAM,OAEhC,IAAkB,EAAE,WACpB,IAAS,EAAE,SACX,IAAS,EAAE,SACX,IAAoB,KAAa,GACjC,IAAU,CAAC;GAAE,GAAG,EAAE;GAAW,GAAG,EAAE;GAAS,GAAG,EAAE;EAAQ,CAAC,GACzD,IAAmB,MAEnB,EAAsB,GACtB,EAAsB,GAGlB,IACF,IAAI;GACF,GAAQ,oBAAoB,EAAE,SAAS;EACzC,QAAQ,CAER;CAEJ;CAEA,SAAS,EAAkB,GAAiB;EAE1C,IADI,MAAoB,EAAE,aACtB,MAAqB,GAAG;EAC5B,IAAmB;EAEnB,IAAM,IAAS,EAAE,UAAU,GACrB,IAAS,EAAE,UAAU;EAE3B,IAAI,CAAC,GAAmB;GAEtB,KADiB,MAAS,MAAM,KAAK,IAAI,CAAM,IAAI,MAAS,MAAM,KAAK,IAAI,CAAM,IAAI,KAAK,MAAM,GAAQ,CAAM,KAC/F,GAAW;GAG1B,IAAI,MAAS,OAAO,KAAK,IAAI,CAAM,IAAI,KAAK,IAAI,CAAM,GAAG;IACvD,EAAO;IACP;GACF;GAEA,IAAI,MAAS,OAAO,KAAK,IAAI,CAAM,IAAI,KAAK,IAAI,CAAM,GAAG;IACvD,EAAO;IACP;GACF;GACA,IAAoB;GACpB,IAAI;IACF,GAAQ,oBAAoB,EAAE,SAAS;GACzC,QAAQ,CAER;EACF;EAGA,AAAI,EAAE,cAAY,EAAE,eAAe;EAEnC,IAAM,IAAM,EAAE;EAEd,KADA,EAAQ,KAAK;GAAE,GAAG;GAAK,GAAG,EAAE;GAAS,GAAG,EAAE;EAAQ,CAAC,GAC5C,EAAQ,SAAS,KAAK,IAAM,EAAQ,EAAE,CAAC,IAAI,MAChD,EAAQ,MAAM;EAGhB,IAAM,EAAE,OAAI,OAAI,SAAM,EAAiB,GAEjC,IAAyB;GAC7B,WAAW,EAAE;GACb,SAAS,EAAE;GACX,SAAS,EAAE;GACX;GACA;GACA,WAAW;GACX,WAAW;GACX,UAAU;GACV,WAAW;EACb;EAEA,IAAS,GAAU,CAAC;CACtB;CAEA,SAAS,EAAgB,GAAiB;EACxC,IAAI,MAAoB,EAAE,WAAW;EAErC,IAAM,IAAM,EAAE;EAEd,KADA,EAAQ,KAAK;GAAK;GAAK,GAAG,EAAE;GAAS,GAAG,EAAE;EAAQ,CAAC,GAC5C,EAAQ,SAAS,KAAK,IAAM,EAAQ,EAAE,CAAC,IAAI,MAChD,EAAQ,MAAM;EAGhB,IAAM,IAAS,EAAE,UAAU,GACrB,IAAS,EAAE,UAAU,GACrB,EAAE,OAAI,OAAI,SAAM,EAAiB,GAEjC,IAAuB;GAC3B,WAAW,EAAE;GACb,SAAS,EAAE;GACX,SAAS,EAAE;GACX;GACA;GACA,WAAW;GACX,WAAW;GACX,UAAU;GACV,WAAW,EAAE;EACf,GAEM,IAAqB;EAG3B,AAFA,EAAW,GAEP,IACF,IAAQ,GAAS,CAAC,KAElB,IAAQ,GAAS,CAAC,GAClB,IAAW,CAAC;CAEhB;CAEA,SAAS,EAAoB,GAAiB;EACxC,MAAoB,EAAE,cAC1B,EAAW,GACX,IAAW,CAAC;CACd;CAEA,SAAS,IAAS;EACZ,MAAoB,SACxB,EAAW,GACX,IAAW;CACb;CAWA,OATI,IACF,EAAkB,CAAa,IACtB,MACT,EAAO,iBAAiB,eAAe,CAAiB,GACxD,EAAO,iBAAiB,eAAe,CAAiB,GACxD,EAAO,iBAAiB,aAAa,CAAe,GACpD,EAAO,iBAAiB,iBAAiB,CAAmB,IAGvD;EACL,kBAAkB,MAAoB;EACtC;EACA,eAAe;GAEb,AADA,EAAO,GACH,CAAC,KAAW,MACd,EAAO,oBAAoB,eAAe,CAAiB,GAC3D,EAAO,oBAAoB,eAAe,CAAiB,GAC3D,EAAO,oBAAoB,aAAa,CAAe,GACvD,EAAO,oBAAoB,iBAAiB,CAAmB;EAEnE;CACF;AACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/shared/gesture/physics.ts
|
|
2
|
+
var e = {
|
|
3
|
+
close: {
|
|
4
|
+
stiffness: 260,
|
|
5
|
+
damping: 34,
|
|
6
|
+
maxSamples: 19
|
|
7
|
+
},
|
|
8
|
+
rebound: {
|
|
9
|
+
stiffness: 220,
|
|
10
|
+
damping: 22,
|
|
11
|
+
maxSamples: 29
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function t(e, t, n) {
|
|
15
|
+
return Math.min(n, Math.max(t, e));
|
|
16
|
+
}
|
|
17
|
+
function n(e, t) {
|
|
18
|
+
if (t.length === 0) return -1;
|
|
19
|
+
let n = Infinity, r = 0;
|
|
20
|
+
for (let i = 0; i < t.length; i++) {
|
|
21
|
+
let a = Math.abs(e - t[i]);
|
|
22
|
+
a < n && (n = a, r = i);
|
|
23
|
+
}
|
|
24
|
+
return r;
|
|
25
|
+
}
|
|
26
|
+
function r(e, n, r) {
|
|
27
|
+
return r <= n ? 0 : (t(e, n, r) - n) / (r - n);
|
|
28
|
+
}
|
|
29
|
+
function i(e, t, n = .15) {
|
|
30
|
+
if (e >= 0) return e;
|
|
31
|
+
let r = e * n;
|
|
32
|
+
return Math.max(r, -t);
|
|
33
|
+
}
|
|
34
|
+
function a(e, t, n, r, i = 16) {
|
|
35
|
+
let a = i / 2e3, o = e, s = n, c = [o];
|
|
36
|
+
for (let e = 0; e < r.maxSamples * 2; e++) {
|
|
37
|
+
let n = -r.stiffness * (o - t) - r.damping * s;
|
|
38
|
+
if (s += n * a, o += s * a, e % 2 == 1 && c.push(o), Math.abs(o - t) < .5 && Math.abs(s) < 40) break;
|
|
39
|
+
}
|
|
40
|
+
return c.push(t), c;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { e as SPRING_PRESETS, t as clamp, r as normalizeProgress, i as rubberband, n as snapToNearest, a as springOffsets };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=physics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics.js","names":[],"sources":["../../../src/shared/gesture/physics.ts"],"sourcesContent":["/**\n * 物理运动与数学辅助函数(阻尼、钳制与半隐式欧拉弹簧轨迹生成)\n */\n\nexport interface SpringParams {\n stiffness: number\n damping: number\n maxSamples: number\n}\n\nexport const SPRING_PRESETS = {\n close: { stiffness: 260, damping: 34, maxSamples: 19 },\n rebound: { stiffness: 220, damping: 22, maxSamples: 29 }\n} as const satisfies Record<string, SpringParams>\n\n/**\n * 钳制数值在 [min, max] 范围内\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/**\n * 寻找与目标值距离最近的采样点索引。\n * @param value 目标数值\n * @param points 采样点数组\n * @returns 最近采样点的下标(空数组返回 -1)\n */\nexport function snapToNearest(value: number, points: number[]): number {\n if (points.length === 0) return -1\n let minDistance = Infinity\n let nearestIndex = 0\n for (let i = 0; i < points.length; i++) {\n const dist = Math.abs(value - points[i])\n if (dist < minDistance) {\n minDistance = dist\n nearestIndex = i\n }\n }\n return nearestIndex\n}\n\n/**\n * 将数值钳制并归一化到 [0, 1] 区间(0 对应 min,1 对应 max)。\n * @param value 输入值\n * @param min 最小值\n * @param max 最大值\n */\nexport function normalizeProgress(value: number, min: number, max: number): number {\n if (max <= min) return 0\n const clamped = clamp(value, min, max)\n return (clamped - min) / (max - min)\n}\n\n/**\n * 阻尼橡皮筋函数:超出边界部分施加衰减阻尼\n * @param offset 当前位移(正数正常,负数为拉伸超出边界)\n * @param maxDistance 最大拉伸上限(绝对值)\n * @param factor 线性衰减因子,默认 0.15\n */\nexport function rubberband(offset: number, maxDistance: number, factor = 0.15): number {\n if (offset >= 0) return offset\n const resisted = offset * factor\n return Math.max(resisted, -maxDistance)\n}\n\n/**\n * 半隐式欧拉积分弹簧轨迹,返回均匀时间间隔的位置采样(末尾附加精确终点)。\n * @param from 起始位置\n * @param to 目标位置\n * @param velocity 初始速度(px/s)\n * @param spring 弹簧刚度与阻尼参数\n * @param sampleMs 采样时间间隔(毫秒,默认 16ms 对应 60fps)\n */\nexport function springOffsets(\n from: number,\n to: number,\n velocity: number,\n spring: SpringParams,\n sampleMs = 16\n): number[] {\n const dt = sampleMs / 2000\n let x = from\n let v = velocity\n const samples = [x]\n for (let i = 0; i < spring.maxSamples * 2; i++) {\n const acceleration = -spring.stiffness * (x - to) - spring.damping * v\n v += acceleration * dt\n x += v * dt\n if (i % 2 === 1) samples.push(x)\n if (Math.abs(x - to) < 0.5 && Math.abs(v) < 40) break\n }\n samples.push(to)\n return samples\n}\n"],"mappings":";AAUA,IAAa,IAAiB;CAC5B,OAAO;EAAE,WAAW;EAAK,SAAS;EAAI,YAAY;CAAG;CACrD,SAAS;EAAE,WAAW;EAAK,SAAS;EAAI,YAAY;CAAG;AACzD;AAKA,SAAgB,EAAM,GAAe,GAAa,GAAqB;CACrE,OAAO,KAAK,IAAI,GAAK,KAAK,IAAI,GAAK,CAAK,CAAC;AAC3C;AAQA,SAAgB,EAAc,GAAe,GAA0B;CACrE,IAAI,EAAO,WAAW,GAAG,OAAO;CAChC,IAAI,IAAc,UACd,IAAe;CACnB,KAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK;EACtC,IAAM,IAAO,KAAK,IAAI,IAAQ,EAAO,EAAE;EACvC,AAAI,IAAO,MACT,IAAc,GACd,IAAe;CAEnB;CACA,OAAO;AACT;AAQA,SAAgB,EAAkB,GAAe,GAAa,GAAqB;CAGjF,OAFI,KAAO,IAAY,KACP,EAAM,GAAO,GAAK,CAC1B,IAAU,MAAQ,IAAM;AAClC;AAQA,SAAgB,EAAW,GAAgB,GAAqB,IAAS,KAAc;CACrF,IAAI,KAAU,GAAG,OAAO;CACxB,IAAM,IAAW,IAAS;CAC1B,OAAO,KAAK,IAAI,GAAU,CAAC,CAAW;AACxC;AAUA,SAAgB,EACd,GACA,GACA,GACA,GACA,IAAW,IACD;CACV,IAAM,IAAK,IAAW,KAClB,IAAI,GACJ,IAAI,GACF,IAAU,CAAC,CAAC;CAClB,KAAK,IAAI,IAAI,GAAG,IAAI,EAAO,aAAa,GAAG,KAAK;EAC9C,IAAM,IAAe,CAAC,EAAO,aAAa,IAAI,KAAM,EAAO,UAAU;EAIrE,IAHA,KAAK,IAAe,GACpB,KAAK,IAAI,GACL,IAAI,KAAM,KAAG,EAAQ,KAAK,CAAC,GAC3B,KAAK,IAAI,IAAI,CAAE,IAAI,MAAO,KAAK,IAAI,CAAC,IAAI,IAAI;CAClD;CAEA,OADA,EAAQ,KAAK,CAAE,GACR;AACT"}
|
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
import { definePlugin as e } from "@greypan/js-kit";
|
|
2
|
+
import { ContextConsumer as t, ContextProvider as n, createContext as r } from "@lit/context";
|
|
2
3
|
//#region src/shared/group-management/index.ts
|
|
3
|
-
var
|
|
4
|
-
function r
|
|
5
|
-
return { [n]: !0 };
|
|
6
|
-
}
|
|
7
|
-
function i(t, n) {
|
|
4
|
+
var i = r(Symbol("web-ui.selection-group-context")), a = r(Symbol("web-ui.button-group-context")), o = i, s = a, c = o, l = s;
|
|
5
|
+
function u(n, r) {
|
|
8
6
|
return e(() => {
|
|
9
|
-
let e,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
let e, i = r.equals ?? Object.is, a = new t(n, {
|
|
8
|
+
context: r.context,
|
|
9
|
+
subscribe: !0,
|
|
10
|
+
callback: (t) => {
|
|
11
|
+
let a = t?.get(n);
|
|
12
|
+
i(e, a) || (e = a, r.requestUpdate());
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return n.addController({ hostDisconnected() {
|
|
16
|
+
i(e, void 0) || (e = void 0, r.requestUpdate());
|
|
17
|
+
} }), {
|
|
18
|
+
getContext() {
|
|
19
|
+
return e;
|
|
20
|
+
},
|
|
21
|
+
isSubscribed() {
|
|
22
|
+
return a.value !== void 0;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
15
25
|
});
|
|
16
26
|
}
|
|
17
|
-
function
|
|
18
|
-
e
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
function d(t) {
|
|
28
|
+
return e(() => {
|
|
29
|
+
let e = new n(t.host, {
|
|
30
|
+
context: t.context,
|
|
31
|
+
initialValue: /* @__PURE__ */ new Map()
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
sync() {
|
|
35
|
+
let n = t.getItems(), r = /* @__PURE__ */ new Map();
|
|
36
|
+
return n.forEach((e, i) => r.set(e, t.getContext(e, i, n))), e.setValue(r), !0;
|
|
37
|
+
},
|
|
38
|
+
disconnect() {
|
|
39
|
+
e.setValue(/* @__PURE__ */ new Map());
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
});
|
|
25
43
|
}
|
|
26
|
-
var
|
|
44
|
+
var f = class {
|
|
27
45
|
constructor(e, t, n = {}) {
|
|
28
46
|
this.host = e, this.lifecycle = t, this.options = n, this.connected = !1, this.observer = null, this.observedSlots = /* @__PURE__ */ new Set(), this.handleChange = (e) => {
|
|
29
47
|
this.lifecycle.handleChange?.(e);
|
|
@@ -63,20 +81,21 @@ var s = class {
|
|
|
63
81
|
for (let e of this.observedSlots) e.removeEventListener("slotchange", this.handleSlotChange);
|
|
64
82
|
this.observedSlots.clear();
|
|
65
83
|
}
|
|
66
|
-
};
|
|
67
|
-
function
|
|
84
|
+
}, p = { disabled: !1 }, m = { disabled: !0 };
|
|
85
|
+
function h(t) {
|
|
68
86
|
return e(() => {
|
|
69
|
-
let e =
|
|
87
|
+
let e = new n(t.host, {
|
|
88
|
+
context: o,
|
|
89
|
+
initialValue: /* @__PURE__ */ new Map()
|
|
90
|
+
}), r, i, a = /* @__PURE__ */ new Set(), s = !1;
|
|
70
91
|
return {
|
|
71
92
|
sync: () => {
|
|
72
|
-
let
|
|
73
|
-
for (let
|
|
74
|
-
|
|
75
|
-
return n = r, i = t.copyValue(d), o = f, s = !0, p;
|
|
93
|
+
let n = t.getItems(), o = t.getValue(), c = t.getDisabled(), l = n.length !== a.size || [...n].some((e) => !a.has(e)), u = !s || !t.valuesEqual(o, r) || c !== i || l;
|
|
94
|
+
if (u) for (let e of n) t.setItemSelected(e, t.isItemSelected(e, o));
|
|
95
|
+
return e.setValue(new Map(n.map((e) => [e, c ? m : p]))), a = new Set(n), r = t.copyValue(o), i = c, s = !0, u;
|
|
76
96
|
},
|
|
77
97
|
disconnect() {
|
|
78
|
-
|
|
79
|
-
n.clear(), s = !1, i = void 0, o = void 0;
|
|
98
|
+
e.setValue(/* @__PURE__ */ new Map()), a.clear(), r = void 0, i = void 0, s = !1;
|
|
80
99
|
},
|
|
81
100
|
handleChange(e) {
|
|
82
101
|
if (t.getDisabled() || !t.isItem(e.target) || !t.host.contains(e.target)) return;
|
|
@@ -86,24 +105,7 @@ function c(t) {
|
|
|
86
105
|
};
|
|
87
106
|
});
|
|
88
107
|
}
|
|
89
|
-
var l = { disabled: !1 }, u = { disabled: !0 };
|
|
90
|
-
function d(t) {
|
|
91
|
-
return e(() => {
|
|
92
|
-
let e = r(), n = /* @__PURE__ */ new Set(), i = [];
|
|
93
|
-
return {
|
|
94
|
-
sync() {
|
|
95
|
-
let r = t.getItems(), o = new Set(r), s = r.length !== i.length || r.some((e, t) => e !== i[t]);
|
|
96
|
-
for (let t of n) o.has(t) || a(t, e, void 0);
|
|
97
|
-
return r.forEach((n, i) => a(n, e, t.getContext(n, i, r))), n = o, i = r, s;
|
|
98
|
-
},
|
|
99
|
-
disconnect() {
|
|
100
|
-
for (let t of n) a(t, e, void 0);
|
|
101
|
-
n.clear(), i = [];
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
108
|
//#endregion
|
|
107
|
-
export {
|
|
109
|
+
export { f as GroupController, l as buttonGroupContextKey, h as defineGroupCoordinator, u as defineGroupManaged, d as defineGroupPresentation, c as selectionGroupContextKey };
|
|
108
110
|
|
|
109
111
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/group-management/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\nconst groupManagedItem = Symbol('web-ui.group-managed-item')\n\n// 组实例的唯一身份。同一子项可能先后属于多个组(如跨组移动),共享通道必须\n// 按 owner 归属写入,防止旧组晚到的清理覆写新组刚安装的 context。\nconst groupOwnerToken = Symbol('web-ui.group-owner-token')\n\ntype GroupOwner = Record<typeof groupOwnerToken, true>\n\ntype GroupManagedItem<Context> = HTMLElement & {\n [groupManagedItem]?: (owner: GroupOwner, context: Context | undefined) => void\n}\n\ninterface GroupControllerHost extends HTMLElement, ReactiveControllerHost {\n readonly renderRoot: Element | DocumentFragment\n}\n\nexport interface SelectionGroupContext {\n readonly disabled: boolean\n}\n\nexport interface ButtonGroupContext {\n readonly direction: 'horizontal' | 'vertical'\n readonly isLast: boolean\n}\n\n/**\n * 创建受管理 group 的子项侧能力:提供只读 context 视图,并注册由 group owner\n * 推送的接收通道。在 `make()` 时完成注册,组件无需再编写 constructor 样板。\n */\nfunction createGroupOwner(): GroupOwner {\n return { [groupOwnerToken]: true }\n}\n\nexport function defineGroupManaged<Context>(\n item: HTMLElement,\n options: {\n requestUpdate: () => void\n equals?: (a: Context | undefined, b: Context | undefined) => boolean\n }\n) {\n return definePlugin(() => {\n let context: Context | undefined\n const equals = options.equals ?? Object.is\n\n const setContext = (next: Context | undefined) => {\n if (equals(context, next)) return\n context = next\n options.requestUpdate()\n }\n\n registerGroupManagedItem(item, setContext)\n\n return {\n getContext(): Context | undefined {\n return context\n }\n }\n })\n}\n\nfunction installGroupContext<Context>(item: HTMLElement, owner: GroupOwner, context: Context | undefined) {\n ;(item as GroupManagedItem<Context>)[groupManagedItem]?.(owner, context)\n}\n\nfunction registerGroupManagedItem<Context>(item: HTMLElement, setContext: (context: Context | undefined) => void) {\n let currentOwner: GroupOwner | undefined\n ;(item as GroupManagedItem<Context>)[groupManagedItem] = (owner, context) => {\n // 安装总是被接受并转移归属(安装方来自实时 DOM 查询,身份可信);\n // 清理只在写入者仍是当前归属者时执行——跨组移动后旧组晚到的\n // 移除同步不得覆写新组刚安装的 context。\n if (context === undefined && currentOwner !== owner) return\n currentOwner = context === undefined ? undefined : owner\n setContext(context)\n }\n}\n\nexport interface GroupLifecycle {\n sync(): boolean\n disconnect(): void\n handleChange?(event: Event): void\n}\n\ninterface GroupControllerOptions {\n afterSync?: () => void\n}\n\n/**\n * 把组生命周期插件接入 Lit,协调规则不落在组件生命周期方法里。\n */\nexport class GroupController implements ReactiveController {\n private connected = false\n private observer: MutationObserver | null = null\n private observedSlots = new Set<HTMLSlotElement>()\n constructor(\n private readonly host: GroupControllerHost,\n private readonly lifecycle: GroupLifecycle,\n private readonly options: GroupControllerOptions = {}\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n this.host.addEventListener('change', this.handleChange, true)\n this.observeHost()\n this.observeSlots()\n this.sync()\n }\n\n hostUpdated() {\n this.observeSlots()\n this.sync()\n }\n\n hostDisconnected() {\n this.connected = false\n this.host.removeEventListener('change', this.handleChange, true)\n this.disconnectSlots()\n this.disconnectHostObserver()\n this.lifecycle.disconnect()\n }\n\n private readonly handleChange = (event: Event) => {\n this.lifecycle.handleChange?.(event)\n }\n\n private readonly handleSlotChange = () => {\n if (!this.connected) return\n this.sync()\n }\n\n private readonly handleMutations = () => {\n if (!this.connected) return\n this.sync()\n }\n\n sync() {\n if (!this.lifecycle.sync()) return\n this.options.afterSync?.()\n }\n\n private observeHost() {\n this.disconnectHostObserver()\n // 监听 light DOM 的增删,覆盖 Vue v-if / React && 的 comment 锚点替换与深层包裹\n this.observer = new MutationObserver(this.handleMutations)\n this.observer.observe(this.host, { childList: true, subtree: true })\n }\n\n private disconnectHostObserver() {\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n private observeSlots() {\n const root = this.host.renderRoot as Element\n const slots = root.querySelectorAll<HTMLSlotElement>('slot')\n for (const slot of slots) {\n if (!this.observedSlots.has(slot)) {\n slot.addEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.add(slot)\n }\n }\n // 清理已移除的 slot(热更新等场景)\n for (const slot of this.observedSlots) {\n if (!root.contains(slot)) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.delete(slot)\n }\n }\n }\n\n private disconnectSlots() {\n for (const slot of this.observedSlots) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n }\n this.observedSlots.clear()\n }\n}\n\nexport interface GroupCoordinatorOptions<Item extends HTMLElement, Value> {\n readonly host: HTMLElement\n readonly getItems: () => readonly Item[]\n readonly getValue: () => Value\n readonly setValue: (value: Value) => void\n readonly getDisabled: () => boolean\n readonly isItem: (target: EventTarget | null) => target is Item\n readonly isItemSelected: (item: Item, value: Value) => boolean\n readonly getNextValue: (item: Item, value: Value) => Value\n readonly valuesEqual: (a: Value, b: Value) => boolean\n readonly copyValue: (value: Value) => Value\n readonly setItemSelected: (item: Item, selected: boolean) => void\n readonly dispatchValueChange: () => void\n}\n\n/**\n * 在一个内部插件接口后收敛选中态、继承禁用与受管子项成员关系。\n */\nexport function defineGroupCoordinator<Item extends HTMLElement, Value>(options: GroupCoordinatorOptions<Item, Value>) {\n return definePlugin(() => {\n const owner = createGroupOwner()\n let items = new Set<Item>()\n let lastValue: Value | undefined\n let lastDisabled: boolean | undefined\n let initialized = false\n\n const sync = (): boolean => {\n const nextItems = new Set(options.getItems())\n const membershipChanged = nextItems.size !== items.size || [...nextItems].some(item => !items.has(item))\n const value = options.getValue()\n const disabled = options.getDisabled()\n const selectionChanged =\n !initialized ||\n !options.valuesEqual(value, lastValue as Value) ||\n disabled !== lastDisabled ||\n membershipChanged\n\n for (const item of items) {\n if (!nextItems.has(item)) installGroupContext<SelectionGroupContext>(item, owner, undefined)\n }\n\n for (const item of nextItems) {\n installGroupContext<SelectionGroupContext>(\n item,\n owner,\n disabled ? DISABLED_SELECTION_CONTEXT : ENABLED_SELECTION_CONTEXT\n )\n if (selectionChanged) options.setItemSelected(item, options.isItemSelected(item, value))\n }\n\n items = nextItems\n lastValue = options.copyValue(value)\n lastDisabled = disabled\n initialized = true\n return selectionChanged\n }\n\n return {\n sync,\n disconnect() {\n for (const item of items) installGroupContext<SelectionGroupContext>(item, owner, undefined)\n items.clear()\n initialized = false\n lastValue = undefined\n lastDisabled = undefined\n },\n handleChange(event: Event) {\n if (options.getDisabled() || !options.isItem(event.target) || !options.host.contains(event.target)) {\n return\n }\n\n const current = options.getValue()\n const next = options.getNextValue(event.target, current)\n if (options.valuesEqual(current, next)) return\n\n options.setValue(next)\n options.dispatchValueChange()\n }\n }\n })\n}\n\nconst ENABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false }\nconst DISABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: true }\n\nexport interface GroupPresentationOptions<Item extends HTMLElement, Context> {\n readonly getItems: () => readonly Item[]\n readonly getContext: (item: Item, index: number, items: readonly Item[]) => Context\n}\n\n/**\n * 为分组子项注册可逆的展示上下文。不持有公开 DOM 属性,成员变化时清空全部上下文。\n */\nexport function defineGroupPresentation<Item extends HTMLElement, Context>(\n options: GroupPresentationOptions<Item, Context>\n) {\n return definePlugin(() => {\n const owner = createGroupOwner()\n let items = new Set<Item>()\n let previousItems: readonly Item[] = []\n\n return {\n sync(): boolean {\n const nextItems = options.getItems()\n const nextSet = new Set(nextItems)\n const membershipChanged =\n nextItems.length !== previousItems.length || nextItems.some((item, index) => item !== previousItems[index])\n\n for (const item of items) {\n if (!nextSet.has(item)) installGroupContext<Context>(item, owner, undefined)\n }\n\n nextItems.forEach((item, index) => installGroupContext(item, owner, options.getContext(item, index, nextItems)))\n items = nextSet\n previousItems = nextItems\n return membershipChanged\n },\n disconnect() {\n for (const item of items) installGroupContext<Context>(item, owner, undefined)\n items.clear()\n previousItems = []\n }\n }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/group-management/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport { ContextConsumer, ContextProvider, createContext, type Context } from '@lit/context'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\n/*\n * 下行通道基于 @lit/context(ADR-0040):root 上的 ContextProvider 以\n * `ReadonlyMap<HTMLElement, Context>` 为载荷向全子树广播,子项侧\n * ContextConsumer 订阅并按自身元素取条目。context-request 事件 composed,\n * 可穿过中间 shadow 边界;custom element 升级顺序保证祖先先于后代升级,\n * provider 总是先于子项请求就绪。成员追踪(querySelectorAll +\n * MutationObserver/slotchange)、点击归因与 setItemSelected 上行写回\n * 不在下行通道内,仍由 GroupController 驱动。\n */\n\nexport interface SelectionGroupContext {\n readonly disabled: boolean\n}\n\nexport interface ButtonGroupContext {\n readonly direction: 'horizontal' | 'vertical'\n readonly isLast: boolean\n}\n\n// 载荷类型仅编译期品牌化;按载荷形状分两个 key,避免异构树串扰。\n// createContext 泛型顺序为 <ValueType, K>。\nconst SELECTION_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, SelectionGroupContext>, symbol>(\n Symbol('web-ui.selection-group-context')\n)\n\nconst BUTTON_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, ButtonGroupContext>, symbol>(\n Symbol('web-ui.button-group-context')\n)\n\n// 两个 key 的运行时形状一致(Map 载荷),泛型插件内部统一按宽松类型驱动。\ntype AnyGroupContext = Context<symbol, ReadonlyMap<HTMLElement, unknown>>\n\nconst SELECTION_GROUP_CONTEXT_LOOSE = SELECTION_GROUP_CONTEXT as unknown as AnyGroupContext\nconst BUTTON_GROUP_CONTEXT_LOOSE = BUTTON_GROUP_CONTEXT as unknown as AnyGroupContext\n\nexport const selectionGroupContextKey: AnyGroupContext = SELECTION_GROUP_CONTEXT_LOOSE\nexport const buttonGroupContextKey: AnyGroupContext = BUTTON_GROUP_CONTEXT_LOOSE\n\ninterface GroupControllerHost extends HTMLElement, ReactiveControllerHost {\n readonly renderRoot: Element | DocumentFragment\n}\n\n/**\n * 创建受管理 group 的子项侧能力:订阅最近 provider 的 context 载荷并提取\n * 自身条目。在 `make()` 时完成注册,组件无需再编写 constructor 样板。\n */\nexport function defineGroupManaged<Context>(\n item: HTMLElement & ReactiveControllerHost,\n options: {\n /** 与所属根组件使用的 context key 一致。 */\n context: AnyGroupContext\n requestUpdate: () => void\n equals?: (a: Context | undefined, b: Context | undefined) => boolean\n }\n) {\n return definePlugin(() => {\n let context: Context | undefined\n const equals = options.equals ?? Object.is\n\n const consumer = new ContextConsumer(item, {\n context: options.context,\n subscribe: true,\n callback: payload => {\n const next = (payload as ReadonlyMap<HTMLElement, Context> | undefined)?.get(item)\n if (equals(context, next)) return\n context = next\n options.requestUpdate()\n }\n })\n\n /*\n * 离开 group(被移出 provider 子树)时 ContextConsumer 只是静默退订,\n * 不会收到「条目已移除」的通知:显式清空上下文,恢复独立控件语义\n * (如 checkbox 恢复自身表单提交)。跨组移动先断连清空、再重连订阅新组。\n */\n item.addController({\n hostDisconnected() {\n if (equals(context, undefined)) return\n context = undefined\n options.requestUpdate()\n }\n })\n\n return {\n getContext(): Context | undefined {\n return context\n },\n // 供测试或特殊场景确认订阅状态。\n isSubscribed(): boolean {\n return consumer.value !== undefined\n }\n }\n })\n}\n\nexport interface GroupPresentationOptions<Item extends HTMLElement, Context> {\n readonly host: HTMLElement\n /** 与子项 defineGroupManaged 使用的 context key 一致。 */\n readonly context: AnyGroupContext\n readonly getItems: () => readonly Item[]\n readonly getContext: (item: Item, index: number, items: readonly Item[]) => Context\n}\n\n/**\n * 为分组子项注册可逆的展示上下文(@lit/context 下行通道)。不持有公开 DOM\n * 属性;sync() 以新 Map 载荷整体广播,成员变化体现为子项条目消失。\n */\nexport function defineGroupPresentation<Item extends HTMLElement, Context>(\n options: GroupPresentationOptions<Item, Context>\n) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: options.context,\n initialValue: new Map<HTMLElement, unknown>()\n })\n\n return {\n sync(): boolean {\n const nextItems = options.getItems()\n const payload = new Map<HTMLElement, unknown>()\n nextItems.forEach((item, index) => payload.set(item, options.getContext(item, index, nextItems)))\n // 新 Map 实例保证 Object.is 判定为变更,全量通知订阅者。\n // 每次 hostUpdated 都重播(即使成员/方向未变)是有意形态:每次新建\n // context 对象,但子项 defineGroupManaged 的 equals 门控 requestUpdate\n // (标量字段比较),重播不产生多余渲染;代价是比「仅成员变化才通知」\n // 更频繁的广播事件,换取零状态追踪的简单性(与 GroupController 的\n // 全量 sync 对齐)。\n provider.setValue(payload)\n return true\n },\n disconnect() {\n provider.setValue(new Map())\n }\n }\n })\n}\n\nexport interface GroupLifecycle {\n sync(): boolean\n disconnect(): void\n handleChange?(event: Event): void\n}\n\ninterface GroupControllerOptions {\n afterSync?: () => void\n}\n\n/**\n * 把组生命周期插件接入 Lit,协调规则不落在组件生命周期方法里。\n */\nexport class GroupController implements ReactiveController {\n private connected = false\n private observer: MutationObserver | null = null\n private observedSlots = new Set<HTMLSlotElement>()\n constructor(\n private readonly host: GroupControllerHost,\n private readonly lifecycle: GroupLifecycle,\n private readonly options: GroupControllerOptions = {}\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n this.host.addEventListener('change', this.handleChange, true)\n this.observeHost()\n this.observeSlots()\n this.sync()\n }\n\n hostUpdated() {\n this.observeSlots()\n this.sync()\n }\n\n hostDisconnected() {\n this.connected = false\n this.host.removeEventListener('change', this.handleChange, true)\n this.disconnectSlots()\n this.disconnectHostObserver()\n this.lifecycle.disconnect()\n }\n\n private readonly handleChange = (event: Event) => {\n this.lifecycle.handleChange?.(event)\n }\n\n private readonly handleSlotChange = () => {\n if (!this.connected) return\n this.sync()\n }\n\n private readonly handleMutations = () => {\n if (!this.connected) return\n this.sync()\n }\n\n sync() {\n if (!this.lifecycle.sync()) return\n this.options.afterSync?.()\n }\n\n private observeHost() {\n this.disconnectHostObserver()\n // 监听 light DOM 的增删,覆盖 Vue v-if / React && 的 comment 锚点替换与深层包裹\n this.observer = new MutationObserver(this.handleMutations)\n this.observer.observe(this.host, { childList: true, subtree: true })\n }\n\n private disconnectHostObserver() {\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n private observeSlots() {\n const root = this.host.renderRoot as Element\n const slots = root.querySelectorAll<HTMLSlotElement>('slot')\n for (const slot of slots) {\n if (!this.observedSlots.has(slot)) {\n slot.addEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.add(slot)\n }\n }\n // 清理已移除的 slot(热更新等场景)\n for (const slot of this.observedSlots) {\n if (!root.contains(slot)) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.delete(slot)\n }\n }\n }\n\n private disconnectSlots() {\n for (const slot of this.observedSlots) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n }\n this.observedSlots.clear()\n }\n}\n\nexport interface GroupCoordinatorOptions<Item extends HTMLElement, Value> {\n readonly host: HTMLElement\n readonly getItems: () => readonly Item[]\n readonly getValue: () => Value\n readonly setValue: (value: Value) => void\n readonly getDisabled: () => boolean\n readonly isItem: (target: EventTarget | null) => target is Item\n readonly isItemSelected: (item: Item, value: Value) => boolean\n readonly getNextValue: (item: Item, value: Value) => Value\n readonly valuesEqual: (a: Value, b: Value) => boolean\n readonly copyValue: (value: Value) => Value\n readonly setItemSelected: (item: Item, selected: boolean) => void\n readonly dispatchValueChange: () => void\n}\n\nconst ENABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false }\nconst DISABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: true }\n\n/**\n * 在一个内部插件接口后收敛选中态、继承禁用与受管子项成员关系。\n * 禁用态经 @lit/context 下行广播;选中态上行写回保持直写。\n */\nexport function defineGroupCoordinator<Item extends HTMLElement, Value>(options: GroupCoordinatorOptions<Item, Value>) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: SELECTION_GROUP_CONTEXT_LOOSE,\n initialValue: new Map<HTMLElement, SelectionGroupContext>()\n })\n\n let lastValue: Value | undefined\n let lastDisabled: boolean | undefined\n let lastItems = new Set<Item>()\n let initialized = false\n\n const sync = (): boolean => {\n const nextItems = options.getItems()\n const value = options.getValue()\n const disabled = options.getDisabled()\n // 成员变化也要重放选中写回:新成员需继承当前选中态。\n const membershipChanged = nextItems.length !== lastItems.size || [...nextItems].some(item => !lastItems.has(item))\n const selectionChanged =\n !initialized ||\n !options.valuesEqual(value, lastValue as Value) ||\n disabled !== lastDisabled ||\n membershipChanged\n\n // 上行选中写回:根写入子项(context 是纯下行,不承担该方向)。\n if (selectionChanged) {\n for (const item of nextItems) {\n options.setItemSelected(item, options.isItemSelected(item, value))\n }\n }\n\n // 禁用下行:每次 sync 以新 Map 载荷整体广播(成员增删与禁用变化一并覆盖,\n // 新 Map 实例保证 Object.is 判定为变更)。\n provider.setValue(\n new Map(\n nextItems.map(item => [\n item as HTMLElement,\n disabled ? DISABLED_SELECTION_CONTEXT : ENABLED_SELECTION_CONTEXT\n ])\n )\n )\n\n lastItems = new Set(nextItems)\n lastValue = options.copyValue(value)\n lastDisabled = disabled\n initialized = true\n return selectionChanged\n }\n\n return {\n sync,\n disconnect() {\n provider.setValue(new Map())\n lastItems.clear()\n lastValue = undefined\n lastDisabled = undefined\n initialized = false\n },\n handleChange(event: Event) {\n if (options.getDisabled() || !options.isItem(event.target) || !options.host.contains(event.target)) {\n return\n }\n\n const current = options.getValue()\n const next = options.getNextValue(event.target, current)\n if (options.valuesEqual(current, next)) return\n\n options.setValue(next)\n options.dispatchValueChange()\n }\n }\n })\n}\n"],"mappings":"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"}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from "../../assets/glass.js";
|
|
2
|
+
import t from "../../assets/overlay-motion.js";
|
|
3
|
+
import n from "../../assets/menu-portal.js";
|
|
4
|
+
import { resolveOverlayContainer as r } from "../overlay/portal.js";
|
|
2
5
|
//#region src/shared/menu-portal/menu-portal.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
var i = `${e}
|
|
7
|
+
${n}
|
|
8
|
+
${t}`;
|
|
9
|
+
function a(e, t) {
|
|
10
|
+
let n = document.createElement("div");
|
|
11
|
+
n.className = `wui-menu-portal-overlay wui-floating-panel wui-glass ${e}`, n.dataset.wuiPresence = "entering";
|
|
8
12
|
let a = document.createElement("div");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
a.className = "wui-menu-scroll";
|
|
14
|
+
let o = document.createElement("div");
|
|
15
|
+
o.className = "wui-menu-content", a.append(o);
|
|
16
|
+
let s = r(void 0, t ?? document.body, { preferRootTheme: t === void 0 });
|
|
17
|
+
if (s instanceof HTMLDialogElement) {
|
|
18
|
+
let e = document.createElement("style");
|
|
19
|
+
e.textContent = i, n.append(e);
|
|
20
|
+
}
|
|
21
|
+
return n.append(a), s.appendChild(n), {
|
|
22
|
+
panel: n,
|
|
23
|
+
content: o
|
|
12
24
|
};
|
|
13
25
|
}
|
|
14
26
|
//#endregion
|
|
15
|
-
export {
|
|
27
|
+
export { a as createMenuPortalOverlay };
|
|
16
28
|
|
|
17
29
|
//# sourceMappingURL=menu-portal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-portal.js","names":[],"sources":["../../../src/shared/menu-portal/menu-portal.ts"],"sourcesContent":["import { resolveOverlayContainer } from '@/shared/overlay/portal'\n\nexport interface MenuPortalOverlay {\n readonly panel: HTMLElement\n readonly content: HTMLElement\n}\n\n/**\n * 菜单浮层面板:面板直接挂到 overlay 容器(与普通 Overlay Portal 共用容器解析),\n * 样式复用容器内已注入的共享菜单样式(glass/menu-portal/overlay-motion),\n * 因此无需自建 shadow。菜单内容由 dropdown/context-menu 自行构建(menu-tree),\n * 这里只提供 panel 与 content 挂载点。\n */\nexport function createMenuPortalOverlay(className: string, target?: Element): MenuPortalOverlay {\n const panel = document.createElement('div')\n panel.className = `wui-menu-portal-overlay wui-floating-panel wui-glass ${className}`\n panel.dataset.wuiPresence = 'entering'\n const scroll = document.createElement('div')\n scroll.className = 'wui-menu-scroll'\n const content = document.createElement('div')\n content.className = 'wui-menu-content'\n scroll.append(content)\n
|
|
1
|
+
{"version":3,"file":"menu-portal.js","names":[],"sources":["../../../src/shared/menu-portal/menu-portal.ts"],"sourcesContent":["import glass from '@/assets/glass.css?inline'\nimport menuPortalStyle from '@/assets/menu-portal.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { resolveOverlayContainer } from '@/shared/overlay/portal'\n\nexport interface MenuPortalOverlay {\n readonly panel: HTMLElement\n readonly content: HTMLElement\n}\n\n// 菜单面板所需的共享样式。常规 overlay 容器已预注入这些样式;但当 overlay 因\n// target 位于已打开原生 dialog 内而被挂到该 dialog(top layer)时,dialog 的\n// shadow root 并不包含这些样式。故面板自携带一份,保证在任何容器下都能正确渲染。\nconst MENU_PANEL_STYLES = `${glass}\n${menuPortalStyle}\n${overlayMotion}`\n\n/**\n * 菜单浮层面板:面板直接挂到 overlay 容器(与普通 Overlay Portal 共用容器解析),\n * 样式复用容器内已注入的共享菜单样式(glass/menu-portal/overlay-motion),\n * 因此无需自建 shadow。菜单内容由 dropdown/context-menu 自行构建(menu-tree),\n * 这里只提供 panel 与 content 挂载点。\n */\nexport function createMenuPortalOverlay(className: string, target?: Element): MenuPortalOverlay {\n const panel = document.createElement('div')\n panel.className = `wui-menu-portal-overlay wui-floating-panel wui-glass ${className}`\n panel.dataset.wuiPresence = 'entering'\n const scroll = document.createElement('div')\n scroll.className = 'wui-menu-scroll'\n const content = document.createElement('div')\n content.className = 'wui-menu-content'\n scroll.append(content)\n\n const container = resolveOverlayContainer(undefined, target ?? document.body, {\n preferRootTheme: target === undefined\n })\n // 常规 overlay root 已预注入共享样式;只有 dialog 没有,因此按容器条件注入。\n if (container instanceof HTMLDialogElement) {\n const style = document.createElement('style')\n style.textContent = MENU_PANEL_STYLES\n panel.append(style)\n }\n panel.append(scroll)\n container.appendChild(panel)\n\n return { panel, content }\n}\n"],"mappings":";;;;;AAaA,IAAM,IAAoB,GAAG,EAAM;EACjC,EAAgB;EAChB;AAQF,SAAgB,EAAwB,GAAmB,GAAqC;CAC9F,IAAM,IAAQ,SAAS,cAAc,KAAK;CAE1C,AADA,EAAM,YAAY,wDAAwD,KAC1E,EAAM,QAAQ,cAAc;CAC5B,IAAM,IAAS,SAAS,cAAc,KAAK;CAC3C,EAAO,YAAY;CACnB,IAAM,IAAU,SAAS,cAAc,KAAK;CAE5C,AADA,EAAQ,YAAY,oBACpB,EAAO,OAAO,CAAO;CAErB,IAAM,IAAY,EAAwB,KAAA,GAAW,KAAU,SAAS,MAAM,EAC5E,iBAAiB,MAAW,KAAA,EAC9B,CAAC;CAED,IAAI,aAAqB,mBAAmB;EAC1C,IAAM,IAAQ,SAAS,cAAc,OAAO;EAE5C,AADA,EAAM,cAAc,GACpB,EAAM,OAAO,CAAK;CACpB;CAIA,OAHA,EAAM,OAAO,CAAM,GACnB,EAAU,YAAY,CAAK,GAEpB;EAAE;EAAO;CAAQ;AAC1B"}
|
|
@@ -20,7 +20,9 @@ function i(e) {
|
|
|
20
20
|
return t;
|
|
21
21
|
}
|
|
22
22
|
function a(e, t) {
|
|
23
|
-
i(e).forEach((e) =>
|
|
23
|
+
i(e).forEach((e) => {
|
|
24
|
+
e.parentNode !== t && t.appendChild(e);
|
|
25
|
+
});
|
|
24
26
|
}
|
|
25
27
|
function o(e, t) {
|
|
26
28
|
e.querySelectorAll("web-ui-dropdown-item[submenu]").forEach((e) => {
|
|
@@ -37,6 +39,6 @@ function l(e) {
|
|
|
37
39
|
return [...e].filter((e) => e !== void 0).flatMap((e) => Array.from(e.querySelectorAll(t))).find((e) => !!e.shadowRoot?.activeElement);
|
|
38
40
|
}
|
|
39
41
|
//#endregion
|
|
40
|
-
export { e as MENU_CONTENT_SELECTOR, t as MENU_ITEM_SELECTOR, l as findFocusedMenuItem, c as focusMenuItem, r as getEnabledMenuItems, n as getMenuChildren, s as getMenuItemFromEvent, o as hideNestedMenuChildren, a as moveMenuChildren };
|
|
42
|
+
export { e as MENU_CONTENT_SELECTOR, t as MENU_ITEM_SELECTOR, l as findFocusedMenuItem, c as focusMenuItem, r as getEnabledMenuItems, n as getMenuChildren, s as getMenuItemFromEvent, i as getMovableMenuSubtrees, o as hideNestedMenuChildren, a as moveMenuChildren };
|
|
41
43
|
|
|
42
44
|
//# sourceMappingURL=menu-tree.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-tree.js","names":[],"sources":["../../../src/shared/menu-portal/menu-tree.ts"],"sourcesContent":["export const MENU_CONTENT_SELECTOR = 'web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header'\nexport const MENU_ITEM_SELECTOR = 'web-ui-dropdown-item'\n\nexport function getMenuChildren(container: ParentNode): HTMLElement[] {\n const children: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR)) {\n children.push(child)\n continue\n }\n\n // 框架条件渲染会用元素包裹自有节点,或留下注释锚点。搜索 wrapper,\n // 但不进入 menu item 内部;其子节点属于嵌套 submenu。\n children.push(...getMenuChildren(child))\n }\n return children\n}\n\nexport function getEnabledMenuItems(container: ParentNode): HTMLElement[] {\n return getMenuChildren(container).filter(item => item.matches(`${MENU_ITEM_SELECTOR}:not([disabled])`))\n}\n\
|
|
1
|
+
{"version":3,"file":"menu-tree.js","names":[],"sources":["../../../src/shared/menu-portal/menu-tree.ts"],"sourcesContent":["export const MENU_CONTENT_SELECTOR = 'web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header'\nexport const MENU_ITEM_SELECTOR = 'web-ui-dropdown-item'\n\nexport function getMenuChildren(container: ParentNode): HTMLElement[] {\n const children: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR)) {\n children.push(child)\n continue\n }\n\n // 框架条件渲染会用元素包裹自有节点,或留下注释锚点。搜索 wrapper,\n // 但不进入 menu item 内部;其子节点属于嵌套 submenu。\n children.push(...getMenuChildren(child))\n }\n return children\n}\n\nexport function getEnabledMenuItems(container: ParentNode): HTMLElement[] {\n return getMenuChildren(container).filter(item => item.matches(`${MENU_ITEM_SELECTOR}:not([disabled])`))\n}\n\nexport function getMovableMenuSubtrees(container: ParentNode): HTMLElement[] {\n const subtrees: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR) || getMenuChildren(child).length > 0) subtrees.push(child)\n }\n return subtrees\n}\n\nexport function moveMenuChildren(source: ParentNode, target: HTMLElement) {\n getMovableMenuSubtrees(source).forEach(subtree => {\n // 正常调用 source !== target,移走后项不再属于 source,天然收敛;\n // 这里兜底防御 source === target 的误用,避免 appendChild 自移动触发观察者刷新循环。\n if (subtree.parentNode === target) return\n target.appendChild(subtree)\n })\n}\n\nexport function hideNestedMenuChildren(root: ParentNode, slot: string) {\n root.querySelectorAll<HTMLElement>('web-ui-dropdown-item[submenu]').forEach(item => {\n getMenuChildren(item).forEach(child => child.setAttribute('slot', slot))\n })\n}\n\nexport function getMenuItemFromEvent(event: Event): HTMLElement | null {\n return (\n event\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(MENU_ITEM_SELECTOR)) ?? null\n )\n}\n\nexport function focusMenuItem(item: HTMLElement | undefined) {\n if (!item || item.hasAttribute('disabled')) return\n ;(item as HTMLElement & { focusItem?: () => void }).focusItem?.()\n}\n\nexport function findFocusedMenuItem(panels: Iterable<HTMLElement | undefined>): HTMLElement | undefined {\n return [...panels]\n .filter((panel): panel is HTMLElement => panel !== undefined)\n .flatMap(panel => Array.from(panel.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR)))\n .find(item => Boolean(item.shadowRoot?.activeElement))\n}\n"],"mappings":";AAAA,IAAa,IAAwB,yEACxB,IAAqB;AAElC,SAAgB,EAAgB,GAAsC;CACpE,IAAM,IAA0B,CAAC;CACjC,KAAK,IAAM,KAAS,MAAM,KAAK,EAAU,QAAQ,GACzC,iBAAiB,aACvB;MAAI,EAAM,QAAA,uEAA6B,GAAG;GACxC,EAAS,KAAK,CAAK;GACnB;EACF;EAIA,EAAS,KAAK,GAAG,EAAgB,CAAK,CAAC;CAJvC;CAMF,OAAO;AACT;AAEA,SAAgB,EAAoB,GAAsC;CACxE,OAAO,EAAgB,CAAS,CAAC,CAAC,QAAO,MAAQ,EAAK,QAAQ,GAAG,EAAmB,iBAAiB,CAAC;AACxG;AAEA,SAAgB,EAAuB,GAAsC;CAC3E,IAAM,IAA0B,CAAC;CACjC,KAAK,IAAM,KAAS,MAAM,KAAK,EAAU,QAAQ,GACzC,aAAiB,gBACnB,EAAM,QAAA,uEAA6B,KAAK,EAAgB,CAAK,CAAC,CAAC,SAAS,MAAG,EAAS,KAAK,CAAK;CAEpG,OAAO;AACT;AAEA,SAAgB,EAAiB,GAAoB,GAAqB;CACxE,EAAuB,CAAM,CAAC,CAAC,SAAQ,MAAW;EAG5C,EAAQ,eAAe,KAC3B,EAAO,YAAY,CAAO;CAC5B,CAAC;AACH;AAEA,SAAgB,EAAuB,GAAkB,GAAc;CACrE,EAAK,iBAA8B,+BAA+B,CAAC,CAAC,SAAQ,MAAQ;EAClF,EAAgB,CAAI,CAAC,CAAC,SAAQ,MAAS,EAAM,aAAa,QAAQ,CAAI,CAAC;CACzE,CAAC;AACH;AAEA,SAAgB,EAAqB,GAAkC;CACrE,OACE,EACG,aAAa,CAAC,CACd,MAAM,MAA8B,aAAgB,eAAe,EAAK,QAAA,sBAA0B,CAAC,KAAK;AAE/G;AAEA,SAAgB,EAAc,GAA+B;CACvD,CAAC,KAAQ,EAAK,aAAa,UAAU,KACxC,EAAmD,YAAY;AAClE;AAEA,SAAgB,EAAoB,GAAoE;CACtG,OAAO,CAAC,GAAG,CAAM,CAAC,CACf,QAAQ,MAAgC,MAAU,KAAA,CAAS,CAAC,CAC5D,SAAQ,MAAS,MAAM,KAAK,EAAM,iBAA8B,CAAkB,CAAC,CAAC,CAAC,CACrF,MAAK,MAAQ,EAAQ,EAAK,YAAY,aAAc;AACzD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-dialog-presence.js","names":[],"sources":["../../../src/shared/overlay/native-dialog-presence.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getTransitionDuration } from './presence'\n\nexport interface NativeDialogPresenceOptions {\n getDialog(): HTMLDialogElement | null\n isConnected(): boolean\n isOpen(): boolean\n}\n\nexport interface NativeDialogPresenceApi {\n sync(open: boolean): void\n handleTransitionEnd(event: TransitionEvent): void\n handleNativeClose(): void\n dispose(): void\n}\n\n// 管理原生 dialog 保持在 top layer 直至退出动画完成的生命周期。\nexport const defineNativeDialogPresence = () =>\n definePlugin<NativeDialogPresenceApi, NativeDialogPresenceOptions>(ctx => {\n let closeFallbackTimer: ReturnType<typeof setTimeout> | undefined\n let openFrame: number | undefined\n let isClosing = false\n\n const clearCloseFallback = () => {\n if (closeFallbackTimer === undefined) return\n clearTimeout(closeFallbackTimer)\n closeFallbackTimer = undefined\n }\n\n const cancelOpenFrame = () => {\n if (openFrame === undefined) return\n cancelAnimationFrame(openFrame)\n openFrame = undefined\n }\n\n const finishClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog || !isClosing || ctx.isOpen()) return\n clearCloseFallback()\n isClosing = false\n dialog.classList.remove('is-closing', 'is-visible')\n if (dialog.open) dialog.close?.()\n }\n\n const startOpening = () => {\n const dialog = ctx.getDialog()\n if (!dialog) return\n isClosing = false\n clearCloseFallback()\n dialog.classList.remove('is-closing')\n if (!dialog.open) {\n try {\n dialog
|
|
1
|
+
{"version":3,"file":"native-dialog-presence.js","names":[],"sources":["../../../src/shared/overlay/native-dialog-presence.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getTransitionDuration } from './presence'\n\nexport interface NativeDialogPresenceOptions {\n getDialog(): HTMLDialogElement | null\n isConnected(): boolean\n isOpen(): boolean\n}\n\nexport interface NativeDialogPresenceApi {\n sync(open: boolean): void\n handleTransitionEnd(event: TransitionEvent): void\n handleNativeClose(): void\n dispose(): void\n}\n\n// 管理原生 dialog 保持在 top layer 直至退出动画完成的生命周期。\nexport const defineNativeDialogPresence = () =>\n definePlugin<NativeDialogPresenceApi, NativeDialogPresenceOptions>(ctx => {\n let closeFallbackTimer: ReturnType<typeof setTimeout> | undefined\n let openFrame: number | undefined\n let isClosing = false\n\n const clearCloseFallback = () => {\n if (closeFallbackTimer === undefined) return\n clearTimeout(closeFallbackTimer)\n closeFallbackTimer = undefined\n }\n\n const cancelOpenFrame = () => {\n if (openFrame === undefined) return\n cancelAnimationFrame(openFrame)\n openFrame = undefined\n }\n\n const finishClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog || !isClosing || ctx.isOpen()) return\n clearCloseFallback()\n isClosing = false\n dialog.classList.remove('is-closing', 'is-visible')\n if (dialog.open) dialog.close?.()\n }\n\n const startOpening = () => {\n const dialog = ctx.getDialog()\n if (!dialog) return\n isClosing = false\n clearCloseFallback()\n dialog.classList.remove('is-closing')\n if (!dialog.open) {\n try {\n // 并发多个 modal dialog(nested drawer 场景)在规范中合法;\n // 此 try/catch 为防御性兜底(如对已在 top layer 的 dialog 重复 showModal\n // 会抛 InvalidStateError),失败时由下方 rAF 回调的 dialog.open 判断中止动画。\n dialog.showModal?.()\n } catch {\n // 保留未打开状态\n }\n }\n\n cancelOpenFrame()\n openFrame = requestAnimationFrame(() => {\n openFrame = undefined\n if (!ctx.isConnected() || !ctx.isOpen() || !dialog.open) return\n dialog.classList.add('is-visible')\n })\n }\n\n const startClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog?.open) return\n cancelOpenFrame()\n isClosing = true\n if (!dialog.classList.contains('is-visible')) {\n finishClosing()\n return\n }\n\n dialog.classList.add('is-closing')\n dialog.classList.remove('is-visible')\n clearCloseFallback()\n closeFallbackTimer = setTimeout(() => finishClosing(), getTransitionDuration(dialog) + 80)\n }\n\n return {\n sync(open) {\n if (open) startOpening()\n else startClosing()\n },\n\n handleTransitionEnd(event) {\n if (event.target !== ctx.getDialog() || event.propertyName !== 'transform') return\n finishClosing()\n },\n\n handleNativeClose() {\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n ctx.getDialog()?.classList.remove('is-closing', 'is-visible')\n },\n\n dispose() {\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n }\n }\n })\n"],"mappings":";;;AAkBA,IAAa,UACX,GAAmE,MAAO;CACxE,IAAI,GACA,GACA,IAAY,IAEV,UAA2B;EAC3B,MAAuB,KAAA,MAC3B,aAAa,CAAkB,GAC/B,IAAqB,KAAA;CACvB,GAEM,UAAwB;EACxB,MAAc,KAAA,MAClB,qBAAqB,CAAS,GAC9B,IAAY,KAAA;CACd,GAEM,UAAsB;EAC1B,IAAM,IAAS,EAAI,UAAU;EACzB,CAAC,KAAU,CAAC,KAAa,EAAI,OAAO,MACxC,EAAmB,GACnB,IAAY,IACZ,EAAO,UAAU,OAAO,cAAc,YAAY,GAC9C,EAAO,QAAM,EAAO,QAAQ;CAClC,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAIL;OAHA,IAAY,IACZ,EAAmB,GACnB,EAAO,UAAU,OAAO,YAAY,GAChC,CAAC,EAAO,MACV,IAAI;IAIF,EAAO,YAAY;GACrB,QAAQ,CAER;GAIF,AADA,EAAgB,GAChB,IAAY,4BAA4B;IACtC,IAAY,KAAA,GACR,GAAC,EAAI,YAAY,KAAK,CAAC,EAAI,OAAO,KAAK,CAAC,EAAO,SACnD,EAAO,UAAU,IAAI,YAAY;GACnC,CAAC;EARC;CASJ,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAAQ,MAGb;OAFA,EAAgB,GAChB,IAAY,IACR,CAAC,EAAO,UAAU,SAAS,YAAY,GAAG;IAC5C,EAAc;IACd;GACF;GAKA,AAHA,EAAO,UAAU,IAAI,YAAY,GACjC,EAAO,UAAU,OAAO,YAAY,GACpC,EAAmB,GACnB,IAAqB,iBAAiB,EAAc,GAAG,EAAsB,CAAM,IAAI,EAAE;EALzF;CAMF;CAEA,OAAO;EACL,KAAK,GAAM;GACT,AAAI,IAAM,EAAa,IAClB,EAAa;EACpB;EAEA,oBAAoB,GAAO;GACrB,EAAM,WAAW,EAAI,UAAU,KAAK,EAAM,iBAAiB,eAC/D,EAAc;EAChB;EAEA,oBAAoB;GAIlB,AAHA,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACZ,EAAI,UAAU,CAAC,EAAE,UAAU,OAAO,cAAc,YAAY;EAC9D;EAEA,UAAU;GAGR,AAFA,EAAgB,GAChB,EAAmB,GACnB,IAAY;EACd;CACF;AACF,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { definePlugin as e } from "@greypan/js-kit";
|
|
2
|
+
//#region src/shared/overlay/nested-drawer-layers.ts
|
|
3
|
+
var t = "--wui-internal-drawer-nested-depth", n = .95, r = 12, i = /* @__PURE__ */ new Set(), a = !1;
|
|
4
|
+
function o() {
|
|
5
|
+
let e = [];
|
|
6
|
+
for (let t of i) {
|
|
7
|
+
let n = t.dialog;
|
|
8
|
+
if (!n.isConnected || !n.open) {
|
|
9
|
+
i.delete(t);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
let r = t.placement() === "left" || t.placement() === "right" ? n.offsetWidth : n.offsetHeight;
|
|
13
|
+
e.push({
|
|
14
|
+
entry: t,
|
|
15
|
+
dialog: n,
|
|
16
|
+
size: r
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
let a = e.length;
|
|
20
|
+
for (let i = 0; i < a; i++) {
|
|
21
|
+
let { dialog: o, size: s } = e[i], c = a - 1 - i, l = n ** +c, u = s * (1 - l) / 2, d = 0;
|
|
22
|
+
for (let t = i + 1; t < a; t++) e[t].size > d && (d = e[t].size);
|
|
23
|
+
let f = Math.max(0, d - s), p = c > 0 ? u + f + c * r : 0;
|
|
24
|
+
o.style.setProperty(t, String(c)), o.style.setProperty("--wui-internal-drawer-nested-scale", l.toFixed(4)), o.style.setProperty("--wui-internal-drawer-nested-shift", `${p.toFixed(2)}px`), c > 0 ? o.classList.add("is-nested-lower") : o.classList.remove("is-nested-lower");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function s(e) {
|
|
28
|
+
e.target instanceof HTMLDialogElement && i.size > 0 && o();
|
|
29
|
+
}
|
|
30
|
+
function c() {
|
|
31
|
+
a ||= (document.addEventListener("close", s, !0), !0);
|
|
32
|
+
}
|
|
33
|
+
var l = () => e((e) => ({
|
|
34
|
+
register() {
|
|
35
|
+
let t = e.getDialog();
|
|
36
|
+
t && (c(), i.add({
|
|
37
|
+
dialog: t,
|
|
38
|
+
placement: () => e.getPlacement()
|
|
39
|
+
}), o());
|
|
40
|
+
},
|
|
41
|
+
unregister() {
|
|
42
|
+
let t = e.getDialog();
|
|
43
|
+
for (let e of i) e.dialog === t && i.delete(e);
|
|
44
|
+
i.size > 0 && o();
|
|
45
|
+
},
|
|
46
|
+
dispose() {
|
|
47
|
+
let t = e.getDialog();
|
|
48
|
+
for (let e of i) e.dialog === t && i.delete(e);
|
|
49
|
+
i.size === 0 && a && (document.removeEventListener("close", s, !0), a = !1);
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
//#endregion
|
|
53
|
+
export { l as defineNestedDrawerLayers };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=nested-drawer-layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-drawer-layers.js","names":[],"sources":["../../../src/shared/overlay/nested-drawer-layers.ts"],"sourcesContent":["/*\n * Nested drawer 层序管理(对齐 Base UI/shadcn nested drawer 行为):\n * 每打开一层 modal drawer,其下所有已打开的 drawer 按 0.95^n 缩放并向\n * 屏幕内侧平移,在顶层抽屉后方露出阶梯式卡片边缘(peeking edge);顶层全尺寸。\n *\n * 层序来源是原生 top layer 本身:depth = 在自身 showModal 之前已打开的\n * modal dialog 数。上层关闭通过 document 捕获阶段的 `close` 事件感知\n * (原生 close 事件不冒泡,但捕获阶段在 document 上可观察所有 target),\n * 底层重算 depth 后由 CSS transition 平滑回弹。\n *\n * 视觉规格:\n * - scale = 0.95^depth\n * - shift = shrink + depth * 12px(向屏幕内侧偏移,露出阶梯卡片边缘)\n * - 过渡 transform 450ms cubic-bezier(0.22, 1, 0.36, 1)\n *\n * 拖拽与弹簧期间 JS 直接写 dialog.style.transform(优先级高于本机制的\n * CSS 变量组合),顶层才有拖拽,故无冲突。\n */\n\nimport { definePlugin } from '@greypan/js-kit'\n\nconst DEPTH_VARIABLE = '--wui-internal-drawer-nested-depth'\nconst NESTED_SCALE = 0.95\nconst NESTED_PEEK_OFFSET = 12\n\ninterface NestedDrawerEntry {\n dialog: HTMLDialogElement\n placement: () => 'right' | 'left' | 'top' | 'bottom'\n}\n\nconst entries = new Set<NestedDrawerEntry>()\n\nlet documentListenerAttached = false\n\nfunction applyLayers() {\n const openEntries: Array<{\n entry: NestedDrawerEntry\n dialog: HTMLDialogElement\n size: number\n }> = []\n\n for (const entry of entries) {\n const dialog = entry.dialog\n if (!dialog.isConnected || !dialog.open) {\n entries.delete(entry)\n continue\n }\n const size =\n entry.placement() === 'left' || entry.placement() === 'right' ? dialog.offsetWidth : dialog.offsetHeight\n openEntries.push({ entry, dialog, size })\n }\n\n const total = openEntries.length\n for (let i = 0; i < total; i++) {\n const { dialog, size } = openEntries[i]\n // 注册顺序:先打开的在前 (index 小),后打开的在后 (index 大)。\n // depth = 在我之后打开的 drawer 数量 = (total - 1) - i\n const depth = total - 1 - i\n const scale = Math.pow(NESTED_SCALE, depth)\n const shrink = (size * (1 - scale)) / 2\n\n // 检查所有排在我之后的上层 drawer 的最大尺寸(若上层更宽,补偿宽度差确保露边)\n let aboveMaxSize = 0\n for (let j = i + 1; j < total; j++) {\n if (openEntries[j].size > aboveMaxSize) {\n aboveMaxSize = openEntries[j].size\n }\n }\n const sizeDiff = Math.max(0, aboveMaxSize - size)\n const shift = depth > 0 ? shrink + sizeDiff + depth * NESTED_PEEK_OFFSET : 0\n\n dialog.style.setProperty(DEPTH_VARIABLE, String(depth))\n dialog.style.setProperty('--wui-internal-drawer-nested-scale', scale.toFixed(4))\n dialog.style.setProperty('--wui-internal-drawer-nested-shift', `${shift.toFixed(2)}px`)\n if (depth > 0) {\n dialog.classList.add('is-nested-lower')\n } else {\n dialog.classList.remove('is-nested-lower')\n }\n }\n}\n\nfunction handleAnyDialogClose(event: Event) {\n if (!(event.target instanceof HTMLDialogElement)) return\n // 上层(或任意层)关闭后,重算所有存活 drawer 的 depth。\n if (entries.size > 0) applyLayers()\n}\n\nfunction ensureDocumentListener() {\n if (documentListenerAttached) return\n document.addEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = true\n}\n\nexport interface NestedDrawerLayerOptions {\n getDialog(): HTMLDialogElement | null\n getPlacement(): 'right' | 'left' | 'top' | 'bottom'\n}\n\nexport interface NestedDrawerLayerApi {\n /** dialog 进入 top layer(打开动画就绪)后调用,纳入层序管理。 */\n register(): void\n /** 关闭流程开始时调用:移出层序并触发底层回弹。 */\n unregister(): void\n dispose(): void\n}\n\nexport const defineNestedDrawerLayers = () =>\n definePlugin<NestedDrawerLayerApi, NestedDrawerLayerOptions>(ctx => ({\n register() {\n const dialog = ctx.getDialog()\n if (!dialog) return\n ensureDocumentListener()\n entries.add({ dialog, placement: () => ctx.getPlacement() })\n applyLayers()\n },\n\n unregister() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n // 移除后重算剩余层(顶层关闭 → 次层回到 depth 0)。\n if (entries.size > 0) applyLayers()\n },\n\n dispose() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n if (entries.size === 0 && documentListenerAttached) {\n document.removeEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = false\n }\n }\n }))\n"],"mappings":";;AAqBA,IAAM,IAAiB,sCACjB,IAAe,KACf,IAAqB,IAOrB,oBAAU,IAAI,IAAuB,GAEvC,IAA2B;AAE/B,SAAS,IAAc;CACrB,IAAM,IAID,CAAC;CAEN,KAAK,IAAM,KAAS,GAAS;EAC3B,IAAM,IAAS,EAAM;EACrB,IAAI,CAAC,EAAO,eAAe,CAAC,EAAO,MAAM;GACvC,EAAQ,OAAO,CAAK;GACpB;EACF;EACA,IAAM,IACJ,EAAM,UAAU,MAAM,UAAU,EAAM,UAAU,MAAM,UAAU,EAAO,cAAc,EAAO;EAC9F,EAAY,KAAK;GAAE;GAAO;GAAQ;EAAK,CAAC;CAC1C;CAEA,IAAM,IAAQ,EAAY;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAO,KAAK;EAC9B,IAAM,EAAE,WAAQ,YAAS,EAAY,IAG/B,IAAQ,IAAQ,IAAI,GACpB,IAAiB,MAAc,GAC/B,IAAU,KAAQ,IAAI,KAAU,GAGlC,IAAe;EACnB,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,GAAO,KAC7B,AAAI,EAAY,EAAE,CAAC,OAAO,MACxB,IAAe,EAAY,EAAE,CAAC;EAGlC,IAAM,IAAW,KAAK,IAAI,GAAG,IAAe,CAAI,GAC1C,IAAQ,IAAQ,IAAI,IAAS,IAAW,IAAQ,IAAqB;EAK3E,AAHA,EAAO,MAAM,YAAY,GAAgB,OAAO,CAAK,CAAC,GACtD,EAAO,MAAM,YAAY,sCAAsC,EAAM,QAAQ,CAAC,CAAC,GAC/E,EAAO,MAAM,YAAY,sCAAsC,GAAG,EAAM,QAAQ,CAAC,EAAE,GAAG,GAClF,IAAQ,IACV,EAAO,UAAU,IAAI,iBAAiB,IAEtC,EAAO,UAAU,OAAO,iBAAiB;CAE7C;AACF;AAEA,SAAS,EAAqB,GAAc;CACpC,EAAM,kBAAkB,qBAE1B,EAAQ,OAAO,KAAG,EAAY;AACpC;AAEA,SAAS,IAAyB;CAC5B,AAEJ,OADA,SAAS,iBAAiB,SAAS,GAAsB,EAAI,GAClC;AAC7B;AAeA,IAAa,UACX,GAA6D,OAAQ;CACnE,WAAW;EACT,IAAM,IAAS,EAAI,UAAU;EACxB,MACL,EAAuB,GACvB,EAAQ,IAAI;GAAE;GAAQ,iBAAiB,EAAI,aAAa;EAAE,CAAC,GAC3D,EAAY;CACd;CAEA,aAAa;EACX,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAGnD,AAAI,EAAQ,OAAO,KAAG,EAAY;CACpC;CAEA,UAAU;EACR,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAEnD,AAAI,EAAQ,SAAS,KAAK,MACxB,SAAS,oBAAoB,SAAS,GAAsB,EAAI,GAChE,IAA2B;CAE/B;AACF,EAAE"}
|