ngwr 11.2.0 → 12.1.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.md +85 -26
- package/burger/styles/_index.scss +1 -1
- package/button/styles/_index.scss +2 -2
- package/calendar/styles/_index.scss +32 -12
- package/calendar-heatmap/styles/_index.scss +20 -0
- package/carousel/styles/_index.scss +23 -1
- package/cascader/styles/_index.scss +15 -3
- package/checkbox/styles/_index.scss +2 -2
- package/collapse/styles/_index.scss +2 -2
- package/color-picker/styles/_index.scss +3 -3
- package/compare/styles/_index.scss +18 -2
- package/context-menu/styles/_index.scss +21 -2
- package/date-picker/styles/_index.scss +5 -5
- package/descriptions/styles/_index.scss +1 -1
- package/drag-drop/styles/_index.scss +28 -1
- package/drawer/styles/_index.scss +37 -0
- package/dropdown/styles/_index.scss +2 -2
- package/event-calendar/styles/_index.scss +67 -10
- package/fesm2022/ngwr-action-sheet.mjs +3 -3
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-aurora.mjs +3 -3
- package/fesm2022/ngwr-avatar.mjs +3 -3
- package/fesm2022/ngwr-back-top.mjs +3 -3
- package/fesm2022/ngwr-badge.mjs +6 -6
- package/fesm2022/ngwr-bar-chart-testing.mjs +20 -2
- package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bar-chart.mjs +3 -3
- package/fesm2022/ngwr-blur-text.mjs +3 -3
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +4 -4
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +29 -17
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-button.mjs +6 -6
- package/fesm2022/ngwr-calendar-heatmap.mjs +17 -5
- package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +187 -9
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +10 -10
- package/fesm2022/ngwr-card.mjs.map +1 -1
- package/fesm2022/ngwr-carousel.mjs +124 -24
- package/fesm2022/ngwr-carousel.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +48 -5
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +6 -6
- package/fesm2022/ngwr-circular-text.mjs +4 -4
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-click-spark.mjs +27 -7
- package/fesm2022/ngwr-click-spark.mjs.map +1 -1
- package/fesm2022/ngwr-clipboard.mjs +3 -3
- package/fesm2022/ngwr-collapse.mjs +9 -9
- package/fesm2022/ngwr-color-picker-testing.mjs +26 -6
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +98 -8
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +14 -5
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-compare.mjs +3 -3
- package/fesm2022/ngwr-confetti.mjs +31 -6
- package/fesm2022/ngwr-confetti.mjs.map +1 -1
- package/fesm2022/ngwr-config.mjs +9 -0
- package/fesm2022/ngwr-config.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu-testing.mjs +30 -11
- package/fesm2022/ngwr-context-menu-testing.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +333 -43
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-cookie.mjs +3 -3
- package/fesm2022/ngwr-counter.mjs +25 -9
- package/fesm2022/ngwr-counter.mjs.map +1 -1
- package/fesm2022/{ngwr-date-adapter-fns.mjs → ngwr-date-adapters-fns.mjs} +37 -10
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -0
- package/fesm2022/{ngwr-date-adapter-luxon.mjs → ngwr-date-adapters-luxon.mjs} +5 -5
- package/fesm2022/ngwr-date-adapters-luxon.mjs.map +1 -0
- package/fesm2022/ngwr-date-picker-testing.mjs +11 -6
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +119 -37
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/{ngwr-date-adapter.mjs → ngwr-date.mjs} +36 -10
- package/fesm2022/ngwr-date.mjs.map +1 -0
- package/fesm2022/ngwr-decrypt-text.mjs +3 -3
- package/fesm2022/ngwr-density.mjs +6 -6
- package/fesm2022/ngwr-descriptions.mjs +6 -6
- package/fesm2022/ngwr-dialog.mjs +73 -18
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +12 -12
- package/fesm2022/ngwr-divider.mjs +52 -5
- package/fesm2022/ngwr-divider.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart-testing.mjs +8 -4
- package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart.mjs +7 -7
- package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +160 -11
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +121 -58
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +26 -10
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-event-calendar.mjs +28 -15
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text-testing.mjs +5 -3
- package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text.mjs +75 -46
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-form.mjs +32 -15
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -11
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gauge.mjs +3 -3
- package/fesm2022/ngwr-glitch-text-testing.mjs +51 -17
- package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-glitch-text.mjs +7 -7
- package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text-testing.mjs +46 -11
- package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +4 -4
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-hotkey.mjs +24 -9
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +73 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +55 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +26 -15
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +6 -6
- package/fesm2022/ngwr-image-cropper-testing.mjs +53 -9
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +150 -12
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs +15 -10
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +17 -10
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp-testing.mjs +12 -6
- package/fesm2022/ngwr-input-otp-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +24 -5
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +15 -15
- package/fesm2022/ngwr-keyboard.mjs +3 -3
- package/fesm2022/ngwr-knob.mjs +3 -3
- package/fesm2022/ngwr-layout.mjs +15 -15
- package/fesm2022/ngwr-lightbox.mjs +4 -4
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +73 -26
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-list.mjs +7 -7
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +7 -7
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +66 -8
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-marquee.mjs +4 -4
- package/fesm2022/ngwr-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-media.mjs +3 -3
- package/fesm2022/ngwr-mention.mjs +108 -13
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-meter-group-testing.mjs +26 -6
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group.mjs +3 -3
- package/fesm2022/ngwr-overlay.mjs +6 -6
- package/fesm2022/ngwr-page-header.mjs +3 -3
- package/fesm2022/ngwr-pagination-testing.mjs +6 -6
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +83 -16
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +39 -26
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +9 -9
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popover-testing.mjs +7 -2
- package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +155 -33
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs +42 -5
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-rating.mjs +3 -3
- package/fesm2022/ngwr-result.mjs +21 -30
- package/fesm2022/ngwr-result.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text.mjs +3 -3
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-segmented-testing.mjs +85 -1
- package/fesm2022/ngwr-segmented-testing.mjs.map +1 -1
- package/fesm2022/ngwr-segmented.mjs +123 -10
- package/fesm2022/ngwr-segmented.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +159 -34
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +11 -9
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +31 -16
- package/fesm2022/ngwr-sidebar.mjs.map +1 -1
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs +100 -9
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline.mjs +3 -3
- package/fesm2022/ngwr-speed-dial-testing.mjs +15 -13
- package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -1
- package/fesm2022/ngwr-speed-dial.mjs +27 -12
- package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-split-text.mjs +17 -5
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-spotlight-card.mjs +7 -7
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +6 -6
- package/fesm2022/ngwr-star-border.mjs +4 -4
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-stepper-testing.mjs +8 -8
- package/fesm2022/ngwr-stepper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-stepper.mjs +33 -8
- package/fesm2022/ngwr-stepper.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +17 -5
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-switch.mjs +3 -3
- package/fesm2022/ngwr-table.mjs +99 -39
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-testing.mjs +11 -10
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +36 -10
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +3 -3
- package/fesm2022/ngwr-theme.mjs +181 -4
- package/fesm2022/ngwr-theme.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card.mjs +7 -7
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-toast.mjs +9 -9
- package/fesm2022/ngwr-toolbar.mjs +3 -3
- package/fesm2022/ngwr-tour.mjs +34 -10
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +3 -3
- package/fesm2022/ngwr-tree.mjs +28 -10
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter-testing.mjs +15 -1
- package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter.mjs +13 -5
- package/fesm2022/ngwr-typewriter.mjs.map +1 -1
- package/fesm2022/ngwr-typography.mjs +3 -3
- package/fesm2022/ngwr-utils.mjs +5 -2
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +4 -4
- package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-waves.mjs +4 -4
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +94 -24
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/file-upload/styles/_index.scss +2 -2
- package/glitch-text/styles/_index.scss +27 -12
- package/i18n/en.json +56 -6
- package/i18n/ru.json +56 -6
- package/image-cropper/styles/_index.scss +19 -0
- package/input/styles/_index.scss +2 -2
- package/input-number/styles/_index.scss +2 -2
- package/input-otp/styles/_index.scss +9 -2
- package/knob/styles/_index.scss +1 -1
- package/lightbox/styles/_index.scss +16 -2
- package/line-chart/styles/_index.scss +28 -14
- package/list/styles/_index.scss +1 -1
- package/llms-full.txt +19 -24
- package/llms.txt +46 -8
- package/markdown/styles/_index.scss +3 -3
- package/mcp/api.js +6 -4
- package/mcp/catalog.js +40 -3
- package/mcp/providers.js +16 -3
- package/mcp/tools.js +21 -1
- package/meter-group/styles/_index.scss +1 -1
- package/overlay/styles/_index.scss +33 -1
- package/package.json +17 -12
- package/pagination/styles/_index.scss +23 -1
- package/popover/styles/_index.scss +16 -7
- package/progress/styles/_index.scss +1 -1
- package/radio/styles/_index.scss +2 -2
- package/rating/styles/_index.scss +1 -1
- package/schematics/collection.json +1 -1
- package/schematics/migrations/v12/index.js +65 -0
- package/schematics/migrations/v7/index.js +12 -1
- package/schematics/migrations/v8/index.js +10 -1
- package/schematics/migrations.json +5 -0
- package/schematics/ng-add/index.js +8 -3
- package/schematics/provider/index.js +80 -13
- package/schematics/use/index.js +9 -129
- package/schematics/use/rule.js +206 -0
- package/schematics/use/symbol-map.json +4 -4
- package/segmented/styles/_index.scss +14 -3
- package/select/styles/_index.scss +14 -2
- package/sidebar/styles/_index.scss +12 -10
- package/skills/ngwr/SKILL.md +7 -5
- package/skills/ngwr/references/catalog.md +9 -9
- package/skills/ngwr/references/setup.md +10 -5
- package/slider/styles/_index.scss +1 -1
- package/speed-dial/styles/_index.scss +1 -1
- package/stepper/styles/_index.scss +12 -3
- package/switch/styles/_index.scss +2 -2
- package/table/styles/_index.scss +61 -12
- package/tabs/styles/_index.scss +1 -1
- package/textarea/styles/_index.scss +1 -1
- package/theme/styles/_colors.scss +142 -56
- package/theme/styles/_dark.scss +46 -3
- package/theme/styles/_focus.scss +28 -5
- package/theme/styles/_mixins.scss +1 -1
- package/theme/styles/_variables.scss +67 -0
- package/transfer/styles/_index.scss +26 -3
- package/tree/styles/_index.scss +29 -6
- package/types/ngwr-bar-chart-testing.d.ts +9 -1
- package/types/ngwr-blur-text.d.ts +8 -4
- package/types/ngwr-breadcrumbs.d.ts +17 -10
- package/types/ngwr-calendar-testing.d.ts +1 -1
- package/types/ngwr-calendar.d.ts +66 -0
- package/types/ngwr-carousel.d.ts +85 -6
- package/types/ngwr-cascader.d.ts +17 -0
- package/types/ngwr-click-spark.d.ts +4 -0
- package/types/ngwr-color-picker-testing.d.ts +8 -0
- package/types/ngwr-color-picker.d.ts +21 -1
- package/types/ngwr-confetti.d.ts +8 -0
- package/types/ngwr-config.d.ts +10 -1
- package/types/ngwr-context-menu-testing.d.ts +24 -11
- package/types/ngwr-context-menu.d.ts +56 -0
- package/types/ngwr-counter.d.ts +1 -0
- package/types/{ngwr-date-adapter-fns.d.ts → ngwr-date-adapters-fns.d.ts} +33 -6
- package/types/{ngwr-date-adapter-luxon.d.ts → ngwr-date-adapters-luxon.d.ts} +1 -1
- package/types/ngwr-date-picker-testing.d.ts +11 -6
- package/types/ngwr-date-picker.d.ts +8 -4
- package/types/{ngwr-date-adapter.d.ts → ngwr-date.d.ts} +24 -5
- package/types/ngwr-dialog.d.ts +38 -1
- package/types/ngwr-divider.d.ts +25 -0
- package/types/ngwr-donut-chart-testing.d.ts +7 -3
- package/types/ngwr-donut-chart.d.ts +3 -3
- package/types/ngwr-drag-drop.d.ts +56 -2
- package/types/ngwr-drawer.d.ts +56 -5
- package/types/ngwr-event-calendar.d.ts +8 -2
- package/types/ngwr-falling-text-testing.d.ts +5 -3
- package/types/ngwr-falling-text.d.ts +1 -1
- package/types/ngwr-form.d.ts +12 -2
- package/types/ngwr-glitch-text-testing.d.ts +37 -8
- package/types/ngwr-gradient-text-testing.d.ts +16 -3
- package/types/ngwr-i18n.d.ts +13 -4
- package/types/ngwr-image-cropper-testing.d.ts +49 -10
- package/types/ngwr-image-cropper.d.ts +70 -5
- package/types/ngwr-input-number-testing.d.ts +15 -10
- package/types/ngwr-input-number.d.ts +11 -4
- package/types/ngwr-input-otp-testing.d.ts +12 -6
- package/types/ngwr-input-otp.d.ts +15 -1
- package/types/ngwr-line-chart.d.ts +20 -3
- package/types/ngwr-markdown.d.ts +11 -0
- package/types/ngwr-mention.d.ts +32 -0
- package/types/ngwr-meter-group-testing.d.ts +12 -2
- package/types/ngwr-overlay.d.ts +1 -0
- package/types/ngwr-pagination-testing.d.ts +4 -4
- package/types/ngwr-pagination.d.ts +11 -0
- package/types/ngwr-pipes.d.ts +1 -1
- package/types/ngwr-popover-testing.d.ts +7 -2
- package/types/ngwr-popover.d.ts +1 -0
- package/types/ngwr-result.d.ts +6 -0
- package/types/ngwr-segmented-testing.d.ts +53 -0
- package/types/ngwr-segmented.d.ts +107 -5
- package/types/ngwr-select.d.ts +68 -6
- package/types/ngwr-shiny-text.d.ts +4 -2
- package/types/ngwr-sidebar.d.ts +14 -5
- package/types/ngwr-slider.d.ts +69 -1
- package/types/ngwr-speed-dial-testing.d.ts +15 -11
- package/types/ngwr-speed-dial.d.ts +22 -3
- package/types/ngwr-split-text.d.ts +9 -0
- package/types/ngwr-stepper-testing.d.ts +5 -5
- package/types/ngwr-stepper.d.ts +8 -0
- package/types/ngwr-storage.d.ts +1 -0
- package/types/ngwr-table.d.ts +104 -11
- package/types/ngwr-tabs-testing.d.ts +11 -10
- package/types/ngwr-tabs.d.ts +2 -0
- package/types/ngwr-theme.d.ts +82 -1
- package/types/ngwr-tour.d.ts +22 -4
- package/types/ngwr-tree.d.ts +5 -4
- package/types/ngwr-typewriter-testing.d.ts +10 -0
- package/types/ngwr-utils.d.ts +5 -2
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window.d.ts +39 -11
- package/typography/styles/_index.scss +1 -1
- package/window/styles/_index.scss +72 -26
- package/fesm2022/ngwr-date-adapter-fns.mjs.map +0 -1
- package/fesm2022/ngwr-date-adapter-luxon.mjs.map +0 -1
- package/fesm2022/ngwr-date-adapter.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-mention.mjs","sources":["../../../projects/lib/mention/caret.ts","../../../projects/lib/mention/mention-panel.ts","../../../projects/lib/mention/mention-panel.html","../../../projects/lib/mention/mention.ts","../../../projects/lib/mention/ngwr-mention.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * CSS properties whose values must be copied onto the mirror to match layout.\n *\n * `as const satisfies` keeps each name checked against `CSSStyleDeclaration`\n * while narrowing the element type to these string-valued literals — the wider\n * `keyof CSSStyleDeclaration` also spans its methods (`setProperty`, `item`, …),\n * which makes the indexed read below look like an unbound method.\n */\nconst MIRROR_PROPS = [\n 'boxSizing',\n 'width',\n 'height',\n 'overflowX',\n 'overflowY',\n 'borderTopWidth',\n 'borderRightWidth',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'lineHeight',\n 'fontFamily',\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration',\n 'letterSpacing',\n 'wordSpacing',\n 'tabSize',\n 'whiteSpace',\n 'wordWrap',\n] as const satisfies readonly (keyof CSSStyleDeclaration)[];\n\n/**\n * Compute the viewport coordinates of a textarea / input caret at a given\n * `position`. Uses the \"mirror div\" technique — a hidden div with the\n * same styling holds the text up to the caret, and we read the bounding\n * rect of a probe span placed at the boundary.\n *\n * Returns viewport-relative `{ top, left }` — pass straight to a CDK\n * Overlay anchored at that point.\n */\nexport function getCaretCoordinates(\n input: HTMLTextAreaElement | HTMLInputElement,\n position: number\n): { top: number; left: number; lineHeight: number } {\n const doc = input.ownerDocument;\n const style = getComputedStyle(input);\n const mirror = doc.createElement('div');\n\n for (const prop of MIRROR_PROPS) {\n mirror.style[prop] = style[prop];\n }\n mirror.style.position = 'absolute';\n mirror.style.visibility = 'hidden';\n mirror.style.top = '0';\n mirror.style.left = '0';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.wordWrap = 'break-word';\n\n // <input> is single-line — force whitespace behaviour so coords are sane.\n if (input.tagName === 'INPUT') {\n mirror.style.whiteSpace = 'pre';\n mirror.style.height = 'auto';\n mirror.style.width = 'auto';\n mirror.style.overflow = 'hidden';\n }\n\n const before = input.value.substring(0, position);\n mirror.textContent = before;\n\n const probe = doc.createElement('span');\n // Single character so the span has measurable size even at the end.\n probe.textContent = input.value.substring(position) || '.';\n mirror.appendChild(probe);\n\n doc.body.appendChild(mirror);\n const probeRect = probe.getBoundingClientRect();\n const mirrorRect = mirror.getBoundingClientRect();\n const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;\n doc.body.removeChild(mirror);\n\n const inputRect = input.getBoundingClientRect();\n return {\n top: inputRect.top + (probeRect.top - mirrorRect.top) - input.scrollTop,\n left: inputRect.left + (probeRect.left - mirrorRect.left) - input.scrollLeft,\n lineHeight,\n };\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, input, output } from '@angular/core';\n\nimport type { WrMentionItem } from './interfaces';\n\n/**\n * The id of the option at `index`, given the listbox's own id.\n *\n * Lives here, and is used from both sides, because this format is a contract\n * between two files: the panel stamps it onto each `<li>`, and the directive\n * names one of them in `aria-activedescendant`. Written out twice, the two would\n * drift and the reference would silently dangle — and a dangling\n * `aria-activedescendant` is not an error the browser reports, it just stops\n * announcing options.\n *\n * @internal\n */\nexport function wrMentionOptionId(listboxId: string, index: number): string {\n return `${listboxId}-opt-${index}`;\n}\n\n/**\n * Overlay panel rendered by {@link WrMention}. Not intended for\n * direct use — the directive instantiates it via a ComponentPortal.\n */\n@Component({\n selector: 'wr-mention-panel',\n templateUrl: './mention-panel.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-mention-panel' },\n})\nexport class WrMentionPanel {\n readonly items = input<readonly WrMentionItem[]>([]);\n readonly activeIndex = input(0);\n readonly displayWith = input<(item: WrMentionItem) => string>(item => item.label);\n\n /**\n * Id of the `<ul role=\"listbox\">`, minted by the directive and pointed at by\n * its `aria-controls`. Owned there rather than here because the panel is\n * disposed and rebuilt whenever the caret leaves a mention, while the\n * attribute on the host has to stay put.\n *\n * Plain `input('')`, never `input.required` — a ComponentPortal-created\n * component throws on the first change detection if a required input is unset.\n */\n readonly listboxId = input('');\n\n /** Accessible name for the listbox. ARIA requires one; the directive supplies it from the catalog. */\n readonly listLabel = input('');\n\n protected optionId(index: number): string {\n return wrMentionOptionId(this.listboxId(), index);\n }\n\n /** Emitted when the user picks an item (click). */\n readonly picked = output<WrMentionItem>();\n\n /** Emitted on mouseenter — used by the directive to track hover. */\n readonly hovered = output<number>();\n\n protected onPick(item: WrMentionItem, event: MouseEvent): void {\n // mousedown semantics — keep focus on the input.\n event.preventDefault();\n this.picked.emit(item);\n }\n}\n","<!--\n No empty branch: `activate()` calls `close()` and returns before `open()` when\n nothing matches, so the panel is never rendered with zero items. The \"No\n matches\" `<div>` that used to live here was unreachable — and a hard-coded\n English string besides. The zero-match case is announced through the\n directive's live region instead.\n-->\n<ul class=\"wr-mention-panel__list\" role=\"listbox\" [attr.id]=\"listboxId()\" [attr.aria-label]=\"listLabel()\">\n @for (item of items(); track $index) {\n <li\n class=\"wr-mention-panel__option\"\n role=\"option\"\n [attr.id]=\"optionId($index)\"\n [class.wr-mention-panel__option--active]=\"$index === activeIndex()\"\n [attr.aria-selected]=\"$index === activeIndex()\"\n (mouseenter)=\"hovered.emit($index)\"\n (mousedown)=\"onPick(item, $event)\"\n >\n {{ displayWith()(item) }}\n </li>\n }\n</ul>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n type ComponentRef,\n DestroyRef,\n Directive,\n ElementRef,\n PLATFORM_ID,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\n\nimport { getCaretCoordinates } from './caret';\nimport type { WrMentionCommit, WrMentionItem } from './interfaces';\nimport { WrMentionPanel, wrMentionOptionId } from './mention-panel';\n\ninterface ActiveState {\n trigger: string;\n query: string;\n startIndex: number;\n}\n\n/**\n * Deterministic under prerender, unlike `randomId()` — `aria-controls` is a\n * static host binding, so it renders on the server too, and a random value would\n * differ between that render and the hydrated client.\n */\nlet mentionUid = 0;\n\n/**\n * `@`-style mention picker for `<textarea>` and `<input>`. Listens for\n * trigger characters (default `'@'`); when typed at a word boundary, an\n * overlay panel opens at the caret and shows filtered options. Pick with\n * Enter / Tab / click — the trigger + query is replaced with the inserted\n * value plus a trailing space.\n *\n * @example\n * ```html\n * <textarea\n * wrMention\n * [wrMentionItems]=\"users\"\n * [displayWith]=\"userLabel\"\n * (wrMentionSelected)=\"onMention($event)\"\n * ></textarea>\n * ```\n *\n * @see https://ngwr.dev/reference/components/mention\n */\n@Directive({\n selector: '[wrMention]',\n host: {\n '(input)': 'onInput()',\n '(click)': 'onClick()',\n '(keyup)': 'onCaretMove($event)',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onBlur()',\n\n // The host keeps its implicit `role=\"textbox\"`. It is NOT a combobox: this\n // field holds prose and the mention is one fragment inside it, not the\n // field's value. `role=\"combobox\"` is also flatly disallowed on `<textarea>`\n // by ARIA in HTML (axe: `aria-allowed-role`), and it would drop\n // `aria-multiline`, which combobox does not support — so a screen reader\n // would stop reporting the field as multi-line for the whole editing\n // session, not just while the popup is open.\n //\n // Static, because `aria-autocomplete` describes a permanent capability of\n // the field; ARIA says authors SHOULD NOT toggle it to signal that\n // suggestions are currently showing. Being static is also what makes the\n // feature discoverable — it is announced on focus, before the user types a\n // trigger char.\n 'aria-autocomplete': 'list',\n // Required alongside `aria-autocomplete=\"list\"`, and must name the popup's\n // role. Measured clean on a textbox with axe-core 4.13.\n 'aria-haspopup': 'listbox',\n // Also static, and deliberately left pointing at an id that only exists\n // while the panel is open: gating it would mean gating `aria-autocomplete`\n // too. An unresolved `aria-controls` is a manual-review note in axe, never a\n // violation — unlike `aria-activedescendant`, which is an author error.\n //\n // This is the attribute that legalises the whole design: for a textbox\n // controlling a popup, `aria-activedescendant` is allowed to name an element\n // owned by the *controlled* element, which is how the reference reaches into\n // the CDK overlay's separate DOM subtree at all.\n '[attr.aria-controls]': 'listboxId',\n '[attr.aria-activedescendant]': 'activeOptionId()',\n\n // No `aria-expanded`. It is not a supported state of `role=\"textbox\"` —\n // axe rates it a *critical* `aria-allowed-attr` violation — so the\n // \"a list appeared\" signal cannot be an ARIA state here. That is what the\n // live region below is for; it is load-bearing, not decoration.\n },\n})\nexport class WrMention<T extends WrMentionItem = WrMentionItem> {\n /** Items to filter against the typed query. */\n readonly wrMentionItems = input<readonly T[]>([]);\n\n /** Trigger characters that open the panel. @default ['@'] */\n readonly triggers = input<readonly string[]>(['@']);\n\n /** Maps an item to its display label. @default `item.label` */\n readonly displayWith = input<(item: T) => string>((item: T) => item.label);\n\n /**\n * Returns the text to insert into the textarea on commit. Receives the\n * picked item and the trigger char. @default `${trigger}${displayWith(item)}`\n */\n readonly valueWith = input<((item: T, trigger: string) => string) | null>(null);\n\n /** Custom filter. @default case-insensitive `includes` over `displayWith(item)`. */\n readonly filterWith = input<((query: string, item: T) => boolean) | null>(null);\n\n /** Maximum number of items shown in the panel. @default 8 */\n readonly maxResults = input(8, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)) });\n\n /** Emits the selected item, trigger, and query whenever the user commits. */\n readonly wrMentionSelected = output<WrMentionCommit<T>>();\n\n private readonly host = inject<ElementRef<HTMLTextAreaElement | HTMLInputElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private overlayRef: OverlayRef | null = null;\n private panelRef: ComponentRef<WrMentionPanel> | null = null;\n private state: ActiveState | null = null;\n private readonly activeIndex = signal(0);\n private readonly filteredItems = signal<readonly T[]>([]);\n private readonly isOpen = signal(false);\n\n /** @internal — read by the `aria-controls` host binding. */\n protected readonly listboxId = `wr-mention-listbox-${++mentionUid}`;\n\n private readonly listLabel = readI18nText('mention.listbox', 'Mentions');\n private readonly availableText = useI18nFormatter('mention.available', 'Matches available: {{count}}');\n private readonly insertedText = useI18nFormatter('mention.inserted', 'Inserted: {{label}}');\n\n /**\n * `aria-activedescendant`, or `null`.\n *\n * Gated hard on `isOpen()`, which is only set once the panel's options are\n * actually in the DOM. An id that resolves to nothing is an author error, and\n * the failure mode is silent: the browser does not complain, it simply stops\n * firing the event that makes a screen reader speak the option. Same\n * discipline `tree.ts` uses for its virtualized rows.\n */\n protected readonly activeOptionId = computed<string | null>(() => {\n if (!this.isOpen()) return null;\n const index = this.activeIndex();\n return index >= 0 && index < this.filteredItems().length ? wrMentionOptionId(this.listboxId, index) : null;\n });\n\n /**\n * Polite live region, parked on `<body>`.\n *\n * Not inside the panel: `close()` disposes the overlay on every keystroke that\n * leaves a mention, so a region living there would be recreated constantly —\n * and a live region has to be in the DOM *before* its text changes to be\n * announced at all. It also could not announce the two cases that matter most,\n * since in both the panel is already gone: \"nothing matched\" and \"inserted X\".\n *\n * Not a sibling of the host either — that would land inside the consumer's\n * markup and start matching their `+`, `~` and `:nth-child()` selectors.\n */\n private liveRegion: HTMLElement | null = null;\n\n constructor() {\n if (this.isBrowser) {\n const doc = this.host.nativeElement.ownerDocument;\n const region = doc.createElement('div');\n region.className = 'wr-mention__status';\n region.setAttribute('role', 'status');\n doc.body.appendChild(region);\n this.liveRegion = region;\n }\n\n this.destroyRef.onDestroy(() => {\n this.close();\n this.liveRegion?.remove();\n this.liveRegion = null;\n });\n }\n\n private announce(message: string): void {\n if (this.liveRegion) this.liveRegion.textContent = message;\n }\n\n // Host listeners\n\n /** @internal */\n protected onInput(): void {\n this.detect();\n }\n\n /** @internal */\n protected onClick(): void {\n this.detect();\n }\n\n /** @internal */\n protected onCaretMove(event: KeyboardEvent): void {\n // Every key `onKeydown` already consumed, Escape included: its keyup would\n // re-detect at a caret that is still sitting inside the mention and reopen the\n // panel Escape had just dismissed. A synthetic keydown-only Escape hides that\n // — the real gesture is a pair, and the second half undid the first.\n if (\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.key === 'Enter' ||\n event.key === 'Tab' ||\n event.key === 'Escape'\n ) {\n return;\n }\n this.detect();\n }\n\n /** @internal */\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.state) return;\n const list = this.filteredItems();\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() + 1) % list.length);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() - 1 + list.length) % list.length);\n break;\n case 'Enter':\n case 'Tab': {\n const item = list[this.activeIndex()];\n if (item) {\n event.preventDefault();\n this.commit(item);\n }\n break;\n }\n case 'Escape':\n event.preventDefault();\n this.close();\n break;\n default:\n break;\n }\n }\n\n /** @internal */\n protected onBlur(): void {\n // Defer so option mousedown lands first.\n setTimeout(() => this.close(), 120);\n }\n\n // Detection\n\n private detect(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const text = el.value.substring(0, caret);\n const triggers = this.triggers();\n\n // Walk back from the caret looking for a trigger char with whitespace\n // (or start-of-string) immediately before it. Stop on whitespace.\n let i = caret;\n while (i > 0) {\n const ch = text[i - 1];\n if (triggers.includes(ch)) {\n const before = i - 2 >= 0 ? text[i - 2] : '';\n if (i - 2 < 0 || /\\s/.test(before)) {\n const query = text.substring(i);\n this.activate(ch, query, i - 1);\n return;\n }\n break;\n }\n if (/\\s/.test(ch)) break;\n i--;\n }\n this.close();\n }\n\n private activate(trigger: string, query: string, startIndex: number): void {\n this.state = { trigger, query, startIndex };\n const previousCount = this.filteredItems().length;\n const filtered = this.filter(query).slice(0, this.maxResults());\n this.filteredItems.set(filtered);\n if (filtered.length === 0) {\n // Announce *after* closing: `close()` clears the region, so announcing\n // first would wipe the message before a screen reader reached it.\n this.close();\n this.announce(this.availableText({ count: 0 }));\n return;\n }\n this.setActive(Math.min(this.activeIndex(), filtered.length - 1));\n this.open();\n // Only when the size actually changes. Re-announcing the same count on every\n // keystroke would talk over the per-option speech from activedescendant.\n if (filtered.length !== previousCount) this.announce(this.availableText({ count: filtered.length }));\n }\n\n private filter(query: string): readonly T[] {\n const items = this.wrMentionItems();\n const custom = this.filterWith();\n const display = this.displayWith();\n if (!query) return items;\n if (custom) return items.filter(item => custom(query, item));\n const q = query.toLowerCase();\n return items.filter(item => display(item).toLowerCase().includes(q));\n }\n\n private setActive(i: number): void {\n this.activeIndex.set(i);\n this.panelRef?.setInput('activeIndex', i);\n }\n\n // Overlay\n\n private open(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const { top, left, lineHeight } = getCaretCoordinates(el, caret);\n const x = left;\n const y = top + lineHeight + 4;\n\n if (!this.overlayRef) {\n this.overlayRef = this.overlay.create({\n positionStrategy: this.overlay.position().global().left(`${x}px`).top(`${y}px`),\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-mention-overlay',\n });\n\n const portal = new ComponentPortal(WrMentionPanel);\n const ref = this.overlayRef.attach(portal);\n this.panelRef = ref;\n ref.setInput('displayWith', this.displayWith());\n ref.setInput('listboxId', this.listboxId);\n ref.setInput('listLabel', this.listLabel);\n ref.instance.picked.subscribe(item => this.commit(item as T));\n ref.instance.hovered.subscribe(i => this.setActive(i));\n } else {\n this.overlayRef.updatePositionStrategy(this.overlay.position().global().left(`${x}px`).top(`${y}px`));\n }\n\n this.panelRef?.setInput('items', this.filteredItems());\n this.panelRef?.setInput('activeIndex', this.activeIndex());\n\n // Render the panel's own view NOW, before `aria-activedescendant` is allowed\n // to name one of its options. `attach()` inserts the host element but leaves\n // it empty — the panel is a separate root view, so on the first open the\n // `<li>` being referenced does not exist anywhere in the document yet.\n // Waiting for the next tick would leave a window where the reference dangles\n // and the announcement is lost with no error anywhere.\n this.panelRef?.changeDetectorRef.detectChanges();\n this.isOpen.set(true);\n }\n\n private close(): void {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n this.panelRef = null;\n }\n this.isOpen.set(false);\n this.state = null;\n this.activeIndex.set(0);\n this.filteredItems.set([]);\n this.announce('');\n }\n\n private commit(item: T): void {\n // Snapshot the state up front. `dispatchEvent` below re-enters this directive\n // synchronously: the listener runs `detect()`, and because assigning\n // `el.value` has already parked the caret at the end of the text — past the\n // space this method appends — `detect()` finds no trigger and calls\n // `close()`, which nulls `this.state`. Reading `this.state` after that line\n // threw, so `wrMentionSelected` never fired for anyone.\n const state = this.state;\n if (!state) return;\n\n const el = this.host.nativeElement;\n const value = el.value;\n const caret = el.selectionStart ?? value.length;\n const valueFn = this.valueWith();\n const label = this.displayWith()(item);\n const inserted = valueFn ? valueFn(item, state.trigger) : `${state.trigger}${label}`;\n const replacement = `${inserted} `;\n const before = value.substring(0, state.startIndex);\n const after = value.substring(caret);\n const next = before + replacement + after;\n\n el.value = next;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n const nextCaret = (before + replacement).length;\n el.setSelectionRange(nextCaret, nextCaret);\n this.wrMentionSelected.emit({ item, trigger: state.trigger, query: state.query });\n\n // Announce after `close()`, never before — it clears the live region.\n // Committing is otherwise silent: the panel simply vanishes.\n this.close();\n this.announce(this.insertedText({ label }));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-mention.mjs","sources":["../../../projects/lib/mention/caret.ts","../../../projects/lib/mention/mention-panel.ts","../../../projects/lib/mention/mention-panel.html","../../../projects/lib/mention/mention.ts","../../../projects/lib/mention/ngwr-mention.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * CSS properties whose values must be copied onto the mirror to match layout.\n *\n * `as const satisfies` keeps each name checked against `CSSStyleDeclaration`\n * while narrowing the element type to these string-valued literals — the wider\n * `keyof CSSStyleDeclaration` also spans its methods (`setProperty`, `item`, …),\n * which makes the indexed read below look like an unbound method.\n */\nconst MIRROR_PROPS = [\n 'boxSizing',\n 'width',\n 'height',\n 'overflowX',\n 'overflowY',\n 'borderTopWidth',\n 'borderRightWidth',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'lineHeight',\n 'fontFamily',\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration',\n 'letterSpacing',\n 'wordSpacing',\n 'tabSize',\n 'whiteSpace',\n 'wordWrap',\n] as const satisfies readonly (keyof CSSStyleDeclaration)[];\n\n/**\n * Compute the viewport coordinates of a textarea / input caret at a given\n * `position`. Uses the \"mirror div\" technique — a hidden div with the\n * same styling holds the text up to the caret, and we read the bounding\n * rect of a probe span placed at the boundary.\n *\n * Returns viewport-relative `{ top, left }` — pass straight to a CDK\n * Overlay anchored at that point.\n */\nexport function getCaretCoordinates(\n input: HTMLTextAreaElement | HTMLInputElement,\n position: number\n): { top: number; left: number; lineHeight: number } {\n const doc = input.ownerDocument;\n const style = getComputedStyle(input);\n const mirror = doc.createElement('div');\n\n for (const prop of MIRROR_PROPS) {\n mirror.style[prop] = style[prop];\n }\n mirror.style.position = 'absolute';\n mirror.style.visibility = 'hidden';\n mirror.style.top = '0';\n mirror.style.left = '0';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.wordWrap = 'break-word';\n\n // <input> is single-line — force whitespace behaviour so coords are sane.\n if (input.tagName === 'INPUT') {\n mirror.style.whiteSpace = 'pre';\n mirror.style.height = 'auto';\n mirror.style.width = 'auto';\n mirror.style.overflow = 'hidden';\n }\n\n const before = input.value.substring(0, position);\n mirror.textContent = before;\n\n const probe = doc.createElement('span');\n // Single character so the span has measurable size even at the end.\n probe.textContent = input.value.substring(position) || '.';\n mirror.appendChild(probe);\n\n doc.body.appendChild(mirror);\n const probeRect = probe.getBoundingClientRect();\n const mirrorRect = mirror.getBoundingClientRect();\n const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;\n doc.body.removeChild(mirror);\n\n const inputRect = input.getBoundingClientRect();\n return {\n top: inputRect.top + (probeRect.top - mirrorRect.top) - input.scrollTop,\n left: inputRect.left + (probeRect.left - mirrorRect.left) - input.scrollLeft,\n lineHeight,\n };\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, input, output } from '@angular/core';\n\nimport type { WrMentionItem } from './interfaces';\n\n/**\n * The id of the option at `index`, given the listbox's own id.\n *\n * Lives here, and is used from both sides, because this format is a contract\n * between two files: the panel stamps it onto each `<li>`, and the directive\n * names one of them in `aria-activedescendant`. Written out twice, the two would\n * drift and the reference would silently dangle — and a dangling\n * `aria-activedescendant` is not an error the browser reports, it just stops\n * announcing options.\n *\n * @internal\n */\nexport function wrMentionOptionId(listboxId: string, index: number): string {\n return `${listboxId}-opt-${index}`;\n}\n\n/**\n * Overlay panel rendered by {@link WrMention}. Not intended for\n * direct use — the directive instantiates it via a ComponentPortal.\n *\n * @internal — the same tag its twin `WrPopconfirmPanel` already carried. It says\n * the intent; it does not by itself keep the class out of the generated API\n * tables, because `WrTableFilter` and `WrTableSort` are tagged AND documented —\n * see `EXPORTED_BUT_INTERNAL` in `scripts/lib/extract-api.ts`.\n */\n@Component({\n selector: 'wr-mention-panel',\n templateUrl: './mention-panel.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-mention-panel' },\n})\nexport class WrMentionPanel {\n readonly items = input<readonly WrMentionItem[]>([]);\n readonly activeIndex = input(0);\n readonly displayWith = input<(item: WrMentionItem) => string>(item => item.label);\n\n /**\n * Id of the `<ul role=\"listbox\">`, minted by the directive and pointed at by\n * its `aria-controls`. Owned there rather than here because the panel is\n * disposed and rebuilt whenever the caret leaves a mention, while the\n * attribute on the host has to stay put.\n *\n * Plain `input('')`, never `input.required` — a ComponentPortal-created\n * component throws on the first change detection if a required input is unset.\n */\n readonly listboxId = input('');\n\n /** Accessible name for the listbox. ARIA requires one; the directive supplies it from the catalog. */\n readonly listLabel = input('');\n\n protected optionId(index: number): string {\n return wrMentionOptionId(this.listboxId(), index);\n }\n\n /** Emitted when the user picks an item (click). */\n readonly picked = output<WrMentionItem>();\n\n /** Emitted on mouseenter — used by the directive to track hover. */\n readonly hovered = output<number>();\n\n protected onPick(item: WrMentionItem, event: MouseEvent): void {\n // mousedown semantics — keep focus on the input.\n event.preventDefault();\n this.picked.emit(item);\n }\n}\n","<!--\n No empty branch: `activate()` calls `close()` and returns before `open()` when\n nothing matches, so the panel is never rendered with zero items. The \"No\n matches\" `<div>` that used to live here was unreachable — and a hard-coded\n English string besides. The zero-match case is announced through the\n directive's live region instead.\n-->\n<ul class=\"wr-mention-panel__list\" role=\"listbox\" [attr.id]=\"listboxId()\" [attr.aria-label]=\"listLabel()\">\n @for (item of items(); track $index) {\n <li\n class=\"wr-mention-panel__option\"\n role=\"option\"\n [attr.id]=\"optionId($index)\"\n [class.wr-mention-panel__option--active]=\"$index === activeIndex()\"\n [attr.aria-selected]=\"$index === activeIndex()\"\n (mouseenter)=\"hovered.emit($index)\"\n (mousedown)=\"onPick(item, $event)\"\n >\n {{ displayWith()(item) }}\n </li>\n }\n</ul>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n type ConnectedPosition,\n type FlexibleConnectedPositionStrategyOrigin,\n type OverlayRef,\n ScrollStrategyOptions,\n} from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n type ComponentRef,\n DestroyRef,\n Directive,\n ElementRef,\n PLATFORM_ID,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\n\nimport { getCaretCoordinates } from './caret';\nimport type { WrMentionCommit, WrMentionItem } from './interfaces';\nimport { WrMentionPanel, wrMentionOptionId } from './mention-panel';\n\ninterface ActiveState {\n trigger: string;\n query: string;\n startIndex: number;\n}\n\n/**\n * Deterministic under prerender, unlike `randomId()` — `aria-controls` is a\n * static host binding, so it renders on the server too, and a random value would\n * differ between that render and the hydrated client.\n */\nlet mentionUid = 0;\n\n/** Gap between the caret's line and the panel, on whichever side it opens. */\nconst CARET_GAP = 4;\n\n/**\n * Below the caret's line first, above it when there is no room — the two sides\n * a caret-anchored list can take. There is no horizontal pair: a panel that\n * jumped to the other side of the caret would read as a different widget, so a\n * caret near the inline end is handled by `withPush` sliding the panel back\n * onto the screen instead. `overlayX: 'start'` is the logical corner, so RTL\n * hangs the panel from the caret's other side without a second table.\n */\nconst CARET_POSITIONS: ConnectedPosition[] = [\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },\n];\n\n/**\n * `@`-style mention picker for `<textarea>` and `<input>`. Listens for\n * trigger characters (default `'@'`); when typed at a word boundary, an\n * overlay panel opens at the caret and shows filtered options. Pick with\n * Enter / Tab / click — the trigger + query is replaced with the inserted\n * value plus a trailing space.\n *\n * @example\n * ```html\n * <textarea\n * wrMention\n * [wrMentionItems]=\"users\"\n * [displayWith]=\"userLabel\"\n * (wrMentionSelected)=\"onMention($event)\"\n * ></textarea>\n * ```\n *\n * @see https://ngwr.dev/reference/components/mention\n */\n@Directive({\n selector: '[wrMention]',\n host: {\n '(input)': 'onInput()',\n '(click)': 'onClick()',\n '(keyup)': 'onCaretMove($event)',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onBlur()',\n\n // The host keeps its implicit `role=\"textbox\"`. It is NOT a combobox: this\n // field holds prose and the mention is one fragment inside it, not the\n // field's value. `role=\"combobox\"` is also flatly disallowed on `<textarea>`\n // by ARIA in HTML (axe: `aria-allowed-role`), and it would drop\n // `aria-multiline`, which combobox does not support — so a screen reader\n // would stop reporting the field as multi-line for the whole editing\n // session, not just while the popup is open.\n //\n // Static, because `aria-autocomplete` describes a permanent capability of\n // the field; ARIA says authors SHOULD NOT toggle it to signal that\n // suggestions are currently showing. Being static is also what makes the\n // feature discoverable — it is announced on focus, before the user types a\n // trigger char.\n 'aria-autocomplete': 'list',\n // Required alongside `aria-autocomplete=\"list\"`, and must name the popup's\n // role. Measured clean on a textbox with axe-core 4.13.\n 'aria-haspopup': 'listbox',\n // Also static, and deliberately left pointing at an id that only exists\n // while the panel is open: gating it would mean gating `aria-autocomplete`\n // too. An unresolved `aria-controls` is a manual-review note in axe, never a\n // violation — unlike `aria-activedescendant`, which is an author error.\n //\n // This is the attribute that legalises the whole design: for a textbox\n // controlling a popup, `aria-activedescendant` is allowed to name an element\n // owned by the *controlled* element, which is how the reference reaches into\n // the CDK overlay's separate DOM subtree at all.\n '[attr.aria-controls]': 'listboxId',\n '[attr.aria-activedescendant]': 'activeOptionId()',\n\n // No `aria-expanded`. It is not a supported state of `role=\"textbox\"` —\n // axe rates it a *critical* `aria-allowed-attr` violation — so the\n // \"a list appeared\" signal cannot be an ARIA state here. That is what the\n // live region below is for; it is load-bearing, not decoration.\n },\n})\nexport class WrMention<T extends WrMentionItem = WrMentionItem> {\n /** Items to filter against the typed query. */\n readonly wrMentionItems = input<readonly T[]>([]);\n\n /** Trigger characters that open the panel. @default ['@'] */\n readonly triggers = input<readonly string[]>(['@']);\n\n /** Maps an item to its display label. @default `item.label` */\n readonly displayWith = input<(item: T) => string>((item: T) => item.label);\n\n /**\n * Returns the text to insert into the textarea on commit. Receives the\n * picked item and the trigger char. @default `${trigger}${displayWith(item)}`\n */\n readonly valueWith = input<((item: T, trigger: string) => string) | null>(null);\n\n /** Custom filter. @default case-insensitive `includes` over `displayWith(item)`. */\n readonly filterWith = input<((query: string, item: T) => boolean) | null>(null);\n\n /** Maximum number of items shown in the panel. @default 8 */\n readonly maxResults = input(8, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)) });\n\n /** Emits the selected item, trigger, and query whenever the user commits. */\n readonly wrMentionSelected = output<WrMentionCommit<T>>();\n\n private readonly host = inject<ElementRef<HTMLTextAreaElement | HTMLInputElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private overlayRef: OverlayRef | null = null;\n private panelRef: ComponentRef<WrMentionPanel> | null = null;\n /**\n * Where the caret sits INSIDE the field — its offset from the field's\n * top-left corner, plus the height of the line it is on. Mutated in place by\n * `open()`, never replaced: `caretAnchor()` closes over this object.\n */\n private readonly caretOffset = { dx: 0, dy: 0, lineHeight: 0 };\n private state: ActiveState | null = null;\n private readonly activeIndex = signal(0);\n private readonly filteredItems = signal<readonly T[]>([]);\n private readonly isOpen = signal(false);\n\n /** @internal — read by the `aria-controls` host binding. */\n protected readonly listboxId = `wr-mention-listbox-${++mentionUid}`;\n\n private readonly listLabel = readI18nText('mention.listbox', 'Mentions');\n private readonly availableText = useI18nFormatter('mention.available', 'Matches available: {{count}}');\n private readonly insertedText = useI18nFormatter('mention.inserted', 'Inserted: {{label}}');\n\n /**\n * `aria-activedescendant`, or `null`.\n *\n * Gated hard on `isOpen()`, which is only set once the panel's options are\n * actually in the DOM. An id that resolves to nothing is an author error, and\n * the failure mode is silent: the browser does not complain, it simply stops\n * firing the event that makes a screen reader speak the option. Same\n * discipline `tree.ts` uses for its virtualized rows.\n */\n protected readonly activeOptionId = computed<string | null>(() => {\n if (!this.isOpen()) return null;\n const index = this.activeIndex();\n return index >= 0 && index < this.filteredItems().length ? wrMentionOptionId(this.listboxId, index) : null;\n });\n\n /**\n * Polite live region, parked on `<body>`.\n *\n * Not inside the panel: `close()` disposes the overlay on every keystroke that\n * leaves a mention, so a region living there would be recreated constantly —\n * and a live region has to be in the DOM *before* its text changes to be\n * announced at all. It also could not announce the two cases that matter most,\n * since in both the panel is already gone: \"nothing matched\" and \"inserted X\".\n *\n * Not a sibling of the host either — that would land inside the consumer's\n * markup and start matching their `+`, `~` and `:nth-child()` selectors.\n */\n private liveRegion: HTMLElement | null = null;\n\n constructor() {\n if (this.isBrowser) {\n const doc = this.host.nativeElement.ownerDocument;\n const region = doc.createElement('div');\n region.className = 'wr-mention__status';\n region.setAttribute('role', 'status');\n doc.body.appendChild(region);\n this.liveRegion = region;\n }\n\n this.destroyRef.onDestroy(() => {\n this.close();\n this.liveRegion?.remove();\n this.liveRegion = null;\n });\n }\n\n private announce(message: string): void {\n if (this.liveRegion) this.liveRegion.textContent = message;\n }\n\n // Host listeners\n\n /** @internal */\n protected onInput(): void {\n this.detect();\n }\n\n /** @internal */\n protected onClick(): void {\n this.detect();\n }\n\n /** @internal */\n protected onCaretMove(event: KeyboardEvent): void {\n // Every key `onKeydown` already consumed, Escape included: its keyup would\n // re-detect at a caret that is still sitting inside the mention and reopen the\n // panel Escape had just dismissed. A synthetic keydown-only Escape hides that\n // — the real gesture is a pair, and the second half undid the first.\n if (\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.key === 'Enter' ||\n event.key === 'Tab' ||\n event.key === 'Escape'\n ) {\n return;\n }\n this.detect();\n }\n\n /** @internal */\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.state) return;\n const list = this.filteredItems();\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() + 1) % list.length);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() - 1 + list.length) % list.length);\n break;\n case 'Enter':\n case 'Tab': {\n const item = list[this.activeIndex()];\n if (item) {\n event.preventDefault();\n this.commit(item);\n }\n break;\n }\n case 'Escape':\n event.preventDefault();\n // The panel is the innermost thing Escape can dismiss, so it keeps the\n // key: without this, the same press also closed the dialog / drawer /\n // window the field sits in.\n //\n // `preventDefault()` alone buys nothing — every host overlay matches on\n // `event.key` and none of them looks at `defaultPrevented` — and letting\n // CDK's `OverlayKeyboardDispatcher` sort it out does not work either.\n // The dispatcher walks the stack newest-first and stops at the first\n // overlay with a `keydownEvents()` subscriber; this panel has none (it\n // is driven from the field's own handler, which runs at the target\n // before the body-phase dispatcher), so Escape falls straight through to\n // the overlay underneath.\n event.stopPropagation();\n this.close();\n break;\n default:\n break;\n }\n }\n\n /** @internal */\n protected onBlur(): void {\n // Defer so option mousedown lands first.\n setTimeout(() => this.close(), 120);\n }\n\n // Detection\n\n private detect(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const text = el.value.substring(0, caret);\n const triggers = this.triggers();\n\n // Walk back from the caret looking for a trigger char with whitespace\n // (or start-of-string) immediately before it. Stop on whitespace.\n let i = caret;\n while (i > 0) {\n const ch = text[i - 1];\n if (triggers.includes(ch)) {\n const before = i - 2 >= 0 ? text[i - 2] : '';\n if (i - 2 < 0 || /\\s/.test(before)) {\n const query = text.substring(i);\n this.activate(ch, query, i - 1);\n return;\n }\n break;\n }\n if (/\\s/.test(ch)) break;\n i--;\n }\n this.close();\n }\n\n private activate(trigger: string, query: string, startIndex: number): void {\n this.state = { trigger, query, startIndex };\n const previousCount = this.filteredItems().length;\n const filtered = this.filter(query).slice(0, this.maxResults());\n this.filteredItems.set(filtered);\n if (filtered.length === 0) {\n // Announce *after* closing: `close()` clears the region, so announcing\n // first would wipe the message before a screen reader reached it.\n this.close();\n this.announce(this.availableText({ count: 0 }));\n return;\n }\n this.setActive(Math.min(this.activeIndex(), filtered.length - 1));\n this.open();\n // Only when the size actually changes. Re-announcing the same count on every\n // keystroke would talk over the per-option speech from activedescendant.\n if (filtered.length !== previousCount) this.announce(this.availableText({ count: filtered.length }));\n }\n\n private filter(query: string): readonly T[] {\n const items = this.wrMentionItems();\n const custom = this.filterWith();\n const display = this.displayWith();\n if (!query) return items;\n if (custom) return items.filter(item => custom(query, item));\n const q = query.toLowerCase();\n return items.filter(item => display(item).toLowerCase().includes(q));\n }\n\n private setActive(i: number): void {\n this.activeIndex.set(i);\n this.panelRef?.setInput('activeIndex', i);\n }\n\n // Overlay\n\n /**\n * The overlay's anchor: the caret's line box, grown by the gap the panel\n * keeps from the text, as a zero-WIDTH virtual origin in viewport\n * coordinates. Inflating the rect rather than passing `offsetY` is what keeps\n * `withPush` honest — CDK applies a position offset as a `transform` AFTER it\n * has clamped, so a pushed panel would land the offset off the screen edge.\n *\n * Live getters, not a snapshot, and that is the whole of the scroll fix.\n * `getCaretCoordinates` answers in viewport coordinates, which a\n * `GlobalPositionStrategy` froze at the instant of the keystroke: the panel\n * then stayed pinned to the screen while the page scrolled out from under it\n * (150px of scroll, 150px of drift, ending up over unrelated content). CDK\n * re-reads a virtual origin on every `apply()`, so pinning it to the FIELD's\n * live rect is what turns `reposition()` into a real re-anchor.\n *\n * Only the caret's offset inside the field is captured up front. Measuring it\n * runs the mirror-div pass in `caret.ts` — a full layout read, far too\n * expensive to repeat per scroll event — and it cannot change without a\n * keystroke, which re-enters `open()` anyway.\n */\n private caretAnchor(): FlexibleConnectedPositionStrategyOrigin {\n const field = this.host.nativeElement;\n const offset = this.caretOffset;\n return {\n width: 0,\n get x(): number {\n return field.getBoundingClientRect().left + offset.dx;\n },\n get y(): number {\n return field.getBoundingClientRect().top + offset.dy - CARET_GAP;\n },\n get height(): number {\n return offset.lineHeight + CARET_GAP * 2;\n },\n };\n }\n\n private open(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const { top, left, lineHeight } = getCaretCoordinates(el, caret);\n const field = el.getBoundingClientRect();\n this.caretOffset.dx = left - field.left;\n this.caretOffset.dy = top - field.top;\n this.caretOffset.lineHeight = lineHeight;\n\n if (!this.overlayRef) {\n this.overlayRef = this.overlay.create({\n // Flexible, not global: a global strategy has no anchor and no fallback\n // positions, so the panel was always drawn downward from the caret no\n // matter how little room was left below it — half the list ended up\n // under the fold, unreachable by pointer AND invisible to a sighted\n // keyboard user walking it with `aria-activedescendant` (WCAG 2.4.11).\n positionStrategy: this.overlay\n .position()\n .flexibleConnectedTo(this.caretAnchor())\n // The panel sizes itself (`max-height` on the list); the flip above\n // and `withPush` are the whole answer to a cramped viewport.\n .withFlexibleDimensions(false)\n .withPush(true)\n .withPositions(CARET_POSITIONS),\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-mention-overlay',\n });\n\n const portal = new ComponentPortal(WrMentionPanel);\n const ref = this.overlayRef.attach(portal);\n this.panelRef = ref;\n ref.setInput('displayWith', this.displayWith());\n ref.setInput('listboxId', this.listboxId);\n // Read once here rather than bound: the panel is created per open, and by\n // then the catalog has long landed.\n ref.setInput('listLabel', this.listLabel());\n ref.instance.picked.subscribe(item => this.commit(item as T));\n ref.instance.hovered.subscribe(i => this.setActive(i));\n }\n // No `else` re-building the strategy: the anchor reads `caretOffset` and the\n // field's live rect, both of which the lines above have already refreshed.\n\n this.panelRef?.setInput('items', this.filteredItems());\n this.panelRef?.setInput('activeIndex', this.activeIndex());\n\n // Render the panel's own view NOW, before `aria-activedescendant` is allowed\n // to name one of its options. `attach()` inserts the host element but leaves\n // it empty — the panel is a separate root view, so on the first open the\n // `<li>` being referenced does not exist anywhere in the document yet.\n // Waiting for the next tick would leave a window where the reference dangles\n // and the announcement is lost with no error anywhere.\n this.panelRef?.changeDetectorRef.detectChanges();\n // Place it only now, with the options rendered: which side of the caret the\n // panel takes is decided by measuring it, and `attach()` defers CDK's own\n // first placement into `afterNextRender` — a frame in which the panel would\n // be painted, full size and fully opaque, at the overlay container's\n // top-left corner.\n this.overlayRef.updatePosition();\n this.isOpen.set(true);\n }\n\n private close(): void {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n this.panelRef = null;\n }\n this.isOpen.set(false);\n this.state = null;\n this.activeIndex.set(0);\n this.filteredItems.set([]);\n this.announce('');\n }\n\n private commit(item: T): void {\n // Snapshot the state up front. `dispatchEvent` below re-enters this directive\n // synchronously: the listener runs `detect()`, and because assigning\n // `el.value` has already parked the caret at the end of the text — past the\n // space this method appends — `detect()` finds no trigger and calls\n // `close()`, which nulls `this.state`. Reading `this.state` after that line\n // threw, so `wrMentionSelected` never fired for anyone.\n const state = this.state;\n if (!state) return;\n\n const el = this.host.nativeElement;\n const value = el.value;\n const caret = el.selectionStart ?? value.length;\n const valueFn = this.valueWith();\n const label = this.displayWith()(item);\n const inserted = valueFn ? valueFn(item, state.trigger) : `${state.trigger}${label}`;\n const replacement = `${inserted} `;\n const before = value.substring(0, state.startIndex);\n const after = value.substring(caret);\n const next = before + replacement + after;\n\n el.value = next;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n const nextCaret = (before + replacement).length;\n el.setSelectionRange(nextCaret, nextCaret);\n this.wrMentionSelected.emit({ item, trigger: state.trigger, query: state.query });\n\n // Announce after `close()`, never before — it clears the live region.\n // Committing is otherwise silent: the panel simply vanishes.\n this.close();\n this.announce(this.insertedText({ label }));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngwr-meta.mjs
CHANGED
|
@@ -247,10 +247,10 @@ class WrMeta {
|
|
|
247
247
|
link.remove();
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
251
|
-
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.
|
|
250
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMeta, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
251
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.4", ngImport: i0, type: WrMeta });
|
|
252
252
|
}
|
|
253
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
253
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMeta, decorators: [{
|
|
254
254
|
type: Service
|
|
255
255
|
}], ctorParameters: () => [] });
|
|
256
256
|
|
|
@@ -292,10 +292,10 @@ class WrMetaBinding {
|
|
|
292
292
|
});
|
|
293
293
|
inject(DestroyRef).onDestroy(() => this.handle?.pop());
|
|
294
294
|
}
|
|
295
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
296
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.
|
|
295
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMetaBinding, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
296
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: WrMetaBinding, isStandalone: true, selector: "[wrMeta]", inputs: { wrMeta: { classPropertyName: "wrMeta", publicName: "wrMeta", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
297
297
|
}
|
|
298
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
298
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMetaBinding, decorators: [{
|
|
299
299
|
type: Directive,
|
|
300
300
|
args: [{ selector: '[wrMeta]' }]
|
|
301
301
|
}], ctorParameters: () => [], propDecorators: { wrMeta: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrMeta", required: true }] }] } });
|
|
@@ -6,14 +6,24 @@ import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
|
6
6
|
* Use of this source code is governed by an MIT-style license that can be
|
|
7
7
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
8
8
|
*/
|
|
9
|
+
/** The `width: <n>%` the component writes onto a band, out of its inline `style`. */
|
|
10
|
+
const SLICE_WIDTH = /(?:^|;)\s*width\s*:\s*([\d.]+)%/;
|
|
9
11
|
/**
|
|
10
12
|
* Test harness for `<wr-meter-group>` — one bar divided into labelled bands.
|
|
11
13
|
*
|
|
12
14
|
* **The bar is a single `progressbar`, and its value is the TOTAL.** The bands
|
|
13
15
|
* themselves announce nothing: they carry a `title` for a mouse and their share as an
|
|
14
16
|
* inline width, and the legend is where their labels and values are readable. So a
|
|
15
|
-
* spec that wants "how much of each" reads {@link
|
|
16
|
-
*
|
|
17
|
+
* spec that wants "how much of each" reads {@link getSlices} for the shares or
|
|
18
|
+
* {@link getLegendLabels} / {@link getLegendValues} for what is printed, and one that
|
|
19
|
+
* wants "how full" reads {@link getValue}.
|
|
20
|
+
*
|
|
21
|
+
* **A band's share is read off the `style` ATTRIBUTE, not through `getCssValue()`**,
|
|
22
|
+
* and the difference is not cosmetic: `getCssValue()` is `getComputedStyle()`, and
|
|
23
|
+
* `width` resolves to the USED value — so the declared `30%` comes straight back in
|
|
24
|
+
* jsdom and arrives as `180px` on a 600px bar in a real browser, and the same harness
|
|
25
|
+
* answered 30 here and 180 under a browser runner with nothing to say which one you
|
|
26
|
+
* got.
|
|
17
27
|
*
|
|
18
28
|
* @see https://ngwr.dev/guides/testing
|
|
19
29
|
*/
|
|
@@ -51,10 +61,20 @@ class WrMeterGroupHarness extends ComponentHarness {
|
|
|
51
61
|
/** The bands, in order, with the share of the bar each takes. */
|
|
52
62
|
async getSlices() {
|
|
53
63
|
const slices = await this.locatorForAll('.wr-meter-group__slice')();
|
|
54
|
-
return Promise.all(slices.map(async (slice) =>
|
|
55
|
-
label
|
|
56
|
-
percent:
|
|
57
|
-
}))
|
|
64
|
+
return Promise.all(slices.map(async (slice) => {
|
|
65
|
+
const label = await slice.getAttribute('title');
|
|
66
|
+
return { label, percent: WrMeterGroupHarness.percentOf(label, await slice.getAttribute('style')) };
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/** Parse one band's declared share. See the class note on why this is not `getCssValue()`. */
|
|
70
|
+
static percentOf(label, style) {
|
|
71
|
+
const match = SLICE_WIDTH.exec(style ?? '');
|
|
72
|
+
if (!match) {
|
|
73
|
+
throw new Error(`WrMeterGroupHarness.getSlices(): the band ${label === null ? '(untitled)' : `"${label}"`} carries no ` +
|
|
74
|
+
`inline width percentage (style: "${style ?? ''}"). The component writes one for every band, since the ` +
|
|
75
|
+
'total always falls back to 1 — so its absence is the bug, and reporting 0 would read as an empty band.');
|
|
76
|
+
}
|
|
77
|
+
return Number.parseFloat(match[1]);
|
|
58
78
|
}
|
|
59
79
|
/** Whether the legend is rendered (`showLegend`, and at least one band). */
|
|
60
80
|
async hasLegend() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-meter-group-testing.mjs","sources":["../../../projects/lib/meter-group/testing/wr-meter-group-harness.ts","../../../projects/lib/meter-group/testing/ngwr-meter-group-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrMeterGroupHarnessFilters, WrMeterGroupSlice } from './interfaces';\n\n/**\n * Test harness for `<wr-meter-group>` — one bar divided into labelled bands.\n *\n * **The bar is a single `progressbar`, and its value is the TOTAL.** The bands\n * themselves announce nothing: they carry a `title` for a mouse and their share as an\n * inline width, and the legend is where their labels and values are readable. So a\n * spec that wants \"how much of each\" reads {@link
|
|
1
|
+
{"version":3,"file":"ngwr-meter-group-testing.mjs","sources":["../../../projects/lib/meter-group/testing/wr-meter-group-harness.ts","../../../projects/lib/meter-group/testing/ngwr-meter-group-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrMeterGroupHarnessFilters, WrMeterGroupSlice } from './interfaces';\n\n/** The `width: <n>%` the component writes onto a band, out of its inline `style`. */\nconst SLICE_WIDTH = /(?:^|;)\\s*width\\s*:\\s*([\\d.]+)%/;\n\n/**\n * Test harness for `<wr-meter-group>` — one bar divided into labelled bands.\n *\n * **The bar is a single `progressbar`, and its value is the TOTAL.** The bands\n * themselves announce nothing: they carry a `title` for a mouse and their share as an\n * inline width, and the legend is where their labels and values are readable. So a\n * spec that wants \"how much of each\" reads {@link getSlices} for the shares or\n * {@link getLegendLabels} / {@link getLegendValues} for what is printed, and one that\n * wants \"how full\" reads {@link getValue}.\n *\n * **A band's share is read off the `style` ATTRIBUTE, not through `getCssValue()`**,\n * and the difference is not cosmetic: `getCssValue()` is `getComputedStyle()`, and\n * `width` resolves to the USED value — so the declared `30%` comes straight back in\n * jsdom and arrives as `180px` on a 600px bar in a real browser, and the same harness\n * answered 30 here and 180 under a browser runner with nothing to say which one you\n * got.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrMeterGroupHarness extends ComponentHarness {\n static hostSelector = 'wr-meter-group';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrMeterGroupHarnessFilters = {}): HarnessPredicate<WrMeterGroupHarness> {\n return new HarnessPredicate(WrMeterGroupHarness, options)\n .addOption('ariaLabel', options.ariaLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\n .addOption('sliceLabel', options.sliceLabel, async (harness, label) => {\n for (const printed of await harness.getLegendLabels()) {\n if (await HarnessPredicate.stringMatches(printed, label)) return true;\n }\n return false;\n });\n }\n\n private readonly bar = this.locatorFor('.wr-meter-group__bar');\n\n /** The role the bar announces — `progressbar`, for the total. */\n async getRole(): Promise<string | null> {\n return (await this.bar()).getAttribute('role');\n }\n\n /** The bar's accessible name. */\n async getAccessibleName(): Promise<string | null> {\n return (await this.bar()).getAttribute('aria-label');\n }\n\n /** The total the bands add up to, from `aria-valuenow`. */\n async getValue(): Promise<number> {\n return Number(await (await this.bar()).getAttribute('aria-valuenow'));\n }\n\n /** The full capacity, from `aria-valuemax` — given, or the sum of the bands. */\n async getMax(): Promise<number> {\n return Number(await (await this.bar()).getAttribute('aria-valuemax'));\n }\n\n /** The bands, in order, with the share of the bar each takes. */\n async getSlices(): Promise<WrMeterGroupSlice[]> {\n const slices = await this.locatorForAll('.wr-meter-group__slice')();\n\n return Promise.all(\n slices.map(async slice => {\n const label = await slice.getAttribute('title');\n return { label, percent: WrMeterGroupHarness.percentOf(label, await slice.getAttribute('style')) };\n })\n );\n }\n\n /** Parse one band's declared share. See the class note on why this is not `getCssValue()`. */\n private static percentOf(label: string | null, style: string | null): number {\n const match = SLICE_WIDTH.exec(style ?? '');\n if (!match) {\n throw new Error(\n `WrMeterGroupHarness.getSlices(): the band ${label === null ? '(untitled)' : `\"${label}\"`} carries no ` +\n `inline width percentage (style: \"${style ?? ''}\"). The component writes one for every band, since the ` +\n 'total always falls back to 1 — so its absence is the bug, and reporting 0 would read as an empty band.'\n );\n }\n return Number.parseFloat(match[1]);\n }\n\n /** Whether the legend is rendered (`showLegend`, and at least one band). */\n async hasLegend(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-meter-group__legend')()) !== null;\n }\n\n /** The legend's labels, in order. */\n async getLegendLabels(): Promise<string[]> {\n const labels = await this.locatorForAll('.wr-meter-group__legend-label')();\n return Promise.all(labels.map(label => label.text()));\n }\n\n /** The legend's values, in order — empty when `showValues` is off. */\n async getLegendValues(): Promise<string[]> {\n const values = await this.locatorForAll('.wr-meter-group__legend-value')();\n return Promise.all(values.map(value => value.text()));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -82,10 +82,10 @@ class WrMeterGroup {
|
|
|
82
82
|
percent: (WrMeterGroup.amount(segment.value) / this.resolvedMax()) * 100,
|
|
83
83
|
})), /* @ts-ignore */
|
|
84
84
|
...(ngDevMode ? [{ debugName: "slices" }] : /* istanbul ignore next */ []));
|
|
85
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
86
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
85
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMeterGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
86
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrMeterGroup, isStandalone: true, selector: "wr-meter-group", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showValues: { classPropertyName: "showValues", publicName: "showValues", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-meter-group" }, ngImport: i0, template: "<div\n class=\"wr-meter-group__bar\"\n role=\"progressbar\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"totalValue()\"\n>\n @for (slice of slices(); track $index) {\n <span\n class=\"wr-meter-group__slice\"\n [title]=\"slice.label\"\n [style.width.%]=\"slice.percent\"\n [style.background]=\"slice.color\"\n ></span>\n }\n</div>\n\n@if (showLegend() && slices().length > 0) {\n <ul class=\"wr-meter-group__legend\">\n @for (slice of slices(); track $index) {\n <li class=\"wr-meter-group__legend-item\">\n <span class=\"wr-meter-group__swatch\" aria-hidden=\"true\" [style.background]=\"slice.color\"></span>\n <span class=\"wr-meter-group__legend-label\">{{ slice.label }}</span>\n @if (showValues()) {\n <span class=\"wr-meter-group__legend-value\">{{ slice.value }}</span>\n }\n </li>\n }\n </ul>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
87
87
|
}
|
|
88
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
88
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMeterGroup, decorators: [{
|
|
89
89
|
type: Component,
|
|
90
90
|
args: [{ selector: 'wr-meter-group', encapsulation: ViewEncapsulation.None, host: { class: 'wr-meter-group' }, template: "<div\n class=\"wr-meter-group__bar\"\n role=\"progressbar\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"0\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"totalValue()\"\n>\n @for (slice of slices(); track $index) {\n <span\n class=\"wr-meter-group__slice\"\n [title]=\"slice.label\"\n [style.width.%]=\"slice.percent\"\n [style.background]=\"slice.color\"\n ></span>\n }\n</div>\n\n@if (showLegend() && slices().length > 0) {\n <ul class=\"wr-meter-group__legend\">\n @for (slice of slices(); track $index) {\n <li class=\"wr-meter-group__legend-item\">\n <span class=\"wr-meter-group__swatch\" aria-hidden=\"true\" [style.background]=\"slice.color\"></span>\n <span class=\"wr-meter-group__legend-label\">{{ slice.label }}</span>\n @if (showValues()) {\n <span class=\"wr-meter-group__legend-value\">{{ slice.value }}</span>\n }\n </li>\n }\n </ul>\n}\n" }]
|
|
91
91
|
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], segments: [{ type: i0.Input, args: [{ isSignal: true, alias: "segments", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], showValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValues", required: false }] }] } });
|
|
@@ -242,10 +242,10 @@ class WrOutsideClick {
|
|
|
242
242
|
this.cleanup = null;
|
|
243
243
|
this.pressOrigin = null;
|
|
244
244
|
}
|
|
245
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
246
|
-
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.
|
|
245
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOutsideClick, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
246
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.4", ngImport: i0, type: WrOutsideClick });
|
|
247
247
|
}
|
|
248
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOutsideClick, decorators: [{
|
|
249
249
|
type: Service
|
|
250
250
|
}] });
|
|
251
251
|
|
|
@@ -276,10 +276,10 @@ class WrOverlayContainer extends OverlayContainer {
|
|
|
276
276
|
super._createContainer();
|
|
277
277
|
this._containerElement?.classList.add('wr-overlay-container');
|
|
278
278
|
}
|
|
279
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
280
|
-
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.
|
|
279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOverlayContainer, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
280
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.4", ngImport: i0, type: WrOverlayContainer });
|
|
281
281
|
}
|
|
282
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOverlayContainer, decorators: [{
|
|
283
283
|
type: Service
|
|
284
284
|
}] });
|
|
285
285
|
|
|
@@ -45,10 +45,10 @@ class WrPageHeader {
|
|
|
45
45
|
* @default false
|
|
46
46
|
*/
|
|
47
47
|
responsive = input(false, { ...(ngDevMode ? { debugName: "responsive" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
48
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
49
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
48
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrPageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
49
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrPageHeader, isStandalone: true, selector: "wr-page-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.wr-page-header--responsive": "responsive()" }, classAttribute: "wr-page-header" }, ngImport: i0, template: "<div class=\"wr-page-header__breadcrumbs\">\n <ng-content select=\"[wrPageHeaderBreadcrumbs]\" />\n</div>\n\n<div class=\"wr-page-header__main\">\n <div class=\"wr-page-header__heading\">\n @if (title()) {\n <h1 class=\"wr-page-header__title\">{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"wr-page-header__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"wr-page-header__actions\">\n <ng-content select=\"[wrPageHeaderActions]\" />\n </div>\n</div>\n\n<div class=\"wr-page-header__extra\">\n <ng-content select=\"[wrPageHeaderExtra]\" />\n</div>\n", encapsulation: i0.ViewEncapsulation.None });
|
|
50
50
|
}
|
|
51
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
51
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrPageHeader, decorators: [{
|
|
52
52
|
type: Component,
|
|
53
53
|
args: [{ selector: 'wr-page-header', encapsulation: ViewEncapsulation.None, host: { class: 'wr-page-header', '[class.wr-page-header--responsive]': 'responsive()' }, template: "<div class=\"wr-page-header__breadcrumbs\">\n <ng-content select=\"[wrPageHeaderBreadcrumbs]\" />\n</div>\n\n<div class=\"wr-page-header__main\">\n <div class=\"wr-page-header__heading\">\n @if (title()) {\n <h1 class=\"wr-page-header__title\">{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"wr-page-header__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"wr-page-header__actions\">\n <ng-content select=\"[wrPageHeaderActions]\" />\n </div>\n</div>\n\n<div class=\"wr-page-header__extra\">\n <ng-content select=\"[wrPageHeaderExtra]\" />\n</div>\n" }]
|
|
54
54
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }] } });
|
|
@@ -191,17 +191,17 @@ class WrPaginationHarness extends ComponentHarness {
|
|
|
191
191
|
* The page the pager announces as current, from `aria-current="page"`.
|
|
192
192
|
*
|
|
193
193
|
* Throws when no cell claims it. The window always keeps the current page on the
|
|
194
|
-
* strip, so getting here means `currentPage` is not one of the pages at all
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* inactive to an assistive tech user.
|
|
194
|
+
* strip, so getting here means `currentPage` is not one of the pages at all. The
|
|
195
|
+
* component clamps a host write into `1..totalPages`, which leaves one survivor:
|
|
196
|
+
* a fractional page, which is in range and matches no cell — and which leaves
|
|
197
|
+
* every cell looking equally inactive to an assistive tech user.
|
|
198
198
|
*/
|
|
199
199
|
async getCurrentPage() {
|
|
200
200
|
const page = await this.currentPageOrNull();
|
|
201
201
|
if (page === null) {
|
|
202
202
|
throw new Error('WrPaginationHarness.getCurrentPage(): no cell carries `aria-current="page"`. The visible window ' +
|
|
203
|
-
`always includes the current page, so \`currentPage\` is not a
|
|
204
|
-
`
|
|
203
|
+
`always includes the current page, so \`currentPage\` is not a whole number — the component ` +
|
|
204
|
+
`clamps it into 1..${await this.getTotalPages()} but does not round it.`);
|
|
205
205
|
}
|
|
206
206
|
return page;
|
|
207
207
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-pagination-testing.mjs","sources":["../../../projects/lib/pagination/testing/wr-pagination-harness.ts","../../../projects/lib/pagination/testing/ngwr-pagination-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport { WrButtonHarness } from 'ngwr/button/testing';\nimport type { WrPaginationSize } from 'ngwr/pagination';\nimport { WrSelectHarness } from 'ngwr/select/testing';\n\nimport type { WrPaginationHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-pagination>` — the numbered strip, its two step controls,\n * the total label and the page-size changer.\n *\n * The strip is not the page list: it is a WINDOW over it, at most seven pages wide,\n * with an `…` standing in for every page it skipped. So \"which pages can I click\"\n * and \"how many pages are there\" are different questions and this harness keeps\n * them apart —\n * {@link getPages} answers the first, {@link getTotalPages} the second, and\n * {@link getStrip} shows the gaps for what they are. A gap is a `<span>`, not a\n * control; counting the strip's children as pages is the mistake this API exists to\n * make impossible.\n *\n * **Which page is current has two answers, and this reads the ARIA one.** The\n * current cell is also PAINTED — the template binds\n * `[color]=\"isCurrent(page) ? 'primary' : null\"`, so `.wr-btn--primary` sits on it\n * too. But colour is a theming decision (`ngwr/config` names this very binding as\n * the reason it ships no app-wide `color` default), while `aria-current=\"page\"` is\n * the navigation contract: it is what a screen-reader user hears, what the\n * component's own spec pins, and the only one of the two that still means\n * \"you are here\" after a restyle. A harness that read the class would be reporting\n * on the paint.\n *\n * **No roving tab stop to disambiguate.** Every cell is a `<wr-btn>` ELEMENT with\n * its own tab stop (`tabindex=\"0\"`, dropped only while the control is off), so the\n * pager has no separate \"focused\" item that could drift from the current page —\n * unlike a tab strip or a radio group, there is one active state here and\n * {@link getCurrentPage} is it.\n *\n * Composed rather than re-queried: the cells and step controls are driven through\n * {@link WrButtonHarness}, which already knows how a `<wr-btn>` says it is off —\n * both the `disabled` attribute (written on every shape, though inert on a custom\n * element) and `aria-disabled`, in that order — and the size changer through\n * {@link WrSelectHarness}, whose panel is a portal in the overlay container and\n * nowhere inside the pager.\n *\n * jsdom notes: every method here is a class / attribute read or a no-arg click, so\n * none of it needs layout. Two things are therefore invisible in a unit test and\n * deliberately absent from this API — the responsive collapse, which is a container\n * query ({@link isResponsive} reports only that it is ALLOWED), and the compact\n * `‹ 3 / 10 ›` pager it swaps in, which is `aria-hidden` decoration duplicating\n * {@link getCurrentPage} and {@link getTotalPages} and which a real browser would\n * read as empty text while CSS keeps it hidden.\n *\n * @example\n * ```ts\n * const pager = await loader.getHarness(WrPaginationHarness);\n *\n * expect(await pager.getPages()).toEqual([1, 2, 3, 4, 5, 10]);\n * await pager.goToPage(3);\n * expect(await pager.getCurrentPage()).toBe(3);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrPaginationHarness extends ComponentHarness {\n static hostSelector = 'wr-pagination';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrPaginationHarnessFilters = {}): HarnessPredicate<WrPaginationHarness> {\n return new HarnessPredicate(WrPaginationHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('currentPage', options.currentPage, async (harness, page) => {\n // The nullable read on purpose: the throwing getter would reject the whole\n // query over one pager that announces no current page, taking its\n // well-behaved neighbours down with it.\n return (await harness.currentPageOrNull()) === page;\n })\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The numbered cells, in DOM order — ascending, and never a gap. */\n private readonly pageCells = this.locatorForAll(WrButtonHarness.with({ selector: '.wr-pagination__page' }));\n\n /**\n * The two controls bracketing the strip: previous, then next.\n *\n * Located by POSITION, not by their labels. `prevLabel` / `nextLabel` are inputs\n * and both fall back to the `pagination.prev` / `pagination.next` catalog entries,\n * so an English `aria-label` match works right up until someone ships a locale —\n * whereas the nav's shape (one step control, the strip, one step control) is\n * structural. `:not(.wr-pagination__page)` is what keeps the cells out of it.\n */\n private readonly stepControls = this.locatorForAll(\n WrButtonHarness.with({ selector: '.wr-pagination__nav > wr-btn:not(.wr-pagination__page)' })\n );\n\n /** Both kinds of strip slot, in DOM order — cells and the gaps between them. */\n private readonly stripSlots = this.locatorForAll('.wr-pagination__page, .wr-pagination__ellipsis');\n\n private readonly totalLabel = this.locatorForOptional('.wr-pagination__total');\n\n private readonly sizeChanger = this.locatorForOptional(WrSelectHarness.with({ selector: '.wr-pagination__size' }));\n\n /**\n * The accessible name of the `role=\"navigation\"` landmark.\n *\n * `''` only if the landmark lost its name, which is a bug rather than a state: a\n * page with two unnamed pagers gives a screen-reader user two identical\n * \"navigation\" landmarks.\n */\n async getLabel(): Promise<string> {\n return (await (await this.host()).getAttribute('aria-label')) ?? '';\n }\n\n /**\n * The size the pager RENDERS at, from its host modifier.\n *\n * The modifier is always emitted — `sm` included — so the fall-through below is\n * only ever reached for the default, and a size the strip is not wearing cannot\n * come back from here.\n *\n * The STRIP resolves nothing through `provideWrConfig()`, which is worth knowing\n * before an app-wide default is blamed for a cell size: the pager passes its own\n * `size` down to every `<wr-btn>` explicitly, so a configured `button.size` never\n * reaches a cell. The page-size changer is the exception and not covered by this\n * value — it is a bare `<wr-select>`, so `provideWrConfig({ select: … })` does\n * reach it.\n */\n async getSize(): Promise<WrPaginationSize> {\n const host = await this.host();\n if (await host.hasClass('wr-pagination--lg')) return 'lg';\n if (await host.hasClass('wr-pagination--md')) return 'md';\n return 'sm';\n }\n\n /**\n * Whether the pager is ALLOWED to collapse into its compact pager.\n *\n * Whether it HAS collapsed is a container query on the control's own box, and a\n * unit test has no layout for one to answer — assert this input in jsdom and the\n * reflow itself in a real browser.\n */\n async isResponsive(): Promise<boolean> {\n return (await this.host()).hasClass('wr-pagination--responsive');\n }\n\n /**\n * Whether the whole control refuses interaction.\n *\n * Read from the host modifier, because a `role=\"navigation\"` landmark has no ARIA\n * disabled state to prefer — the per-control answer lives on the buttons\n * themselves (`disabled` plus `aria-disabled` on each), which is what\n * {@link isPreviousDisabled} and {@link isNextDisabled} read.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-pagination--disabled');\n }\n\n /**\n * The whole strip in DOM order: a `number` for every page cell, and the gap\n * marker's own text (`…`) for every ellipsis.\n *\n * The marker is read from the DOM rather than compared against a literal, so a\n * component that changes what it draws in a gap changes this too instead of\n * quietly reporting a page.\n */\n async getStrip(): Promise<(number | string)[]> {\n const slots = await this.stripSlots();\n return Promise.all(\n slots.map(async slot => {\n const text = (await slot.text()).trim();\n return (await slot.hasClass('wr-pagination__ellipsis')) ? text : Number(text);\n })\n );\n }\n\n /**\n * The pages that can be clicked right now, ascending. Gaps are not pages and are\n * not here — `[1, 8, 9, 10, 11, 12, 20]` for a twenty-page list sitting on 10.\n */\n async getPages(): Promise<number[]> {\n const cells = await this.pageCells();\n return Promise.all(cells.map(async cell => Number(await cell.getText())));\n }\n\n /**\n * How many pages there are — not how many are on the strip.\n *\n * Taken from the LAST cell, which the window always pins to the final page (the\n * first is pinned the same way), so this survives the gap that usually precedes\n * it. Deliberately not read from the compact `3 / 10` pager: that span is\n * `aria-hidden` and CSS-hidden outside responsive mode, so a browser-based\n * harness environment would read it as empty.\n */\n async getTotalPages(): Promise<number> {\n const pages = await this.getPages();\n const last = pages.at(-1);\n if (last === undefined) {\n throw new Error(\n 'WrPaginationHarness.getTotalPages(): the strip has no page cells at all. The pager offers page ' +\n '1 even for an empty list, so this is a rendering failure rather than an empty state.'\n );\n }\n return last;\n }\n\n /**\n * The page the pager announces as current, from `aria-current=\"page\"`.\n *\n * Throws when no cell claims it. The window always keeps the current page on the\n * strip, so getting here means `currentPage` is not one of the pages at all — it\n * is below 1, or not a whole number. The component clamps a page ABOVE the last\n * one and nothing else, and either survivor leaves every cell looking equally\n * inactive to an assistive tech user.\n */\n async getCurrentPage(): Promise<number> {\n const page = await this.currentPageOrNull();\n if (page === null) {\n throw new Error(\n 'WrPaginationHarness.getCurrentPage(): no cell carries `aria-current=\"page\"`. The visible window ' +\n `always includes the current page, so \\`currentPage\\` is not a page: below 1, or not a whole ` +\n `number (the pager clamps above ${await this.getTotalPages()}, and nothing else).`\n );\n }\n return page;\n }\n\n /**\n * Click the cell for a page.\n *\n * Throws when that page is not on the strip — a seven-slot window over twenty\n * pages simply has no cell for page 15, and a silent no-op there reads as\n * \"navigation is broken\" three assertions later. Step with {@link next} /\n * {@link previous}, or move the host's own `currentPage` model.\n *\n * A disabled pager is still clicked; the component's own guard is what refuses\n * the move, so assert the page rather than assume it. Clicking the CURRENT page\n * is a no-op by the same guard.\n */\n async goToPage(page: number): Promise<void> {\n for (const cell of await this.pageCells()) {\n if (Number(await cell.getText()) === page) {\n await cell.click();\n return;\n }\n }\n\n throw new Error(\n `WrPaginationHarness.goToPage(): page ${page} is not on the strip, which offers ` +\n `${(await this.getStrip()).join(' ')}. The window shows at most seven pages — step with next() / ` +\n 'previous(), or set the page on the host.'\n );\n }\n\n /**\n * Jump to page 1.\n *\n * There is no dedicated first / last control in this component; the window pins\n * the first and last page to the ends of the strip instead, so these two click\n * cells like any other page.\n */\n async goToFirst(): Promise<void> {\n return this.goToPage(1);\n }\n\n /** Jump to the last page — the trailing cell, not the last one before a gap. */\n async goToLast(): Promise<void> {\n return this.goToPage(await this.getTotalPages());\n }\n\n /**\n * Click the next-page control.\n *\n * Disabled on the last page, and a disabled `<wr-btn>` is still clicked here (see\n * {@link goToPage}) — {@link isNextDisabled} is the question, this is the action.\n */\n async next(): Promise<void> {\n return (await this.stepControl('next')).click();\n }\n\n /** Click the previous-page control. Disabled on the first page. */\n async previous(): Promise<void> {\n return (await this.stepControl('previous')).click();\n }\n\n /**\n * Whether the next-page control refuses the move — true on the last page, and\n * true for every control while the pager is disabled.\n *\n * Answered by {@link WrButtonHarness}, which reads the `disabled` attribute first\n * and `aria-disabled` second. A `<wr-btn>` element carries both: the attribute is\n * inert on anything but a real `<button>`, which is why the component adds the\n * ARIA state as well — so this is the accessible answer whichever branch supplies\n * it.\n */\n async isNextDisabled(): Promise<boolean> {\n return (await this.stepControl('next')).isDisabled();\n }\n\n /** Whether the previous-page control refuses the move — true on the first page. */\n async isPreviousDisabled(): Promise<boolean> {\n return (await this.stepControl('previous')).isDisabled();\n }\n\n /** Whether the pager shows its \"X–Y of Z\" label (the `showTotal` input). */\n async hasTotal(): Promise<boolean> {\n return (await this.totalLabel()) !== null;\n }\n\n /**\n * The total label exactly as rendered, e.g. `1-10 of 95`.\n *\n * Both halves are localizable — the word between range and total comes from\n * `ofLabel` or the `pagination.of` catalog entry — so a spec asserting the whole\n * string should own the copy it asserts.\n *\n * Throws when the label is not rendered, which is a `showTotal` that was never\n * set rather than a total of zero (a zero total still renders `0-0 of 0`).\n */\n async getTotalText(): Promise<string> {\n const label = await this.totalLabel();\n if (!label) {\n throw new Error(\n 'WrPaginationHarness.getTotalText(): this pager renders no total label — set `showTotal`. A ' +\n 'total of 0 still renders one.'\n );\n }\n return label.text();\n }\n\n /** Whether the pager renders its page-size changer (the `showSizeChanger` input). */\n async hasPageSizeChanger(): Promise<boolean> {\n return (await this.sizeChanger()) !== null;\n }\n\n /**\n * The page-size changer as a {@link WrSelectHarness} — for anything this harness\n * does not wrap: the offered labels, the open state, keyboard driving.\n *\n * Composed rather than re-queried on purpose: the changer is a real\n * `<wr-select>`, its options live in the overlay container rather than inside the\n * pager, and the select harness is the thing that already knows how to scope them\n * to their own trigger.\n *\n * Throws when there is no changer — an `undefined` here would turn into a\n * confusing failure inside a select call.\n */\n async getPageSizeSelect(): Promise<WrSelectHarness> {\n const select = await this.sizeChanger();\n if (!select) {\n throw new Error('WrPaginationHarness.getPageSizeSelect(): no page-size changer here — set `showSizeChanger`.');\n }\n return select;\n }\n\n /**\n * The page size currently in effect.\n *\n * The changer's trigger is the only place the size reaches the DOM, so this reads\n * it there and takes the digits: the label is localizable (`pagination.perPage`,\n * `{{size}} / page`) but interpolates the number in every language — unless a\n * catalog entry drops the placeholder, which is the second throw below.\n *\n * Throws when there is no changer at all: a pager without one says nothing on\n * screen about its page size, and the host's own model is where to assert it.\n */\n async getPageSize(): Promise<number> {\n const text = await (await this.getPageSizeSelect()).getValueText();\n const digits = /\\d+/.exec(text);\n if (!digits) {\n throw new Error(\n `WrPaginationHarness.getPageSize(): the changer reads \"${text}\", which names no number — a ` +\n 'catalog entry for `pagination.perPage` that drops `{{size}}` takes the size off the screen.'\n );\n }\n return Number(digits[0]);\n }\n\n /**\n * Pick a page size through the changer, the way a user does.\n *\n * Matched on the digits rather than the whole label, for the same reason\n * {@link getPageSize} reads them: `20 / page` and `20 / стр.` are the same option.\n * Throws when no option offers that size, naming what is on offer.\n *\n * Changing the size can move the page — the component pulls `currentPage` back\n * when the new size leaves it past the end — so re-read {@link getCurrentPage}\n * afterwards rather than assuming it held.\n */\n async setPageSize(size: number): Promise<void> {\n const select = await this.getPageSizeSelect();\n if (await this.isDisabled()) {\n // Without this the call fails inside the select harness, whose message blames\n // a tag-mode panel and a `minChars` search that have nothing to do with it.\n throw new Error(\n 'WrPaginationHarness.setPageSize(): the pager is disabled, so its changer refuses to open — the ' +\n 'pager hands its own `disabled` straight to it.'\n );\n }\n\n await select.open();\n\n const labels = await select.getOptionLabels();\n const digits = new RegExp(`(^|\\\\D)${size}(\\\\D|$)`);\n const match = labels.find(label => digits.test(label));\n if (match === undefined) {\n throw new Error(\n `WrPaginationHarness.setPageSize(): no option offers ${size} — the changer offers ` +\n `${labels.join(', ')}. \\`pageSizeOptions\\` is what decides that list.`\n );\n }\n\n await select.selectOption({ text: match });\n }\n\n /** The current page, or `null` when no cell announces one. @see getCurrentPage */\n private async currentPageOrNull(): Promise<number | null> {\n const cell = await this.locatorForOptional(\n WrButtonHarness.with({ selector: '.wr-pagination__page[aria-current=\"page\"]' })\n )();\n return cell ? Number(await cell.getText()) : null;\n }\n\n private async stepControl(which: 'previous' | 'next'): Promise<WrButtonHarness> {\n const controls = await this.stepControls();\n if (controls.length !== 2) {\n throw new Error(\n `WrPaginationHarness: expected the nav to bracket the strip with a previous and a next control, ` +\n `found ${controls.length}. They are addressed by position, since their labels are localizable.`\n );\n }\n return which === 'previous' ? controls[0] : controls[1];\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-pagination-testing.mjs","sources":["../../../projects/lib/pagination/testing/wr-pagination-harness.ts","../../../projects/lib/pagination/testing/ngwr-pagination-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport { WrButtonHarness } from 'ngwr/button/testing';\nimport type { WrPaginationSize } from 'ngwr/pagination';\nimport { WrSelectHarness } from 'ngwr/select/testing';\n\nimport type { WrPaginationHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-pagination>` — the numbered strip, its two step controls,\n * the total label and the page-size changer.\n *\n * The strip is not the page list: it is a WINDOW over it, at most seven pages wide,\n * with an `…` standing in for every page it skipped. So \"which pages can I click\"\n * and \"how many pages are there\" are different questions and this harness keeps\n * them apart —\n * {@link getPages} answers the first, {@link getTotalPages} the second, and\n * {@link getStrip} shows the gaps for what they are. A gap is a `<span>`, not a\n * control; counting the strip's children as pages is the mistake this API exists to\n * make impossible.\n *\n * **Which page is current has two answers, and this reads the ARIA one.** The\n * current cell is also PAINTED — the template binds\n * `[color]=\"isCurrent(page) ? 'primary' : null\"`, so `.wr-btn--primary` sits on it\n * too. But colour is a theming decision (`ngwr/config` names this very binding as\n * the reason it ships no app-wide `color` default), while `aria-current=\"page\"` is\n * the navigation contract: it is what a screen-reader user hears, what the\n * component's own spec pins, and the only one of the two that still means\n * \"you are here\" after a restyle. A harness that read the class would be reporting\n * on the paint.\n *\n * **No roving tab stop to disambiguate.** Every cell is a `<wr-btn>` ELEMENT with\n * its own tab stop (`tabindex=\"0\"`, dropped only while the control is off), so the\n * pager has no separate \"focused\" item that could drift from the current page —\n * unlike a tab strip or a radio group, there is one active state here and\n * {@link getCurrentPage} is it.\n *\n * Composed rather than re-queried: the cells and step controls are driven through\n * {@link WrButtonHarness}, which already knows how a `<wr-btn>` says it is off —\n * both the `disabled` attribute (written on every shape, though inert on a custom\n * element) and `aria-disabled`, in that order — and the size changer through\n * {@link WrSelectHarness}, whose panel is a portal in the overlay container and\n * nowhere inside the pager.\n *\n * jsdom notes: every method here is a class / attribute read or a no-arg click, so\n * none of it needs layout. Two things are therefore invisible in a unit test and\n * deliberately absent from this API — the responsive collapse, which is a container\n * query ({@link isResponsive} reports only that it is ALLOWED), and the compact\n * `‹ 3 / 10 ›` pager it swaps in, which is `aria-hidden` decoration duplicating\n * {@link getCurrentPage} and {@link getTotalPages} and which a real browser would\n * read as empty text while CSS keeps it hidden.\n *\n * @example\n * ```ts\n * const pager = await loader.getHarness(WrPaginationHarness);\n *\n * expect(await pager.getPages()).toEqual([1, 2, 3, 4, 5, 10]);\n * await pager.goToPage(3);\n * expect(await pager.getCurrentPage()).toBe(3);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrPaginationHarness extends ComponentHarness {\n static hostSelector = 'wr-pagination';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrPaginationHarnessFilters = {}): HarnessPredicate<WrPaginationHarness> {\n return new HarnessPredicate(WrPaginationHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('currentPage', options.currentPage, async (harness, page) => {\n // The nullable read on purpose: the throwing getter would reject the whole\n // query over one pager that announces no current page, taking its\n // well-behaved neighbours down with it.\n return (await harness.currentPageOrNull()) === page;\n })\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The numbered cells, in DOM order — ascending, and never a gap. */\n private readonly pageCells = this.locatorForAll(WrButtonHarness.with({ selector: '.wr-pagination__page' }));\n\n /**\n * The two controls bracketing the strip: previous, then next.\n *\n * Located by POSITION, not by their labels. `prevLabel` / `nextLabel` are inputs\n * and both fall back to the `pagination.prev` / `pagination.next` catalog entries,\n * so an English `aria-label` match works right up until someone ships a locale —\n * whereas the nav's shape (one step control, the strip, one step control) is\n * structural. `:not(.wr-pagination__page)` is what keeps the cells out of it.\n */\n private readonly stepControls = this.locatorForAll(\n WrButtonHarness.with({ selector: '.wr-pagination__nav > wr-btn:not(.wr-pagination__page)' })\n );\n\n /** Both kinds of strip slot, in DOM order — cells and the gaps between them. */\n private readonly stripSlots = this.locatorForAll('.wr-pagination__page, .wr-pagination__ellipsis');\n\n private readonly totalLabel = this.locatorForOptional('.wr-pagination__total');\n\n private readonly sizeChanger = this.locatorForOptional(WrSelectHarness.with({ selector: '.wr-pagination__size' }));\n\n /**\n * The accessible name of the `role=\"navigation\"` landmark.\n *\n * `''` only if the landmark lost its name, which is a bug rather than a state: a\n * page with two unnamed pagers gives a screen-reader user two identical\n * \"navigation\" landmarks.\n */\n async getLabel(): Promise<string> {\n return (await (await this.host()).getAttribute('aria-label')) ?? '';\n }\n\n /**\n * The size the pager RENDERS at, from its host modifier.\n *\n * The modifier is always emitted — `sm` included — so the fall-through below is\n * only ever reached for the default, and a size the strip is not wearing cannot\n * come back from here.\n *\n * The STRIP resolves nothing through `provideWrConfig()`, which is worth knowing\n * before an app-wide default is blamed for a cell size: the pager passes its own\n * `size` down to every `<wr-btn>` explicitly, so a configured `button.size` never\n * reaches a cell. The page-size changer is the exception and not covered by this\n * value — it is a bare `<wr-select>`, so `provideWrConfig({ select: … })` does\n * reach it.\n */\n async getSize(): Promise<WrPaginationSize> {\n const host = await this.host();\n if (await host.hasClass('wr-pagination--lg')) return 'lg';\n if (await host.hasClass('wr-pagination--md')) return 'md';\n return 'sm';\n }\n\n /**\n * Whether the pager is ALLOWED to collapse into its compact pager.\n *\n * Whether it HAS collapsed is a container query on the control's own box, and a\n * unit test has no layout for one to answer — assert this input in jsdom and the\n * reflow itself in a real browser.\n */\n async isResponsive(): Promise<boolean> {\n return (await this.host()).hasClass('wr-pagination--responsive');\n }\n\n /**\n * Whether the whole control refuses interaction.\n *\n * Read from the host modifier, because a `role=\"navigation\"` landmark has no ARIA\n * disabled state to prefer — the per-control answer lives on the buttons\n * themselves (`disabled` plus `aria-disabled` on each), which is what\n * {@link isPreviousDisabled} and {@link isNextDisabled} read.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-pagination--disabled');\n }\n\n /**\n * The whole strip in DOM order: a `number` for every page cell, and the gap\n * marker's own text (`…`) for every ellipsis.\n *\n * The marker is read from the DOM rather than compared against a literal, so a\n * component that changes what it draws in a gap changes this too instead of\n * quietly reporting a page.\n */\n async getStrip(): Promise<(number | string)[]> {\n const slots = await this.stripSlots();\n return Promise.all(\n slots.map(async slot => {\n const text = (await slot.text()).trim();\n return (await slot.hasClass('wr-pagination__ellipsis')) ? text : Number(text);\n })\n );\n }\n\n /**\n * The pages that can be clicked right now, ascending. Gaps are not pages and are\n * not here — `[1, 8, 9, 10, 11, 12, 20]` for a twenty-page list sitting on 10.\n */\n async getPages(): Promise<number[]> {\n const cells = await this.pageCells();\n return Promise.all(cells.map(async cell => Number(await cell.getText())));\n }\n\n /**\n * How many pages there are — not how many are on the strip.\n *\n * Taken from the LAST cell, which the window always pins to the final page (the\n * first is pinned the same way), so this survives the gap that usually precedes\n * it. Deliberately not read from the compact `3 / 10` pager: that span is\n * `aria-hidden` and CSS-hidden outside responsive mode, so a browser-based\n * harness environment would read it as empty.\n */\n async getTotalPages(): Promise<number> {\n const pages = await this.getPages();\n const last = pages.at(-1);\n if (last === undefined) {\n throw new Error(\n 'WrPaginationHarness.getTotalPages(): the strip has no page cells at all. The pager offers page ' +\n '1 even for an empty list, so this is a rendering failure rather than an empty state.'\n );\n }\n return last;\n }\n\n /**\n * The page the pager announces as current, from `aria-current=\"page\"`.\n *\n * Throws when no cell claims it. The window always keeps the current page on the\n * strip, so getting here means `currentPage` is not one of the pages at all. The\n * component clamps a host write into `1..totalPages`, which leaves one survivor:\n * a fractional page, which is in range and matches no cell — and which leaves\n * every cell looking equally inactive to an assistive tech user.\n */\n async getCurrentPage(): Promise<number> {\n const page = await this.currentPageOrNull();\n if (page === null) {\n throw new Error(\n 'WrPaginationHarness.getCurrentPage(): no cell carries `aria-current=\"page\"`. The visible window ' +\n `always includes the current page, so \\`currentPage\\` is not a whole number — the component ` +\n `clamps it into 1..${await this.getTotalPages()} but does not round it.`\n );\n }\n return page;\n }\n\n /**\n * Click the cell for a page.\n *\n * Throws when that page is not on the strip — a seven-slot window over twenty\n * pages simply has no cell for page 15, and a silent no-op there reads as\n * \"navigation is broken\" three assertions later. Step with {@link next} /\n * {@link previous}, or move the host's own `currentPage` model.\n *\n * A disabled pager is still clicked; the component's own guard is what refuses\n * the move, so assert the page rather than assume it. Clicking the CURRENT page\n * is a no-op by the same guard.\n */\n async goToPage(page: number): Promise<void> {\n for (const cell of await this.pageCells()) {\n if (Number(await cell.getText()) === page) {\n await cell.click();\n return;\n }\n }\n\n throw new Error(\n `WrPaginationHarness.goToPage(): page ${page} is not on the strip, which offers ` +\n `${(await this.getStrip()).join(' ')}. The window shows at most seven pages — step with next() / ` +\n 'previous(), or set the page on the host.'\n );\n }\n\n /**\n * Jump to page 1.\n *\n * There is no dedicated first / last control in this component; the window pins\n * the first and last page to the ends of the strip instead, so these two click\n * cells like any other page.\n */\n async goToFirst(): Promise<void> {\n return this.goToPage(1);\n }\n\n /** Jump to the last page — the trailing cell, not the last one before a gap. */\n async goToLast(): Promise<void> {\n return this.goToPage(await this.getTotalPages());\n }\n\n /**\n * Click the next-page control.\n *\n * Disabled on the last page, and a disabled `<wr-btn>` is still clicked here (see\n * {@link goToPage}) — {@link isNextDisabled} is the question, this is the action.\n */\n async next(): Promise<void> {\n return (await this.stepControl('next')).click();\n }\n\n /** Click the previous-page control. Disabled on the first page. */\n async previous(): Promise<void> {\n return (await this.stepControl('previous')).click();\n }\n\n /**\n * Whether the next-page control refuses the move — true on the last page, and\n * true for every control while the pager is disabled.\n *\n * Answered by {@link WrButtonHarness}, which reads the `disabled` attribute first\n * and `aria-disabled` second. A `<wr-btn>` element carries both: the attribute is\n * inert on anything but a real `<button>`, which is why the component adds the\n * ARIA state as well — so this is the accessible answer whichever branch supplies\n * it.\n */\n async isNextDisabled(): Promise<boolean> {\n return (await this.stepControl('next')).isDisabled();\n }\n\n /** Whether the previous-page control refuses the move — true on the first page. */\n async isPreviousDisabled(): Promise<boolean> {\n return (await this.stepControl('previous')).isDisabled();\n }\n\n /** Whether the pager shows its \"X–Y of Z\" label (the `showTotal` input). */\n async hasTotal(): Promise<boolean> {\n return (await this.totalLabel()) !== null;\n }\n\n /**\n * The total label exactly as rendered, e.g. `1-10 of 95`.\n *\n * Both halves are localizable — the word between range and total comes from\n * `ofLabel` or the `pagination.of` catalog entry — so a spec asserting the whole\n * string should own the copy it asserts.\n *\n * Throws when the label is not rendered, which is a `showTotal` that was never\n * set rather than a total of zero (a zero total still renders `0-0 of 0`).\n */\n async getTotalText(): Promise<string> {\n const label = await this.totalLabel();\n if (!label) {\n throw new Error(\n 'WrPaginationHarness.getTotalText(): this pager renders no total label — set `showTotal`. A ' +\n 'total of 0 still renders one.'\n );\n }\n return label.text();\n }\n\n /** Whether the pager renders its page-size changer (the `showSizeChanger` input). */\n async hasPageSizeChanger(): Promise<boolean> {\n return (await this.sizeChanger()) !== null;\n }\n\n /**\n * The page-size changer as a {@link WrSelectHarness} — for anything this harness\n * does not wrap: the offered labels, the open state, keyboard driving.\n *\n * Composed rather than re-queried on purpose: the changer is a real\n * `<wr-select>`, its options live in the overlay container rather than inside the\n * pager, and the select harness is the thing that already knows how to scope them\n * to their own trigger.\n *\n * Throws when there is no changer — an `undefined` here would turn into a\n * confusing failure inside a select call.\n */\n async getPageSizeSelect(): Promise<WrSelectHarness> {\n const select = await this.sizeChanger();\n if (!select) {\n throw new Error('WrPaginationHarness.getPageSizeSelect(): no page-size changer here — set `showSizeChanger`.');\n }\n return select;\n }\n\n /**\n * The page size currently in effect.\n *\n * The changer's trigger is the only place the size reaches the DOM, so this reads\n * it there and takes the digits: the label is localizable (`pagination.perPage`,\n * `{{size}} / page`) but interpolates the number in every language — unless a\n * catalog entry drops the placeholder, which is the second throw below.\n *\n * Throws when there is no changer at all: a pager without one says nothing on\n * screen about its page size, and the host's own model is where to assert it.\n */\n async getPageSize(): Promise<number> {\n const text = await (await this.getPageSizeSelect()).getValueText();\n const digits = /\\d+/.exec(text);\n if (!digits) {\n throw new Error(\n `WrPaginationHarness.getPageSize(): the changer reads \"${text}\", which names no number — a ` +\n 'catalog entry for `pagination.perPage` that drops `{{size}}` takes the size off the screen.'\n );\n }\n return Number(digits[0]);\n }\n\n /**\n * Pick a page size through the changer, the way a user does.\n *\n * Matched on the digits rather than the whole label, for the same reason\n * {@link getPageSize} reads them: `20 / page` and `20 / стр.` are the same option.\n * Throws when no option offers that size, naming what is on offer.\n *\n * Changing the size can move the page — the component pulls `currentPage` back\n * when the new size leaves it past the end — so re-read {@link getCurrentPage}\n * afterwards rather than assuming it held.\n */\n async setPageSize(size: number): Promise<void> {\n const select = await this.getPageSizeSelect();\n if (await this.isDisabled()) {\n // Without this the call fails inside the select harness, whose message blames\n // a tag-mode panel and a `minChars` search that have nothing to do with it.\n throw new Error(\n 'WrPaginationHarness.setPageSize(): the pager is disabled, so its changer refuses to open — the ' +\n 'pager hands its own `disabled` straight to it.'\n );\n }\n\n await select.open();\n\n const labels = await select.getOptionLabels();\n const digits = new RegExp(`(^|\\\\D)${size}(\\\\D|$)`);\n const match = labels.find(label => digits.test(label));\n if (match === undefined) {\n throw new Error(\n `WrPaginationHarness.setPageSize(): no option offers ${size} — the changer offers ` +\n `${labels.join(', ')}. \\`pageSizeOptions\\` is what decides that list.`\n );\n }\n\n await select.selectOption({ text: match });\n }\n\n /** The current page, or `null` when no cell announces one. @see getCurrentPage */\n private async currentPageOrNull(): Promise<number | null> {\n const cell = await this.locatorForOptional(\n WrButtonHarness.with({ selector: '.wr-pagination__page[aria-current=\"page\"]' })\n )();\n return cell ? Number(await cell.getText()) : null;\n }\n\n private async stepControl(which: 'previous' | 'next'): Promise<WrButtonHarness> {\n const controls = await this.stepControls();\n if (controls.length !== 2) {\n throw new Error(\n `WrPaginationHarness: expected the nav to bracket the strip with a previous and a next control, ` +\n `found ${controls.length}. They are addressed by position, since their labels are localizable.`\n );\n }\n return which === 'previous' ? controls[0] : controls[1];\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|