@greypan/web-ui 3.0.1 → 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 +495 -125
- package/README.md +504 -125
- package/dist/_virtual/{_@oxc-project_runtime@0.140.0 → _@oxc-project_runtime@0.146.0}/helpers/esm/decorate.js +1 -1
- 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/assets/overlay-motion.js +1 -1
- package/dist/assets/overlay-motion.js.map +1 -1
- package/dist/assets/toast-containers.js +1 -1
- package/dist/assets/toast-containers.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +12 -12
- package/dist/components/autocomplete/index.js +162 -111
- 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/avatar/index.d.ts +0 -1
- package/dist/components/avatar/index.js +8 -13
- package/dist/components/avatar/index.js.map +1 -1
- package/dist/components/back-top/index.js +2 -2
- 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/badge/index.d.ts +1 -0
- package/dist/components/badge/index.js +12 -14
- package/dist/components/badge/index.js.map +1 -1
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.js +52 -38
- 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.d.ts +1 -2
- package/dist/components/button-group/index.js +21 -23
- 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.d.ts +4 -7
- package/dist/components/checkbox/index.js +56 -38
- 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/checkbox-group/index.d.ts +4 -10
- package/dist/components/checkbox-group/index.js +74 -60
- package/dist/components/checkbox-group/index.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 +168 -53
- 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 +34 -13
- 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 +45 -0
- package/dist/components/drawer/index.js +270 -56
- 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 +24 -14
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown-divider/index.js +1 -1
- package/dist/components/dropdown-header/index.js +1 -1
- package/dist/components/dropdown-header/style.js +1 -1
- package/dist/components/dropdown-header/style.js.map +1 -1
- package/dist/components/dropdown-item/index.js +3 -2
- package/dist/components/dropdown-item/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 +24 -22
- 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/icon/index.js +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +21 -20
- package/dist/components/input/index.d.ts +2 -3
- package/dist/components/input/index.js +62 -52
- package/dist/components/input/index.js.map +1 -1
- package/dist/components/input/style.js +1 -1
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/input-number/index.d.ts +2 -3
- package/dist/components/input-number/index.js +58 -38
- package/dist/components/input-number/index.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 +76 -3
- package/dist/components/layout/index.js +250 -39
- 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/index.js +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 +14 -8
- 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.d.ts +4 -7
- package/dist/components/radio/index.js +51 -37
- 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/radio-group/index.d.ts +4 -10
- package/dist/components/radio-group/index.js +70 -52
- package/dist/components/radio-group/index.js.map +1 -1
- package/dist/components/segmented/index.d.ts +14 -12
- package/dist/components/segmented/index.js +132 -54
- 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.d.ts +1 -2
- package/dist/components/segmented-trigger/index.js +23 -21
- 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.d.ts +13 -8
- package/dist/components/select/index.js +114 -75
- 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 +6 -7
- package/dist/components/slider/index.js +99 -64
- 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.d.ts +0 -2
- package/dist/components/spinner/index.js +14 -13
- package/dist/components/spinner/index.js.map +1 -1
- package/dist/components/spinner/style.js +1 -1
- package/dist/components/spinner/style.js.map +1 -1
- package/dist/components/svg-draw-lines/index.js +1 -1
- package/dist/components/switch/index.d.ts +13 -6
- package/dist/components/switch/index.js +102 -41
- 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.d.ts +5 -6
- package/dist/components/textarea/index.js +74 -67
- 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/index.js +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 +12 -12
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/tooltip/index.js +3 -3
- 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-clapperboard.d.ts +6 -0
- package/dist/icons/generated/lucide-clapperboard.js +10 -0
- package/dist/icons/generated/lucide-clapperboard.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-eye.d.ts +6 -0
- package/dist/icons/generated/lucide-eye.js +10 -0
- package/dist/icons/generated/lucide-eye.js.map +1 -0
- package/dist/icons/generated/lucide-headphones.d.ts +6 -0
- package/dist/icons/generated/lucide-headphones.js +10 -0
- package/dist/icons/generated/lucide-headphones.js.map +1 -0
- package/dist/icons/generated/lucide-link.d.ts +6 -0
- package/dist/icons/generated/lucide-link.js +10 -0
- package/dist/icons/generated/lucide-link.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-pen-line.d.ts +6 -0
- package/dist/icons/generated/lucide-pen-line.js +10 -0
- package/dist/icons/generated/lucide-pen-line.js.map +1 -0
- package/dist/icons/generated/lucide-play.d.ts +6 -0
- package/dist/icons/generated/lucide-play.js +10 -0
- package/dist/icons/generated/lucide-play.js.map +1 -0
- package/dist/icons/generated/lucide-sliders-horizontal.d.ts +6 -0
- package/dist/icons/generated/lucide-sliders-horizontal.js +10 -0
- package/dist/icons/generated/lucide-sliders-horizontal.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 +15 -0
- package/dist/icons/index.js +18 -3
- package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +1 -3
- package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +1 -1
- package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +2 -1
- package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +1 -1
- package/dist/shared/events/user-change.js.map +1 -1
- package/dist/shared/form-association/index.js +41 -0
- package/dist/shared/form-association/index.js.map +1 -0
- 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 +111 -0
- package/dist/shared/group-management/index.js.map +1 -0
- 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 +22 -7
- package/dist/shared/menu-portal/menu-tree.js.map +1 -1
- package/dist/shared/option-portal/index.js +65 -0
- package/dist/shared/option-portal/index.js.map +1 -0
- 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/overlay.js +1 -1
- package/dist/shared/overlay/overlay.js.map +1 -1
- package/dist/shared/overlay/portal.js +47 -13
- package/dist/shared/overlay/portal.js.map +1 -1
- package/dist/shared/overlay/presence.js.map +1 -1
- package/dist/shared/visible-area/index.js +69 -0
- package/dist/shared/visible-area/index.js.map +1 -0
- package/package.json +8 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { definePlugin as e } from "@greypan/js-kit";
|
|
2
|
+
//#region src/shared/form-association/index.ts
|
|
3
|
+
function t(t) {
|
|
4
|
+
return e(() => {
|
|
5
|
+
let e, n, r = !1, i = !1, a = t.copyState ?? ((e) => e), o = () => {
|
|
6
|
+
e && (e.setFormValue?.(t.getFormValue(), t.getFormState()), t.syncValidity?.(e));
|
|
7
|
+
};
|
|
8
|
+
return {
|
|
9
|
+
connect() {
|
|
10
|
+
e ||= t.host.attachInternals(), r ||= (t.initialize?.(), n = a(t.getState()), !0), o();
|
|
11
|
+
},
|
|
12
|
+
sync: o,
|
|
13
|
+
reset() {
|
|
14
|
+
!r || t.isStateManaged?.() || (t.setState(a(n)), o());
|
|
15
|
+
},
|
|
16
|
+
restore(e) {
|
|
17
|
+
t.isStateManaged?.() || (t.restoreState(e), o());
|
|
18
|
+
},
|
|
19
|
+
setDisabled(e) {
|
|
20
|
+
i !== e && (i = e, t.host.requestUpdate(), o());
|
|
21
|
+
},
|
|
22
|
+
isFormDisabled: () => i,
|
|
23
|
+
getInternals: () => e
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
var n = class {
|
|
28
|
+
constructor(e, t) {
|
|
29
|
+
this.host = e, this.lifecycle = t, e.addController(this);
|
|
30
|
+
}
|
|
31
|
+
hostConnected() {
|
|
32
|
+
this.lifecycle.connect();
|
|
33
|
+
}
|
|
34
|
+
hostUpdated() {
|
|
35
|
+
this.lifecycle.sync();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { n as FormAssociationController, t as defineFormAssociation };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/form-association/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\ntype FormSubmissionValue = File | FormData | string | null\ntype FormStateValue = File | FormData | string | null\n\ninterface FormAssociationHost extends HTMLElement, ReactiveControllerHost {}\n\nexport interface FormAssociationOptions<State> {\n readonly host: FormAssociationHost\n readonly initialize?: () => void\n readonly getState: () => State\n readonly setState: (state: State) => void\n readonly copyState?: (state: State) => State\n readonly getFormValue: () => FormSubmissionValue\n readonly getFormState: () => FormStateValue\n readonly restoreState: (state: FormStateValue) => void\n readonly isStateManaged?: () => boolean\n readonly syncValidity?: (internals: ElementInternals) => void\n}\n\nexport interface FormAssociationLifecycle {\n connect(): void\n sync(): void\n reset(): void\n restore(state: FormStateValue): void\n setDisabled(disabled: boolean): void\n isFormDisabled(): boolean\n getInternals(): ElementInternals | undefined\n}\n\n/**\n * 承接原生 custom element 表单生命周期,值序列化与校验语义仍由各控件组件自己表达。\n */\nexport function defineFormAssociation<State>(options: FormAssociationOptions<State>) {\n return definePlugin((): FormAssociationLifecycle => {\n let internals: ElementInternals | undefined\n let initialState: State | undefined\n let initialized = false\n let formDisabled = false\n const copyState = options.copyState ?? (state => state)\n\n const sync = () => {\n if (!internals) return\n internals.setFormValue?.(options.getFormValue(), options.getFormState())\n options.syncValidity?.(internals)\n }\n\n return {\n connect() {\n if (!internals) internals = options.host.attachInternals()\n if (!initialized) {\n options.initialize?.()\n initialState = copyState(options.getState())\n initialized = true\n }\n sync()\n },\n sync,\n reset() {\n if (!initialized || options.isStateManaged?.()) return\n options.setState(copyState(initialState as State))\n sync()\n },\n restore(state) {\n if (options.isStateManaged?.()) return\n options.restoreState(state)\n sync()\n },\n setDisabled(disabled) {\n if (formDisabled === disabled) return\n formDisabled = disabled\n options.host.requestUpdate()\n sync()\n },\n isFormDisabled: () => formDisabled,\n getInternals: () => internals\n }\n })\n}\n\n/** 把表单生命周期插件接入宿主,无需共享组件基类。 */\nexport class FormAssociationController implements ReactiveController {\n constructor(\n private readonly host: FormAssociationHost,\n private readonly lifecycle: FormAssociationLifecycle\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.lifecycle.connect()\n }\n\n hostUpdated() {\n this.lifecycle.sync()\n }\n}\n"],"mappings":";;AAkCA,SAAgB,EAA6B,GAAwC;CACnF,OAAO,QAA6C;EAClD,IAAI,GACA,GACA,IAAc,IACd,IAAe,IACb,IAAY,EAAQ,eAAc,MAAS,IAE3C,UAAa;GACZ,MACL,EAAU,eAAe,EAAQ,aAAa,GAAG,EAAQ,aAAa,CAAC,GACvE,EAAQ,eAAe,CAAS;EAClC;EAEA,OAAO;GACL,UAAU;IAOR,AANA,AAAgB,MAAY,EAAQ,KAAK,gBAAgB,GACzD,AAGE,OAFA,EAAQ,aAAa,GACrB,IAAe,EAAU,EAAQ,SAAS,CAAC,GAC7B,KAEhB,EAAK;GACP;GACA;GACA,QAAQ;IACF,CAAC,KAAe,EAAQ,iBAAiB,MAC7C,EAAQ,SAAS,EAAU,CAAqB,CAAC,GACjD,EAAK;GACP;GACA,QAAQ,GAAO;IACT,EAAQ,iBAAiB,MAC7B,EAAQ,aAAa,CAAK,GAC1B,EAAK;GACP;GACA,YAAY,GAAU;IAChB,MAAiB,MACrB,IAAe,GACf,EAAQ,KAAK,cAAc,GAC3B,EAAK;GACP;GACA,sBAAsB;GACtB,oBAAoB;EACtB;CACF,CAAC;AACH;AAGA,IAAa,IAAb,MAAqE;CACnE,YACE,GACA,GACA;EACA,AAHiB,KAAA,OAAA,GACA,KAAA,YAAA,GAEjB,EAAK,cAAc,IAAI;CACzB;CAEA,gBAAgB;EACd,KAAK,UAAU,QAAQ;CACzB;CAEA,cAAc;EACZ,KAAK,UAAU,KAAK;CACtB;AACF"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { definePlugin as e } from "@greypan/js-kit";
|
|
2
|
+
import { ContextConsumer as t, ContextProvider as n, createContext as r } from "@lit/context";
|
|
3
|
+
//#region src/shared/group-management/index.ts
|
|
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) {
|
|
6
|
+
return e(() => {
|
|
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
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
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
|
+
});
|
|
43
|
+
}
|
|
44
|
+
var f = class {
|
|
45
|
+
constructor(e, t, n = {}) {
|
|
46
|
+
this.host = e, this.lifecycle = t, this.options = n, this.connected = !1, this.observer = null, this.observedSlots = /* @__PURE__ */ new Set(), this.handleChange = (e) => {
|
|
47
|
+
this.lifecycle.handleChange?.(e);
|
|
48
|
+
}, this.handleSlotChange = () => {
|
|
49
|
+
this.connected && this.sync();
|
|
50
|
+
}, this.handleMutations = () => {
|
|
51
|
+
this.connected && this.sync();
|
|
52
|
+
}, e.addController(this);
|
|
53
|
+
}
|
|
54
|
+
hostConnected() {
|
|
55
|
+
this.connected = !0, this.host.addEventListener("change", this.handleChange, !0), this.observeHost(), this.observeSlots(), this.sync();
|
|
56
|
+
}
|
|
57
|
+
hostUpdated() {
|
|
58
|
+
this.observeSlots(), this.sync();
|
|
59
|
+
}
|
|
60
|
+
hostDisconnected() {
|
|
61
|
+
this.connected = !1, this.host.removeEventListener("change", this.handleChange, !0), this.disconnectSlots(), this.disconnectHostObserver(), this.lifecycle.disconnect();
|
|
62
|
+
}
|
|
63
|
+
sync() {
|
|
64
|
+
this.lifecycle.sync() && this.options.afterSync?.();
|
|
65
|
+
}
|
|
66
|
+
observeHost() {
|
|
67
|
+
this.disconnectHostObserver(), this.observer = new MutationObserver(this.handleMutations), this.observer.observe(this.host, {
|
|
68
|
+
childList: !0,
|
|
69
|
+
subtree: !0
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
disconnectHostObserver() {
|
|
73
|
+
this.observer &&= (this.observer.disconnect(), null);
|
|
74
|
+
}
|
|
75
|
+
observeSlots() {
|
|
76
|
+
let e = this.host.renderRoot, t = e.querySelectorAll("slot");
|
|
77
|
+
for (let e of t) this.observedSlots.has(e) || (e.addEventListener("slotchange", this.handleSlotChange), this.observedSlots.add(e));
|
|
78
|
+
for (let t of this.observedSlots) e.contains(t) || (t.removeEventListener("slotchange", this.handleSlotChange), this.observedSlots.delete(t));
|
|
79
|
+
}
|
|
80
|
+
disconnectSlots() {
|
|
81
|
+
for (let e of this.observedSlots) e.removeEventListener("slotchange", this.handleSlotChange);
|
|
82
|
+
this.observedSlots.clear();
|
|
83
|
+
}
|
|
84
|
+
}, p = { disabled: !1 }, m = { disabled: !0 };
|
|
85
|
+
function h(t) {
|
|
86
|
+
return 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;
|
|
91
|
+
return {
|
|
92
|
+
sync: () => {
|
|
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;
|
|
96
|
+
},
|
|
97
|
+
disconnect() {
|
|
98
|
+
e.setValue(/* @__PURE__ */ new Map()), a.clear(), r = void 0, i = void 0, s = !1;
|
|
99
|
+
},
|
|
100
|
+
handleChange(e) {
|
|
101
|
+
if (t.getDisabled() || !t.isItem(e.target) || !t.host.contains(e.target)) return;
|
|
102
|
+
let n = t.getValue(), r = t.getNextValue(e.target, n);
|
|
103
|
+
t.valuesEqual(n, r) || (t.setValue(r), t.dispatchValueChange());
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { f as GroupController, l as buttonGroupContextKey, h as defineGroupCoordinator, u as defineGroupManaged, d as defineGroupPresentation, c as selectionGroupContextKey };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
//#region src/shared/menu-portal/menu-tree.ts
|
|
2
2
|
var e = "web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header", t = "web-ui-dropdown-item";
|
|
3
3
|
function n(e) {
|
|
4
|
-
|
|
4
|
+
let t = [];
|
|
5
|
+
for (let r of Array.from(e.children)) if (r instanceof HTMLElement) {
|
|
6
|
+
if (r.matches("web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header")) {
|
|
7
|
+
t.push(r);
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
t.push(...n(r));
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
5
13
|
}
|
|
6
14
|
function r(e) {
|
|
7
15
|
return n(e).filter((e) => e.matches(`${t}:not([disabled])`));
|
|
8
16
|
}
|
|
9
|
-
function i(e
|
|
10
|
-
|
|
17
|
+
function i(e) {
|
|
18
|
+
let t = [];
|
|
19
|
+
for (let r of Array.from(e.children)) r instanceof HTMLElement && (r.matches("web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header") || n(r).length > 0) && t.push(r);
|
|
20
|
+
return t;
|
|
11
21
|
}
|
|
12
22
|
function a(e, t) {
|
|
23
|
+
i(e).forEach((e) => {
|
|
24
|
+
e.parentNode !== t && t.appendChild(e);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function o(e, t) {
|
|
13
28
|
e.querySelectorAll("web-ui-dropdown-item[submenu]").forEach((e) => {
|
|
14
29
|
n(e).forEach((e) => e.setAttribute("slot", t));
|
|
15
30
|
});
|
|
16
31
|
}
|
|
17
|
-
function
|
|
32
|
+
function s(e) {
|
|
18
33
|
return e.composedPath().find((e) => e instanceof HTMLElement && e.matches("web-ui-dropdown-item")) ?? null;
|
|
19
34
|
}
|
|
20
|
-
function
|
|
35
|
+
function c(e) {
|
|
21
36
|
!e || e.hasAttribute("disabled") || e.focusItem?.();
|
|
22
37
|
}
|
|
23
|
-
function
|
|
38
|
+
function l(e) {
|
|
24
39
|
return [...e].filter((e) => e !== void 0).flatMap((e) => Array.from(e.querySelectorAll(t))).find((e) => !!e.shadowRoot?.activeElement);
|
|
25
40
|
}
|
|
26
41
|
//#endregion
|
|
27
|
-
export { e as MENU_CONTENT_SELECTOR, t as MENU_ITEM_SELECTOR,
|
|
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 };
|
|
28
43
|
|
|
29
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
|
|
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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { definePlugin as e } from "@greypan/js-kit";
|
|
2
|
+
//#region src/shared/option-portal/index.ts
|
|
3
|
+
var t = "web-ui-option", n = () => e((e) => {
|
|
4
|
+
let n = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new WeakMap(), i = 0, a = (t) => {
|
|
5
|
+
e.bindOption(t), t.addEventListener("option-unregister", s);
|
|
6
|
+
}, o = (t) => {
|
|
7
|
+
e.unbindOption(t), t.removeEventListener("option-unregister", s);
|
|
8
|
+
}, s = (e) => {
|
|
9
|
+
e.target instanceof HTMLElement && e.target.localName === t && n.add(e.target), l.scheduleRefresh();
|
|
10
|
+
}, c = () => {
|
|
11
|
+
l.scheduleRefresh();
|
|
12
|
+
}, l = {
|
|
13
|
+
queryOptions() {
|
|
14
|
+
let n = e.getPortal()?.panel ?? e.getPortalContent(), r = n ? [...n.querySelectorAll(t)] : [];
|
|
15
|
+
return r.length > 0 ? r : [...e.element.querySelectorAll(t)];
|
|
16
|
+
},
|
|
17
|
+
refresh(e, t) {
|
|
18
|
+
let n = l.queryOptions();
|
|
19
|
+
for (let t of e) n.includes(t) || o(t);
|
|
20
|
+
return n.forEach(a), {
|
|
21
|
+
options: n,
|
|
22
|
+
activeIndex: t && n.includes(t) ? n.indexOf(t) : -1
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
ensureOptionIds(t) {
|
|
26
|
+
let n = new Set(t.map((e) => e.id).filter((e) => !!e));
|
|
27
|
+
t.forEach((t) => {
|
|
28
|
+
if (t.id) return;
|
|
29
|
+
let a = r.get(t);
|
|
30
|
+
if (!a || n.has(a)) {
|
|
31
|
+
do
|
|
32
|
+
a = `${e.idPrefix}-option-${++i}`;
|
|
33
|
+
while (n.has(a));
|
|
34
|
+
r.set(t, a);
|
|
35
|
+
}
|
|
36
|
+
t.id = a, n.add(a);
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
syncPortalContent() {
|
|
40
|
+
let t = e.getPortal(), r = e.getPortalContent();
|
|
41
|
+
if (!t || !r) return;
|
|
42
|
+
for (let i of n) !r.contains(i) && !e.element.contains(i) && t.removeContent([i]);
|
|
43
|
+
if (n.clear(), !e.isOpen()) return;
|
|
44
|
+
let i = e.getMigratableNodes();
|
|
45
|
+
i.length && t.appendContent(i, r);
|
|
46
|
+
},
|
|
47
|
+
scheduleRefresh() {
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
e.element.isConnected && (l.syncPortalContent(), e.requestUpdate(), e.isOpen() && e.hasUpdated() && e.onImmediateSync?.());
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
handleOptionUnregister: s,
|
|
53
|
+
bindHost() {
|
|
54
|
+
e.element.addEventListener("option-register", c), e.element.addEventListener("option-unregister", s);
|
|
55
|
+
},
|
|
56
|
+
dispose() {
|
|
57
|
+
e.element.removeEventListener("option-register", c), e.element.removeEventListener("option-unregister", s), n.clear();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return l;
|
|
61
|
+
});
|
|
62
|
+
//#endregion
|
|
63
|
+
export { n as defineOptionPortal };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/option-portal/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { WebUiOption } from '@/components/option'\nimport type { OverlayPortal } from '@/shared/overlay/portal'\n\n/**\n * select / autocomplete 共用的 portal option 域:宿主侧 option 注册表、\n * portal 内容同步(打开期迁移 + 真删除清理)与微任务调度。\n *\n * 交互语义与 Lit 响应式由各组件保留(同 anchored-panel 的边界约定):\n * 组件通过 host 回调注入差异点(迁移节点来源、打开期即时同步等),\n * 并持有 _options/_activeIndex 的 @state,shared 只经返回值同步数据。\n */\n\nexport interface OptionPortalHost {\n /** 承载宿主级监听的元素(select / autocomplete 宿主自身)。 */\n readonly element: HTMLElement\n /** option id 前缀;宿主应保证跨实例唯一(如含实例序号),供 aria 引用与样式定位。 */\n readonly idPrefix: string\n getPortal(): OverlayPortal | undefined\n getPortalContent(): HTMLElement | undefined\n /** 打开判定:syncPortalContent 仅在打开期执行内容迁移。 */\n isOpen(): boolean\n /**\n * 打开期待迁移的节点来源。两组件粒度不同(select 迁裸 option,\n * autocomplete 迁全部 childNodes),保持各自现状不在此统一。\n */\n getMigratableNodes(): Node[]\n /** 微任务回调中判断宿主是否已完成首次渲染(Lit hasUpdated)。 */\n hasUpdated(): boolean\n /** 微任务末的宿主刷新(requestUpdate)。 */\n requestUpdate(): void\n /**\n * 打开期即时同步钩子。select 在微任务内立即刷新标签与激活态\n * (portal 打开时 willUpdate 查不到面板外的新增项),autocomplete 无此需要。\n */\n onImmediateSync?(): void\n /**\n * 组件侧的逐元素交互监听(click/pointerover/pointerdown/option-update)。\n * shared 负责随 refresh 配对调用,语义由组件定义。\n */\n bindOption(option: WebUiOption): void\n unbindOption(option: WebUiOption): void\n}\n\nexport interface OptionPortalApi {\n /** 面板优先查询,面板无 option 或关闭时回退 light DOM。 */\n queryOptions(): WebUiOption[]\n /**\n * diff 出旧集合解绑、新集合绑定;按 option 身份保持激活索引。\n * 返回新注册表数组与新的激活索引(-1 表示激活项已失效,如移除/迁移)。\n */\n refresh(\n previous: readonly WebUiOption[],\n activeOption: WebUiOption | undefined\n ): {\n options: WebUiOption[]\n activeIndex: number\n }\n /** 稳定 id 分配(WeakMap 身份 + 冲突循环),跨 remove/reorder 保持不变。 */\n ensureOptionIds(options: readonly WebUiOption[]): void\n /** pending 真删除清理 + 打开期把滞留 light DOM 的节点迁入面板。 */\n syncPortalContent(): void\n /** register/unregister/contentChange 的统一微任务收敛入口(slot 监听由组件模板持有)。 */\n scheduleRefresh(): void\n /** 宿主级与逐元素 unregister 监听共用的处理器。 */\n handleOptionUnregister(event: Event): void\n /** 宿主级监听配对(connectedCallback 调用,与 dispose 配对)。 */\n bindHost(): void\n /** 断连清理:解绑宿主监听并清空 pending 集合。 */\n dispose(): void\n}\n\nconst OPTION_TAG = 'web-ui-option'\n\nexport const defineOptionPortal = () =>\n definePlugin<OptionPortalApi, OptionPortalHost>(host => {\n const pendingUnregistered = new Set<WebUiOption>()\n // 稳定 id:WeakMap 按 option 身份记忆分配结果,remove/reorder 后 id 不变;\n // aria-activedescendant / 外部样式都依赖 id 的时序稳定性。\n const optionIds = new WeakMap<WebUiOption, string>()\n let nextOptionId = 0\n\n const bindOption = (option: WebUiOption) => {\n host.bindOption(option)\n option.addEventListener('option-unregister', handleOptionUnregister)\n }\n\n const unbindOption = (option: WebUiOption) => {\n host.unbindOption(option)\n option.removeEventListener('option-unregister', handleOptionUnregister)\n }\n\n const handleOptionUnregister = (event: Event) => {\n // Portal 迁移与真实删除都会触发 unregister;先记录候选,\n // 微任务末 syncPortalContent 按最终位置区分二者。\n if (event.target instanceof HTMLElement && event.target.localName === OPTION_TAG) {\n pendingUnregistered.add(event.target as WebUiOption)\n }\n api.scheduleRefresh()\n }\n\n const handleRegister = () => {\n api.scheduleRefresh()\n }\n\n const api: OptionPortalApi = {\n queryOptions() {\n const panel = host.getPortal()?.panel ?? host.getPortalContent()\n const panelOptions = panel ? [...panel.querySelectorAll<WebUiOption>(OPTION_TAG)] : []\n // 面板内容仅在浮层打开、moveContent 之后存在;关闭时回退 light DOM,\n // 否则 portal 组件首次加载时注册表为空。\n return panelOptions.length > 0 ? panelOptions : [...host.element.querySelectorAll<WebUiOption>(OPTION_TAG)]\n },\n\n refresh(previous, activeOption) {\n const nextOptions = api.queryOptions()\n\n // register/unregister 是 composed 事件:Portal 迁移时 target 会 retarget 成宿主,\n // 不能作为监听器绑定依据;绑定只发生在按 DOM 查询确认身份的 option 上。\n for (const option of previous) if (!nextOptions.includes(option)) unbindOption(option)\n nextOptions.forEach(bindOption)\n\n // 激活索引按 option 身份保持,避免移除中间项后静默偏移到相邻项\n return {\n options: nextOptions,\n activeIndex: activeOption && nextOptions.includes(activeOption) ? nextOptions.indexOf(activeOption) : -1\n }\n },\n\n ensureOptionIds(registryOptions) {\n const usedIds = new Set(registryOptions.map(option => option.id).filter((id): id is string => Boolean(id)))\n registryOptions.forEach(option => {\n if (option.id) return\n\n let id = optionIds.get(option)\n if (!id || usedIds.has(id)) {\n do {\n id = `${host.idPrefix}-option-${++nextOptionId}`\n } while (usedIds.has(id))\n optionIds.set(option, id)\n }\n option.id = id\n usedIds.add(id)\n })\n },\n\n syncPortalContent() {\n const portal = host.getPortal()\n const portalContent = host.getPortalContent()\n if (!portal || !portalContent) return\n\n // 真删除(既不在面板也不在 light DOM)的 option 从 portal 追踪列表摘除,\n // 否则关闭时 restoreContent 会把它复活回 light DOM。\n for (const option of pendingUnregistered) {\n if (!portalContent.contains(option) && !host.element.contains(option)) portal.removeContent([option])\n }\n pendingUnregistered.clear()\n\n if (!host.isOpen()) return\n\n // Portal 打开期间框架条件渲染(v-if)可能在 light DOM 插入新节点;\n // 面板内已有内容时 queryOptions 不会回退 light DOM,必须显式迁入面板。\n const nodes = host.getMigratableNodes()\n if (nodes.length) portal.appendContent(nodes, portalContent)\n },\n\n scheduleRefresh() {\n queueMicrotask(() => {\n if (!host.element.isConnected) return\n api.syncPortalContent()\n host.requestUpdate()\n // 关闭状态下 willUpdate 查不到 portal 面板内容;打开期的新增/删除\n // 由宿主在微任务内立即同步标签与激活态。\n if (host.isOpen() && host.hasUpdated()) host.onImmediateSync?.()\n })\n },\n\n handleOptionUnregister,\n\n bindHost() {\n host.element.addEventListener('option-register', handleRegister)\n host.element.addEventListener('option-unregister', handleOptionUnregister)\n },\n\n dispose() {\n host.element.removeEventListener('option-register', handleRegister)\n host.element.removeEventListener('option-unregister', handleOptionUnregister)\n pendingUnregistered.clear()\n }\n }\n\n return api\n })\n"],"mappings":";;AAyEA,IAAM,IAAa,iBAEN,UACX,GAAgD,MAAQ;CACtD,IAAM,oBAAsB,IAAI,IAAiB,GAG3C,oBAAY,IAAI,QAA6B,GAC/C,IAAe,GAEb,KAAc,MAAwB;EAE1C,AADA,EAAK,WAAW,CAAM,GACtB,EAAO,iBAAiB,qBAAqB,CAAsB;CACrE,GAEM,KAAgB,MAAwB;EAE5C,AADA,EAAK,aAAa,CAAM,GACxB,EAAO,oBAAoB,qBAAqB,CAAsB;CACxE,GAEM,KAA0B,MAAiB;EAM/C,AAHI,EAAM,kBAAkB,eAAe,EAAM,OAAO,cAAc,KACpE,EAAoB,IAAI,EAAM,MAAqB,GAErD,EAAI,gBAAgB;CACtB,GAEM,UAAuB;EAC3B,EAAI,gBAAgB;CACtB,GAEM,IAAuB;EAC3B,eAAe;GACb,IAAM,IAAQ,EAAK,UAAU,CAAC,EAAE,SAAS,EAAK,iBAAiB,GACzD,IAAe,IAAQ,CAAC,GAAG,EAAM,iBAA8B,CAAU,CAAC,IAAI,CAAC;GAGrF,OAAO,EAAa,SAAS,IAAI,IAAe,CAAC,GAAG,EAAK,QAAQ,iBAA8B,CAAU,CAAC;EAC5G;EAEA,QAAQ,GAAU,GAAc;GAC9B,IAAM,IAAc,EAAI,aAAa;GAIrC,KAAK,IAAM,KAAU,GAAU,AAAK,EAAY,SAAS,CAAM,KAAG,EAAa,CAAM;GAIrF,OAHA,EAAY,QAAQ,CAAU,GAGvB;IACL,SAAS;IACT,aAAa,KAAgB,EAAY,SAAS,CAAY,IAAI,EAAY,QAAQ,CAAY,IAAI;GACxG;EACF;EAEA,gBAAgB,GAAiB;GAC/B,IAAM,IAAU,IAAI,IAAI,EAAgB,KAAI,MAAU,EAAO,EAAE,CAAC,CAAC,QAAQ,MAAqB,EAAQ,CAAG,CAAC;GAC1G,EAAgB,SAAQ,MAAU;IAChC,IAAI,EAAO,IAAI;IAEf,IAAI,IAAK,EAAU,IAAI,CAAM;IAC7B,IAAI,CAAC,KAAM,EAAQ,IAAI,CAAE,GAAG;KAC1B;MACE,IAAK,GAAG,EAAK,SAAS,UAAU,EAAE;YAC3B,EAAQ,IAAI,CAAE;KACvB,EAAU,IAAI,GAAQ,CAAE;IAC1B;IAEA,AADA,EAAO,KAAK,GACZ,EAAQ,IAAI,CAAE;GAChB,CAAC;EACH;EAEA,oBAAoB;GAClB,IAAM,IAAS,EAAK,UAAU,GACxB,IAAgB,EAAK,iBAAiB;GAC5C,IAAI,CAAC,KAAU,CAAC,GAAe;GAI/B,KAAK,IAAM,KAAU,GACnB,AAAI,CAAC,EAAc,SAAS,CAAM,KAAK,CAAC,EAAK,QAAQ,SAAS,CAAM,KAAG,EAAO,cAAc,CAAC,CAAM,CAAC;GAItG,IAFA,EAAoB,MAAM,GAEtB,CAAC,EAAK,OAAO,GAAG;GAIpB,IAAM,IAAQ,EAAK,mBAAmB;GACtC,AAAI,EAAM,UAAQ,EAAO,cAAc,GAAO,CAAa;EAC7D;EAEA,kBAAkB;GAChB,qBAAqB;IACd,EAAK,QAAQ,gBAClB,EAAI,kBAAkB,GACtB,EAAK,cAAc,GAGf,EAAK,OAAO,KAAK,EAAK,WAAW,KAAG,EAAK,kBAAkB;GACjE,CAAC;EACH;EAEA;EAEA,WAAW;GAET,AADA,EAAK,QAAQ,iBAAiB,mBAAmB,CAAc,GAC/D,EAAK,QAAQ,iBAAiB,qBAAqB,CAAsB;EAC3E;EAEA,UAAU;GAGR,AAFA,EAAK,QAAQ,oBAAoB,mBAAmB,CAAc,GAClE,EAAK,QAAQ,oBAAoB,qBAAqB,CAAsB,GAC5E,EAAoB,MAAM;EAC5B;CACF;CAEA,OAAO;AACT,CAAC"}
|