@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/shared/group-management/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport { ContextConsumer, ContextProvider, createContext, type Context } from '@lit/context'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\n/*\n * 下行通道基于 @lit/context(ADR-0032):root 上的 ContextProvider 以\n * `ReadonlyMap<HTMLElement, Context>` 为载荷向全子树广播,子项侧\n * ContextConsumer 订阅并按自身元素取条目。context-request 事件 composed,\n * 可穿过中间 shadow 边界;custom element 升级顺序保证祖先先于后代升级,\n * provider 总是先于子项请求就绪。成员追踪(querySelectorAll +\n * MutationObserver/slotchange)、点击归因与 setItemSelected 上行写回\n * 不在下行通道内,仍由 GroupController 驱动。\n */\n\nexport interface SelectionGroupContext {\n readonly disabled: boolean\n readonly rovingTabStop: boolean\n readonly tabStop?: boolean\n}\n\nexport interface ButtonGroupContext {\n readonly direction: 'horizontal' | 'vertical'\n readonly isLast: boolean\n}\n\n// 载荷类型仅编译期品牌化;按载荷形状分两个 key,避免异构树串扰。\n// createContext 泛型顺序为 <ValueType, K>。\nconst SELECTION_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, SelectionGroupContext>, symbol>(\n Symbol('web-ui.selection-group-context')\n)\n\nconst BUTTON_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, ButtonGroupContext>, symbol>(\n Symbol('web-ui.button-group-context')\n)\n\n// 两个 key 的运行时形状一致(Map 载荷),泛型插件内部统一按宽松类型驱动。\ntype AnyGroupContext = Context<symbol, ReadonlyMap<HTMLElement, unknown>>\n\nconst SELECTION_GROUP_CONTEXT_LOOSE = SELECTION_GROUP_CONTEXT as unknown as AnyGroupContext\nconst BUTTON_GROUP_CONTEXT_LOOSE = BUTTON_GROUP_CONTEXT as unknown as AnyGroupContext\n\nexport const selectionGroupContextKey: AnyGroupContext = SELECTION_GROUP_CONTEXT_LOOSE\nexport const buttonGroupContextKey: AnyGroupContext = BUTTON_GROUP_CONTEXT_LOOSE\nexport const GROUP_MEMBER_DISABLED_CHANGE_EVENT = 'wui-group-member-disabled-change'\n\ninterface GroupControllerHost extends HTMLElement, ReactiveControllerHost {\n readonly renderRoot: Element | DocumentFragment\n}\n\n/**\n * 创建受管理 group 的子项侧能力:订阅最近 provider 的 context 载荷并提取\n * 自身条目。在 `make()` 时完成注册,组件无需再编写 constructor 样板。\n */\nexport function defineGroupManaged<Context>(\n item: HTMLElement & ReactiveControllerHost,\n options: {\n /** 与所属根组件使用的 context key 一致。 */\n context: AnyGroupContext\n requestUpdate: () => void\n equals?: (a: Context | undefined, b: Context | undefined) => boolean\n }\n) {\n return definePlugin(() => {\n let context: Context | undefined\n const equals = options.equals ?? Object.is\n\n const consumer = new ContextConsumer(item, {\n context: options.context,\n subscribe: true,\n callback: payload => {\n const next = (payload as ReadonlyMap<HTMLElement, Context> | undefined)?.get(item)\n if (equals(context, next)) return\n context = next\n options.requestUpdate()\n }\n })\n\n /*\n * 离开 group(被移出 provider 子树)时 ContextConsumer 只是静默退订,\n * 不会收到「条目已移除」的通知:显式清空上下文,恢复独立控件语义\n * (如 checkbox 恢复自身表单提交)。跨组移动先断连清空、再重连订阅新组。\n */\n item.addController({\n hostDisconnected() {\n if (equals(context, undefined)) return\n context = undefined\n options.requestUpdate()\n }\n })\n\n return {\n getContext(): Context | undefined {\n return context\n },\n // 供测试或特殊场景确认订阅状态。\n isSubscribed(): boolean {\n return consumer.value !== undefined\n }\n }\n })\n}\n\nexport interface GroupPresentationOptions<Item extends HTMLElement, Context> {\n readonly host: HTMLElement\n /** 与子项 defineGroupManaged 使用的 context key 一致。 */\n readonly context: AnyGroupContext\n readonly getItems: () => readonly Item[]\n readonly getContext: (item: Item, index: number, items: readonly Item[]) => Context\n}\n\n/**\n * 为分组子项注册可逆的展示上下文(@lit/context 下行通道)。不持有公开 DOM\n * 属性;sync() 以新 Map 载荷整体广播,成员变化体现为子项条目消失。\n */\nexport function defineGroupPresentation<Item extends HTMLElement, Context>(\n options: GroupPresentationOptions<Item, Context>\n) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: options.context,\n initialValue: new Map<HTMLElement, unknown>()\n })\n\n return {\n sync(): boolean {\n const nextItems = options.getItems()\n const payload = new Map<HTMLElement, unknown>()\n nextItems.forEach((item, index) => payload.set(item, options.getContext(item, index, nextItems)))\n // 新 Map 实例保证 Object.is 判定为变更,全量通知订阅者。\n // 每次 hostUpdated 都重播(即使成员/方向未变)是有意形态:每次新建\n // context 对象,但子项 defineGroupManaged 的 equals 门控 requestUpdate\n // (标量字段比较),重播不产生多余渲染;代价是比「仅成员变化才通知」\n // 更频繁的广播事件,换取零状态追踪的简单性(与 GroupController 的\n // 全量 sync 对齐)。\n provider.setValue(payload)\n return true\n },\n disconnect() {\n provider.setValue(new Map())\n }\n }\n })\n}\n\nexport interface GroupLifecycle {\n sync(): boolean\n disconnect(): void\n handleChange?(event: Event): void\n handleKeyDown?(event: KeyboardEvent): void\n}\n\ninterface GroupControllerOptions {\n afterSync?: () => void\n observeAttributes?: readonly string[]\n}\n\n/**\n * 把组生命周期插件接入 Lit,协调规则不落在组件生命周期方法里。\n */\nexport class GroupController implements ReactiveController {\n private connected = false\n private observer: MutationObserver | null = null\n private observedSlots = new Set<HTMLSlotElement>()\n constructor(\n private readonly host: GroupControllerHost,\n private readonly lifecycle: GroupLifecycle,\n private readonly options: GroupControllerOptions = {}\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n this.host.addEventListener('change', this.handleChange, true)\n this.host.addEventListener('keydown', this.handleKeyDown, true)\n this.host.addEventListener(GROUP_MEMBER_DISABLED_CHANGE_EVENT, this.handleMemberDisabledChange, true)\n this.observeHost()\n this.observeSlots()\n this.sync()\n }\n\n hostUpdated() {\n this.observeSlots()\n this.sync()\n }\n\n hostDisconnected() {\n this.connected = false\n this.host.removeEventListener('change', this.handleChange, true)\n this.host.removeEventListener('keydown', this.handleKeyDown, true)\n this.host.removeEventListener(GROUP_MEMBER_DISABLED_CHANGE_EVENT, this.handleMemberDisabledChange, true)\n this.disconnectSlots()\n this.disconnectHostObserver()\n this.lifecycle.disconnect()\n }\n\n private readonly handleChange = (event: Event) => {\n this.lifecycle.handleChange?.(event)\n }\n\n private readonly handleKeyDown = (event: KeyboardEvent) => {\n this.lifecycle.handleKeyDown?.(event)\n }\n\n private readonly handleMemberDisabledChange = () => {\n this.sync()\n }\n\n private readonly handleSlotChange = () => {\n if (!this.connected) return\n this.sync()\n }\n\n private readonly handleMutations = () => {\n if (!this.connected) return\n this.sync()\n }\n\n sync() {\n if (!this.lifecycle.sync()) return\n this.options.afterSync?.()\n }\n\n private observeHost() {\n this.disconnectHostObserver()\n // 监听 light DOM 的增删,覆盖 Vue v-if / React && 的 comment 锚点替换与深层包裹\n this.observer = new MutationObserver(this.handleMutations)\n const observerOptions: MutationObserverInit = { childList: true, subtree: true }\n if (this.options.observeAttributes?.length) {\n observerOptions.attributes = true\n observerOptions.attributeFilter = [...this.options.observeAttributes]\n }\n this.observer.observe(this.host, observerOptions)\n }\n\n private disconnectHostObserver() {\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n private observeSlots() {\n const root = this.host.renderRoot as Element\n const slots = root.querySelectorAll<HTMLSlotElement>('slot')\n for (const slot of slots) {\n if (!this.observedSlots.has(slot)) {\n slot.addEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.add(slot)\n }\n }\n // 清理已移除的 slot(热更新等场景)\n for (const slot of this.observedSlots) {\n if (!root.contains(slot)) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.delete(slot)\n }\n }\n }\n\n private disconnectSlots() {\n for (const slot of this.observedSlots) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n }\n this.observedSlots.clear()\n }\n}\n\nexport interface GroupCoordinatorOptions<Item extends HTMLElement, Value> {\n readonly host: HTMLElement\n readonly getItems: () => readonly Item[]\n readonly getValue: () => Value\n readonly setValue: (value: Value) => void\n readonly getDisabled: () => boolean\n readonly isItem: (target: EventTarget | null) => target is Item\n readonly isItemSelected: (item: Item, value: Value) => boolean\n readonly isItemTabStop?: (item: Item, items: readonly Item[]) => boolean\n readonly getItemSelected?: (item: Item) => boolean\n readonly getNextValue: (item: Item, value: Value) => Value\n readonly valuesEqual: (a: Value, b: Value) => boolean\n readonly copyValue: (value: Value) => Value\n readonly setItemSelected: (item: Item, selected: boolean) => void\n readonly dispatchValueChange: () => void\n readonly getRovingTabStop?: (item: Item, index: number, items: readonly Item[], value: Value) => boolean\n readonly handleKeyDown?: (event: KeyboardEvent) => void\n /**\n * 交互已发生但值没有变化时的回调(例如单选组成员 value 重复:点击同值成员\n * 不会改变 value,却必须把选中态收到被点击的那一项)。缺省视为无事可做。\n */\n readonly resyncOnUnchangedValue?: (item: Item) => void\n}\n\nconst ENABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false, rovingTabStop: false }\nconst ENABLED_ROVING_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false, rovingTabStop: true }\nconst DISABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: true, rovingTabStop: false }\n\nfunction getSelectionContext(disabled: boolean, rovingTabStop: boolean, tabStop?: boolean): SelectionGroupContext {\n const context = disabled\n ? DISABLED_SELECTION_CONTEXT\n : rovingTabStop\n ? ENABLED_ROVING_SELECTION_CONTEXT\n : ENABLED_SELECTION_CONTEXT\n return tabStop === undefined ? context : { ...context, tabStop }\n}\n\n/**\n * 在一个内部插件接口后收敛选中态、继承禁用与受管子项成员关系。\n * 禁用态经 @lit/context 下行广播;选中态上行写回保持直写。\n */\nexport function defineGroupCoordinator<Item extends HTMLElement, Value>(options: GroupCoordinatorOptions<Item, Value>) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: SELECTION_GROUP_CONTEXT_LOOSE,\n initialValue: new Map<HTMLElement, SelectionGroupContext>()\n })\n\n let lastValue: Value | undefined\n let lastDisabled: boolean | undefined\n let lastItems = new Set<Item>()\n let initialized = false\n\n const sync = (): boolean => {\n const nextItems = options.getItems()\n const value = options.getValue()\n const disabled = options.getDisabled()\n // 成员变化也要重放选中写回:新成员需继承当前选中态。\n const membershipChanged = nextItems.length !== lastItems.size || [...nextItems].some(item => !lastItems.has(item))\n // 子项可能在宿主首次连接后才完成 custom-element upgrade;值变得可读时,\n // 即使组值和成员集合没变,也要修复之前写入的过期选中态。\n const itemSelectionChanged =\n options.getItemSelected !== undefined &&\n nextItems.some(item => options.getItemSelected?.(item) !== options.isItemSelected(item, value))\n const selectionChanged =\n !initialized ||\n !options.valuesEqual(value, lastValue as Value) ||\n disabled !== lastDisabled ||\n membershipChanged ||\n itemSelectionChanged\n\n // 上行选中写回:根写入子项(context 是纯下行,不承担该方向)。\n if (selectionChanged) {\n for (const item of nextItems) {\n options.setItemSelected(item, options.isItemSelected(item, value))\n }\n }\n\n // 禁用下行:每次 sync 以新 Map 载荷整体广播(成员增删与禁用变化一并覆盖,\n // 新 Map 实例保证 Object.is 判定为变更)。\n provider.setValue(\n new Map(\n nextItems.map((item, index) => [\n item as HTMLElement,\n getSelectionContext(\n disabled,\n options.getRovingTabStop?.(item, index, nextItems, value) === true,\n options.isItemTabStop?.(item, nextItems)\n )\n ])\n )\n )\n\n lastItems = new Set(nextItems)\n lastValue = options.copyValue(value)\n lastDisabled = disabled\n initialized = true\n return selectionChanged\n }\n\n return {\n sync,\n disconnect() {\n provider.setValue(new Map())\n lastItems.clear()\n lastValue = undefined\n lastDisabled = undefined\n initialized = false\n },\n handleChange(event: Event) {\n if (options.getDisabled() || !options.isItem(event.target) || !options.host.contains(event.target)) {\n return\n }\n\n const current = options.getValue()\n const next = options.getNextValue(event.target, current)\n if (options.valuesEqual(current, next)) {\n options.resyncOnUnchangedValue?.(event.target)\n if (options.resyncOnUnchangedValue) sync()\n return\n }\n\n options.setValue(next)\n options.dispatchValueChange()\n },\n handleKeyDown(event: KeyboardEvent) {\n options.handleKeyDown?.(event)\n }\n }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/group-management/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport { ContextConsumer, ContextProvider, createContext, type Context } from '@lit/context'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\n/*\n * 下行通道基于 @lit/context(ADR-0032):root 上的 ContextProvider 以\n * `ReadonlyMap<HTMLElement, Context>` 为载荷向全子树广播,子项侧\n * ContextConsumer 订阅并按自身元素取条目。context-request 事件 composed,\n * 可穿过中间 shadow 边界;custom element 升级顺序保证祖先先于后代升级,\n * provider 总是先于子项请求就绪。成员追踪(querySelectorAll +\n * MutationObserver/slotchange)、点击归因与 setItemSelected 上行写回\n * 不在下行通道内,仍由 GroupController 驱动。\n */\n\nexport interface SelectionGroupContext {\n readonly disabled: boolean\n readonly rovingTabStop: boolean\n readonly tabStop?: boolean\n}\n\nexport interface ButtonGroupContext {\n readonly direction: 'horizontal' | 'vertical'\n readonly isLast: boolean\n}\n\n// 载荷类型仅编译期品牌化;按载荷形状分两个 key,避免异构树串扰。\n// createContext 泛型顺序为 <ValueType, K>。\nconst SELECTION_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, SelectionGroupContext>, symbol>(\n Symbol('web-ui.selection-group-context')\n)\n\nconst BUTTON_GROUP_CONTEXT = createContext<ReadonlyMap<HTMLElement, ButtonGroupContext>, symbol>(\n Symbol('web-ui.button-group-context')\n)\n\n// 两个 key 的运行时形状一致(Map 载荷),泛型插件内部统一按宽松类型驱动。\ntype AnyGroupContext = Context<symbol, ReadonlyMap<HTMLElement, unknown>>\n\nconst SELECTION_GROUP_CONTEXT_LOOSE = SELECTION_GROUP_CONTEXT as unknown as AnyGroupContext\nconst BUTTON_GROUP_CONTEXT_LOOSE = BUTTON_GROUP_CONTEXT as unknown as AnyGroupContext\n\nexport const selectionGroupContextKey: AnyGroupContext = SELECTION_GROUP_CONTEXT_LOOSE\nexport const buttonGroupContextKey: AnyGroupContext = BUTTON_GROUP_CONTEXT_LOOSE\nexport const GROUP_MEMBER_DISABLED_CHANGE_EVENT = 'wui-group-member-disabled-change'\n\ninterface GroupControllerHost extends HTMLElement, ReactiveControllerHost {\n readonly renderRoot: Element | DocumentFragment\n}\n\n/**\n * 创建受管理 group 的子项侧能力:订阅最近 provider 的 context 载荷并提取\n * 自身条目。在 `make()` 时完成注册,组件无需再编写 constructor 样板。\n */\nexport function defineGroupManaged<Context>(\n item: HTMLElement & ReactiveControllerHost,\n options: {\n /** 与所属根组件使用的 context key 一致。 */\n context: AnyGroupContext\n requestUpdate: () => void\n equals?: (a: Context | undefined, b: Context | undefined) => boolean\n }\n) {\n return definePlugin(() => {\n let context: Context | undefined\n const equals = options.equals ?? Object.is\n\n const consumer = new ContextConsumer(item, {\n context: options.context,\n subscribe: true,\n callback: payload => {\n const next = (payload as ReadonlyMap<HTMLElement, Context> | undefined)?.get(item)\n if (equals(context, next)) return\n context = next\n options.requestUpdate()\n }\n })\n\n /*\n * 离开 group(被移出 provider 子树)时 ContextConsumer 只是静默退订,\n * 不会收到「条目已移除」的通知:显式清空上下文,恢复独立控件语义\n * (如 checkbox 恢复自身表单提交)。跨组移动先断连清空、再重连订阅新组。\n */\n item.addController({\n hostDisconnected() {\n if (equals(context, undefined)) return\n context = undefined\n options.requestUpdate()\n }\n })\n\n return {\n getContext(): Context | undefined {\n return context\n },\n // 供测试或特殊场景确认订阅状态。\n isSubscribed(): boolean {\n return consumer.value !== undefined\n }\n }\n })\n}\n\nexport interface GroupPresentationOptions<Item extends HTMLElement, Context> {\n readonly host: HTMLElement\n /** 与子项 defineGroupManaged 使用的 context key 一致。 */\n readonly context: AnyGroupContext\n readonly getItems: () => readonly Item[]\n readonly getContext: (item: Item, index: number, items: readonly Item[]) => Context\n}\n\n/**\n * 为分组子项注册可逆的展示上下文(@lit/context 下行通道)。不持有公开 DOM\n * 属性;sync() 以新 Map 载荷整体广播,成员变化体现为子项条目消失。\n */\nexport function defineGroupPresentation<Item extends HTMLElement, Context>(\n options: GroupPresentationOptions<Item, Context>\n) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: options.context,\n initialValue: new Map<HTMLElement, unknown>()\n })\n\n return {\n sync(): boolean {\n const nextItems = options.getItems()\n const payload = new Map<HTMLElement, unknown>()\n nextItems.forEach((item, index) => payload.set(item, options.getContext(item, index, nextItems)))\n // 新 Map 实例保证 Object.is 判定为变更,全量通知订阅者。\n // 每次 hostUpdated 都重播(即使成员/方向未变)是有意形态:每次新建\n // context 对象,但子项 defineGroupManaged 的 equals 门控 requestUpdate\n // (标量字段比较),重播不产生多余渲染;代价是比「仅成员变化才通知」\n // 更频繁的广播事件,换取零状态追踪的简单性(与 GroupController 的\n // 全量 sync 对齐)。\n provider.setValue(payload)\n return true\n },\n disconnect() {\n provider.setValue(new Map())\n }\n }\n })\n}\n\nexport interface GroupLifecycle {\n sync(): boolean\n disconnect(): void\n handleChange?(event: Event): void\n handleKeyDown?(event: KeyboardEvent): void\n}\n\ninterface GroupControllerOptions {\n afterSync?: () => void\n observeAttributes?: readonly string[]\n}\n\n/**\n * 把组生命周期插件接入 Lit,协调规则不落在组件生命周期方法里。\n */\nexport class GroupController implements ReactiveController {\n private connected = false\n private observer: MutationObserver | null = null\n private observedSlots = new Set<HTMLSlotElement>()\n constructor(\n private readonly host: GroupControllerHost,\n private readonly lifecycle: GroupLifecycle,\n private readonly options: GroupControllerOptions = {}\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n this.host.addEventListener('change', this.handleChange, true)\n this.host.addEventListener('keydown', this.handleKeyDown, true)\n this.host.addEventListener(GROUP_MEMBER_DISABLED_CHANGE_EVENT, this.handleMemberDisabledChange, true)\n this.observeHost()\n this.observeSlots()\n this.sync()\n }\n\n hostUpdated() {\n this.observeSlots()\n this.sync()\n }\n\n hostDisconnected() {\n this.connected = false\n this.host.removeEventListener('change', this.handleChange, true)\n this.host.removeEventListener('keydown', this.handleKeyDown, true)\n this.host.removeEventListener(GROUP_MEMBER_DISABLED_CHANGE_EVENT, this.handleMemberDisabledChange, true)\n this.disconnectSlots()\n this.disconnectHostObserver()\n this.lifecycle.disconnect()\n }\n\n private readonly handleChange = (event: Event) => {\n this.lifecycle.handleChange?.(event)\n }\n\n private readonly handleKeyDown = (event: KeyboardEvent) => {\n this.lifecycle.handleKeyDown?.(event)\n }\n\n private readonly handleMemberDisabledChange = () => {\n this.sync()\n }\n\n private readonly handleSlotChange = () => {\n if (!this.connected) return\n this.sync()\n }\n\n private readonly handleMutations = () => {\n if (!this.connected) return\n this.sync()\n }\n\n sync() {\n if (!this.lifecycle.sync()) return\n this.options.afterSync?.()\n }\n\n private observeHost() {\n this.disconnectHostObserver()\n // 监听 light DOM 的增删,覆盖 Vue v-if / React && 的 comment 锚点替换与深层包裹\n this.observer = new MutationObserver(this.handleMutations)\n const observerOptions: MutationObserverInit = { childList: true, subtree: true }\n if (this.options.observeAttributes?.length) {\n observerOptions.attributes = true\n observerOptions.attributeFilter = [...this.options.observeAttributes]\n }\n this.observer.observe(this.host, observerOptions)\n }\n\n private disconnectHostObserver() {\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n private observeSlots() {\n const root = this.host.renderRoot as Element\n const slots = root.querySelectorAll<HTMLSlotElement>('slot')\n for (const slot of slots) {\n if (!this.observedSlots.has(slot)) {\n slot.addEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.add(slot)\n }\n }\n // 清理已移除的 slot(热更新等场景)\n for (const slot of this.observedSlots) {\n if (!root.contains(slot)) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n this.observedSlots.delete(slot)\n }\n }\n }\n\n private disconnectSlots() {\n for (const slot of this.observedSlots) {\n slot.removeEventListener('slotchange', this.handleSlotChange)\n }\n this.observedSlots.clear()\n }\n}\n\nexport interface GroupCoordinatorOptions<Item extends HTMLElement, Value> {\n readonly host: HTMLElement\n readonly getItems: () => readonly Item[]\n readonly getValue: () => Value\n readonly setValue: (value: Value) => void\n readonly getDisabled: () => boolean\n readonly isItem: (target: EventTarget | null) => target is Item\n readonly isItemSelected: (item: Item, value: Value) => boolean\n readonly isItemTabStop?: (item: Item, items: readonly Item[]) => boolean\n readonly getItemSelected?: (item: Item) => boolean\n readonly getNextValue: (item: Item, value: Value) => Value\n readonly valuesEqual: (a: Value, b: Value) => boolean\n readonly copyValue: (value: Value) => Value\n readonly setItemSelected: (item: Item, selected: boolean) => void\n readonly dispatchValueChange: () => void\n readonly getRovingTabStop?: (item: Item, index: number, items: readonly Item[], value: Value) => boolean\n readonly handleKeyDown?: (event: KeyboardEvent) => void\n /**\n * 交互已发生但值没有变化时的回调(例如单选组成员 value 重复:点击同值成员\n * 不会改变 value,却必须把选中态收到被点击的那一项)。缺省视为无事可做。\n */\n readonly resyncOnUnchangedValue?: (item: Item) => void\n}\n\nconst ENABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false, rovingTabStop: false }\nconst ENABLED_ROVING_SELECTION_CONTEXT: SelectionGroupContext = { disabled: false, rovingTabStop: true }\nconst DISABLED_SELECTION_CONTEXT: SelectionGroupContext = { disabled: true, rovingTabStop: false }\n\nfunction getSelectionContext(disabled: boolean, rovingTabStop: boolean, tabStop?: boolean): SelectionGroupContext {\n const context = disabled\n ? DISABLED_SELECTION_CONTEXT\n : rovingTabStop\n ? ENABLED_ROVING_SELECTION_CONTEXT\n : ENABLED_SELECTION_CONTEXT\n return tabStop === undefined ? context : { ...context, tabStop }\n}\n\n/**\n * 在一个内部插件接口后收敛选中态、继承禁用与受管子项成员关系。\n * 禁用态经 @lit/context 下行广播;选中态上行写回保持直写。\n */\nexport function defineGroupCoordinator<Item extends HTMLElement, Value>(options: GroupCoordinatorOptions<Item, Value>) {\n return definePlugin(() => {\n const provider = new ContextProvider(options.host, {\n context: SELECTION_GROUP_CONTEXT_LOOSE,\n initialValue: new Map<HTMLElement, SelectionGroupContext>()\n })\n\n let lastValue: Value | undefined\n let lastDisabled: boolean | undefined\n let lastItems = new Set<Item>()\n let initialized = false\n\n const sync = (): boolean => {\n const nextItems = options.getItems()\n const value = options.getValue()\n const disabled = options.getDisabled()\n // 成员变化也要重放选中写回:新成员需继承当前选中态。\n const membershipChanged = nextItems.length !== lastItems.size || [...nextItems].some(item => !lastItems.has(item))\n // 子项可能在宿主首次连接后才完成 custom-element upgrade;值变得可读时,\n // 即使组值和成员集合没变,也要修复之前写入的过期选中态。\n const itemSelectionChanged =\n options.getItemSelected !== undefined &&\n nextItems.some(item => options.getItemSelected?.(item) !== options.isItemSelected(item, value))\n const selectionChanged =\n !initialized ||\n !options.valuesEqual(value, lastValue as Value) ||\n disabled !== lastDisabled ||\n membershipChanged ||\n itemSelectionChanged\n\n // 上行选中写回:根写入子项(context 是纯下行,不承担该方向)。\n if (selectionChanged) {\n for (const item of nextItems) {\n options.setItemSelected(item, options.isItemSelected(item, value))\n }\n }\n\n // 禁用下行:每次 sync 以新 Map 载荷整体广播(成员增删与禁用变化一并覆盖,\n // 新 Map 实例保证 Object.is 判定为变更)。\n provider.setValue(\n new Map(\n nextItems.map((item, index) => [\n item as HTMLElement,\n getSelectionContext(\n disabled,\n options.getRovingTabStop?.(item, index, nextItems, value) === true,\n options.isItemTabStop?.(item, nextItems)\n )\n ])\n )\n )\n\n lastItems = new Set(nextItems)\n lastValue = options.copyValue(value)\n lastDisabled = disabled\n initialized = true\n return selectionChanged\n }\n\n return {\n sync,\n disconnect() {\n provider.setValue(new Map())\n lastItems.clear()\n lastValue = undefined\n lastDisabled = undefined\n initialized = false\n },\n handleChange(event: Event) {\n if (options.getDisabled() || !options.isItem(event.target) || !options.host.contains(event.target)) {\n return\n }\n\n const current = options.getValue()\n const next = options.getNextValue(event.target, current)\n if (options.valuesEqual(current, next)) {\n options.resyncOnUnchangedValue?.(event.target)\n if (options.resyncOnUnchangedValue) sync()\n return\n }\n\n options.setValue(next)\n options.dispatchValueChange()\n },\n handleKeyDown(event: KeyboardEvent) {\n options.handleKeyDown?.(event)\n }\n }\n })\n}\n"],"mappings":"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"}
|
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// src/shared/label-emptiness/index.ts
|
|
2
|
+
const watched = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
let observer;
|
|
4
|
+
/**
|
|
5
|
+
* 全模块共用一个 ResizeObserver:一个列表里可能有上百个选择控件,逐实例建观察者要给每个控件单独排
|
|
6
|
+
* 一次尺寸回调。没有 ResizeObserver 的环境(jsdom)没有布局可测,直接不接。
|
|
7
|
+
*/
|
|
8
|
+
function getObserver() {
|
|
9
|
+
if (typeof ResizeObserver === "undefined") return null;
|
|
10
|
+
if (observer) return observer;
|
|
11
|
+
observer = new ResizeObserver((entries) => {
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const target = watched.get(entry.target);
|
|
14
|
+
if (!target) continue;
|
|
15
|
+
const empty = entry.contentRect.width === 0;
|
|
16
|
+
if (target.empty === empty) continue;
|
|
17
|
+
target.empty = empty;
|
|
18
|
+
target.notify(empty);
|
|
10
19
|
}
|
|
11
|
-
})
|
|
20
|
+
});
|
|
21
|
+
return observer;
|
|
12
22
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
function watch(target, notify) {
|
|
24
|
+
const shared = getObserver();
|
|
25
|
+
if (!shared) return;
|
|
26
|
+
watched.set(target, {
|
|
27
|
+
notify,
|
|
28
|
+
empty: false
|
|
29
|
+
});
|
|
30
|
+
shared.observe(target);
|
|
19
31
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
32
|
+
function unwatch(target) {
|
|
33
|
+
watched.delete(target);
|
|
34
|
+
observer?.unobserve(target);
|
|
22
35
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
/** 把 label 盒子的空 / 非空变化报给宿主;断开重连后重新接上观察。 */
|
|
37
|
+
var LabelEmptinessController = class {
|
|
38
|
+
constructor(host, selector, notify) {
|
|
39
|
+
this.host = host;
|
|
40
|
+
this.selector = selector;
|
|
41
|
+
this.notify = notify;
|
|
42
|
+
host.addController(this);
|
|
26
43
|
}
|
|
27
44
|
hostConnected() {
|
|
28
45
|
this.attach();
|
|
@@ -31,15 +48,17 @@ var a = class {
|
|
|
31
48
|
this.attach();
|
|
32
49
|
}
|
|
33
50
|
hostDisconnected() {
|
|
34
|
-
|
|
51
|
+
if (this.target) unwatch(this.target);
|
|
52
|
+
this.target = void 0;
|
|
35
53
|
}
|
|
36
54
|
attach() {
|
|
37
55
|
if (this.target) return;
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
const target = this.host.renderRoot.querySelector(this.selector);
|
|
57
|
+
if (!target) return;
|
|
58
|
+
this.target = target;
|
|
59
|
+
watch(target, this.notify);
|
|
40
60
|
}
|
|
41
61
|
};
|
|
42
|
-
|
|
43
|
-
export { a as LabelEmptinessController };
|
|
62
|
+
export { LabelEmptinessController };
|
|
44
63
|
|
|
45
64
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/label-emptiness/index.ts"],"sourcesContent":["/**\n * 判断 label 槽位有没有真的画出东西。\n *\n * radio / checkbox 的触发行是 `inline-flex` + `gap: 10px`,宿主宽度由内容撑开,所以那条 gap 是\n * indicator 与 label 之间的距离。但 gap 只认「这里有个 flex item」,不认它有没有宽度:槽位空着,或者只放了\n * 一个视觉隐藏的无障碍名(`.sr-only` 出流,不产生行内盒),label 盒子是 0 宽,indicator 右边仍然多出 10px。\n *\n * 判据因此取渲染宽度而不是「槽位有没有被指派内容」——两者只在无障碍名那一种用法上分开,而那正是这里\n * 要修的一种。CSS 也判不出这件事:`slot:empty` 看的是 slot 自己的子节点,被 assign 进来的节点并不是\n * 它的孩子。同理不能用 `hidden` 把盒子收掉,display: none 会连带把里面的文字从无障碍名里去掉。\n */\nimport type { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'\n\ninterface Watched {\n readonly notify: (empty: boolean) => void\n empty: boolean\n}\n\nconst watched = new WeakMap<Element, Watched>()\nlet observer: ResizeObserver | undefined\n\n/**\n * 全模块共用一个 ResizeObserver:一个列表里可能有上百个选择控件,逐实例建观察者要给每个控件单独排\n * 一次尺寸回调。没有 ResizeObserver 的环境(jsdom)没有布局可测,直接不接。\n */\nfunction getObserver(): ResizeObserver | null {\n if (typeof ResizeObserver === 'undefined') return null\n if (observer) return observer\n\n observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n const target = watched.get(entry.target)\n if (!target) continue\n\n const empty = entry.contentRect.width === 0\n if (target.empty === empty) continue\n\n target.empty = empty\n target.notify(empty)\n }\n })\n\n return observer\n}\n\nfunction watch(target: Element, notify: (empty: boolean) => void) {\n const shared = getObserver()\n if (!shared) return\n\n // 初值跟宿主的默认状态一致:非空。首帧的真实尺寸由 observe() 的初始回调报上来。\n watched.set(target, { notify, empty: false })\n shared.observe(target)\n}\n\nfunction unwatch(target: Element) {\n watched.delete(target)\n observer?.unobserve(target)\n}\n\n/** 把 label 盒子的空 / 非空变化报给宿主;断开重连后重新接上观察。 */\nexport class LabelEmptinessController implements ReactiveController {\n private target: Element | undefined\n\n constructor(\n private readonly host: ReactiveControllerHost & LitElement,\n private readonly selector: string,\n private readonly notify: (empty: boolean) => void\n ) {\n host.addController(this)\n }\n\n /*\n * 两个生命周期都要接:重连(移出 DOM 再放回去、列表重排、Teleport)不会排一次更新,所以只挂在\n * hostUpdated 上的话,观察在断开时被拆掉就再也接不回来,label 后来改成什么都不显示宽度都停在旧值上。\n * 而首连时 shadow root 还没有子节点,这里查不到目标,得靠 hostUpdated 补上第一次。\n */\n hostConnected() {\n this.attach()\n }\n\n hostUpdated() {\n this.attach()\n }\n\n hostDisconnected() {\n if (this.target) unwatch(this.target)\n this.target = undefined\n }\n\n private attach() {\n if (this.target) return\n\n const target = this.host.renderRoot.querySelector(this.selector)\n if (!target) return\n\n this.target = target\n watch(target, this.notify)\n }\n}\n"],"mappings":";AAkBA,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/label-emptiness/index.ts"],"sourcesContent":["/**\n * 判断 label 槽位有没有真的画出东西。\n *\n * radio / checkbox 的触发行是 `inline-flex` + `gap: 10px`,宿主宽度由内容撑开,所以那条 gap 是\n * indicator 与 label 之间的距离。但 gap 只认「这里有个 flex item」,不认它有没有宽度:槽位空着,或者只放了\n * 一个视觉隐藏的无障碍名(`.sr-only` 出流,不产生行内盒),label 盒子是 0 宽,indicator 右边仍然多出 10px。\n *\n * 判据因此取渲染宽度而不是「槽位有没有被指派内容」——两者只在无障碍名那一种用法上分开,而那正是这里\n * 要修的一种。CSS 也判不出这件事:`slot:empty` 看的是 slot 自己的子节点,被 assign 进来的节点并不是\n * 它的孩子。同理不能用 `hidden` 把盒子收掉,display: none 会连带把里面的文字从无障碍名里去掉。\n */\nimport type { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'\n\ninterface Watched {\n readonly notify: (empty: boolean) => void\n empty: boolean\n}\n\nconst watched = new WeakMap<Element, Watched>()\nlet observer: ResizeObserver | undefined\n\n/**\n * 全模块共用一个 ResizeObserver:一个列表里可能有上百个选择控件,逐实例建观察者要给每个控件单独排\n * 一次尺寸回调。没有 ResizeObserver 的环境(jsdom)没有布局可测,直接不接。\n */\nfunction getObserver(): ResizeObserver | null {\n if (typeof ResizeObserver === 'undefined') return null\n if (observer) return observer\n\n observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n const target = watched.get(entry.target)\n if (!target) continue\n\n const empty = entry.contentRect.width === 0\n if (target.empty === empty) continue\n\n target.empty = empty\n target.notify(empty)\n }\n })\n\n return observer\n}\n\nfunction watch(target: Element, notify: (empty: boolean) => void) {\n const shared = getObserver()\n if (!shared) return\n\n // 初值跟宿主的默认状态一致:非空。首帧的真实尺寸由 observe() 的初始回调报上来。\n watched.set(target, { notify, empty: false })\n shared.observe(target)\n}\n\nfunction unwatch(target: Element) {\n watched.delete(target)\n observer?.unobserve(target)\n}\n\n/** 把 label 盒子的空 / 非空变化报给宿主;断开重连后重新接上观察。 */\nexport class LabelEmptinessController implements ReactiveController {\n private target: Element | undefined\n\n constructor(\n private readonly host: ReactiveControllerHost & LitElement,\n private readonly selector: string,\n private readonly notify: (empty: boolean) => void\n ) {\n host.addController(this)\n }\n\n /*\n * 两个生命周期都要接:重连(移出 DOM 再放回去、列表重排、Teleport)不会排一次更新,所以只挂在\n * hostUpdated 上的话,观察在断开时被拆掉就再也接不回来,label 后来改成什么都不显示宽度都停在旧值上。\n * 而首连时 shadow root 还没有子节点,这里查不到目标,得靠 hostUpdated 补上第一次。\n */\n hostConnected() {\n this.attach()\n }\n\n hostUpdated() {\n this.attach()\n }\n\n hostDisconnected() {\n if (this.target) unwatch(this.target)\n this.target = undefined\n }\n\n private attach() {\n if (this.target) return\n\n const target = this.host.renderRoot.querySelector(this.selector)\n if (!target) return\n\n this.target = target\n watch(target, this.notify)\n }\n}\n"],"mappings":";AAkBA,MAAM,0BAAU,IAAI,QAA0B;AAC9C,IAAI;;;;;AAMJ,SAAS,cAAqC;CAC5C,IAAI,OAAO,mBAAmB,aAAa,OAAO;CAClD,IAAI,UAAU,OAAO;CAErB,WAAW,IAAI,gBAAe,YAAW;EACvC,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAS,QAAQ,IAAI,MAAM,MAAM;GACvC,IAAI,CAAC,QAAQ;GAEb,MAAM,QAAQ,MAAM,YAAY,UAAU;GAC1C,IAAI,OAAO,UAAU,OAAO;GAE5B,OAAO,QAAQ;GACf,OAAO,OAAO,KAAK;EACrB;CACF,CAAC;CAED,OAAO;AACT;AAEA,SAAS,MAAM,QAAiB,QAAkC;CAChE,MAAM,SAAS,YAAY;CAC3B,IAAI,CAAC,QAAQ;CAGb,QAAQ,IAAI,QAAQ;EAAE;EAAQ,OAAO;CAAM,CAAC;CAC5C,OAAO,QAAQ,MAAM;AACvB;AAEA,SAAS,QAAQ,QAAiB;CAChC,QAAQ,OAAO,MAAM;CACrB,UAAU,UAAU,MAAM;AAC5B;;AAGA,IAAa,2BAAb,MAAoE;CAGlE,YACE,MACA,UACA,QACA;EAHiB,KAAA,OAAA;EACA,KAAA,WAAA;EACA,KAAA,SAAA;EAEjB,KAAK,cAAc,IAAI;CACzB;CAOA,gBAAgB;EACd,KAAK,OAAO;CACd;CAEA,cAAc;EACZ,KAAK,OAAO;CACd;CAEA,mBAAmB;EACjB,IAAI,KAAK,QAAQ,QAAQ,KAAK,MAAM;EACpC,KAAK,SAAS,KAAA;CAChB;CAEA,SAAiB;EACf,IAAI,KAAK,QAAQ;EAEjB,MAAM,SAAS,KAAK,KAAK,WAAW,cAAc,KAAK,QAAQ;EAC/D,IAAI,CAAC,QAAQ;EAEb,KAAK,SAAS;EACd,MAAM,QAAQ,KAAK,MAAM;CAC3B;AACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/shared/locale/builtin-labels.ts
|
|
2
|
+
const LABELS = {
|
|
3
|
+
clear: "Clear",
|
|
4
|
+
decrease: "Decrease",
|
|
5
|
+
increase: "Increase",
|
|
6
|
+
close: "Close",
|
|
7
|
+
backToTop: "Back to top",
|
|
8
|
+
collapseSidebar: "Collapse sidebar",
|
|
9
|
+
expandSidebar: "Expand sidebar",
|
|
10
|
+
resizeSidebar: "Resize sidebar",
|
|
11
|
+
openNavigationMenu: "Open navigation menu",
|
|
12
|
+
mainNavigation: "Main navigation",
|
|
13
|
+
unread: "Unread",
|
|
14
|
+
unreadCount: "{count} unread messages",
|
|
15
|
+
noUnread: "No unread messages",
|
|
16
|
+
previousImage: "Previous image",
|
|
17
|
+
nextImage: "Next image",
|
|
18
|
+
zoomIn: "Zoom in",
|
|
19
|
+
zoomOut: "Zoom out",
|
|
20
|
+
resetZoom: "Reset zoom"
|
|
21
|
+
};
|
|
22
|
+
/** 显式覆盖优先(trim 后非空才生效);否则回退内置英文文案。 */
|
|
23
|
+
function resolveBuiltinLabel(key, override) {
|
|
24
|
+
const custom = override?.trim();
|
|
25
|
+
if (custom) return custom;
|
|
26
|
+
return LABELS[key];
|
|
27
|
+
}
|
|
28
|
+
/** 成组覆盖(`ariaLabels` 对象 property)里取单个 key;未提供的 key 回退内置英文。 */
|
|
29
|
+
function resolveBuiltinLabelSet(labels, key) {
|
|
30
|
+
return resolveBuiltinLabel(key, labels?.[key]);
|
|
31
|
+
}
|
|
32
|
+
/** 把 `{count}` 这类占位替换成实参;没有对应实参的占位原样保留。 */
|
|
33
|
+
function formatBuiltinLabel(template, params) {
|
|
34
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match);
|
|
35
|
+
}
|
|
36
|
+
export { formatBuiltinLabel, resolveBuiltinLabel, resolveBuiltinLabelSet };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=builtin-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin-labels.js","names":[],"sources":["../../../src/shared/locale/builtin-labels.ts"],"sourcesContent":["/**\n * 内置无障碍文案的唯一样表:图标控件的可访问名必须有值(无名称的图标按钮是\n * 默认的无障碍违规),内容文案不属于这里,始终由外部传入。\n *\n * fallback 固定英文,不跟随运行环境语言;需要其他语言时经覆盖通道改写——\n * 单标签组件用专用 attribute(`aria-close-label` 这类),多 key 成套组件用\n * `ariaLabels` 对象 property。分界规则见 ADR-0016。\n */\nexport type BuiltinLabelKey =\n | 'clear'\n | 'decrease'\n | 'increase'\n | 'close'\n | 'backToTop'\n | 'collapseSidebar'\n | 'expandSidebar'\n | 'resizeSidebar'\n | 'openNavigationMenu'\n | 'mainNavigation'\n | 'unread'\n | 'unreadCount'\n | 'noUnread'\n | 'previousImage'\n | 'nextImage'\n | 'zoomIn'\n | 'zoomOut'\n | 'resetZoom'\n\nconst LABELS: Record<BuiltinLabelKey, string> = {\n clear: 'Clear',\n decrease: 'Decrease',\n increase: 'Increase',\n close: 'Close',\n backToTop: 'Back to top',\n collapseSidebar: 'Collapse sidebar',\n expandSidebar: 'Expand sidebar',\n resizeSidebar: 'Resize sidebar',\n openNavigationMenu: 'Open navigation menu',\n mainNavigation: 'Main navigation',\n unread: 'Unread',\n unreadCount: '{count} unread messages',\n noUnread: 'No unread messages',\n previousImage: 'Previous image',\n nextImage: 'Next image',\n zoomIn: 'Zoom in',\n zoomOut: 'Zoom out',\n resetZoom: 'Reset zoom'\n}\n\n/** 显式覆盖优先(trim 后非空才生效);否则回退内置英文文案。 */\nexport function resolveBuiltinLabel(key: BuiltinLabelKey, override?: string | null): string {\n const custom = override?.trim()\n if (custom) return custom\n return LABELS[key]\n}\n\n/** 成组覆盖(`ariaLabels` 对象 property)里取单个 key;未提供的 key 回退内置英文。 */\nexport function resolveBuiltinLabelSet<K extends BuiltinLabelKey>(\n labels: Partial<Record<K, string>> | undefined,\n key: K\n): string {\n return resolveBuiltinLabel(key, labels?.[key])\n}\n\n/** 把 `{count}` 这类占位替换成实参;没有对应实参的占位原样保留。 */\nexport function formatBuiltinLabel(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => (name in params ? String(params[name]) : match))\n}\n"],"mappings":";AA4BA,MAAM,SAA0C;CAC9C,OAAO;CACP,UAAU;CACV,UAAU;CACV,OAAO;CACP,WAAW;CACX,iBAAiB;CACjB,eAAe;CACf,eAAe;CACf,oBAAoB;CACpB,gBAAgB;CAChB,QAAQ;CACR,aAAa;CACb,UAAU;CACV,eAAe;CACf,WAAW;CACX,QAAQ;CACR,SAAS;CACT,WAAW;AACb;;AAGA,SAAgB,oBAAoB,KAAsB,UAAkC;CAC1F,MAAM,SAAS,UAAU,KAAK;CAC9B,IAAI,QAAQ,OAAO;CACnB,OAAO,OAAO;AAChB;;AAGA,SAAgB,uBACd,QACA,KACQ;CACR,OAAO,oBAAoB,KAAK,SAAS,IAAI;AAC/C;;AAGA,SAAgB,mBAAmB,UAAkB,QAAiD;CACpG,OAAO,SAAS,QAAQ,eAAe,OAAO,SAAkB,QAAQ,SAAS,OAAO,OAAO,KAAK,IAAI,KAAM;AAChH"}
|
|
@@ -1,111 +1,164 @@
|
|
|
1
|
-
import { hideOverlayPresence
|
|
2
|
-
import { findFocusedMenuItem
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { hideOverlayPresence } from "../overlay/presence.js";
|
|
2
|
+
import { findFocusedMenuItem, focusMenuItem, getEnabledMenuItems } from "../menu-portal/menu-tree.js";
|
|
3
|
+
// src/shared/menu-behavior/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* menu 行为层:dropdown 与 context-menu 共用的交互行为。
|
|
6
|
+
*
|
|
7
|
+
* 结构层(panel 构建、迁移、定位)由 menu-portal/menu-tree 与各组件自己承载;
|
|
8
|
+
* 这里只收口四种在两个组件中逐行复制的行为:
|
|
9
|
+
* 1. outside-click 守卫(同帧吞掉打开点击 + composedPath 内含判定)
|
|
10
|
+
* 2. 层级 hover 绑定(200ms 打开计时、touch 守卫、hover 收起更深层级)
|
|
11
|
+
* 3. roving 键盘导航(Arrow/Home/End/Enter/Space/Escape)
|
|
12
|
+
* 4. 关闭中的 submenu presence 收尾栈(动画结束后归还子项并销毁 panel)
|
|
13
|
+
*
|
|
14
|
+
* 组件是 adapter:提供层级容器/项的查询与打开/关闭原语,行为由此驱动。
|
|
15
|
+
*/
|
|
16
|
+
function createMenuOutsideClickGuard(host, isInsideOverlayPanel) {
|
|
17
|
+
let armed = false;
|
|
18
|
+
let disarmTimer;
|
|
19
|
+
const isInside = (event) => {
|
|
20
|
+
for (const node of event.composedPath()) {
|
|
21
|
+
if (node === host || node === host.shadowRoot) return true;
|
|
22
|
+
if (node instanceof Node && node.getRootNode() === host.shadowRoot) return true;
|
|
23
|
+
if (node instanceof Node && isInsideOverlayPanel(node)) return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
};
|
|
6
27
|
return {
|
|
7
28
|
arm() {
|
|
8
|
-
|
|
9
|
-
|
|
29
|
+
armed = true;
|
|
30
|
+
clearTimeout(disarmTimer);
|
|
31
|
+
disarmTimer = setTimeout(() => {
|
|
32
|
+
armed = false;
|
|
10
33
|
});
|
|
11
34
|
},
|
|
12
|
-
isArmed: () =>
|
|
13
|
-
isInside
|
|
14
|
-
for (let r of n.composedPath()) if (r === e || r === e.shadowRoot || r instanceof Node && r.getRootNode() === e.shadowRoot || r instanceof Node && t(r)) return !0;
|
|
15
|
-
return !1;
|
|
16
|
-
},
|
|
35
|
+
isArmed: () => armed,
|
|
36
|
+
isInside,
|
|
17
37
|
dispose() {
|
|
18
|
-
clearTimeout(
|
|
38
|
+
clearTimeout(disarmTimer);
|
|
39
|
+
armed = false;
|
|
19
40
|
}
|
|
20
41
|
};
|
|
21
42
|
}
|
|
22
|
-
|
|
23
|
-
function
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
const SUBMENU_HOVER_DELAY = 200;
|
|
44
|
+
function createMenuHoverBinder(delegate, itemSelector) {
|
|
45
|
+
let openTimer;
|
|
46
|
+
let cleanupFns = [];
|
|
47
|
+
function bind() {
|
|
48
|
+
cleanupFns.forEach((fn) => fn());
|
|
49
|
+
cleanupFns = [];
|
|
50
|
+
const depth = delegate.getOpenDepth();
|
|
51
|
+
for (let level = 0; level <= depth + 1; level++) {
|
|
52
|
+
const items = delegate.getLevelItems(level);
|
|
53
|
+
if (!items.length) continue;
|
|
54
|
+
items.forEach((item) => {
|
|
55
|
+
if (!item.matches(itemSelector) || item.hasAttribute("disabled")) return;
|
|
56
|
+
const handler = (event) => {
|
|
57
|
+
if (event.pointerType === "touch") return;
|
|
58
|
+
clearTimeout(openTimer);
|
|
59
|
+
if (delegate.isSubmenuItem(item)) {
|
|
60
|
+
if (delegate.getActiveItem(level) !== item) openTimer = setTimeout(() => delegate.openSubmenu(item), SUBMENU_HOVER_DELAY);
|
|
61
|
+
} else if (delegate.getOpenDepth() > level) delegate.closeFrom(level);
|
|
34
62
|
};
|
|
35
|
-
|
|
63
|
+
item.addEventListener("pointerenter", handler, { passive: true });
|
|
64
|
+
cleanupFns.push(() => item.removeEventListener("pointerenter", handler));
|
|
36
65
|
});
|
|
37
66
|
}
|
|
38
67
|
}
|
|
39
68
|
return {
|
|
40
|
-
bind
|
|
69
|
+
bind,
|
|
41
70
|
dispose() {
|
|
42
|
-
clearTimeout(
|
|
71
|
+
clearTimeout(openTimer);
|
|
72
|
+
cleanupFns.forEach((fn) => fn());
|
|
73
|
+
cleanupFns = [];
|
|
43
74
|
}
|
|
44
75
|
};
|
|
45
76
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
/**
|
|
78
|
+
* 处理 menu 键盘事件;未命中的键原样返回,调用方不做 preventDefault。
|
|
79
|
+
* 事件监听的挂载位置(宿主 / panel / document)由组件自己决定。
|
|
80
|
+
*/
|
|
81
|
+
function handleMenuKeyboard(delegate, e) {
|
|
82
|
+
const focused = delegate.getFocusedItem(e);
|
|
83
|
+
const level = focused ? delegate.getLevelOf(focused) : void 0;
|
|
84
|
+
if (level === void 0) return;
|
|
85
|
+
const items = delegate.getEnabledItems(level);
|
|
86
|
+
const currentIndex = focused ? items.indexOf(focused) : -1;
|
|
87
|
+
switch (e.key) {
|
|
51
88
|
case "ArrowDown":
|
|
52
|
-
|
|
89
|
+
focusMenuItem(items[(currentIndex + 1 + items.length) % items.length]);
|
|
53
90
|
break;
|
|
54
91
|
case "ArrowUp":
|
|
55
|
-
|
|
92
|
+
focusMenuItem(items[(currentIndex - 1 + items.length) % items.length]);
|
|
56
93
|
break;
|
|
57
94
|
case "Home":
|
|
58
|
-
|
|
95
|
+
focusMenuItem(items[0]);
|
|
59
96
|
break;
|
|
60
97
|
case "End":
|
|
61
|
-
|
|
98
|
+
focusMenuItem(items.at(-1));
|
|
62
99
|
break;
|
|
63
100
|
case "ArrowRight":
|
|
64
|
-
|
|
101
|
+
if (focused?.hasAttribute("submenu")) {
|
|
102
|
+
delegate.openSubmenuInstant(focused);
|
|
103
|
+
requestAnimationFrame(() => requestAnimationFrame(() => focusMenuItem(delegate.getEnabledItems(level + 1)[0])));
|
|
104
|
+
}
|
|
65
105
|
break;
|
|
66
106
|
case "ArrowLeft":
|
|
67
|
-
|
|
107
|
+
if (level > 0) focusMenuItem(delegate.closeToParent(level));
|
|
68
108
|
break;
|
|
69
109
|
case "Enter":
|
|
70
110
|
case " ":
|
|
71
|
-
|
|
111
|
+
focused?.click();
|
|
72
112
|
break;
|
|
73
113
|
default: return;
|
|
74
114
|
}
|
|
75
|
-
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
e.stopPropagation();
|
|
76
117
|
}
|
|
77
|
-
|
|
78
|
-
|
|
118
|
+
/** 供 getFocusedItem 实现:优先取事件 composedPath 上的 menu item,其次查找 panel 内已聚焦项。 */
|
|
119
|
+
function getFocusedMenuItemFromPanels(event, panels, itemSelector) {
|
|
120
|
+
const eventItem = event?.composedPath().find((node) => node instanceof HTMLElement && node.matches(itemSelector));
|
|
121
|
+
if (eventItem) return eventItem;
|
|
122
|
+
return findFocusedMenuItem(panels);
|
|
79
123
|
}
|
|
80
|
-
|
|
81
|
-
|
|
124
|
+
/** 供 getEnabledItems 实现:从层级 content 查询可用项。 */
|
|
125
|
+
function getEnabledMenuLevelItems(content) {
|
|
126
|
+
return content ? getEnabledMenuItems(content) : [];
|
|
82
127
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
128
|
+
function createClosingSubmenuStack(adapter) {
|
|
129
|
+
const closing = /* @__PURE__ */ new Map();
|
|
85
130
|
return {
|
|
86
|
-
take(
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
take(parentItem) {
|
|
132
|
+
const container = closing.get(parentItem);
|
|
133
|
+
if (container) closing.delete(parentItem);
|
|
134
|
+
return container;
|
|
89
135
|
},
|
|
90
|
-
closeAsync(
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
closeAsync(parentItem, container) {
|
|
137
|
+
closing.set(parentItem, container);
|
|
138
|
+
(async () => {
|
|
139
|
+
if (!await hideOverlayPresence(adapter.getPanel(container))) return;
|
|
140
|
+
if (closing.get(parentItem) !== container) return;
|
|
141
|
+
closing.delete(parentItem);
|
|
142
|
+
adapter.restoreItems(container, parentItem);
|
|
143
|
+
adapter.dispose(container);
|
|
93
144
|
})();
|
|
94
145
|
},
|
|
95
146
|
restoreAll() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
147
|
+
closing.forEach((container, parentItem) => {
|
|
148
|
+
adapter.restoreItems(container, parentItem);
|
|
149
|
+
adapter.dispose(container);
|
|
150
|
+
});
|
|
151
|
+
closing.clear();
|
|
99
152
|
},
|
|
100
153
|
disposeAll() {
|
|
101
|
-
|
|
154
|
+
closing.forEach((container) => adapter.dispose(container));
|
|
155
|
+
closing.clear();
|
|
102
156
|
},
|
|
103
157
|
closing() {
|
|
104
|
-
return [...
|
|
158
|
+
return [...closing.values()];
|
|
105
159
|
}
|
|
106
160
|
};
|
|
107
161
|
}
|
|
108
|
-
|
|
109
|
-
export { u as createClosingSubmenuStack, o as createMenuHoverBinder, i as createMenuOutsideClickGuard, l as getEnabledMenuLevelItems, c as getFocusedMenuItemFromPanels, s as handleMenuKeyboard };
|
|
162
|
+
export { createClosingSubmenuStack, createMenuHoverBinder, createMenuOutsideClickGuard, getEnabledMenuLevelItems, getFocusedMenuItemFromPanels, handleMenuKeyboard };
|
|
110
163
|
|
|
111
164
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/menu-behavior/index.ts"],"sourcesContent":["/**\n * menu 行为层:dropdown 与 context-menu 共用的交互行为。\n *\n * 结构层(panel 构建、迁移、定位)由 menu-portal/menu-tree 与各组件自己承载;\n * 这里只收口四种在两个组件中逐行复制的行为:\n * 1. outside-click 守卫(同帧吞掉打开点击 + composedPath 内含判定)\n * 2. 层级 hover 绑定(200ms 打开计时、touch 守卫、hover 收起更深层级)\n * 3. roving 键盘导航(Arrow/Home/End/Enter/Space/Escape)\n * 4. 关闭中的 submenu presence 收尾栈(动画结束后归还子项并销毁 panel)\n *\n * 组件是 adapter:提供层级容器/项的查询与打开/关闭原语,行为由此驱动。\n */\nimport { findFocusedMenuItem, focusMenuItem, getEnabledMenuItems } from '@/shared/menu-portal/menu-tree'\nimport { hideOverlayPresence as hidePanelPresence } from '@/shared/overlay/presence'\n\n/** menu hierarchy panel 的最小视图:行为层只需要 panel(presence/contains)与内容容器(项查询)。 */\nexport interface MenuLevelSurface {\n panel: HTMLElement\n content: HTMLElement\n}\n\nexport interface MenuOutsideClickGuard {\n /** 打开动作后调用:吞掉同一帧内随后冒泡到 document 的本次 click。 */\n arm(): void\n isArmed(): boolean\n /** 事件是否发生在宿主 shadow root 或任何已打开 panel 内部。 */\n isInside(event: MouseEvent): boolean\n dispose(): void\n}\n\nexport function createMenuOutsideClickGuard(\n host: HTMLElement,\n isInsideOverlayPanel: (node: Node) => boolean\n): MenuOutsideClickGuard {\n let armed = false\n let disarmTimer: ReturnType<typeof setTimeout> | undefined\n\n const isInside = (event: MouseEvent): boolean => {\n for (const node of event.composedPath()) {\n if (node === host || node === host.shadowRoot) return true\n if (node instanceof Node && node.getRootNode() === host.shadowRoot) return true\n if (node instanceof Node && isInsideOverlayPanel(node)) return true\n }\n return false\n }\n\n return {\n arm() {\n armed = true\n clearTimeout(disarmTimer)\n disarmTimer = setTimeout(() => {\n armed = false\n })\n },\n isArmed: () => armed,\n isInside,\n dispose() {\n clearTimeout(disarmTimer)\n armed = false\n }\n }\n}\n\nexport interface MenuHoverDelegate {\n /** 当前展开的 submenu 深度;行为层会绑定 0..depth+1 每一层。 */\n getOpenDepth(): number\n getLevelItems(level: number): HTMLElement[]\n /** 该层当前激活(子 panel 已展开)的项;用于避免重复打开。 */\n getActiveItem(level: number): HTMLElement | undefined\n isSubmenuItem(item: HTMLElement): boolean\n /** hover 到 submenu item 且它不是当前激活项。 */\n openSubmenu(item: HTMLElement): void\n /** hover 到叶子项且存在更深层级:关闭比它深的子 panel(内部自行重新 bind)。 */\n closeFrom(level: number): void\n}\n\nexport interface MenuHoverBinder {\n bind(): void\n dispose(): void\n}\n\nconst SUBMENU_HOVER_DELAY = 200\n\nexport function createMenuHoverBinder(delegate: MenuHoverDelegate, itemSelector: string): MenuHoverBinder {\n let openTimer: ReturnType<typeof setTimeout> | undefined\n let cleanupFns: (() => void)[] = []\n\n function bind() {\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n\n const depth = delegate.getOpenDepth()\n for (let level = 0; level <= depth + 1; level++) {\n const items = delegate.getLevelItems(level)\n if (!items.length) continue\n items.forEach(item => {\n if (!item.matches(itemSelector) || item.hasAttribute('disabled')) return\n const handler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return\n clearTimeout(openTimer)\n if (delegate.isSubmenuItem(item)) {\n if (delegate.getActiveItem(level) !== item) {\n openTimer = setTimeout(() => delegate.openSubmenu(item), SUBMENU_HOVER_DELAY)\n }\n } else if (delegate.getOpenDepth() > level) {\n delegate.closeFrom(level)\n }\n }\n item.addEventListener('pointerenter', handler, { passive: true })\n cleanupFns.push(() => item.removeEventListener('pointerenter', handler))\n })\n }\n }\n\n return {\n bind,\n dispose() {\n clearTimeout(openTimer)\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n }\n }\n}\n\nexport interface MenuKeyboardDelegate {\n getFocusedItem(event?: KeyboardEvent): HTMLElement | undefined\n /** 焦点项所在层级;不在任何 panel 内时返回 undefined。 */\n getLevelOf(item: HTMLElement): number | undefined\n getEnabledItems(level: number): HTMLElement[]\n isSubmenuItem(item: HTMLElement): boolean\n /** 键盘 ArrowRight:以 instant 语义打开该项的子 panel。 */\n openSubmenuInstant(item: HTMLElement): void\n /** 键盘 ArrowLeft:关闭 level 层子 panel 并返回其父项。 */\n closeToParent(level: number): HTMLElement | undefined\n /** Escape:关闭最深一层子 panel;没有子 panel 时关闭整个 menu。 */\n closeDeepestOrAll(): void\n}\n\n/**\n * 处理 menu 键盘事件;未命中的键原样返回,调用方不做 preventDefault。\n * 事件监听的挂载位置(宿主 / panel / document)由组件自己决定。\n */\nexport function handleMenuKeyboard(delegate: MenuKeyboardDelegate, e: KeyboardEvent): void {\n /*\n * Escape 不在本函数处理:它必须跨组件归属(issue #120 Block 1),由共享仲裁者在\n * document 捕获阶段判定最内层后调用 `closeDeepestOrAll()`。留在各组件自己的\n * keydown 里会让一次 Escape 同时命中内外两层。\n */\n const focused = delegate.getFocusedItem(e)\n const level = focused ? delegate.getLevelOf(focused) : undefined\n if (level === undefined) return\n const items = delegate.getEnabledItems(level)\n const currentIndex = focused ? items.indexOf(focused) : -1\n\n switch (e.key) {\n case 'ArrowDown':\n focusMenuItem(items[(currentIndex + 1 + items.length) % items.length])\n break\n case 'ArrowUp':\n focusMenuItem(items[(currentIndex - 1 + items.length) % items.length])\n break\n case 'Home':\n focusMenuItem(items[0])\n break\n case 'End':\n focusMenuItem(items.at(-1))\n break\n case 'ArrowRight':\n if (focused?.hasAttribute('submenu')) {\n delegate.openSubmenuInstant(focused)\n // 双 rAF:等子 panel 构建/迁移完成后再聚焦其第一个可用项\n requestAnimationFrame(() => requestAnimationFrame(() => focusMenuItem(delegate.getEnabledItems(level + 1)[0])))\n }\n break\n case 'ArrowLeft':\n if (level > 0) {\n focusMenuItem(delegate.closeToParent(level))\n }\n break\n case 'Enter':\n case ' ':\n focused?.click()\n break\n default:\n return\n }\n e.preventDefault()\n e.stopPropagation()\n}\n\n/** 供 getFocusedItem 实现:优先取事件 composedPath 上的 menu item,其次查找 panel 内已聚焦项。 */\nexport function getFocusedMenuItemFromPanels(\n event: KeyboardEvent | undefined,\n panels: Iterable<HTMLElement | undefined>,\n itemSelector: string\n): HTMLElement | undefined {\n const eventItem = event\n ?.composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(itemSelector))\n if (eventItem) return eventItem\n\n return findFocusedMenuItem(panels)\n}\n\n/** 供 getEnabledItems 实现:从层级 content 查询可用项。 */\nexport function getEnabledMenuLevelItems(content: HTMLElement | undefined): HTMLElement[] {\n return content ? getEnabledMenuItems(content) : []\n}\n\nexport interface ClosingSubmenuStack<C> {\n /** 取出正在关闭的容器以复用(同帧关闭→重开同一父项)。 */\n take(parentItem: HTMLElement): C | undefined\n /** 挂起关闭:等出场动画结束后归还子项并销毁 panel。 */\n closeAsync(parentItem: HTMLElement, container: C): void\n /** 立即归还并销毁全部关闭中的容器(整 menu 关闭 / 重开路径)。 */\n restoreAll(): void\n /** 全部强制销毁(断开连接时),不再归还子项。 */\n disposeAll(): void\n /**\n * 正在关闭、尚未销毁的容器快照(只读)。\n * 这些 panel 仍在 DOM 里、可能仍然可见,但已经离开调用方的活跃集合(`_overlays` /\n * `_activeSubmenus`)—— 根层重新 claim 时必须把它们一并重新挂回新会话的子树,\n * 否则它们会被判成「panel 外」。\n */\n closing(): C[]\n}\n\nexport interface ClosingSubmenuStackAdapter<C> {\n getPanel(container: C): HTMLElement\n /** 归子项回父项(moveMenuChildren 与隐藏补丁由 adapter 决定)。 */\n restoreItems(container: C, parentItem: HTMLElement): void\n /** 销毁 panel(remove + overlay api.dispose 等)。 */\n dispose(container: C): void\n}\n\nexport function createClosingSubmenuStack<C>(adapter: ClosingSubmenuStackAdapter<C>): ClosingSubmenuStack<C> {\n const closing = new Map<HTMLElement, C>()\n\n return {\n take(parentItem) {\n const container = closing.get(parentItem)\n if (container) closing.delete(parentItem)\n return container\n },\n closeAsync(parentItem, container) {\n // 先登记再等出场动画:同帧关闭→重开时 take() 会从这里取走复用\n closing.set(parentItem, container)\n void (async () => {\n if (!(await hidePanelPresence(adapter.getPanel(container)))) return\n if (closing.get(parentItem) !== container) return\n\n closing.delete(parentItem)\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })()\n },\n restoreAll() {\n closing.forEach((container, parentItem) => {\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })\n closing.clear()\n },\n disposeAll() {\n closing.forEach(container => adapter.dispose(container))\n closing.clear()\n },\n closing() {\n return [...closing.values()]\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["hidePanelPresence"],"sources":["../../../src/shared/menu-behavior/index.ts"],"sourcesContent":["/**\n * menu 行为层:dropdown 与 context-menu 共用的交互行为。\n *\n * 结构层(panel 构建、迁移、定位)由 menu-portal/menu-tree 与各组件自己承载;\n * 这里只收口四种在两个组件中逐行复制的行为:\n * 1. outside-click 守卫(同帧吞掉打开点击 + composedPath 内含判定)\n * 2. 层级 hover 绑定(200ms 打开计时、touch 守卫、hover 收起更深层级)\n * 3. roving 键盘导航(Arrow/Home/End/Enter/Space/Escape)\n * 4. 关闭中的 submenu presence 收尾栈(动画结束后归还子项并销毁 panel)\n *\n * 组件是 adapter:提供层级容器/项的查询与打开/关闭原语,行为由此驱动。\n */\nimport { findFocusedMenuItem, focusMenuItem, getEnabledMenuItems } from '@/shared/menu-portal/menu-tree'\nimport { hideOverlayPresence as hidePanelPresence } from '@/shared/overlay/presence'\n\n/** menu hierarchy panel 的最小视图:行为层只需要 panel(presence/contains)与内容容器(项查询)。 */\nexport interface MenuLevelSurface {\n panel: HTMLElement\n content: HTMLElement\n}\n\nexport interface MenuOutsideClickGuard {\n /** 打开动作后调用:吞掉同一帧内随后冒泡到 document 的本次 click。 */\n arm(): void\n isArmed(): boolean\n /** 事件是否发生在宿主 shadow root 或任何已打开 panel 内部。 */\n isInside(event: MouseEvent): boolean\n dispose(): void\n}\n\nexport function createMenuOutsideClickGuard(\n host: HTMLElement,\n isInsideOverlayPanel: (node: Node) => boolean\n): MenuOutsideClickGuard {\n let armed = false\n let disarmTimer: ReturnType<typeof setTimeout> | undefined\n\n const isInside = (event: MouseEvent): boolean => {\n for (const node of event.composedPath()) {\n if (node === host || node === host.shadowRoot) return true\n if (node instanceof Node && node.getRootNode() === host.shadowRoot) return true\n if (node instanceof Node && isInsideOverlayPanel(node)) return true\n }\n return false\n }\n\n return {\n arm() {\n armed = true\n clearTimeout(disarmTimer)\n disarmTimer = setTimeout(() => {\n armed = false\n })\n },\n isArmed: () => armed,\n isInside,\n dispose() {\n clearTimeout(disarmTimer)\n armed = false\n }\n }\n}\n\nexport interface MenuHoverDelegate {\n /** 当前展开的 submenu 深度;行为层会绑定 0..depth+1 每一层。 */\n getOpenDepth(): number\n getLevelItems(level: number): HTMLElement[]\n /** 该层当前激活(子 panel 已展开)的项;用于避免重复打开。 */\n getActiveItem(level: number): HTMLElement | undefined\n isSubmenuItem(item: HTMLElement): boolean\n /** hover 到 submenu item 且它不是当前激活项。 */\n openSubmenu(item: HTMLElement): void\n /** hover 到叶子项且存在更深层级:关闭比它深的子 panel(内部自行重新 bind)。 */\n closeFrom(level: number): void\n}\n\nexport interface MenuHoverBinder {\n bind(): void\n dispose(): void\n}\n\nconst SUBMENU_HOVER_DELAY = 200\n\nexport function createMenuHoverBinder(delegate: MenuHoverDelegate, itemSelector: string): MenuHoverBinder {\n let openTimer: ReturnType<typeof setTimeout> | undefined\n let cleanupFns: (() => void)[] = []\n\n function bind() {\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n\n const depth = delegate.getOpenDepth()\n for (let level = 0; level <= depth + 1; level++) {\n const items = delegate.getLevelItems(level)\n if (!items.length) continue\n items.forEach(item => {\n if (!item.matches(itemSelector) || item.hasAttribute('disabled')) return\n const handler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return\n clearTimeout(openTimer)\n if (delegate.isSubmenuItem(item)) {\n if (delegate.getActiveItem(level) !== item) {\n openTimer = setTimeout(() => delegate.openSubmenu(item), SUBMENU_HOVER_DELAY)\n }\n } else if (delegate.getOpenDepth() > level) {\n delegate.closeFrom(level)\n }\n }\n item.addEventListener('pointerenter', handler, { passive: true })\n cleanupFns.push(() => item.removeEventListener('pointerenter', handler))\n })\n }\n }\n\n return {\n bind,\n dispose() {\n clearTimeout(openTimer)\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n }\n }\n}\n\nexport interface MenuKeyboardDelegate {\n getFocusedItem(event?: KeyboardEvent): HTMLElement | undefined\n /** 焦点项所在层级;不在任何 panel 内时返回 undefined。 */\n getLevelOf(item: HTMLElement): number | undefined\n getEnabledItems(level: number): HTMLElement[]\n isSubmenuItem(item: HTMLElement): boolean\n /** 键盘 ArrowRight:以 instant 语义打开该项的子 panel。 */\n openSubmenuInstant(item: HTMLElement): void\n /** 键盘 ArrowLeft:关闭 level 层子 panel 并返回其父项。 */\n closeToParent(level: number): HTMLElement | undefined\n /** Escape:关闭最深一层子 panel;没有子 panel 时关闭整个 menu。 */\n closeDeepestOrAll(): void\n}\n\n/**\n * 处理 menu 键盘事件;未命中的键原样返回,调用方不做 preventDefault。\n * 事件监听的挂载位置(宿主 / panel / document)由组件自己决定。\n */\nexport function handleMenuKeyboard(delegate: MenuKeyboardDelegate, e: KeyboardEvent): void {\n /*\n * Escape 不在本函数处理:它必须跨组件归属(issue #120 Block 1),由共享仲裁者在\n * document 捕获阶段判定最内层后调用 `closeDeepestOrAll()`。留在各组件自己的\n * keydown 里会让一次 Escape 同时命中内外两层。\n */\n const focused = delegate.getFocusedItem(e)\n const level = focused ? delegate.getLevelOf(focused) : undefined\n if (level === undefined) return\n const items = delegate.getEnabledItems(level)\n const currentIndex = focused ? items.indexOf(focused) : -1\n\n switch (e.key) {\n case 'ArrowDown':\n focusMenuItem(items[(currentIndex + 1 + items.length) % items.length])\n break\n case 'ArrowUp':\n focusMenuItem(items[(currentIndex - 1 + items.length) % items.length])\n break\n case 'Home':\n focusMenuItem(items[0])\n break\n case 'End':\n focusMenuItem(items.at(-1))\n break\n case 'ArrowRight':\n if (focused?.hasAttribute('submenu')) {\n delegate.openSubmenuInstant(focused)\n // 双 rAF:等子 panel 构建/迁移完成后再聚焦其第一个可用项\n requestAnimationFrame(() => requestAnimationFrame(() => focusMenuItem(delegate.getEnabledItems(level + 1)[0])))\n }\n break\n case 'ArrowLeft':\n if (level > 0) {\n focusMenuItem(delegate.closeToParent(level))\n }\n break\n case 'Enter':\n case ' ':\n focused?.click()\n break\n default:\n return\n }\n e.preventDefault()\n e.stopPropagation()\n}\n\n/** 供 getFocusedItem 实现:优先取事件 composedPath 上的 menu item,其次查找 panel 内已聚焦项。 */\nexport function getFocusedMenuItemFromPanels(\n event: KeyboardEvent | undefined,\n panels: Iterable<HTMLElement | undefined>,\n itemSelector: string\n): HTMLElement | undefined {\n const eventItem = event\n ?.composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(itemSelector))\n if (eventItem) return eventItem\n\n return findFocusedMenuItem(panels)\n}\n\n/** 供 getEnabledItems 实现:从层级 content 查询可用项。 */\nexport function getEnabledMenuLevelItems(content: HTMLElement | undefined): HTMLElement[] {\n return content ? getEnabledMenuItems(content) : []\n}\n\nexport interface ClosingSubmenuStack<C> {\n /** 取出正在关闭的容器以复用(同帧关闭→重开同一父项)。 */\n take(parentItem: HTMLElement): C | undefined\n /** 挂起关闭:等出场动画结束后归还子项并销毁 panel。 */\n closeAsync(parentItem: HTMLElement, container: C): void\n /** 立即归还并销毁全部关闭中的容器(整 menu 关闭 / 重开路径)。 */\n restoreAll(): void\n /** 全部强制销毁(断开连接时),不再归还子项。 */\n disposeAll(): void\n /**\n * 正在关闭、尚未销毁的容器快照(只读)。\n * 这些 panel 仍在 DOM 里、可能仍然可见,但已经离开调用方的活跃集合(`_overlays` /\n * `_activeSubmenus`)—— 根层重新 claim 时必须把它们一并重新挂回新会话的子树,\n * 否则它们会被判成「panel 外」。\n */\n closing(): C[]\n}\n\nexport interface ClosingSubmenuStackAdapter<C> {\n getPanel(container: C): HTMLElement\n /** 归子项回父项(moveMenuChildren 与隐藏补丁由 adapter 决定)。 */\n restoreItems(container: C, parentItem: HTMLElement): void\n /** 销毁 panel(remove + overlay api.dispose 等)。 */\n dispose(container: C): void\n}\n\nexport function createClosingSubmenuStack<C>(adapter: ClosingSubmenuStackAdapter<C>): ClosingSubmenuStack<C> {\n const closing = new Map<HTMLElement, C>()\n\n return {\n take(parentItem) {\n const container = closing.get(parentItem)\n if (container) closing.delete(parentItem)\n return container\n },\n closeAsync(parentItem, container) {\n // 先登记再等出场动画:同帧关闭→重开时 take() 会从这里取走复用\n closing.set(parentItem, container)\n void (async () => {\n if (!(await hidePanelPresence(adapter.getPanel(container)))) return\n if (closing.get(parentItem) !== container) return\n\n closing.delete(parentItem)\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })()\n },\n restoreAll() {\n closing.forEach((container, parentItem) => {\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })\n closing.clear()\n },\n disposeAll() {\n closing.forEach(container => adapter.dispose(container))\n closing.clear()\n },\n closing() {\n return [...closing.values()]\n }\n }\n}\n"],"mappings":"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"}
|