@greypan/web-ui 11.2.0 → 12.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 +79 -68
- package/README.md +81 -72
- package/dist/_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js +8 -0
- package/dist/assets/menu-portal.js +3 -4
- package/dist/assets/menu-portal.js.map +1 -1
- package/dist/assets/modal-surface.js +3 -4
- package/dist/assets/modal-surface.js.map +1 -1
- package/dist/assets/overlay-motion.js +3 -4
- package/dist/assets/overlay-motion.js.map +1 -1
- package/dist/assets/scrollbar.js +5 -0
- package/dist/assets/scrollbar.js.map +1 -0
- package/dist/assets/selection-control.js +3 -4
- package/dist/assets/selection-control.js.map +1 -1
- package/dist/assets/toast-containers.js +3 -4
- package/dist/assets/toast-containers.js.map +1 -1
- package/dist/classes/glass.js +3 -4
- package/dist/classes/glass.js.map +1 -1
- package/dist/classes/glow.js +3 -4
- package/dist/classes/glow.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +485 -483
- package/dist/components/autocomplete/index.d.ts.map +1 -0
- package/dist/components/autocomplete/index.js +475 -267
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js +3 -4
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/avatar/index.d.ts +26 -24
- package/dist/components/avatar/index.d.ts.map +1 -0
- package/dist/components/avatar/index.js +72 -52
- package/dist/components/avatar/index.js.map +1 -1
- package/dist/components/avatar/style.js +3 -4
- package/dist/components/avatar/style.js.map +1 -1
- package/dist/components/back-top/index.d.ts +30 -27
- package/dist/components/back-top/index.d.ts.map +1 -0
- package/dist/components/back-top/index.js +96 -62
- package/dist/components/back-top/index.js.map +1 -1
- package/dist/components/back-top/style.js +3 -4
- package/dist/components/back-top/style.js.map +1 -1
- package/dist/components/badge/index.d.ts +32 -26
- package/dist/components/badge/index.d.ts.map +1 -0
- package/dist/components/badge/index.js +116 -74
- package/dist/components/badge/index.js.map +1 -1
- package/dist/components/badge/style.js +3 -4
- package/dist/components/badge/style.js.map +1 -1
- package/dist/components/button/index.d.ts +392 -390
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +108 -74
- package/dist/components/button/index.js.map +1 -1
- package/dist/components/button/style.js +3 -4
- package/dist/components/button/style.js.map +1 -1
- package/dist/components/button-group/index.d.ts +14 -13
- package/dist/components/button-group/index.d.ts.map +1 -0
- package/dist/components/button-group/index.js +43 -33
- package/dist/components/button-group/index.js.map +1 -1
- package/dist/components/button-group/style.js +3 -4
- package/dist/components/button-group/style.js.map +1 -1
- package/dist/components/checkbox/index.d.ts +399 -397
- package/dist/components/checkbox/index.d.ts.map +1 -0
- package/dist/components/checkbox/index.js +103 -74
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox/style.js +3 -4
- package/dist/components/checkbox/style.js.map +1 -1
- package/dist/components/checkbox-group/index.d.ts +397 -396
- package/dist/components/checkbox-group/index.d.ts.map +1 -0
- package/dist/components/checkbox-group/index.js +92 -70
- package/dist/components/checkbox-group/index.js.map +1 -1
- package/dist/components/checkbox-group/style.js +3 -4
- package/dist/components/checkbox-group/style.js.map +1 -1
- package/dist/components/collapse/index.d.ts +72 -71
- package/dist/components/collapse/index.d.ts.map +1 -0
- package/dist/components/collapse/index.js +225 -130
- package/dist/components/collapse/index.js.map +1 -1
- package/dist/components/collapse/style.js +3 -4
- package/dist/components/collapse/style.js.map +1 -1
- package/dist/components/context-menu/index.d.ts +307 -310
- package/dist/components/context-menu/index.d.ts.map +1 -0
- package/dist/components/context-menu/index.js +685 -270
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/context-menu/scrim.js +3 -4
- package/dist/components/context-menu/scrim.js.map +1 -1
- package/dist/components/context-menu/style.js +3 -4
- package/dist/components/context-menu/style.js.map +1 -1
- package/dist/components/dialog/index.d.ts +87 -78
- package/dist/components/dialog/index.d.ts.map +1 -0
- package/dist/components/dialog/index.js +183 -88
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +3 -4
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.d.ts +159 -150
- package/dist/components/drawer/index.d.ts.map +1 -0
- package/dist/components/drawer/index.js +503 -242
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/style.js +3 -4
- package/dist/components/drawer/style.js.map +1 -1
- package/dist/components/dropdown/index.d.ts +125 -124
- package/dist/components/dropdown/index.d.ts.map +1 -0
- package/dist/components/dropdown/index.js +556 -328
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown/style.js +3 -4
- package/dist/components/dropdown/style.js.map +1 -1
- package/dist/components/dropdown-divider/index.d.ts +7 -6
- package/dist/components/dropdown-divider/index.d.ts.map +1 -0
- package/dist/components/dropdown-divider/index.js +10 -11
- package/dist/components/dropdown-divider/index.js.map +1 -1
- package/dist/components/dropdown-divider/style.js +3 -4
- package/dist/components/dropdown-divider/style.js.map +1 -1
- package/dist/components/dropdown-header/index.d.ts +7 -6
- package/dist/components/dropdown-header/index.d.ts.map +1 -0
- package/dist/components/dropdown-header/index.js +10 -11
- package/dist/components/dropdown-header/index.js.map +1 -1
- package/dist/components/dropdown-header/style.js +3 -4
- package/dist/components/dropdown-header/style.js.map +1 -1
- package/dist/components/dropdown-item/index.d.ts +13 -11
- package/dist/components/dropdown-item/index.d.ts.map +1 -0
- package/dist/components/dropdown-item/index.js +32 -25
- package/dist/components/dropdown-item/index.js.map +1 -1
- package/dist/components/dropdown-item/style.js +3 -4
- package/dist/components/dropdown-item/style.js.map +1 -1
- package/dist/components/editable-text/index.d.ts +506 -505
- package/dist/components/editable-text/index.d.ts.map +1 -0
- package/dist/components/editable-text/index.js +281 -127
- package/dist/components/editable-text/index.js.map +1 -1
- package/dist/components/editable-text/style.js +3 -4
- package/dist/components/editable-text/style.js.map +1 -1
- package/dist/components/empty/index.d.ts +17 -15
- package/dist/components/empty/index.d.ts.map +1 -0
- package/dist/components/empty/index.js +66 -37
- package/dist/components/empty/index.js.map +1 -1
- package/dist/components/empty/style.js +3 -4
- package/dist/components/empty/style.js.map +1 -1
- package/dist/components/icon/index.d.ts +12 -11
- package/dist/components/icon/index.d.ts.map +1 -0
- package/dist/components/icon/index.js +34 -27
- package/dist/components/icon/index.js.map +1 -1
- package/dist/components/icon/style.js +3 -4
- package/dist/components/icon/style.js.map +1 -1
- package/dist/components/image-preview/index.d.ts +58 -44
- package/dist/components/image-preview/index.d.ts.map +1 -0
- package/dist/components/image-preview/index.js +536 -265
- package/dist/components/image-preview/index.js.map +1 -1
- package/dist/components/image-preview/style.js +3 -4
- package/dist/components/image-preview/style.js.map +1 -1
- package/dist/components/index.d.ts +46 -43
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +41 -41
- package/dist/components/input/index.d.ts +418 -413
- package/dist/components/input/index.d.ts.map +1 -0
- package/dist/components/input/index.js +148 -84
- package/dist/components/input/index.js.map +1 -1
- package/dist/components/input/style.js +3 -4
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/input-number/index.d.ts +453 -447
- package/dist/components/input-number/index.d.ts.map +1 -0
- package/dist/components/input-number/index.js +484 -292
- package/dist/components/input-number/index.js.map +1 -1
- package/dist/components/input-number/style.js +3 -4
- package/dist/components/input-number/style.js.map +1 -1
- package/dist/components/layout/index.d.ts +110 -87
- package/dist/components/layout/index.d.ts.map +1 -0
- package/dist/components/layout/index.js +238 -144
- package/dist/components/layout/index.js.map +1 -1
- package/dist/components/layout/style.js +3 -4
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/middle-ellipsis/index.d.ts +93 -92
- package/dist/components/middle-ellipsis/index.d.ts.map +1 -0
- package/dist/components/middle-ellipsis/index.js +261 -107
- package/dist/components/middle-ellipsis/index.js.map +1 -1
- package/dist/components/middle-ellipsis/style.js +3 -4
- package/dist/components/middle-ellipsis/style.js.map +1 -1
- package/dist/components/middle-ellipsis/truncate.js +60 -30
- package/dist/components/middle-ellipsis/truncate.js.map +1 -1
- package/dist/components/option/index.d.ts +20 -18
- package/dist/components/option/index.d.ts.map +1 -0
- package/dist/components/option/index.js +53 -40
- package/dist/components/option/index.js.map +1 -1
- package/dist/components/option/style.js +3 -4
- package/dist/components/option/style.js.map +1 -1
- package/dist/components/popover/index.d.ts +69 -68
- package/dist/components/popover/index.d.ts.map +1 -0
- package/dist/components/popover/index.js +221 -104
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/style.js +3 -4
- package/dist/components/popover/style.js.map +1 -1
- package/dist/components/radio/index.d.ts +396 -395
- package/dist/components/radio/index.d.ts.map +1 -0
- package/dist/components/radio/index.js +91 -62
- package/dist/components/radio/index.js.map +1 -1
- package/dist/components/radio/interaction.js +10 -11
- package/dist/components/radio/interaction.js.map +1 -1
- package/dist/components/radio/style.js +3 -4
- package/dist/components/radio/style.js.map +1 -1
- package/dist/components/radio-group/index.d.ts +417 -416
- package/dist/components/radio-group/index.d.ts.map +1 -0
- package/dist/components/radio-group/index.js +307 -207
- package/dist/components/radio-group/index.js.map +1 -1
- package/dist/components/radio-group/style.js +3 -4
- package/dist/components/radio-group/style.js.map +1 -1
- package/dist/components/segmented/index.d.ts +408 -402
- package/dist/components/segmented/index.d.ts.map +1 -0
- package/dist/components/segmented/index.js +267 -158
- package/dist/components/segmented/index.js.map +1 -1
- package/dist/components/segmented/style.js +3 -4
- package/dist/components/segmented/style.js.map +1 -1
- package/dist/components/segmented-trigger/index.d.ts +18 -17
- package/dist/components/segmented-trigger/index.d.ts.map +1 -0
- package/dist/components/segmented-trigger/index.js +45 -36
- package/dist/components/segmented-trigger/index.js.map +1 -1
- package/dist/components/segmented-trigger/style.js +3 -4
- package/dist/components/segmented-trigger/style.js.map +1 -1
- package/dist/components/select/index.d.ts +450 -448
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +342 -188
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/select/style.js +3 -4
- package/dist/components/select/style.js.map +1 -1
- package/dist/components/slider/index.d.ts +428 -427
- package/dist/components/slider/index.d.ts.map +1 -0
- package/dist/components/slider/index.js +308 -216
- package/dist/components/slider/index.js.map +1 -1
- package/dist/components/slider/style.js +3 -4
- package/dist/components/slider/style.js.map +1 -1
- package/dist/components/spinner/index.d.ts +39 -38
- package/dist/components/spinner/index.d.ts.map +1 -0
- package/dist/components/spinner/index.js +118 -65
- package/dist/components/spinner/index.js.map +1 -1
- package/dist/components/spinner/style.js +3 -4
- package/dist/components/spinner/style.js.map +1 -1
- package/dist/components/svg-draw-lines/index.d.ts +40 -39
- package/dist/components/svg-draw-lines/index.d.ts.map +1 -0
- package/dist/components/svg-draw-lines/index.js +189 -106
- package/dist/components/svg-draw-lines/index.js.map +1 -1
- package/dist/components/svg-draw-lines/style.js +3 -4
- package/dist/components/svg-draw-lines/style.js.map +1 -1
- package/dist/components/switch/index.d.ts +411 -409
- package/dist/components/switch/index.d.ts.map +1 -0
- package/dist/components/switch/index.js +193 -106
- package/dist/components/switch/index.js.map +1 -1
- package/dist/components/switch/style.js +3 -4
- package/dist/components/switch/style.js.map +1 -1
- package/dist/components/textarea/index.d.ts +430 -425
- package/dist/components/textarea/index.d.ts.map +1 -0
- package/dist/components/textarea/index.js +187 -100
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/style.js +3 -4
- package/dist/components/textarea/style.js.map +1 -1
- package/dist/components/theme/index.d.ts +45 -44
- package/dist/components/theme/index.d.ts.map +1 -0
- package/dist/components/theme/index.js +268 -161
- package/dist/components/theme/index.js.map +1 -1
- package/dist/components/theme/style.js +3 -4
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/toast/index.d.ts +4 -3
- package/dist/components/toast/index.js +3 -3
- package/dist/components/toast/manager.d.ts +9 -8
- package/dist/components/toast/manager.d.ts.map +1 -0
- package/dist/components/toast/manager.js +215 -119
- package/dist/components/toast/manager.js.map +1 -1
- package/dist/components/toast/style.js +3 -4
- package/dist/components/toast/style.js.map +1 -1
- package/dist/components/toast/toast.d.ts +75 -71
- package/dist/components/toast/toast.d.ts.map +1 -0
- package/dist/components/toast/toast.js +203 -96
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/toast/types.d.ts +10 -9
- package/dist/components/toast/types.d.ts.map +1 -0
- package/dist/components/tooltip/index.d.ts +72 -62
- package/dist/components/tooltip/index.d.ts.map +1 -0
- package/dist/components/tooltip/index.js +251 -93
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/style.js +3 -4
- package/dist/components/tooltip/style.js.map +1 -1
- package/dist/icons/generated/akar-icons-check.d.ts +5 -4
- package/dist/icons/generated/akar-icons-check.d.ts.map +1 -0
- package/dist/icons/generated/akar-icons-check.js +3 -4
- package/dist/icons/generated/akar-icons-check.js.map +1 -1
- package/dist/icons/generated/akar-icons-circle-check.d.ts +5 -4
- package/dist/icons/generated/akar-icons-circle-check.d.ts.map +1 -0
- package/dist/icons/generated/akar-icons-circle-check.js +3 -4
- package/dist/icons/generated/akar-icons-circle-check.js.map +1 -1
- package/dist/icons/generated/andrsrxn-inbox.d.ts +5 -4
- package/dist/icons/generated/andrsrxn-inbox.d.ts.map +1 -0
- package/dist/icons/generated/andrsrxn-inbox.js +3 -4
- package/dist/icons/generated/andrsrxn-inbox.js.map +1 -1
- package/dist/icons/generated/bi-check-lg.d.ts +5 -4
- package/dist/icons/generated/bi-check-lg.d.ts.map +1 -0
- package/dist/icons/generated/bi-check-lg.js +3 -4
- package/dist/icons/generated/bi-check-lg.js.map +1 -1
- package/dist/icons/generated/bi-check.d.ts +5 -4
- package/dist/icons/generated/bi-check.d.ts.map +1 -0
- package/dist/icons/generated/bi-check.js +3 -4
- package/dist/icons/generated/bi-check.js.map +1 -1
- package/dist/icons/generated/fluent-tag-search-24-regular.d.ts +5 -4
- package/dist/icons/generated/fluent-tag-search-24-regular.d.ts.map +1 -0
- package/dist/icons/generated/fluent-tag-search-24-regular.js +3 -4
- package/dist/icons/generated/fluent-tag-search-24-regular.js.map +1 -1
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.d.ts +5 -4
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.d.ts.map +1 -0
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.js +3 -4
- package/dist/icons/generated/heroicons-bars-arrow-down-16-solid.js.map +1 -1
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.d.ts +5 -4
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.d.ts.map +1 -0
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.js +3 -4
- package/dist/icons/generated/heroicons-bars-arrow-up-16-solid.js.map +1 -1
- package/dist/icons/generated/heroicons-check-16-solid.d.ts +5 -4
- package/dist/icons/generated/heroicons-check-16-solid.d.ts.map +1 -0
- package/dist/icons/generated/heroicons-check-16-solid.js +3 -4
- package/dist/icons/generated/heroicons-check-16-solid.js.map +1 -1
- package/dist/icons/generated/heroicons-x-mark-16-solid.d.ts +5 -4
- package/dist/icons/generated/heroicons-x-mark-16-solid.d.ts.map +1 -0
- package/dist/icons/generated/heroicons-x-mark-16-solid.js +3 -4
- package/dist/icons/generated/heroicons-x-mark-16-solid.js.map +1 -1
- package/dist/icons/generated/jam-close-circle-f.d.ts +7 -6
- package/dist/icons/generated/jam-close-circle-f.d.ts.map +1 -0
- package/dist/icons/generated/jam-close-circle-f.js +3 -4
- package/dist/icons/generated/jam-close-circle-f.js.map +1 -1
- package/dist/icons/generated/lucide-arrow-down-to-line.d.ts +5 -4
- package/dist/icons/generated/lucide-arrow-down-to-line.d.ts.map +1 -0
- package/dist/icons/generated/lucide-arrow-down-to-line.js +3 -4
- package/dist/icons/generated/lucide-arrow-down-to-line.js.map +1 -1
- package/dist/icons/generated/lucide-arrow-up-to-line.d.ts +5 -4
- package/dist/icons/generated/lucide-arrow-up-to-line.d.ts.map +1 -0
- package/dist/icons/generated/lucide-arrow-up-to-line.js +3 -4
- package/dist/icons/generated/lucide-arrow-up-to-line.js.map +1 -1
- package/dist/icons/generated/lucide-bell.d.ts +5 -4
- package/dist/icons/generated/lucide-bell.d.ts.map +1 -0
- package/dist/icons/generated/lucide-bell.js +3 -4
- package/dist/icons/generated/lucide-bell.js.map +1 -1
- package/dist/icons/generated/lucide-check.d.ts +5 -4
- package/dist/icons/generated/lucide-check.d.ts.map +1 -0
- package/dist/icons/generated/lucide-check.js +3 -4
- package/dist/icons/generated/lucide-check.js.map +1 -1
- package/dist/icons/generated/lucide-chevron-down.d.ts +5 -4
- package/dist/icons/generated/lucide-chevron-down.d.ts.map +1 -0
- package/dist/icons/generated/lucide-chevron-down.js +3 -4
- package/dist/icons/generated/lucide-chevron-down.js.map +1 -1
- package/dist/icons/generated/lucide-chevron-left.d.ts +5 -4
- package/dist/icons/generated/lucide-chevron-left.d.ts.map +1 -0
- package/dist/icons/generated/lucide-chevron-left.js +3 -4
- package/dist/icons/generated/lucide-chevron-left.js.map +1 -1
- package/dist/icons/generated/lucide-chevron-right.d.ts +5 -4
- package/dist/icons/generated/lucide-chevron-right.d.ts.map +1 -0
- package/dist/icons/generated/lucide-chevron-right.js +3 -4
- package/dist/icons/generated/lucide-chevron-right.js.map +1 -1
- package/dist/icons/generated/lucide-chevron-up.d.ts +5 -4
- package/dist/icons/generated/lucide-chevron-up.d.ts.map +1 -0
- package/dist/icons/generated/lucide-chevron-up.js +3 -4
- package/dist/icons/generated/lucide-chevron-up.js.map +1 -1
- package/dist/icons/generated/lucide-circle-alert.d.ts +5 -4
- package/dist/icons/generated/lucide-circle-alert.d.ts.map +1 -0
- package/dist/icons/generated/lucide-circle-alert.js +3 -4
- package/dist/icons/generated/lucide-circle-alert.js.map +1 -1
- package/dist/icons/generated/lucide-clapperboard.d.ts +5 -4
- package/dist/icons/generated/lucide-clapperboard.d.ts.map +1 -0
- package/dist/icons/generated/lucide-clapperboard.js +3 -4
- package/dist/icons/generated/lucide-clapperboard.js.map +1 -1
- package/dist/icons/generated/lucide-clipboard-paste.d.ts +5 -4
- package/dist/icons/generated/lucide-clipboard-paste.d.ts.map +1 -0
- package/dist/icons/generated/lucide-clipboard-paste.js +3 -4
- package/dist/icons/generated/lucide-clipboard-paste.js.map +1 -1
- package/dist/icons/generated/lucide-code.d.ts +5 -4
- package/dist/icons/generated/lucide-code.d.ts.map +1 -0
- package/dist/icons/generated/lucide-code.js +3 -4
- package/dist/icons/generated/lucide-code.js.map +1 -1
- package/dist/icons/generated/lucide-copy.d.ts +5 -4
- package/dist/icons/generated/lucide-copy.d.ts.map +1 -0
- package/dist/icons/generated/lucide-copy.js +3 -4
- package/dist/icons/generated/lucide-copy.js.map +1 -1
- package/dist/icons/generated/lucide-download.d.ts +5 -4
- package/dist/icons/generated/lucide-download.d.ts.map +1 -0
- package/dist/icons/generated/lucide-download.js +3 -4
- package/dist/icons/generated/lucide-download.js.map +1 -1
- package/dist/icons/generated/lucide-ellipsis-vertical.d.ts +5 -4
- package/dist/icons/generated/lucide-ellipsis-vertical.d.ts.map +1 -0
- package/dist/icons/generated/lucide-ellipsis-vertical.js +3 -4
- package/dist/icons/generated/lucide-ellipsis-vertical.js.map +1 -1
- package/dist/icons/generated/lucide-external-link.d.ts +5 -4
- package/dist/icons/generated/lucide-external-link.d.ts.map +1 -0
- package/dist/icons/generated/lucide-external-link.js +3 -4
- package/dist/icons/generated/lucide-external-link.js.map +1 -1
- package/dist/icons/generated/lucide-eye.d.ts +5 -4
- package/dist/icons/generated/lucide-eye.d.ts.map +1 -0
- package/dist/icons/generated/lucide-eye.js +3 -4
- package/dist/icons/generated/lucide-eye.js.map +1 -1
- package/dist/icons/generated/lucide-file-text.d.ts +5 -4
- package/dist/icons/generated/lucide-file-text.d.ts.map +1 -0
- package/dist/icons/generated/lucide-file-text.js +3 -4
- package/dist/icons/generated/lucide-file-text.js.map +1 -1
- package/dist/icons/generated/lucide-file.d.ts +5 -4
- package/dist/icons/generated/lucide-file.d.ts.map +1 -0
- package/dist/icons/generated/lucide-file.js +3 -4
- package/dist/icons/generated/lucide-file.js.map +1 -1
- package/dist/icons/generated/lucide-film.d.ts +5 -4
- package/dist/icons/generated/lucide-film.d.ts.map +1 -0
- package/dist/icons/generated/lucide-film.js +3 -4
- package/dist/icons/generated/lucide-film.js.map +1 -1
- package/dist/icons/generated/lucide-folder-open.d.ts +5 -4
- package/dist/icons/generated/lucide-folder-open.d.ts.map +1 -0
- package/dist/icons/generated/lucide-folder-open.js +3 -4
- package/dist/icons/generated/lucide-folder-open.js.map +1 -1
- package/dist/icons/generated/lucide-folder.d.ts +5 -4
- package/dist/icons/generated/lucide-folder.d.ts.map +1 -0
- package/dist/icons/generated/lucide-folder.js +3 -4
- package/dist/icons/generated/lucide-folder.js.map +1 -1
- package/dist/icons/generated/lucide-globe.d.ts +5 -4
- package/dist/icons/generated/lucide-globe.d.ts.map +1 -0
- package/dist/icons/generated/lucide-globe.js +3 -4
- package/dist/icons/generated/lucide-globe.js.map +1 -1
- package/dist/icons/generated/lucide-headphones.d.ts +5 -4
- package/dist/icons/generated/lucide-headphones.d.ts.map +1 -0
- package/dist/icons/generated/lucide-headphones.js +3 -4
- package/dist/icons/generated/lucide-headphones.js.map +1 -1
- package/dist/icons/generated/lucide-heart.d.ts +5 -4
- package/dist/icons/generated/lucide-heart.d.ts.map +1 -0
- package/dist/icons/generated/lucide-heart.js +3 -4
- package/dist/icons/generated/lucide-heart.js.map +1 -1
- package/dist/icons/generated/lucide-image.d.ts +5 -4
- package/dist/icons/generated/lucide-image.d.ts.map +1 -0
- package/dist/icons/generated/lucide-image.js +3 -4
- package/dist/icons/generated/lucide-image.js.map +1 -1
- package/dist/icons/generated/lucide-inbox.d.ts +5 -4
- package/dist/icons/generated/lucide-inbox.d.ts.map +1 -0
- package/dist/icons/generated/lucide-inbox.js +3 -4
- package/dist/icons/generated/lucide-inbox.js.map +1 -1
- package/dist/icons/generated/lucide-info.d.ts +5 -4
- package/dist/icons/generated/lucide-info.d.ts.map +1 -0
- package/dist/icons/generated/lucide-info.js +3 -4
- package/dist/icons/generated/lucide-info.js.map +1 -1
- package/dist/icons/generated/lucide-layout-grid.d.ts +5 -4
- package/dist/icons/generated/lucide-layout-grid.d.ts.map +1 -0
- package/dist/icons/generated/lucide-layout-grid.js +3 -4
- package/dist/icons/generated/lucide-layout-grid.js.map +1 -1
- package/dist/icons/generated/lucide-link.d.ts +5 -4
- package/dist/icons/generated/lucide-link.d.ts.map +1 -0
- package/dist/icons/generated/lucide-link.js +3 -4
- package/dist/icons/generated/lucide-link.js.map +1 -1
- package/dist/icons/generated/lucide-list-filter.d.ts +5 -4
- package/dist/icons/generated/lucide-list-filter.d.ts.map +1 -0
- package/dist/icons/generated/lucide-list-filter.js +3 -4
- package/dist/icons/generated/lucide-list-filter.js.map +1 -1
- package/dist/icons/generated/lucide-list-restart.d.ts +5 -4
- package/dist/icons/generated/lucide-list-restart.d.ts.map +1 -0
- package/dist/icons/generated/lucide-list-restart.js +3 -4
- package/dist/icons/generated/lucide-list-restart.js.map +1 -1
- package/dist/icons/generated/lucide-loader-circle.d.ts +5 -4
- package/dist/icons/generated/lucide-loader-circle.d.ts.map +1 -0
- package/dist/icons/generated/lucide-loader-circle.js +3 -4
- package/dist/icons/generated/lucide-loader-circle.js.map +1 -1
- package/dist/icons/generated/lucide-mail.d.ts +5 -4
- package/dist/icons/generated/lucide-mail.d.ts.map +1 -0
- package/dist/icons/generated/lucide-mail.js +3 -4
- package/dist/icons/generated/lucide-mail.js.map +1 -1
- package/dist/icons/generated/lucide-minus.d.ts +5 -4
- package/dist/icons/generated/lucide-minus.d.ts.map +1 -0
- package/dist/icons/generated/lucide-minus.js +3 -4
- package/dist/icons/generated/lucide-minus.js.map +1 -1
- package/dist/icons/generated/lucide-music.d.ts +5 -4
- package/dist/icons/generated/lucide-music.d.ts.map +1 -0
- package/dist/icons/generated/lucide-music.js +3 -4
- package/dist/icons/generated/lucide-music.js.map +1 -1
- package/dist/icons/generated/lucide-package.d.ts +5 -4
- package/dist/icons/generated/lucide-package.d.ts.map +1 -0
- package/dist/icons/generated/lucide-package.js +3 -4
- package/dist/icons/generated/lucide-package.js.map +1 -1
- package/dist/icons/generated/lucide-pen-line.d.ts +5 -4
- package/dist/icons/generated/lucide-pen-line.d.ts.map +1 -0
- package/dist/icons/generated/lucide-pen-line.js +3 -4
- package/dist/icons/generated/lucide-pen-line.js.map +1 -1
- package/dist/icons/generated/lucide-pencil.d.ts +5 -4
- package/dist/icons/generated/lucide-pencil.d.ts.map +1 -0
- package/dist/icons/generated/lucide-pencil.js +3 -4
- package/dist/icons/generated/lucide-pencil.js.map +1 -1
- package/dist/icons/generated/lucide-play.d.ts +5 -4
- package/dist/icons/generated/lucide-play.d.ts.map +1 -0
- package/dist/icons/generated/lucide-play.js +3 -4
- package/dist/icons/generated/lucide-play.js.map +1 -1
- package/dist/icons/generated/lucide-plus.d.ts +5 -4
- package/dist/icons/generated/lucide-plus.d.ts.map +1 -0
- package/dist/icons/generated/lucide-plus.js +3 -4
- package/dist/icons/generated/lucide-plus.js.map +1 -1
- package/dist/icons/generated/lucide-printer.d.ts +5 -4
- package/dist/icons/generated/lucide-printer.d.ts.map +1 -0
- package/dist/icons/generated/lucide-printer.js +3 -4
- package/dist/icons/generated/lucide-printer.js.map +1 -1
- package/dist/icons/generated/lucide-redo-2.d.ts +5 -4
- package/dist/icons/generated/lucide-redo-2.d.ts.map +1 -0
- package/dist/icons/generated/lucide-redo-2.js +3 -4
- package/dist/icons/generated/lucide-redo-2.js.map +1 -1
- package/dist/icons/generated/lucide-refresh-cw.d.ts +5 -4
- package/dist/icons/generated/lucide-refresh-cw.d.ts.map +1 -0
- package/dist/icons/generated/lucide-refresh-cw.js +3 -4
- package/dist/icons/generated/lucide-refresh-cw.js.map +1 -1
- package/dist/icons/generated/lucide-scissors.d.ts +5 -4
- package/dist/icons/generated/lucide-scissors.d.ts.map +1 -0
- package/dist/icons/generated/lucide-scissors.js +3 -4
- package/dist/icons/generated/lucide-scissors.js.map +1 -1
- package/dist/icons/generated/lucide-search.d.ts +5 -4
- package/dist/icons/generated/lucide-search.d.ts.map +1 -0
- package/dist/icons/generated/lucide-search.js +3 -4
- package/dist/icons/generated/lucide-search.js.map +1 -1
- package/dist/icons/generated/lucide-settings.d.ts +5 -4
- package/dist/icons/generated/lucide-settings.d.ts.map +1 -0
- package/dist/icons/generated/lucide-settings.js +3 -4
- package/dist/icons/generated/lucide-settings.js.map +1 -1
- package/dist/icons/generated/lucide-sliders-horizontal.d.ts +5 -4
- package/dist/icons/generated/lucide-sliders-horizontal.d.ts.map +1 -0
- package/dist/icons/generated/lucide-sliders-horizontal.js +3 -4
- package/dist/icons/generated/lucide-sliders-horizontal.js.map +1 -1
- package/dist/icons/generated/lucide-star.d.ts +5 -4
- package/dist/icons/generated/lucide-star.d.ts.map +1 -0
- package/dist/icons/generated/lucide-star.js +3 -4
- package/dist/icons/generated/lucide-star.js.map +1 -1
- package/dist/icons/generated/lucide-tag.d.ts +5 -4
- package/dist/icons/generated/lucide-tag.d.ts.map +1 -0
- package/dist/icons/generated/lucide-tag.js +3 -4
- package/dist/icons/generated/lucide-tag.js.map +1 -1
- package/dist/icons/generated/lucide-tags.d.ts +5 -4
- package/dist/icons/generated/lucide-tags.d.ts.map +1 -0
- package/dist/icons/generated/lucide-tags.js +3 -4
- package/dist/icons/generated/lucide-tags.js.map +1 -1
- package/dist/icons/generated/lucide-trash-2.d.ts +5 -4
- package/dist/icons/generated/lucide-trash-2.d.ts.map +1 -0
- package/dist/icons/generated/lucide-trash-2.js +3 -4
- package/dist/icons/generated/lucide-trash-2.js.map +1 -1
- package/dist/icons/generated/lucide-triangle-alert.d.ts +5 -4
- package/dist/icons/generated/lucide-triangle-alert.d.ts.map +1 -0
- package/dist/icons/generated/lucide-triangle-alert.js +3 -4
- package/dist/icons/generated/lucide-triangle-alert.js.map +1 -1
- package/dist/icons/generated/lucide-undo-2.d.ts +5 -4
- package/dist/icons/generated/lucide-undo-2.d.ts.map +1 -0
- package/dist/icons/generated/lucide-undo-2.js +3 -4
- package/dist/icons/generated/lucide-undo-2.js.map +1 -1
- package/dist/icons/generated/lucide-upload.d.ts +5 -4
- package/dist/icons/generated/lucide-upload.d.ts.map +1 -0
- package/dist/icons/generated/lucide-upload.js +3 -4
- package/dist/icons/generated/lucide-upload.js.map +1 -1
- package/dist/icons/generated/lucide-user.d.ts +5 -4
- package/dist/icons/generated/lucide-user.d.ts.map +1 -0
- package/dist/icons/generated/lucide-user.js +3 -4
- package/dist/icons/generated/lucide-user.js.map +1 -1
- package/dist/icons/generated/lucide-wrench.d.ts +5 -4
- package/dist/icons/generated/lucide-wrench.d.ts.map +1 -0
- package/dist/icons/generated/lucide-wrench.js +3 -4
- package/dist/icons/generated/lucide-wrench.js.map +1 -1
- package/dist/icons/generated/lucide-x.d.ts +5 -4
- package/dist/icons/generated/lucide-x.d.ts.map +1 -0
- package/dist/icons/generated/lucide-x.js +3 -4
- package/dist/icons/generated/lucide-x.js.map +1 -1
- package/dist/icons/generated/mdi-tag-search-outline.d.ts +5 -4
- package/dist/icons/generated/mdi-tag-search-outline.d.ts.map +1 -0
- package/dist/icons/generated/mdi-tag-search-outline.js +3 -4
- package/dist/icons/generated/mdi-tag-search-outline.js.map +1 -1
- package/dist/icons/generated/ooui-close.d.ts +5 -4
- package/dist/icons/generated/ooui-close.d.ts.map +1 -0
- package/dist/icons/generated/ooui-close.js +3 -4
- package/dist/icons/generated/ooui-close.js.map +1 -1
- package/dist/icons/generated/radix-icons-panel-left-minimized.d.ts +5 -4
- package/dist/icons/generated/radix-icons-panel-left-minimized.d.ts.map +1 -0
- package/dist/icons/generated/radix-icons-panel-left-minimized.js +3 -4
- package/dist/icons/generated/radix-icons-panel-left-minimized.js.map +1 -1
- package/dist/icons/generated/radix-icons-reset.d.ts +5 -4
- package/dist/icons/generated/radix-icons-reset.d.ts.map +1 -0
- package/dist/icons/generated/radix-icons-reset.js +3 -4
- package/dist/icons/generated/radix-icons-reset.js.map +1 -1
- package/dist/icons/generated/tabler-sort-ascending-letters.d.ts +5 -4
- package/dist/icons/generated/tabler-sort-ascending-letters.d.ts.map +1 -0
- package/dist/icons/generated/tabler-sort-ascending-letters.js +3 -4
- package/dist/icons/generated/tabler-sort-ascending-letters.js.map +1 -1
- package/dist/icons/index.d.ts +75 -74
- package/dist/icons/index.js +75 -75
- package/dist/shared/element-height/index.js +51 -25
- package/dist/shared/element-height/index.js.map +1 -1
- package/dist/shared/element-width/index.js +39 -24
- package/dist/shared/element-width/index.js.map +1 -1
- package/dist/shared/events/user-change.js +14 -8
- package/dist/shared/events/user-change.js.map +1 -1
- package/dist/shared/focus/pointer-focus.js +56 -30
- package/dist/shared/focus/pointer-focus.js.map +1 -1
- package/dist/shared/focus/tabbable.js +96 -0
- package/dist/shared/focus/tabbable.js.map +1 -0
- package/dist/shared/form-association/field-id.js +13 -6
- package/dist/shared/form-association/field-id.js.map +1 -1
- package/dist/shared/form-association/index.js +75 -42
- package/dist/shared/form-association/index.js.map +1 -1
- package/dist/shared/gesture/drag-gesture.js +239 -99
- package/dist/shared/gesture/drag-gesture.js.map +1 -1
- package/dist/shared/gesture/physics.js +49 -16
- package/dist/shared/gesture/physics.js.map +1 -1
- package/dist/shared/gesture/pinch-gesture.js +111 -60
- package/dist/shared/gesture/pinch-gesture.js.map +1 -1
- package/dist/shared/group-management/index.js +181 -84
- package/dist/shared/group-management/index.js.map +1 -1
- package/dist/shared/label-emptiness/index.js +45 -26
- package/dist/shared/label-emptiness/index.js.map +1 -1
- package/dist/shared/locale/builtin-labels.js +38 -0
- package/dist/shared/locale/builtin-labels.js.map +1 -0
- package/dist/shared/menu-behavior/index.js +113 -60
- package/dist/shared/menu-behavior/index.js.map +1 -1
- package/dist/shared/menu-portal/menu-portal.js +47 -26
- package/dist/shared/menu-portal/menu-portal.js.map +1 -1
- package/dist/shared/menu-portal/menu-tree.js +132 -75
- package/dist/shared/menu-portal/menu-tree.js.map +1 -1
- package/dist/shared/normalize/index.js +16 -7
- package/dist/shared/normalize/index.js.map +1 -1
- package/dist/shared/open-state/index.js +7 -8
- package/dist/shared/open-state/index.js.map +1 -1
- package/dist/shared/option-portal/combobox-shell.js +121 -56
- package/dist/shared/option-portal/combobox-shell.js.map +1 -1
- package/dist/shared/option-portal/index.js +65 -44
- package/dist/shared/option-portal/index.js.map +1 -1
- package/dist/shared/overlay/anchored-panel.js +75 -40
- package/dist/shared/overlay/anchored-panel.js.map +1 -1
- package/dist/shared/overlay/native-dialog-presence.js +116 -52
- package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
- package/dist/shared/overlay/nested-drawer-layers.js +76 -46
- package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
- package/dist/shared/overlay/open-overlay.js +240 -136
- package/dist/shared/overlay/open-overlay.js.map +1 -1
- package/dist/shared/overlay/overlay-root.js +33 -24
- package/dist/shared/overlay/overlay-root.js.map +1 -1
- package/dist/shared/overlay/overlay.js +109 -66
- package/dist/shared/overlay/overlay.js.map +1 -1
- package/dist/shared/overlay/placement-props.js +9 -4
- package/dist/shared/overlay/placement-props.js.map +1 -1
- package/dist/shared/overlay/portal.d.ts +2 -0
- package/dist/shared/overlay/portal.d.ts.map +1 -0
- package/dist/shared/overlay/portal.js +202 -117
- package/dist/shared/overlay/portal.js.map +1 -1
- package/dist/shared/overlay/positioning-generation.js +9 -10
- package/dist/shared/overlay/positioning-generation.js.map +1 -1
- package/dist/shared/overlay/presence.js +43 -21
- package/dist/shared/overlay/presence.js.map +1 -1
- package/dist/shared/overlay/theme-overlay-scope.js +22 -19
- package/dist/shared/overlay/theme-overlay-scope.js.map +1 -1
- package/dist/shared/scroll-lock/scroll-lock.js +49 -15
- package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
- package/dist/shared/theme/duration.js +13 -6
- package/dist/shared/theme/duration.js.map +1 -1
- package/dist/shared/theme/reduced-motion.js +14 -9
- package/dist/shared/theme/reduced-motion.js.map +1 -1
- package/dist/shared/theme/root-sync.js +85 -32
- package/dist/shared/theme/root-sync.js.map +1 -1
- package/dist/shared/visible-area/index.js +81 -37
- package/dist/shared/visible-area/index.js.map +1 -1
- package/dist/types/iconify.d.ts +1 -1
- package/dist/types/iconify.js +0 -0
- package/dist/types/react.d.ts +8 -10
- package/dist/types/react.d.ts.map +1 -0
- package/dist/types/react.js +0 -0
- package/dist/types/utils.d.ts +8 -13
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +0 -0
- package/dist/types/vue.d.ts +9 -13
- package/dist/types/vue.d.ts.map +1 -0
- package/dist/types/vue.js +0 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +30 -4
- package/dist/vite/index.js.map +1 -1
- package/package.json +10 -23
- package/dist/_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js +0 -9
- package/dist/components/middle-ellipsis/truncate.d.ts +0 -62
- package/dist/components/radio/interaction.d.ts +0 -4
- package/dist/scrollbars/index.d.ts +0 -33
- package/dist/scrollbars/index.js +0 -18
- package/dist/scrollbars/index.js.map +0 -1
- package/dist/scrollbars/internal.js +0 -6
- package/dist/scrollbars/internal.js.map +0 -1
- package/dist/scrollbars/style.css +0 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { findNearestTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开 menu 后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与 menu 关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、menu 也没关闭」这种不该发生的路径。\n *\n * **窗口的起点是手势结束而不是 menu 打开**(见 `_longPressGestureEndedAt`):补发事件由\n * 引擎在 touchend 处理时生成,主线程被长任务占住时这一步能比 menu 打开晚 1s 以上\n * (实测 click 比打开晚 1325ms)。以打开时刻为起点,这个上限会被那段延迟挤爆——\n * 补发 click 落到窗口外,被当成用户真实点击把**同一次长按刚打开**的 menu 关掉。\n * 以手势结束为起点,补发事件相对它只有几毫秒,窗口才真的覆盖「同一次手势的补发」。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * menu 打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— panel 与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** panel 与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开 menu。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开 menu、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开 menu 的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的 menu 立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开 menu(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步 menu 开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n /**\n * 长按手势结束(pointerup)的时刻,补发事件窗口的起点。\n *\n * 为 null 表示「这次长按的手势还没结束」——窗口退回以 menu 打开时刻为起点。手势被\n * pointercancel 打断、或组件在抬手前就被卸载时都会走到这条兜底,行为与此前一致。\n */\n private _longPressGestureEndedAt: number | null = null\n // submenu dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级 submenu 各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。menu 打开期间存在并处于 `showModal()` 状态,关闭时随 panel 一起移除。\n *\n * panel 必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是 panel 的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,menu 在它正下方居中展开,\n * 正是 iOS / Android 原生长按 menu 的形态;左对齐会让 menu 整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是 menu**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在 panel 上缘正中 —— 也就是落在第一项的命中区里。后果是 menu 一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开 submenu)会打在 menu item 上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有 menu item 激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /**\n * 触发「menu item 激活」的那次事件对象;作用域与理由见 `_onMenuItemActivation`。\n *\n * 存**事件**而不是布尔标志:判定「这次 `close()` 是不是发生在这一次派发里」可以问事件自己\n * (`eventPhase !== 0`),不必用定时器去近似。布尔标志只能靠「派发结束时清掉」来划界,\n * 而划界的那一步必须与派发末尾落在同一个任务、同一个微任务检查点之后——那是个会错的假设。\n *\n * 存在的理由是 `close()` 的出顶层判定不能只看 `_closeSource`:消费方完全可以在自己的\n * menu item 回调里直接调 `close()`(interweave 的右键重命名就是 `emit` 之后自己关),\n * 那时 `_closeSource` 还是 `'default'`,scrim 就一直留在 top layer 里没退出,\n * 消费方随后把焦点送进编辑器的动作落在 inert 文档上被浏览器静默丢弃。\n *\n * 消费方先关、组件后关的路径(`_onMenuClick` 的 `_closeFromUser('menu-item')`)随后看到的是\n * `'menu-item'`,两条判定同向,不会互相抵消。\n */\n private _menuItemActivationEvent: Event | null = null\n /** 根 panel 的开启会话 handle;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * submenu panel → 会话 handle。子层经 `adopt` 进根 handle 子树(默认不成为独立候选),\n * 但仍各自持有 handle:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根 handle 的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态 overlay(issue #120 Block 1)。根 panel 是登记对象:submenu 经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深 submenu 优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主 menu dialog 路径与 submenu 一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // menu 打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到 menu 上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在宿主以外的容器里(theme overlay root,无主题时 document.body),不随宿主一起\n * 脱离文档。不在这里收掉就会留下一张仍然 `showModal()` 的全屏 dialog:top layer 不受宿主\n * 移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,menu 无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把 panel 挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。panel 随后 `_focusFirstItem()` 把焦点带进 menu,关闭时 UA 还回去。\n *\n * panel 挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,panel 若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', 'Context menu')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n this._menu.panel.addEventListener('click', this._onMenuItemActivation, true)\n }\n /*\n * 登记与 panel 建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把 handle\n * 撤了;此时重开必须补 claim。否则 menu 开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括 panel\n * 内部与 submenu 父项——都被 `_outsideClickGuard` 判成外部点击而关闭 menu。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的 submenu panel 要重新 adopt 回\n // 新 handle 的子树,否则根 handle 的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的 submenu panel 同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,panel 仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在 handle 的登记树之外,被判成「panel 外」,点它内部会关掉整张 menu。\n // 用例 `退场窗口内重开后,收尾中的 submenu panel 在取回前后都不可被当成 panel 外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取 handle 释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级 submenu 在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 模态化之前没有这一步,也就没有这场竞争。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在 menu item 内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。menu 已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开 menu。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n // 新会话:上一次派发留下的事件对象(若 `disconnectedCallback` 那条路径绕过了 `close()`)\n // 一并丢掉。它那时 `eventPhase` 已是 0、判定为假,但不该跨会话留着。\n this._menuItemActivationEvent = null\n return true\n }\n\n /**\n * 关闭 menu。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * menu item 激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而 menu item 的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * **判定不能只看 `_closeSource`。** 消费方可以在自己的 menu item 回调里直接调 `close()`:\n * 那时 `_onMenuClick` 还没跑到(它在 panel 上是冒泡阶段,而回调挂在条目上、先跑),\n * `_closeSource` 仍是 `'default'`,scrim 就留在 top layer 里不退,模态期间整份文档\n * 是 inert 的 —— 消费方紧接着送进编辑器的焦点被浏览器静默丢弃,症状是「重命名后焦点\n * 停在 menu item 上、编辑器根本没进编辑态」,与本段要防的 blur 是同一个根因的两种落点。\n * `_menuItemActivationEvent`(捕获阶段记下的那次派发)把那一条路径也纳入判定,\n * 理由见它的注释。\n *\n * 此刻焦点还在 menu 里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到 panel 真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item' || this._isDispatchingMenuItemActivation()) {\n this._releaseScrimModality()\n }\n this._closeSource = 'default'\n this._menuItemActivationEvent = null\n }\n\n /**\n * 「当前是否正处在一次可激活 menu item 的点击派发里」。\n *\n * 判据是 `eventPhase !== 0`(DOM 规范:派发开始前置 CAPTURING_PHASE,结束后由派发算法的\n * terminating 步骤置回 NONE),即**事件自己知道它此刻有没有在派发中**。于是作用域精确等于\n * 「这一次派发」,既不需要定时器也不需要猜微任务检查点的位置:\n *\n * - 消费方在条目自己的监听器里同步调 `close()` → 派发未结束 → 判定为真(要的就是这个)。\n * - 消费方 `stopPropagation()` 让 menu 留着 → 派发照样结束 → 之后的 Escape / 点外面 / 程序式\n * `close()` 一律判定为假,README 承诺的「那三条关闭不抢焦点」不会被这一次点击污染。\n * - 消费方把 `close()` 推到下一个任务 → 判定为假,退回不提前出顶层(与没有这个标记时一致)。\n */\n private _isDispatchingMenuItemActivation(): boolean {\n return this._menuItemActivationEvent !== null && this._menuItemActivationEvent.eventPhase !== 0\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「panel 上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 之前有一条手写的贴底分支(先按点、再按视口下缘算 `top`),那是把「贴视口底」硬编码\n * 成算术;真机反馈证明它错(menu 跑到屏幕最底部,与手指按下的位置完全脱节)。现在\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图。\n *\n * 不再按「是否触屏」分流:窄视口的鼠标右键同样期望 menu 贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。分流所需的 `_anchorAtPressPoint`\n * 连同 `_positionMenuInViewport` / `_positionMenuAtPressPoint` 两个方法一起删掉 ——\n * panel 恒为 open scrim `<dialog>` 的直接子节点(`createMenuPortalOverlay` 第三参传入\n * scrim,`menu-portal.ts` 里 `container = scrim ?? …`),所以旧代码那条\n * 「非 dialog 容器」的分支对 context-menu 本来就不可达。\n *\n * 算出来的 left/top 是**视口坐标**。panel 是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰 panel:`getComputedStyle` 会强制一次同步布局,让它发生在 panel 进入\n * 「可测」状态之前,而不是夹在「panel 刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因。把这一行移到\n * 最前面实测并没有让那个偏差消失(改前改后都是 204 对期望 206)。那个 2px 来自 flip 的\n * reset 会多跑一轮测量:那一刻 panel 宽 202px、落定后 200px。相关用例已经不再钉与视口\n * 边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把 panel 贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把 panel 下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把 panel 整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的 option,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):panel 上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进 panel 上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的 panel 会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完 panel 仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— panel 下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且 panel 极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明 panel\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,menu 才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 改按压点展开之后它**仍然必需**,而且不再只服务触屏:menu 默认从触发点向下长,触发点\n * 落在屏幕下半部时 panel 下缘会进入 home indicator 区域。它现在折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不再需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下 panel 坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「panel 顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进 menu 的第一项。\n *\n * `attempt` 是重试计数:menu item 是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「menu 开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— menu item 若始终渲染不出来\n * (空 menu、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在 menu 打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级 menu 上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供 panel 挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,scrim 又是本组件自己造的节点、不进\n * 宿主 shadow root,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— menu 一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n this._scrimParent().append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * scrim 的挂载点:**与 panel 在没有 scrim 时会落到的容器同源** —— 最近主题的 theme-owned\n * overlay root。scrim 就是那个容器的替身(panel 必须挂进 scrim,见 `updated()` 的顺序注释),\n * 所以它得站在容器该站的位置上。\n *\n * 为什么不能在整棵 DOM 里随便挑位置:`--wui-color-*` 由 `<web-ui-theme>` 写在自己的 `:host`\n * 上,只沿 DOM 树向下继承。scrim 若挂到 `document.body`,而 `<web-ui-theme>` 是 body 的**子节点**,\n * 这条继承链就断了 —— panel 与 scrim 双双落回 `var(--wui-color-*, <浅色字面量>)` 的 fallback,\n * 深色外观下 menu 是一块浅色玻璃(app 侧的 `dark:` 变体也够不到它)。挂进 theme overlay root\n * 之后,panel 与 scrim 跟 dropdown / select 一样吃同一份 token。\n *\n * 无主题时保持历史行为挂在 `document.body`:那条路径本来就没有 token 作用域可继承,改用\n * fallback overlay root 只会改变既有测试与消费者的观察面,不换来任何东西。\n */\n private _scrimParent(): HTMLElement {\n return findNearestTheme(this)?.getOverlayRoot() ?? document.body\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于 panel `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。panel 挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且 panel 的层级退回普通流 —— 在「menu 开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在 menu item 激活时。\n * 摘除仍在 `_closeScrim()`,那时 panel 已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关 menu(Q12:必关,不给 prop option),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在 panel(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:menu 打开期间下层收不到命中,事件只可能\n * 落在 scrim 或 panel 上,两者都由本组件判定。\n *\n * 更早的实现在 document 捕获阶段挂 click 监听,把「点外面」理解成「命中测试落在 menu\n * panel 之外」—— 宿主 light DOM 的每一行都满足这个条件,于是判定与行激活成了同一个\n * click 的两个后果,中间没有任何仲裁,下层目标先被激活。那条监听器连同它的实现已一并\n * 删除:模态化之后它没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的 menu 关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深 submenu 还是关整张 menu。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张 menu 没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除 panel 之前做完:panel 一脱离文档,「焦点仍在 menu 内」就再也读不到了。\n * submenu 层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘 panel,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不再由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——panel 先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * menu item 激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护不是被删除,是被**移到了更早的位置**:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是 menu item 激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的 panel 写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的 handle。\n * 注:「panel 在根 handle 子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的 panel 一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取 handle),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * submenu 与根 menu 挂进**同一个** scrim。只改根 menu 会让两层行为分裂:根层被 scrim\n * 罩住时,submenu 若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张 menu。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', 'submenu')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n submenu.panel.addEventListener('click', this._onMenuItemActivation, true)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根 handle 的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。原先按 level===0 选择根 panel 还是上一层子 panel 的父级判断随之消失:contains\n * 沿子树递归,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // submenu 打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主 menu 同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n // 宽度取 offsetWidth,不能取 rect:本方法在 `showOverlayPresence` **之前**调用,此刻 panel 要么刚由\n // `createMenuPortalOverlay` 标成 entering(新 panel),要么还带着 closing(复用 closing 缓存重开),\n // 两种 presence 都施加 `transform: scale(var(--wui-scale-enter, 0.9))`(assets/overlay-motion.css),\n // rect 因而含缩放,判据偏向「右侧放得下」。\n // 落位走下面的 computePosition,Floating UI 量的是布局尺寸、transform 不进它的读数;\n // 这条判据得自己避开同一个坑。代价是 offsetWidth 取整(border-box 到整数),换来与缩放无关。\n const canOpenRight = itemRect.right + submenu.panel.offsetWidth + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主 menu dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n /*\n * scrim 不在模态态时的手算分支。\n *\n * 这条分支**是活的**,不是历史遗留:menu item 激活走 `_releaseScrimModality()` 把 scrim `close()` 掉,\n * 但元素留在 DOM、`_scrim` 也没清(摘除只在 `_closeScrim()`);这次关闭的退场若被重开打断,\n * `_closeMenuAfterPresence` 提前退出而 `_menu` 存活,`updated()` 的 `if (!this._menu)` 便不再建新\n * scrim —— menu 开着、scrim 却 `open === false`,上面那条守卫为假。窗口是一次退场过渡(默认 160ms,\n * 随 `--wui-duration-float-exit` 拉长),且只有 menu item 激活才进得去。\n *\n * 尺寸只能取 offsetWidth/offsetHeight:与上面那条判据同因,本方法在 panel 仍带 entering/closing\n * 缩放时被调用,rect 含 `transform: scale(...)`,取 rect 会让开合方向与夹取一起算错。\n */\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const submenuWidth = submenu.panel.offsetWidth\n const submenuHeight = submenu.panel.offsetHeight\n const canOpenRight = itemRect.right + submenuWidth + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuWidth)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuHeight - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._clearLongPressFollowUp()\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:panel 上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不再二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n // 补发事件窗口改以手势结束为起点:引擎的补发 contextmenu / click 与这次 pointerup\n // 同一个输入批处理生成(实测 click 的 timeStamp 与 pointerup 处理时刻相差 ≤3ms),\n // 所以相对它窗口覆盖得到;相对「menu 打开时刻」则覆盖不到(负载下能差出 1300ms+)。\n // 只认**开 menu 那一次**手势的抬手:之后用户又按了一次时,它的 click 属于新手势,\n // 把窗口重新锚过去会让本该 dismiss 的点击被吞(「窗口过期后点击照常 dismiss」\n // 钉的正是这条)。所以只在还没锚过时记一次。\n if (this._longPressOpenedAt !== null && this._longPressGestureEndedAt === null) {\n this._longPressGestureEndedAt = performance.now()\n }\n this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开 menu 之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 起点取手势结束(补发事件与它同批生成),拿不到时才退回打开时刻。\n const anchor = this._longPressGestureEndedAt ?? openedAt\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - anchor > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._clearLongPressFollowUp()\n return false\n }\n return true\n }\n\n /** 结束「同一次手势的补发事件」窗口。与 _longPressOpenedAt 的清零同源,两处都要清。 */\n private _clearLongPressFollowUp() {\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n /*\n * 捕获阶段记「可激活的 menu item 被点了」,早于条目自己的监听器。\n *\n * 判据与 `_onMenuClick` 同源(同一个 `getMenuItemFromEvent` + 同样的 disabled /\n * submenu 排除),但只置标志、不做关闭。submenu 父项与禁用项在这一层就被排除,\n * 它们不触发关闭,不该让后续关闭提前出顶层。\n *\n * 挂在 panel 上而不是条目上:条目在开 menu 期间被搬进 panel,panel 是它们此刻共同的祖先。\n *\n * **作用域是「这一次事件派发」,不是整个会话。** menu item 被点并不必然导致关闭:消费方在\n * 自己的条目回调里 `stopPropagation()` 是常见写法(勾选型条目要让 menu 留着),此时 panel 上\n * 的冒泡监听收不到,menu 不关。若标志一直留到会话结束,那一次点击之后的**每一次**关闭——\n * Escape、点外面、程序式 `close()`——都会被误判成 menu item 激活而提前出顶层,把 README 承诺的\n * 「点外面 / Escape / 程序式关闭不抢焦点」整个破坏掉。\n *\n * 划界不靠定时器,靠事件自己:`close()` 判的是 `eventPhase !== 0`(见 `_isDispatchingMenuItemActivation`)。\n * **不能用微任务复位**——真实输入下事件由浏览器任务派发,每个监听器返回后都做一次微任务检查点,\n * 于是捕获阶段排的微任务会在条目自己的监听器(消费方同步调 `close()` 的地方)**之前**跑掉,\n * 标志到 `close()` 时已是 false,scrim 留在 top layer,消费方送进编辑器的焦点被 inert 文档丢弃。\n * 从脚本里合成 `dispatchEvent` 时调用栈非空、检查点被推迟,所以那种驱动方式会给这条缺陷\n * 盖上假绿——一条能红的回归用例必须走 `userEvent`(真实输入)而不能用 `element.click()`。\n */\n private _onMenuItemActivation = (e: Event) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled') || item.hasAttribute('submenu')) return\n this._menuItemActivationEvent = e\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的 menu 焦点不在 menu 内,e.target !== this 时也必须能 Escape 关闭;\n // menu 为模态 overlay,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层 submenu 的登记;handle 已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["style","scrimStyle"],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { findNearestTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开 menu 后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与 menu 关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、menu 也没关闭」这种不该发生的路径。\n *\n * **窗口的起点是手势结束而不是 menu 打开**(见 `_longPressGestureEndedAt`):补发事件由\n * 引擎在 touchend 处理时生成,主线程被长任务占住时这一步能比 menu 打开晚 1s 以上\n * (实测 click 比打开晚 1325ms)。以打开时刻为起点,这个上限会被那段延迟挤爆——\n * 补发 click 落到窗口外,被当成用户真实点击把**同一次长按刚打开**的 menu 关掉。\n * 以手势结束为起点,补发事件相对它只有几毫秒,窗口才真的覆盖「同一次手势的补发」。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * menu 打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— panel 与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** panel 与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开 menu。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开 menu、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开 menu 的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的 menu 立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开 menu(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步 menu 开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n /**\n * 长按手势结束(pointerup)的时刻,补发事件窗口的起点。\n *\n * 为 null 表示「这次长按的手势还没结束」——窗口退回以 menu 打开时刻为起点。手势被\n * pointercancel 打断、或组件在抬手前就被卸载时都会走到这条兜底。\n */\n private _longPressGestureEndedAt: number | null = null\n // submenu dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级 submenu 各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。menu 打开期间存在并处于 `showModal()` 状态,关闭时随 panel 一起移除。\n *\n * panel 必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是 panel 的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,menu 在它正下方居中展开,\n * 正是 iOS / Android 原生长按 menu 的形态;左对齐会让 menu 整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是 menu**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在 panel 上缘正中 —— 也就是落在第一项的命中区里。后果是 menu 一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开 submenu)会打在 menu item 上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有 menu item 激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /**\n * 触发「menu item 激活」的那次事件对象;作用域与理由见 `_onMenuItemActivation`。\n *\n * 存**事件**而不是布尔标志:判定「这次 `close()` 是不是发生在这一次派发里」可以问事件自己\n * (`eventPhase !== 0`),不必用定时器去近似。布尔标志只能靠「派发结束时清掉」来划界,\n * 而划界的那一步必须与派发末尾落在同一个任务、同一个微任务检查点之后——那是个会错的假设。\n *\n * 存在的理由是 `close()` 的出顶层判定不能只看 `_closeSource`:消费方完全可以在自己的\n * menu item 回调里直接调 `close()`(interweave 的右键重命名就是 `emit` 之后自己关),\n * 那时 `_closeSource` 还是 `'default'`,scrim 就一直留在 top layer 里没退出,\n * 消费方随后把焦点送进编辑器的动作落在 inert 文档上被浏览器静默丢弃。\n *\n * 消费方先关、组件后关的路径(`_onMenuClick` 的 `_closeFromUser('menu-item')`)随后看到的是\n * `'menu-item'`,两条判定同向,不会互相抵消。\n */\n private _menuItemActivationEvent: Event | null = null\n /** 根 panel 的开启会话 handle;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * submenu panel → 会话 handle。子层经 `adopt` 进根 handle 子树(默认不成为独立候选),\n * 但仍各自持有 handle:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根 handle 的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态 overlay(issue #120 Block 1)。根 panel 是登记对象:submenu 经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深 submenu 优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主 menu dialog 路径与 submenu 一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // menu 打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到 menu 上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在宿主以外的容器里(theme overlay root,无主题时 document.body),不随宿主一起\n * 脱离文档。不在这里收掉就会留下一张仍然 `showModal()` 的全屏 dialog:top layer 不受宿主\n * 移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,menu 无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把 panel 挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。panel 随后 `_focusFirstItem()` 把焦点带进 menu,关闭时 UA 还回去。\n *\n * panel 挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,panel 若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', 'Context menu')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n this._menu.panel.addEventListener('click', this._onMenuItemActivation, true)\n }\n /*\n * 登记与 panel 建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把 handle\n * 撤了;此时重开必须补 claim。否则 menu 开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括 panel\n * 内部与 submenu 父项——都被 `_outsideClickGuard` 判成外部点击而关闭 menu。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的 submenu panel 要重新 adopt 回\n // 新 handle 的子树,否则根 handle 的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的 submenu panel 同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,panel 仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在 handle 的登记树之外,被判成「panel 外」,点它内部会关掉整张 menu。\n // 用例 `退场窗口内重开后,收尾中的 submenu panel 在取回前后都不可被当成 panel 外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取 handle 释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级 submenu 在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 这场竞争正来自这一步。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在 menu item 内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。menu 已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开 menu。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n // 新会话:上一次派发留下的事件对象(若 `disconnectedCallback` 那条路径绕过了 `close()`)\n // 一并丢掉。它那时 `eventPhase` 已是 0、判定为假,但不该跨会话留着。\n this._menuItemActivationEvent = null\n return true\n }\n\n /**\n * 关闭 menu。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * menu item 激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而 menu item 的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * **判定不能只看 `_closeSource`。** 消费方可以在自己的 menu item 回调里直接调 `close()`:\n * 那时 `_onMenuClick` 还没跑到(它在 panel 上是冒泡阶段,而回调挂在条目上、先跑),\n * `_closeSource` 仍是 `'default'`,scrim 就留在 top layer 里不退,模态期间整份文档\n * 是 inert 的 —— 消费方紧接着送进编辑器的焦点被浏览器静默丢弃,症状是「重命名后焦点\n * 停在 menu item 上、编辑器根本没进编辑态」,与本段要防的 blur 是同一个根因的两种落点。\n * `_menuItemActivationEvent`(捕获阶段记下的那次派发)把那一条路径也纳入判定,\n * 理由见它的注释。\n *\n * 此刻焦点还在 menu 里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到 panel 真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item' || this._isDispatchingMenuItemActivation()) {\n this._releaseScrimModality()\n }\n this._closeSource = 'default'\n this._menuItemActivationEvent = null\n }\n\n /**\n * 「当前是否正处在一次可激活 menu item 的点击派发里」。\n *\n * 判据是 `eventPhase !== 0`(DOM 规范:派发开始前置 CAPTURING_PHASE,结束后由派发算法的\n * terminating 步骤置回 NONE),即**事件自己知道它此刻有没有在派发中**。于是作用域精确等于\n * 「这一次派发」,既不需要定时器也不需要猜微任务检查点的位置:\n *\n * - 消费方在条目自己的监听器里同步调 `close()` → 派发未结束 → 判定为真(要的就是这个)。\n * - 消费方 `stopPropagation()` 让 menu 留着 → 派发照样结束 → 之后的 Escape / 点外面 / 程序式\n * `close()` 一律判定为假,README 承诺的「那三条关闭不抢焦点」不会被这一次点击污染。\n * - 消费方把 `close()` 推到下一个任务 → 判定为假,退回不提前出顶层(与没有这个标记时一致)。\n */\n private _isDispatchingMenuItemActivation(): boolean {\n return this._menuItemActivationEvent !== null && this._menuItemActivationEvent.eventPhase !== 0\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「panel 上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图:把「贴视口底」硬编码\n * 成算术,真机反馈证明那是错的(menu 跑到屏幕最底部,与手指按下的位置完全脱节)。\n *\n * 不按「是否触屏」分流:窄视口的鼠标右键同样期望 menu 贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。panel 恒为 open scrim `<dialog>` 的\n * 直接子节点(`createMenuPortalOverlay` 第三参传入 scrim,`menu-portal.ts` 里\n * `container = scrim ?? …`),所以「非 dialog 容器」的分支对 context-menu 不可达。\n *\n * 算出来的 left/top 是**视口坐标**。panel 是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰 panel:`getComputedStyle` 会强制一次同步布局,让它发生在 panel 进入\n * 「可测」状态之前,而不是夹在「panel 刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因:那个 2px 来自\n * flip 的 reset 会多跑一轮测量——那一刻 panel 宽 202px、落定后 200px。相关用例不钉与\n * 视口边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把 panel 贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把 panel 下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把 panel 整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的 option,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):panel 上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进 panel 上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的 panel 会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完 panel 仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— panel 下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且 panel 极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明 panel\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,menu 才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 它**仍然必需**,而且不只服务触屏:menu 默认从触发点向下长,触发点\n * 落在屏幕下半部时 panel 下缘会进入 home indicator 区域。它折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下 panel 坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「panel 顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进 menu 的第一项。\n *\n * `attempt` 是重试计数:menu item 是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「menu 开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— menu item 若始终渲染不出来\n * (空 menu、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在 menu 打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级 menu 上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供 panel 挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,scrim 又是本组件自己造的节点、不进\n * 宿主 shadow root,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— menu 一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n this._scrimParent().append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * scrim 的挂载点:**与 panel 在没有 scrim 时会落到的容器同源** —— 最近主题的 theme-owned\n * overlay root。scrim 就是那个容器的替身(panel 必须挂进 scrim,见 `updated()` 的顺序注释),\n * 所以它得站在容器该站的位置上。\n *\n * 为什么不能在整棵 DOM 里随便挑位置:`--wui-color-*` 由 `<web-ui-theme>` 写在自己的 `:host`\n * 上,只沿 DOM 树向下继承。scrim 若挂到 `document.body`,而 `<web-ui-theme>` 是 body 的**子节点**,\n * 这条继承链就断了 —— panel 与 scrim 双双落回 `var(--wui-color-*, <浅色字面量>)` 的 fallback,\n * 深色外观下 menu 是一块浅色玻璃(app 侧的 `dark:` 变体也够不到它)。挂进 theme overlay root\n * 之后,panel 与 scrim 跟 dropdown / select 一样吃同一份 token。\n *\n * 无主题时挂在 `document.body`:那条路径本来就没有 token 作用域可继承,改用\n * fallback overlay root 只会改变既有测试与消费者的观察面,不换来任何东西。\n */\n private _scrimParent(): HTMLElement {\n return findNearestTheme(this)?.getOverlayRoot() ?? document.body\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于 panel `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。panel 挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且 panel 的层级退回普通流 —— 在「menu 开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在 menu item 激活时。\n * 摘除仍在 `_closeScrim()`,那时 panel 已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关 menu(Q12:必关,不给 prop option),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在 panel(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:menu 打开期间下层收不到命中,事件只可能\n * 落在 scrim 或 panel 上,两者都由本组件判定。\n *\n * 落点判据不能理解成「命中测试落在 menu panel 之外」—— 宿主 light DOM 的每一行都满足这\n * 个条件,于是判定与行激活就成了同一个 click 的两个后果,中间没有任何仲裁,下层目标\n * 先被激活。模态化之后这条路没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的 menu 关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深 submenu 还是关整张 menu。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张 menu 没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除 panel 之前做完:panel 一脱离文档,「焦点仍在 menu 内」就再也读不到了。\n * submenu 层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘 panel,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——panel 先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * menu item 激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护落在 menu item 激活那一刻:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是 menu item 激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的 panel 写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的 handle。\n * 注:「panel 在根 handle 子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的 panel 一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取 handle),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * submenu 与根 menu 挂进**同一个** scrim。只改根 menu 会让两层行为分裂:根层被 scrim\n * 罩住时,submenu 若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张 menu。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', 'submenu')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n submenu.panel.addEventListener('click', this._onMenuItemActivation, true)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根 handle 的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。父级判断由 contains 沿子树递归完成,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // submenu 打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主 menu 同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n // 宽度取 offsetWidth,不能取 rect:本方法在 `showOverlayPresence` **之前**调用,此刻 panel 要么刚由\n // `createMenuPortalOverlay` 标成 entering(新 panel),要么还带着 closing(复用 closing 缓存重开),\n // 两种 presence 都施加 `transform: scale(var(--wui-scale-enter, 0.9))`(assets/overlay-motion.css),\n // rect 因而含缩放,判据偏向「右侧放得下」。\n // 落位走下面的 computePosition,Floating UI 量的是布局尺寸、transform 不进它的读数;\n // 这条判据得自己避开同一个坑。代价是 offsetWidth 取整(border-box 到整数),换来与缩放无关。\n const canOpenRight = itemRect.right + submenu.panel.offsetWidth + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主 menu dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n /*\n * scrim 不在模态态时的手算分支。\n *\n * 这条分支**是活的**:menu item 激活走 `_releaseScrimModality()` 把 scrim `close()` 掉,\n * 但元素留在 DOM、`_scrim` 也没清(摘除只在 `_closeScrim()`);这次关闭的退场若被重开打断,\n * `_closeMenuAfterPresence` 提前退出而 `_menu` 存活,`updated()` 的 `if (!this._menu)` 便不再建新\n * scrim —— menu 开着、scrim 却 `open === false`,上面那条守卫为假。窗口是一次退场过渡(默认 160ms,\n * 随 `--wui-duration-float-exit` 拉长),且只有 menu item 激活才进得去。\n *\n * 尺寸只能取 offsetWidth/offsetHeight:与上面那条判据同因,本方法在 panel 仍带 entering/closing\n * 缩放时被调用,rect 含 `transform: scale(...)`,取 rect 会让开合方向与夹取一起算错。\n */\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const submenuWidth = submenu.panel.offsetWidth\n const submenuHeight = submenu.panel.offsetHeight\n const canOpenRight = itemRect.right + submenuWidth + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuWidth)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuHeight - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._clearLongPressFollowUp()\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:panel 上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不做二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n // 补发事件窗口改以手势结束为起点:引擎的补发 contextmenu / click 与这次 pointerup\n // 同一个输入批处理生成(实测 click 的 timeStamp 与 pointerup 处理时刻相差 ≤3ms),\n // 所以相对它窗口覆盖得到;相对「menu 打开时刻」则覆盖不到(负载下能差出 1300ms+)。\n // 只认**开 menu 那一次**手势的抬手:之后用户又按了一次时,它的 click 属于新手势,\n // 把窗口重新锚过去会让本该 dismiss 的点击被吞(「窗口过期后点击照常 dismiss」\n // 钉的正是这条)。所以只在还没锚过时记一次。\n if (this._longPressOpenedAt !== null && this._longPressGestureEndedAt === null) {\n this._longPressGestureEndedAt = performance.now()\n }\n this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开 menu 之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 起点取手势结束(补发事件与它同批生成),拿不到时才退回打开时刻。\n const anchor = this._longPressGestureEndedAt ?? openedAt\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - anchor > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._clearLongPressFollowUp()\n return false\n }\n return true\n }\n\n /** 结束「同一次手势的补发事件」窗口。与 _longPressOpenedAt 的清零同源,两处都要清。 */\n private _clearLongPressFollowUp() {\n this._longPressOpenedAt = null\n this._longPressGestureEndedAt = null\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n /*\n * 捕获阶段记「可激活的 menu item 被点了」,早于条目自己的监听器。\n *\n * 判据与 `_onMenuClick` 同源(同一个 `getMenuItemFromEvent` + 同样的 disabled /\n * submenu 排除),但只置标志、不做关闭。submenu 父项与禁用项在这一层就被排除,\n * 它们不触发关闭,不该让后续关闭提前出顶层。\n *\n * 挂在 panel 上而不是条目上:条目在开 menu 期间被搬进 panel,panel 是它们此刻共同的祖先。\n *\n * **作用域是「这一次事件派发」,不是整个会话。** menu item 被点并不必然导致关闭:消费方在\n * 自己的条目回调里 `stopPropagation()` 是常见写法(勾选型条目要让 menu 留着),此时 panel 上\n * 的冒泡监听收不到,menu 不关。若标志一直留到会话结束,那一次点击之后的**每一次**关闭——\n * Escape、点外面、程序式 `close()`——都会被误判成 menu item 激活而提前出顶层,把 README 承诺的\n * 「点外面 / Escape / 程序式关闭不抢焦点」整个破坏掉。\n *\n * 划界不靠定时器,靠事件自己:`close()` 判的是 `eventPhase !== 0`(见 `_isDispatchingMenuItemActivation`)。\n * **不能用微任务复位**——真实输入下事件由浏览器任务派发,每个监听器返回后都做一次微任务检查点,\n * 于是捕获阶段排的微任务会在条目自己的监听器(消费方同步调 `close()` 的地方)**之前**跑掉,\n * 标志到 `close()` 时已是 false,scrim 留在 top layer,消费方送进编辑器的焦点被 inert 文档丢弃。\n * 从脚本里合成 `dispatchEvent` 时调用栈非空、检查点被推迟,所以那种驱动方式会给这条缺陷\n * 盖上假绿——一条能红的回归用例必须走 `userEvent`(真实输入)而不能用 `element.click()`。\n */\n private _onMenuItemActivation = (e: Event) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled') || item.hasAttribute('submenu')) return\n this._menuItemActivationEvent = e\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的 menu 焦点不在 menu 内,e.target !== this 时也必须能 Escape 关闭;\n // menu 为模态 overlay,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层 submenu 的登记;handle 已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
export { e as default };
|
|
1
|
+
// src/components/context-menu/scrim.css?inline
|
|
2
|
+
var scrim_default = "dialog.wui-context-menu-scrim{width:auto;max-width:none;height:auto;max-height:none;color:inherit;background:0 0;border:0;margin:0;padding:0;position:fixed;inset:0;overflow:visible}dialog.wui-context-menu-scrim::backdrop{background:0 0}dialog.wui-context-menu-scrim:not([open]){display:block}dialog.wui-context-menu-scrim:focus,dialog.wui-context-menu-scrim:focus-visible{outline:none}\n";
|
|
3
|
+
export { scrim_default as default };
|
|
5
4
|
|
|
6
5
|
//# sourceMappingURL=scrim.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrim.js","names":[],"sources":["../../../src/components/context-menu/scrim.css?inline"],"sourcesContent":["/*\n * context-menu 的模态 scrim。以内联 style 子节点挂在 scrim 元素自身,随 scrim 一起摘除。\n *\n * 三条硬约束,改动前先读:\n *\n * 1. **绝不能**出现 transform / filter / contain / backdrop-filter / will-change /\n * perspective。menu panel 是 `position: fixed` + Floating UI `strategy: 'fixed'`,\n * 上面任意一条都会让 scrim 成为 fixed 的包含块,Floating UI 返回的坐标随即整体偏移。\n * drawer 的 dialog 带着 `transform: translate(0,0)`,`_positionSubmenu` 里的\n * `instanceof HTMLDialogElement && .open` 分支正是为那种情况才存在的。\n *\n * 2. 盒子必须**精确铺满视口**(下面把 margin / max-* / padding / border 全部清零)。\n * Floating UI 的 `strategy: 'fixed'` 会把最近的 `position: fixed` 祖先当作 offsetParent,\n * scrim 正是这样一个祖先 —— 它返回的坐标是**相对 scrim padding box** 的。\n * padding box 与视口逐像素相等时两套坐标系恰好重合,位置才对。默认 UA 的\n * `dialog:modal` 带 `margin: auto` 与 `max-width: calc(100% - 6px - 2em)`,\n * 会让 scrim 居中且比视口小一圈 —— 那是静默偏移,测试里必须用几何断言钉住。\n *\n * 3. 视觉上保持透明。scrim 的职责是**吃掉下层命中**(点外面必关 menu,且下层收不到这次\n * click),不是给页面加一层 scrim 变暗。给上下文 menu 加全屏压暗会与 dialog / drawer 的\n * scrim 语义混淆,而它们是有可见边框的实体卡片。\n * **「保持透明」必须同时管住元素与 ::backdrop**,两者是各自独立的绘制层,见下方规则。\n */\ndialog.wui-context-menu-scrim {\n position: fixed;\n inset: 0;\n\n /* panel 是 fixed,不在流内;UA 的 overflow: auto 在此既无内容可滚,也会引入 scrollbar。 */\n overflow: visible;\n\n width: auto;\n max-width: none;\n height: auto;\n max-height: none;\n margin: 0;\n padding: 0;\n border: 0;\n\n color: inherit;\n\n /* 透明底:不加变暗 scrim,理由见文件头第 3 条。 */\n background: transparent;\n}\n\n/*\n * `::backdrop` 必须单独清,否则整屏蒙一层 10% 黑。\n *\n * 它是独立伪元素,**不继承**元素的 background:上面那条 `background: transparent`\n * 只作用于元素本身,`showModal()` 一开 UA 照样按 `dialog::backdrop` 的默认值画\n * `rgba(0, 0, 0, 0.1)`。真机表现为「menu 一打开整页灰了一层」,而元素级检查\n * (scrim 的 background / filter / backdrop-filter 全是透明或 none)完全查不出来 ——\n * 那层颜色根本不在元素上。\n *\n * drawer / dialog 的 ::backdrop 是**有意**的实体卡片 scrim,不要照抄它们的取值;\n * context-menu 按裁定不压暗,这里是纯透明。\n */\ndialog.wui-context-menu-scrim::backdrop {\n background: transparent;\n}\n\n/*\n * menu item 激活触发的关闭会**提前**出顶层(见 index.ts 的 `_releaseScrimModality`):\n * 此刻焦点还在 menu 里,UA 的焦点归还正好落在 opener 上;等退场结束再让顶层就晚了,\n * 那时焦点已经被应用送走,归还会把它拽回来 blur 掉编辑器。\n *\n * scrim 元素本身必须留到 panel 真正被摘除 —— panel 挂在它里面,一起摘掉就没有退场动画了。\n * 而 UA 的 `dialog:not([open]) { display: none }` 会把 panel 一起藏掉,所以这里显式恢复可见。\n *\n * 代价是这个尾巴上 scrim 已经在 top layer 之外:下层恢复可命中,panel 的层级退回普通流。\n * 尾巴只有一次退场过渡的时长(默认 160ms),且只发生在 menu item 激活时。\n */\ndialog.wui-context-menu-scrim:not([open]) {\n display: block;\n}\n\n/*\n * scrim 是模态表面,不是可聚焦控件:`showModal()` 的 focusing steps 在 panel 还没挂进去时\n * 会把焦点交给 scrim 本身,键盘路径下 UA 会给它画一枚默认 ring(整屏一圈蓝框)。\n * 与 dialog / drawer 同一条策略(见 assets/modal-surface.css);这里就地收口,因为\n * scrim 的样式是注入到 theme overlay root / body 的独立样式表,够不到组件 shadow 内的共享层。\n */\ndialog.wui-context-menu-scrim:focus,\ndialog.wui-context-menu-scrim:focus-visible {\n outline: none;\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"scrim.js","names":[],"sources":["../../../src/components/context-menu/scrim.css?inline"],"sourcesContent":["/*\n * context-menu 的模态 scrim。以内联 style 子节点挂在 scrim 元素自身,随 scrim 一起摘除。\n *\n * 三条硬约束,改动前先读:\n *\n * 1. **绝不能**出现 transform / filter / contain / backdrop-filter / will-change /\n * perspective。menu panel 是 `position: fixed` + Floating UI `strategy: 'fixed'`,\n * 上面任意一条都会让 scrim 成为 fixed 的包含块,Floating UI 返回的坐标随即整体偏移。\n * drawer 的 dialog 带着 `transform: translate(0,0)`,`_positionSubmenu` 里的\n * `instanceof HTMLDialogElement && .open` 分支正是为那种情况才存在的。\n *\n * 2. 盒子必须**精确铺满视口**(下面把 margin / max-* / padding / border 全部清零)。\n * Floating UI 的 `strategy: 'fixed'` 会把最近的 `position: fixed` 祖先当作 offsetParent,\n * scrim 正是这样一个祖先 —— 它返回的坐标是**相对 scrim padding box** 的。\n * padding box 与视口逐像素相等时两套坐标系恰好重合,位置才对。默认 UA 的\n * `dialog:modal` 带 `margin: auto` 与 `max-width: calc(100% - 6px - 2em)`,\n * 会让 scrim 居中且比视口小一圈 —— 那是静默偏移,测试里必须用几何断言钉住。\n *\n * 3. 视觉上保持透明。scrim 的职责是**吃掉下层命中**(点外面必关 menu,且下层收不到这次\n * click),不是给页面加一层 scrim 变暗。给上下文 menu 加全屏压暗会与 dialog / drawer 的\n * scrim 语义混淆,而它们是有可见边框的实体卡片。\n * **「保持透明」必须同时管住元素与 ::backdrop**,两者是各自独立的绘制层,见下方规则。\n */\ndialog.wui-context-menu-scrim {\n position: fixed;\n inset: 0;\n\n /* panel 是 fixed,不在流内;UA 的 overflow: auto 在此既无内容可滚,也会引入 scrollbar。 */\n overflow: visible;\n\n width: auto;\n max-width: none;\n height: auto;\n max-height: none;\n margin: 0;\n padding: 0;\n border: 0;\n\n color: inherit;\n\n /* 透明底:不加变暗 scrim,理由见文件头第 3 条。 */\n background: transparent;\n}\n\n/*\n * `::backdrop` 必须单独清,否则整屏蒙一层 10% 黑。\n *\n * 它是独立伪元素,**不继承**元素的 background:上面那条 `background: transparent`\n * 只作用于元素本身,`showModal()` 一开 UA 照样按 `dialog::backdrop` 的默认值画\n * `rgba(0, 0, 0, 0.1)`。真机表现为「menu 一打开整页灰了一层」,而元素级检查\n * (scrim 的 background / filter / backdrop-filter 全是透明或 none)完全查不出来 ——\n * 那层颜色根本不在元素上。\n *\n * drawer / dialog 的 ::backdrop 是**有意**的实体卡片 scrim,不要照抄它们的取值;\n * context-menu 按裁定不压暗,这里是纯透明。\n */\ndialog.wui-context-menu-scrim::backdrop {\n background: transparent;\n}\n\n/*\n * menu item 激活触发的关闭会**提前**出顶层(见 index.ts 的 `_releaseScrimModality`):\n * 此刻焦点还在 menu 里,UA 的焦点归还正好落在 opener 上;等退场结束再让顶层就晚了,\n * 那时焦点已经被应用送走,归还会把它拽回来 blur 掉编辑器。\n *\n * scrim 元素本身必须留到 panel 真正被摘除 —— panel 挂在它里面,一起摘掉就没有退场动画了。\n * 而 UA 的 `dialog:not([open]) { display: none }` 会把 panel 一起藏掉,所以这里显式恢复可见。\n *\n * 代价是这个尾巴上 scrim 已经在 top layer 之外:下层恢复可命中,panel 的层级退回普通流。\n * 尾巴只有一次退场过渡的时长(默认 160ms),且只发生在 menu item 激活时。\n */\ndialog.wui-context-menu-scrim:not([open]) {\n display: block;\n}\n\n/*\n * scrim 是模态表面,不是可聚焦控件:`showModal()` 的 focusing steps 在 panel 还没挂进去时\n * 会把焦点交给 scrim 本身,键盘路径下 UA 会给它画一枚默认 ring(整屏一圈蓝框)。\n * 与 dialog / drawer 同一条策略(见 assets/modal-surface.css);这里就地收口,因为\n * scrim 的样式是注入到 theme overlay root / body 的独立样式表,够不到组件 shadow 内的共享层。\n */\ndialog.wui-context-menu-scrim:focus,\ndialog.wui-context-menu-scrim:focus-visible {\n outline: none;\n}\n"],"mappings":";AAuBA,IAAA,gBAAA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
export { e as default };
|
|
1
|
+
// src/components/context-menu/style.css?inline
|
|
2
|
+
var style_default = "*,:before,:after{box-sizing:border-box}:host{--wui-context-menu-safe-area-bottom:env(safe-area-inset-bottom,0px);display:inline-block}.context-menu-anchor{display:contents}\n";
|
|
3
|
+
export { style_default as default };
|
|
5
4
|
|
|
6
5
|
//# sourceMappingURL=style.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/context-menu/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n /*\n * 视口下缘的安全区高度。贴底展开时 menu 要让开它,否则 iOS 的 home indicator 会压住\n * 最下面一项。用自定义属性而不是在 JS 里直接算:JS 侧没有 `env()` 的求值入口,\n * 只能经由声明它的属性读回**计算后的**解析值。宿主可覆盖(如刘海屏模拟)。\n */\n --wui-context-menu-safe-area-bottom: env(safe-area-inset-bottom, 0px);\n display: inline-block;\n}\n\n/*\n * disabled 不在宿主上做视觉降级。它只抑制 menu 行为:menu 根本不渲染,宿主降级唯一能作用到的\n * 就是消费者塞进 default slot 的内容(触发区),而禁用 context menu 不等于禁用触发区。\n */\n\n.context-menu-anchor {\n display: contents;\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/context-menu/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n /*\n * 视口下缘的安全区高度。贴底展开时 menu 要让开它,否则 iOS 的 home indicator 会压住\n * 最下面一项。用自定义属性而不是在 JS 里直接算:JS 侧没有 `env()` 的求值入口,\n * 只能经由声明它的属性读回**计算后的**解析值。宿主可覆盖(如刘海屏模拟)。\n */\n --wui-context-menu-safe-area-bottom: env(safe-area-inset-bottom, 0px);\n display: inline-block;\n}\n\n/*\n * disabled 不在宿主上做视觉降级。它只抑制 menu 行为:menu 根本不渲染,宿主降级唯一能作用到的\n * 就是消费者塞进 default slot 的内容(触发区),而禁用 context menu 不等于禁用触发区。\n */\n\n.context-menu-anchor {\n display: contents;\n}\n"],"mappings":";AAAA,IAAA,gBAAA"}
|