ngwr 10.2.1 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -19
- package/anchor/styles/_index.scss +4 -4
- package/back-top/styles/_index.scss +6 -1
- package/blur-text/styles/_index.scss +13 -0
- package/button/styles/_index.scss +11 -6
- package/carousel/styles/_index.scss +13 -2
- package/cascader/styles/_index.scss +2 -2
- package/circular-text/styles/_index.scss +2 -0
- package/collapse/styles/_index.scss +1 -1
- package/color-picker/styles/_index.scss +6 -1
- package/compare/styles/_index.scss +1 -2
- package/context-menu/styles/_index.scss +6 -1
- package/descriptions/styles/_index.scss +2 -2
- package/dialog/styles/_index.scss +7 -0
- package/drawer/styles/_index.scss +20 -1
- package/dropdown/styles/_index.scss +19 -5
- package/empty/styles/_index.scss +10 -0
- package/fesm2022/ngwr-action-sheet.mjs +13 -3
- package/fesm2022/ngwr-action-sheet.mjs.map +1 -1
- package/fesm2022/ngwr-affix.mjs +40 -13
- package/fesm2022/ngwr-affix.mjs.map +1 -1
- package/fesm2022/ngwr-anchor.mjs +18 -6
- package/fesm2022/ngwr-anchor.mjs.map +1 -1
- package/fesm2022/ngwr-aurora.mjs +2 -2
- package/fesm2022/ngwr-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-avatar.mjs +31 -7
- package/fesm2022/ngwr-avatar.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +6 -2
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-badge.mjs +1 -1
- package/fesm2022/ngwr-badge.mjs.map +1 -1
- package/fesm2022/ngwr-bar-chart.mjs +30 -9
- package/fesm2022/ngwr-bar-chart.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +2 -2
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +2 -2
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +9 -4
- package/fesm2022/ngwr-burger.mjs.map +1 -1
- package/fesm2022/ngwr-button-testing.mjs +96 -0
- package/fesm2022/ngwr-button-testing.mjs.map +1 -0
- package/fesm2022/ngwr-button.mjs +31 -14
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap.mjs +40 -8
- package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +20 -1
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +2 -2
- package/fesm2022/ngwr-card.mjs.map +1 -1
- package/fesm2022/ngwr-carousel-testing.mjs +377 -0
- package/fesm2022/ngwr-carousel-testing.mjs.map +1 -0
- package/fesm2022/ngwr-carousel.mjs +67 -13
- package/fesm2022/ngwr-carousel.mjs.map +1 -1
- package/fesm2022/ngwr-cascader-testing.mjs +534 -0
- package/fesm2022/ngwr-cascader-testing.mjs.map +1 -0
- package/fesm2022/ngwr-cascader.mjs +2 -2
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox-testing.mjs +90 -0
- package/fesm2022/ngwr-checkbox-testing.mjs.map +1 -0
- package/fesm2022/ngwr-checkbox.mjs +9 -3
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +2 -2
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-click-spark.mjs +2 -2
- package/fesm2022/ngwr-click-spark.mjs.map +1 -1
- package/fesm2022/ngwr-collapse-testing.mjs +368 -0
- package/fesm2022/ngwr-collapse-testing.mjs.map +1 -0
- package/fesm2022/ngwr-color-picker.mjs +134 -14
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette-testing.mjs +589 -0
- package/fesm2022/ngwr-command-palette-testing.mjs.map +1 -0
- package/fesm2022/ngwr-command-palette.mjs +58 -8
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-compare.mjs +103 -13
- package/fesm2022/ngwr-compare.mjs.map +1 -1
- package/fesm2022/ngwr-config.mjs +106 -0
- package/fesm2022/ngwr-config.mjs.map +1 -0
- package/fesm2022/ngwr-context-menu-testing.mjs +631 -0
- package/fesm2022/ngwr-context-menu-testing.mjs.map +1 -0
- package/fesm2022/ngwr-context-menu.mjs +94 -8
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-cookie.mjs +5 -1
- package/fesm2022/ngwr-cookie.mjs.map +1 -1
- package/fesm2022/ngwr-counter.mjs +11 -3
- package/fesm2022/ngwr-counter.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapter-luxon.mjs +52 -4
- package/fesm2022/ngwr-date-adapter-luxon.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapter.mjs +54 -8
- package/fesm2022/ngwr-date-adapter.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +766 -0
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -0
- package/fesm2022/ngwr-date-picker.mjs +73 -3
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text.mjs +27 -9
- package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
- package/fesm2022/ngwr-dialog-testing.mjs +89 -0
- package/fesm2022/ngwr-dialog-testing.mjs.map +1 -0
- package/fesm2022/ngwr-directives.mjs +36 -12
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart.mjs +22 -5
- package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +2 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer-testing.mjs +250 -0
- package/fesm2022/ngwr-drawer-testing.mjs.map +1 -0
- package/fesm2022/ngwr-dropdown-testing.mjs +291 -0
- package/fesm2022/ngwr-dropdown-testing.mjs.map +1 -0
- package/fesm2022/ngwr-dropdown.mjs +10 -2
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-empty.mjs +24 -7
- package/fesm2022/ngwr-empty.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +57 -10
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text.mjs +2 -2
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload-testing.mjs +332 -0
- package/fesm2022/ngwr-file-upload-testing.mjs.map +1 -0
- package/fesm2022/ngwr-form-testing.mjs +451 -0
- package/fesm2022/ngwr-form-testing.mjs.map +1 -0
- package/fesm2022/ngwr-form.mjs +19 -3
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +9 -2
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gauge.mjs +19 -5
- package/fesm2022/ngwr-gauge.mjs.map +1 -1
- package/fesm2022/ngwr-glitch-text.mjs +2 -2
- package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +2 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +50 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +48 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +23 -0
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs +306 -0
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -0
- package/fesm2022/ngwr-input-number.mjs +35 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp-testing.mjs +419 -0
- package/fesm2022/ngwr-input-otp-testing.mjs.map +1 -0
- package/fesm2022/ngwr-input-otp.mjs +36 -5
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input-testing.mjs +102 -0
- package/fesm2022/ngwr-input-testing.mjs.map +1 -0
- package/fesm2022/ngwr-input.mjs +47 -10
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +44 -8
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-layout.mjs +21 -5
- package/fesm2022/ngwr-layout.mjs.map +1 -1
- package/fesm2022/ngwr-lightbox.mjs +33 -6
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +17 -4
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-list.mjs +2 -2
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +2 -2
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown-testing.mjs +516 -0
- package/fesm2022/ngwr-markdown-testing.mjs.map +1 -0
- package/fesm2022/ngwr-markdown.mjs +1492 -0
- package/fesm2022/ngwr-markdown.mjs.map +1 -0
- package/fesm2022/ngwr-marquee.mjs +17 -6
- package/fesm2022/ngwr-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-mention-testing.mjs +513 -0
- package/fesm2022/ngwr-mention-testing.mjs.map +1 -0
- package/fesm2022/ngwr-mention.mjs +10 -1
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-meta.mjs +22 -7
- package/fesm2022/ngwr-meta.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group.mjs +19 -5
- package/fesm2022/ngwr-meter-group.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +41 -1
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs +389 -0
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -0
- package/fesm2022/ngwr-pagination.mjs +14 -2
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm-testing.mjs +309 -0
- package/fesm2022/ngwr-popconfirm-testing.mjs.map +1 -0
- package/fesm2022/ngwr-popconfirm.mjs +95 -17
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover-testing.mjs +316 -0
- package/fesm2022/ngwr-popover-testing.mjs.map +1 -0
- package/fesm2022/ngwr-popover.mjs +21 -3
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-pull-to-refresh.mjs +17 -2
- package/fesm2022/ngwr-pull-to-refresh.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +13 -3
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-radio-testing.mjs +392 -0
- package/fesm2022/ngwr-radio-testing.mjs.map +1 -0
- package/fesm2022/ngwr-radio.mjs +10 -3
- package/fesm2022/ngwr-radio.mjs.map +1 -1
- package/fesm2022/ngwr-rating-testing.mjs +384 -0
- package/fesm2022/ngwr-rating-testing.mjs.map +1 -0
- package/fesm2022/ngwr-rating.mjs +37 -2
- package/fesm2022/ngwr-rating.mjs.map +1 -1
- package/fesm2022/ngwr-segmented-testing.mjs +512 -0
- package/fesm2022/ngwr-segmented-testing.mjs.map +1 -0
- package/fesm2022/ngwr-select-testing.mjs +268 -0
- package/fesm2022/ngwr-select-testing.mjs.map +1 -0
- package/fesm2022/ngwr-select.mjs +87 -16
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +2 -2
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +65 -12
- package/fesm2022/ngwr-sidebar.mjs.map +1 -1
- package/fesm2022/ngwr-slider-testing.mjs +478 -0
- package/fesm2022/ngwr-slider-testing.mjs.map +1 -0
- package/fesm2022/ngwr-slider.mjs +57 -13
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline.mjs +26 -6
- package/fesm2022/ngwr-sparkline.mjs.map +1 -1
- package/fesm2022/ngwr-speed-dial.mjs +37 -4
- package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
- package/fesm2022/ngwr-spinner.mjs +12 -3
- package/fesm2022/ngwr-spinner.mjs.map +1 -1
- package/fesm2022/ngwr-splash-cursor.mjs +2 -2
- package/fesm2022/ngwr-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-split-text.mjs +2 -2
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter.mjs +70 -9
- package/fesm2022/ngwr-splitter.mjs.map +1 -1
- package/fesm2022/ngwr-spotlight-card.mjs +2 -2
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-star-border.mjs +2 -2
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +24 -7
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-stepper-testing.mjs +477 -0
- package/fesm2022/ngwr-stepper-testing.mjs.map +1 -0
- package/fesm2022/ngwr-switch-testing.mjs +77 -0
- package/fesm2022/ngwr-switch-testing.mjs.map +1 -0
- package/fesm2022/ngwr-switch.mjs +9 -3
- package/fesm2022/ngwr-switch.mjs.map +1 -1
- package/fesm2022/ngwr-table-testing.mjs +580 -0
- package/fesm2022/ngwr-table-testing.mjs.map +1 -0
- package/fesm2022/ngwr-table.mjs +87 -14
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-testing.mjs +617 -0
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tabs.mjs +38 -6
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea-testing.mjs +238 -0
- package/fesm2022/ngwr-textarea-testing.mjs.map +1 -0
- package/fesm2022/ngwr-textarea.mjs +32 -4
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card.mjs +2 -2
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-toast-testing.mjs +101 -0
- package/fesm2022/ngwr-toast-testing.mjs.map +1 -0
- package/fesm2022/ngwr-toast.mjs +2 -2
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +61 -9
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer-testing.mjs +538 -0
- package/fesm2022/ngwr-transfer-testing.mjs.map +1 -0
- package/fesm2022/ngwr-transfer.mjs +45 -14
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree-testing.mjs +734 -0
- package/fesm2022/ngwr-tree-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tree.mjs +73 -7
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter.mjs +5 -3
- package/fesm2022/ngwr-typewriter.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 +31 -5
- package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-waves.mjs +3 -3
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +8 -0
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/gauge/styles/_index.scss +1 -1
- package/glitch-text/styles/_index.scss +4 -0
- package/i18n/en.json +48 -0
- package/i18n/ru.json +48 -0
- package/image-cropper/styles/_index.scss +13 -1
- package/input/styles/_index.scss +2 -2
- package/input-number/styles/_index.scss +7 -2
- package/knob/styles/_index.scss +8 -1
- package/layout/styles/_index.scss +3 -3
- package/lightbox/styles/_index.scss +10 -2
- package/line-chart/styles/_index.scss +6 -0
- package/llms-full.txt +151 -4
- package/llms.txt +57 -8
- package/loading-bar/styles/_index.scss +1 -2
- package/markdown/styles/_index.scss +296 -0
- package/marquee/styles/_index.scss +12 -0
- package/mcp/api.js +512 -0
- package/mcp/catalog.js +157 -0
- package/mcp/server.js +273 -0
- package/mcp/tools.js +366 -0
- package/meter-group/styles/_index.scss +1 -1
- package/package.json +186 -35
- package/pagination/styles/_index.scss +6 -6
- package/popover/styles/_index.scss +37 -5
- package/rating/styles/_index.scss +10 -0
- package/schematics/use/symbol-map.json +2 -0
- package/segmented/styles/_index.scss +7 -0
- package/select/styles/_index.scss +29 -9
- package/sidebar/styles/_index.scss +30 -5
- package/slider/styles/_index.scss +15 -0
- package/speed-dial/styles/_index.scss +20 -0
- package/split-text/styles/_index.scss +13 -0
- package/star-border/styles/_index.scss +2 -0
- package/stepper/styles/_index.scss +1 -1
- package/styles/_grid.scss +4 -4
- package/styles.scss +1 -0
- package/switch/styles/_index.scss +5 -3
- package/table/styles/_index.scss +22 -11
- package/tabs/styles/_index.scss +11 -1
- package/textarea/styles/_index.scss +5 -0
- package/theme/styles/_colors.scss +29 -13
- package/theme/styles/_dark.scss +11 -3
- package/theme/styles/_mixins.scss +3 -0
- package/timeline/styles/_index.scss +2 -2
- package/toast/styles/_index.scss +17 -8
- package/toolbar/styles/_index.scss +3 -3
- package/tour/styles/_index.scss +7 -1
- package/tree/styles/_index.scss +5 -3
- package/types/ngwr-action-sheet.d.ts +9 -1
- package/types/ngwr-affix.d.ts +7 -1
- package/types/ngwr-anchor.d.ts +8 -1
- package/types/ngwr-avatar.d.ts +13 -3
- package/types/ngwr-bar-chart.d.ts +16 -0
- package/types/ngwr-burger.d.ts +6 -2
- package/types/ngwr-button-testing.d.ts +85 -0
- package/types/ngwr-button.d.ts +26 -10
- package/types/ngwr-calendar-heatmap.d.ts +16 -2
- package/types/ngwr-calendar.d.ts +10 -0
- package/types/ngwr-carousel-testing.d.ts +257 -0
- package/types/ngwr-carousel.d.ts +20 -4
- package/types/ngwr-cascader-testing.d.ts +392 -0
- package/types/ngwr-checkbox-testing.d.ts +77 -0
- package/types/ngwr-checkbox.d.ts +7 -2
- package/types/ngwr-collapse-testing.d.ts +290 -0
- package/types/ngwr-color-picker.d.ts +20 -11
- package/types/ngwr-command-palette-testing.d.ts +437 -0
- package/types/ngwr-command-palette.d.ts +25 -2
- package/types/ngwr-compare.d.ts +50 -2
- package/types/ngwr-config.d.ts +176 -0
- package/types/ngwr-context-menu-testing.d.ts +401 -0
- package/types/ngwr-context-menu.d.ts +65 -17
- package/types/ngwr-counter.d.ts +6 -1
- package/types/ngwr-date-adapter-luxon.d.ts +26 -0
- package/types/ngwr-date-picker-testing.d.ts +518 -0
- package/types/ngwr-date-picker.d.ts +4 -0
- package/types/ngwr-dialog-testing.d.ts +79 -0
- package/types/ngwr-directives.d.ts +10 -1
- package/types/ngwr-donut-chart.d.ts +8 -1
- package/types/ngwr-drawer-testing.d.ts +196 -0
- package/types/ngwr-dropdown-testing.d.ts +202 -0
- package/types/ngwr-dropdown.d.ts +7 -1
- package/types/ngwr-empty.d.ts +19 -4
- package/types/ngwr-event-calendar.d.ts +26 -1
- package/types/ngwr-file-upload-testing.d.ts +194 -0
- package/types/ngwr-form-testing.d.ts +379 -0
- package/types/ngwr-form.d.ts +17 -0
- package/types/ngwr-gauge.d.ts +13 -1
- package/types/ngwr-image-cropper.d.ts +10 -0
- package/types/ngwr-input-number-testing.d.ts +226 -0
- package/types/ngwr-input-number.d.ts +29 -1
- package/types/ngwr-input-otp-testing.d.ts +299 -0
- package/types/ngwr-input-otp.d.ts +23 -2
- package/types/ngwr-input-testing.d.ts +84 -0
- package/types/ngwr-input.d.ts +20 -6
- package/types/ngwr-knob.d.ts +18 -0
- package/types/ngwr-layout.d.ts +1 -0
- package/types/ngwr-lightbox.d.ts +17 -3
- package/types/ngwr-line-chart.d.ts +9 -2
- package/types/ngwr-markdown-testing.d.ts +462 -0
- package/types/ngwr-markdown.d.ts +534 -0
- package/types/ngwr-marquee.d.ts +12 -2
- package/types/ngwr-mention-testing.d.ts +359 -0
- package/types/ngwr-meta.d.ts +4 -2
- package/types/ngwr-meter-group.d.ts +11 -1
- package/types/ngwr-overlay.d.ts +33 -2
- package/types/ngwr-pagination-testing.d.ts +291 -0
- package/types/ngwr-pagination.d.ts +1 -0
- package/types/ngwr-popconfirm-testing.d.ts +235 -0
- package/types/ngwr-popconfirm.d.ts +31 -21
- package/types/ngwr-popover-testing.d.ts +221 -0
- package/types/ngwr-popover.d.ts +9 -0
- package/types/ngwr-pull-to-refresh.d.ts +7 -0
- package/types/ngwr-qr.d.ts +9 -1
- package/types/ngwr-radio-testing.d.ts +296 -0
- package/types/ngwr-radio.d.ts +8 -2
- package/types/ngwr-rating-testing.d.ts +291 -0
- package/types/ngwr-rating.d.ts +23 -0
- package/types/ngwr-segmented-testing.d.ts +401 -0
- package/types/ngwr-select-testing.d.ts +175 -0
- package/types/ngwr-select.d.ts +45 -4
- package/types/ngwr-sidebar.d.ts +17 -2
- package/types/ngwr-slider-testing.d.ts +329 -0
- package/types/ngwr-slider.d.ts +35 -1
- package/types/ngwr-sparkline.d.ts +12 -2
- package/types/ngwr-speed-dial.d.ts +17 -18
- package/types/ngwr-spinner.d.ts +8 -1
- package/types/ngwr-splitter.d.ts +24 -1
- package/types/ngwr-statistic.d.ts +12 -2
- package/types/ngwr-stepper-testing.d.ts +346 -0
- package/types/ngwr-switch-testing.d.ts +66 -0
- package/types/ngwr-switch.d.ts +7 -2
- package/types/ngwr-table-testing.d.ts +423 -0
- package/types/ngwr-table.d.ts +42 -0
- package/types/ngwr-tabs-testing.d.ts +470 -0
- package/types/ngwr-tabs.d.ts +9 -0
- package/types/ngwr-textarea-testing.d.ts +184 -0
- package/types/ngwr-textarea.d.ts +7 -2
- package/types/ngwr-toast-testing.d.ts +80 -0
- package/types/ngwr-tour.d.ts +27 -0
- package/types/ngwr-transfer-testing.d.ts +444 -0
- package/types/ngwr-transfer.d.ts +16 -1
- package/types/ngwr-tree-testing.d.ts +501 -0
- package/types/ngwr-tree.d.ts +33 -0
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-virtual-scroll.d.ts +13 -1
- package/typewriter/styles/_index.scss +1 -1
- package/typography/styles/_index.scss +1 -1
- package/window/styles/_index.scss +32 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-marquee.mjs","sources":["../../../projects/lib/marquee/marquee.ts","../../../projects/lib/marquee/marquee.html","../../../projects/lib/marquee/ngwr-marquee.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 * Angular adaptation of the LogoLoop effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/logo-loop\n *\n * Renamed `wr-marquee` in ngwr — same rAF-driven infinite carousel,\n * supports image + template items, optional fade/scale/pause-on-hover.\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrMarqueeNode, WrMarqueeItem } from './interfaces';\n\nconst SMOOTH_TAU = 0.25;\nconst MIN_COPIES = 2;\nconst COPY_HEADROOM = 2;\n\nfunction isNode(item: WrMarqueeItem): item is WrMarqueeNode {\n return 'node' in item;\n}\n\n/**\n * Endless horizontal marquee. Auto-duplicates the items list until it\n * covers the container width, then animates the track with a smooth\n * rAF-driven scroll. Optional edge fades, scale-on-hover, and pause /\n * variable-speed hover behaviour.\n *\n * @example\n * ```html\n * <wr-marquee\n * [items]=\"logos\"\n * [speed]=\"80\"\n * [gap]=\"48\"\n * [itemHeight]=\"36\"\n * [fadeOut]=\"true\"\n * [scaleOnHover]=\"true\"\n * [pauseOnHover]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/animations/logo-loop\n */\n@Component({\n selector: 'wr-marquee',\n templateUrl: './marquee.html',\n styleUrl: './marquee.scss',\n encapsulation: ViewEncapsulation.None,\n imports: [NgTemplateOutlet],\n host: {\n class: 'wr-marquee',\n '[class.wr-marquee--fade]': 'fadeOut()',\n '[class.wr-marquee--scale-hover]': 'scaleOnHover()',\n '[style.--wr-marquee-gap.px]': 'gap()',\n '[style.--wr-marquee-height.px]': 'itemHeight()',\n '[style.--wr-marquee-fade-color]': 'fadeOutColor() || null',\n '[attr.role]': '\"region\"',\n '[attr.aria-label]': 'ariaLabel()',\n },\n})\nexport class WrMarquee {\n /** Items to display. Each entry is either an image source or a TemplateRef node. */\n readonly items = input.required<readonly WrMarqueeItem[]>();\n\n /** Scroll speed in pixels per second. Negative reverses direction. @default 120 */\n readonly speed = input(120, { transform: numAttr(120) });\n\n /** Track direction. @default 'left' */\n readonly direction = input<'left' | 'right'>('left');\n\n /** Item height in pixels. @default 28 */\n readonly itemHeight = input(28, { transform: numAttr(28) });\n\n /** Gap between items in pixels. @default 32 */\n readonly gap = input(32, { transform: numAttr(32) });\n\n /** Pause the loop on hover. @default false */\n readonly pauseOnHover = input(false, { transform: coerceBooleanProperty });\n\n /** Custom speed (px/s) when hovered. Overrides `pauseOnHover`. */\n readonly hoverSpeed = input<number | undefined>(undefined, {\n transform: (v: unknown): number | undefined => (v == null || v === '' ? undefined : coerceNumberProperty(v, 0)),\n });\n\n /** Apply edge fade-out gradients. @default false */\n readonly fadeOut = input(false, { transform: coerceBooleanProperty });\n\n /** CSS colour for the fade-out gradient. Defaults to the page background. */\n readonly fadeOutColor = input<string>('');\n\n /** Scale individual items up on hover. @default false */\n readonly scaleOnHover = input(false, { transform: coerceBooleanProperty });\n\n /** Accessible label for the carousel region. @default 'Marquee' */\n readonly ariaLabel = input('Marquee');\n\n private readonly trackEl = viewChild.required<ElementRef<HTMLElement>>('track');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n protected readonly seqWidth = signal(0);\n protected readonly copyCount = signal(MIN_COPIES);\n protected readonly isHovered = signal(false);\n\n protected readonly copyIndices = computed(() => Array.from({ length: this.copyCount() }, (_, i) => i));\n\n /** Resolved hover speed: `hoverSpeed` overrides, else 0 when `pauseOnHover`, else live. */\n private readonly effectiveHoverSpeed = computed<number | undefined>(() => {\n const explicit = this.hoverSpeed();\n if (explicit !== undefined) return explicit;\n return this.pauseOnHover() ? 0 : undefined;\n });\n\n /** Target velocity in px/s, sign baked in. */\n private readonly targetVelocity = computed(() => {\n const s = this.speed();\n const sign = this.direction() === 'left' ? 1 : -1;\n return Math.abs(s) * sign * (s < 0 ? -1 : 1);\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n this.measure();\n this.watchResize();\n this.watchImageLoads();\n this.startLoop();\n });\n\n // Re-measure on input changes that affect layout (items, gap, height).\n effect(() => {\n this.items();\n this.gap();\n this.itemHeight();\n queueMicrotask(() => this.measure());\n });\n }\n\n protected onEnter(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(true);\n }\n protected onLeave(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(false);\n }\n protected isNodeItem = isNode;\n\n // Internals\n\n /** Read first sequence width + decide how many copies cover the viewport. */\n private measure(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const seqWidth = Math.ceil(seqEl.getBoundingClientRect().width);\n if (seqWidth <= 0) return;\n this.seqWidth.set(seqWidth);\n const containerWidth = hostEl.clientWidth || seqWidth;\n const copies = Math.max(MIN_COPIES, Math.ceil(containerWidth / seqWidth) + COPY_HEADROOM);\n this.copyCount.set(copies);\n }\n\n private watchResize(): void {\n const hostEl = this.host.nativeElement;\n if (typeof ResizeObserver === 'undefined') {\n const onResize = (): void => this.measure();\n window.addEventListener('resize', onResize);\n this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));\n return;\n }\n const ro = new ResizeObserver(() => this.measure());\n ro.observe(hostEl);\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (seqEl) ro.observe(seqEl);\n this.destroyRef.onDestroy(() => ro.disconnect());\n }\n\n private watchImageLoads(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const imgs = seqEl.querySelectorAll('img');\n if (imgs.length === 0) return;\n let remaining = imgs.length;\n const onDone = (): void => {\n remaining -= 1;\n if (remaining === 0) this.measure();\n };\n imgs.forEach(img => {\n if (img.complete) onDone();\n else {\n img.addEventListener('load', onDone, { once: true });\n img.addEventListener('error', onDone, { once: true });\n }\n });\n }\n\n private startLoop(): void {\n const track = this.trackEl().nativeElement;\n let raf = 0;\n let lastTs: number | null = null;\n let offset = 0;\n let velocity = 0;\n\n const tick = (ts: number): void => {\n lastTs ??= ts;\n const dt = Math.max(0, ts - lastTs) / 1000;\n lastTs = ts;\n\n const target = this.isHovered() ? (this.effectiveHoverSpeed() ?? this.targetVelocity()) : this.targetVelocity();\n const ease = 1 - Math.exp(-dt / SMOOTH_TAU);\n velocity += (target - velocity) * ease;\n\n const w = this.seqWidth();\n if (w > 0) {\n offset = (((offset + velocity * dt) % w) + w) % w;\n track.style.transform = `translate3d(${-offset}px, 0, 0)`;\n }\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n this.destroyRef.onDestroy(() => cancelAnimationFrame(raf));\n }\n}\n\nexport type { WrMarqueeImage, WrMarqueeNode, WrMarqueeItem } from './interfaces';\n","<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <ul class=\"wr-marquee__list\" role=\"list\" [attr.aria-hidden]=\"copyIndex > 0\" [attr.data-copy]=\"copyIndex\">\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n isNodeItem(item) ? item.ariaLabel || item.title || 'link' : item.alt || item.title || 'link'\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span class=\"wr-marquee__node\" [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\">\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\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-marquee.mjs","sources":["../../../projects/lib/marquee/marquee.ts","../../../projects/lib/marquee/marquee.html","../../../projects/lib/marquee/ngwr-marquee.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 * Angular adaptation of the LogoLoop effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/logo-loop\n *\n * Renamed `wr-marquee` in ngwr — same rAF-driven infinite carousel,\n * supports image + template items, optional fade/scale/pause-on-hover.\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrMarqueeNode, WrMarqueeItem } from './interfaces';\n\nconst SMOOTH_TAU = 0.25;\nconst MIN_COPIES = 2;\nconst COPY_HEADROOM = 2;\n\nfunction isNode(item: WrMarqueeItem): item is WrMarqueeNode {\n return 'node' in item;\n}\n\n/**\n * Endless horizontal marquee. Auto-duplicates the items list until it\n * covers the container width, then animates the track with a smooth\n * rAF-driven scroll. Optional edge fades, scale-on-hover, and pause /\n * variable-speed hover behaviour.\n *\n * @example\n * ```html\n * <wr-marquee\n * [items]=\"logos\"\n * [speed]=\"80\"\n * [gap]=\"48\"\n * [itemHeight]=\"36\"\n * [fadeOut]=\"true\"\n * [scaleOnHover]=\"true\"\n * [pauseOnHover]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/animations/logo-loop\n */\n@Component({\n selector: 'wr-marquee',\n templateUrl: './marquee.html',\n styleUrl: './marquee.scss',\n encapsulation: ViewEncapsulation.None,\n imports: [NgTemplateOutlet],\n host: {\n class: 'wr-marquee',\n '[class.wr-marquee--fade]': 'fadeOut()',\n '[class.wr-marquee--scale-hover]': 'scaleOnHover()',\n '[style.--wr-marquee-gap.px]': 'gap()',\n '[style.--wr-marquee-height.px]': 'itemHeight()',\n '[style.--wr-marquee-fade-color]': 'fadeOutColor() || null',\n '[attr.role]': '\"region\"',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n },\n})\nexport class WrMarquee {\n /** Items to display. Each entry is either an image source or a TemplateRef node. */\n readonly items = input.required<readonly WrMarqueeItem[]>();\n\n /** Scroll speed in pixels per second. Negative reverses direction. @default 120 */\n readonly speed = input(120, { transform: numAttr(120) });\n\n /** Track direction. @default 'left' */\n readonly direction = input<'left' | 'right'>('left');\n\n /** Item height in pixels. @default 28 */\n readonly itemHeight = input(28, { transform: numAttr(28) });\n\n /** Gap between items in pixels. @default 32 */\n readonly gap = input(32, { transform: numAttr(32) });\n\n /** Pause the loop on hover. @default false */\n readonly pauseOnHover = input(false, { transform: coerceBooleanProperty });\n\n /** Custom speed (px/s) when hovered. Overrides `pauseOnHover`. */\n readonly hoverSpeed = input<number | undefined>(undefined, {\n transform: (v: unknown): number | undefined => (v == null || v === '' ? undefined : coerceNumberProperty(v, 0)),\n });\n\n /** Apply edge fade-out gradients. @default false */\n readonly fadeOut = input(false, { transform: coerceBooleanProperty });\n\n /** CSS colour for the fade-out gradient. Defaults to the page background. */\n readonly fadeOutColor = input<string>('');\n\n /** Scale individual items up on hover. @default false */\n readonly scaleOnHover = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Accessible name for the region. Falls back to `marquee.label`, then\n * `'Marquee'`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'marquee.label', 'Marquee');\n\n /**\n * Name used for an item that links somewhere but carries no text of its own.\n * Falls back to `marquee.link`, then `'link'` — a bare `'link'` in English was\n * what a screen reader announced for every unlabelled logo in the strip.\n */\n protected readonly resolvedLinkLabel = useI18nText(signal(null).asReadonly(), 'marquee.link', 'link');\n\n private readonly trackEl = viewChild.required<ElementRef<HTMLElement>>('track');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n protected readonly seqWidth = signal(0);\n protected readonly copyCount = signal(MIN_COPIES);\n protected readonly isHovered = signal(false);\n\n protected readonly copyIndices = computed(() => Array.from({ length: this.copyCount() }, (_, i) => i));\n\n /** Resolved hover speed: `hoverSpeed` overrides, else 0 when `pauseOnHover`, else live. */\n private readonly effectiveHoverSpeed = computed<number | undefined>(() => {\n const explicit = this.hoverSpeed();\n if (explicit !== undefined) return explicit;\n return this.pauseOnHover() ? 0 : undefined;\n });\n\n /** Target velocity in px/s, sign baked in. */\n private readonly targetVelocity = computed(() => {\n const s = this.speed();\n const sign = this.direction() === 'left' ? 1 : -1;\n return Math.abs(s) * sign * (s < 0 ? -1 : 1);\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n this.measure();\n this.watchResize();\n this.watchImageLoads();\n this.startLoop();\n });\n\n // Re-measure on input changes that affect layout (items, gap, height).\n effect(() => {\n this.items();\n this.gap();\n this.itemHeight();\n queueMicrotask(() => this.measure());\n });\n }\n\n protected onEnter(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(true);\n }\n protected onLeave(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(false);\n }\n protected isNodeItem = isNode;\n\n // Internals\n\n /** Read first sequence width + decide how many copies cover the viewport. */\n private measure(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const seqWidth = Math.ceil(seqEl.getBoundingClientRect().width);\n if (seqWidth <= 0) return;\n this.seqWidth.set(seqWidth);\n const containerWidth = hostEl.clientWidth || seqWidth;\n const copies = Math.max(MIN_COPIES, Math.ceil(containerWidth / seqWidth) + COPY_HEADROOM);\n this.copyCount.set(copies);\n }\n\n private watchResize(): void {\n const hostEl = this.host.nativeElement;\n if (typeof ResizeObserver === 'undefined') {\n const onResize = (): void => this.measure();\n window.addEventListener('resize', onResize);\n this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));\n return;\n }\n const ro = new ResizeObserver(() => this.measure());\n ro.observe(hostEl);\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (seqEl) ro.observe(seqEl);\n this.destroyRef.onDestroy(() => ro.disconnect());\n }\n\n private watchImageLoads(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const imgs = seqEl.querySelectorAll('img');\n if (imgs.length === 0) return;\n let remaining = imgs.length;\n const onDone = (): void => {\n remaining -= 1;\n if (remaining === 0) this.measure();\n };\n imgs.forEach(img => {\n if (img.complete) onDone();\n else {\n img.addEventListener('load', onDone, { once: true });\n img.addEventListener('error', onDone, { once: true });\n }\n });\n }\n\n private startLoop(): void {\n const track = this.trackEl().nativeElement;\n let raf = 0;\n let lastTs: number | null = null;\n let offset = 0;\n let velocity = 0;\n\n const tick = (ts: number): void => {\n lastTs ??= ts;\n const dt = Math.max(0, ts - lastTs) / 1000;\n lastTs = ts;\n\n const target = this.isHovered() ? (this.effectiveHoverSpeed() ?? this.targetVelocity()) : this.targetVelocity();\n const ease = 1 - Math.exp(-dt / SMOOTH_TAU);\n velocity += (target - velocity) * ease;\n\n const w = this.seqWidth();\n if (w > 0) {\n offset = (((offset + velocity * dt) % w) + w) % w;\n track.style.transform = `translate3d(${-offset}px, 0, 0)`;\n }\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n this.destroyRef.onDestroy(() => cancelAnimationFrame(raf));\n }\n}\n\nexport type { WrMarqueeImage, WrMarqueeNode, WrMarqueeItem } from './interfaces';\n","<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <ul class=\"wr-marquee__list\" role=\"list\" [attr.aria-hidden]=\"copyIndex > 0\" [attr.data-copy]=\"copyIndex\">\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n (isNodeItem(item) ? item.ariaLabel || item.title : item.alt || item.title) || resolvedLinkLabel()\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span class=\"wr-marquee__node\" [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\">\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;;;;;;;AAWG;AAwBH,MAAM,UAAU,GAAG,IAAI;AACvB,MAAM,UAAU,GAAG,CAAC;AACpB,MAAM,aAAa,GAAG,CAAC;AAEvB,SAAS,MAAM,CAAC,IAAmB,EAAA;IACjC,OAAO,MAAM,IAAI,IAAI;AACvB;AAEA;;;;;;;;;;;;;;;;;;;;AAoBG;MAkBU,SAAS,CAAA;;IAEX,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAA4B;;AAGlD,IAAA,KAAK,GAAG,KAAK,CAAC,GAAG,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,OAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,EAAA,CAAG;;IAG/C,SAAS,GAAG,KAAK,CAAmB,MAAM;kFAAC;;AAG3C,IAAA,UAAU,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,YAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;AAGlD,IAAA,GAAG,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,KAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;IAG3C,YAAY,GAAG,KAAK,CAAC,KAAK,oFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;;AAGjE,IAAA,UAAU,GAAG,KAAK,CAAqB,SAAS,kFACvD,SAAS,EAAE,CAAC,CAAU,MAA0B,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,SAAS,GAAG,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC/G;;IAGO,OAAO,GAAG,KAAK,CAAC,KAAK,+EAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;;IAG5D,YAAY,GAAG,KAAK,CAAS,EAAE;qFAAC;;IAGhC,YAAY,GAAG,KAAK,CAAC,KAAK,oFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;AAE1E;;;AAGG;IACM,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;IAE5B,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,SAAS,CAAC;AAE9F;;;;AAIG;AACgB,IAAA,iBAAiB,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC;AAEpF,IAAA,OAAO,GAAG,SAAS,CAAC,QAAQ,CAA0B,OAAO;gFAAC;AAE9D,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC;AAClD,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAEhD,QAAQ,GAAG,MAAM,CAAC,CAAC;iFAAC;IACpB,SAAS,GAAG,MAAM,CAAC,UAAU;kFAAC;IAC9B,SAAS,GAAG,MAAM,CAAC,KAAK;kFAAC;IAEzB,WAAW,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;oFAAC;;AAGrF,IAAA,mBAAmB,GAAG,QAAQ,CAAqB,MAAK;AACvE,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE;QAClC,IAAI,QAAQ,KAAK,SAAS;AAAE,YAAA,OAAO,QAAQ;AAC3C,QAAA,OAAO,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,GAAG,SAAS;IAC5C,CAAC;4FAAC;;AAGe,IAAA,cAAc,GAAG,QAAQ,CAAC,MAAK;AAC9C,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;AACtB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;uFAAC;AAEF,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE;QAErB,eAAe,CAAC,MAAK;YACnB,IAAI,CAAC,OAAO,EAAE;YACd,IAAI,CAAC,WAAW,EAAE;YAClB,IAAI,CAAC,eAAe,EAAE;YACtB,IAAI,CAAC,SAAS,EAAE;AAClB,QAAA,CAAC,CAAC;;QAGF,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,KAAK,EAAE;YACZ,IAAI,CAAC,GAAG,EAAE;YACV,IAAI,CAAC,UAAU,EAAE;YACjB,cAAc,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;AACtC,QAAA,CAAC,CAAC;IACJ;IAEU,OAAO,GAAA;AACf,QAAA,IAAI,IAAI,CAAC,mBAAmB,EAAE,KAAK,SAAS;AAAE,YAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;IACxE;IACU,OAAO,GAAA;AACf,QAAA,IAAI,IAAI,CAAC,mBAAmB,EAAE,KAAK,SAAS;AAAE,YAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;IACzE;IACU,UAAU,GAAG,MAAM;;;IAKrB,OAAO,GAAA;AACb,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAc,mBAAmB,CAAC;AACpE,QAAA,IAAI,CAAC,KAAK;YAAE;AACZ,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC;QAC/D,IAAI,QAAQ,IAAI,CAAC;YAAE;AACnB,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;AAC3B,QAAA,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,IAAI,QAAQ;AACrD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,GAAG,aAAa,CAAC;AACzF,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;IAC5B;IAEQ,WAAW,GAAA;AACjB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa;AACtC,QAAA,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;YACzC,MAAM,QAAQ,GAAG,MAAY,IAAI,CAAC,OAAO,EAAE;AAC3C,YAAA,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC;AAC3C,YAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC/E;QACF;AACA,QAAA,MAAM,EAAE,GAAG,IAAI,cAAc,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;AACnD,QAAA,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAc,mBAAmB,CAAC;AACpE,QAAA,IAAI,KAAK;AAAE,YAAA,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;AAC5B,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;IAClD;IAEQ,eAAe,GAAA;AACrB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAc,mBAAmB,CAAC;AACpE,QAAA,IAAI,CAAC,KAAK;YAAE;QACZ,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC;AAC1C,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE;AACvB,QAAA,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM;QAC3B,MAAM,MAAM,GAAG,MAAW;YACxB,SAAS,IAAI,CAAC;YACd,IAAI,SAAS,KAAK,CAAC;gBAAE,IAAI,CAAC,OAAO,EAAE;AACrC,QAAA,CAAC;AACD,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,IAAG;YACjB,IAAI,GAAG,CAAC,QAAQ;AAAE,gBAAA,MAAM,EAAE;iBACrB;AACH,gBAAA,GAAG,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACpD,gBAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YACvD;AACF,QAAA,CAAC,CAAC;IACJ;IAEQ,SAAS,GAAA;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,aAAa;QAC1C,IAAI,GAAG,GAAG,CAAC;QACX,IAAI,MAAM,GAAkB,IAAI;QAChC,IAAI,MAAM,GAAG,CAAC;QACd,IAAI,QAAQ,GAAG,CAAC;AAEhB,QAAA,MAAM,IAAI,GAAG,CAAC,EAAU,KAAU;YAChC,MAAM,KAAK,EAAE;AACb,YAAA,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI;YAC1C,MAAM,GAAG,EAAE;YAEX,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,mBAAmB,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE;AAC/G,YAAA,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC;YAC3C,QAAQ,IAAI,CAAC,MAAM,GAAG,QAAQ,IAAI,IAAI;AAEtC,YAAA,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE;AACzB,YAAA,IAAI,CAAC,GAAG,CAAC,EAAE;AACT,gBAAA,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,QAAQ,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBACjD,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAA,YAAA,EAAe,CAAC,MAAM,CAAA,SAAA,CAAW;YAC3D;AACA,YAAA,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC;AACnC,QAAA,CAAC;AAED,QAAA,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC;AACjC,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC5D;uGAjLW,SAAS,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAT,SAAS,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,GAAA,EAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,UAAA,EAAA,KAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,wBAAA,EAAA,WAAA,EAAA,+BAAA,EAAA,gBAAA,EAAA,2BAAA,EAAA,OAAA,EAAA,8BAAA,EAAA,cAAA,EAAA,+BAAA,EAAA,wBAAA,EAAA,WAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,EAAA,cAAA,EAAA,YAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,SAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,OAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECjFtB,inDA8CA,EAAA,MAAA,EAAA,CAAA,44hBAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDuBY,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAYf,SAAS,EAAA,UAAA,EAAA,CAAA;kBAjBrB,SAAS;+BACE,YAAY,EAAA,aAAA,EAGP,iBAAiB,CAAC,IAAI,WAC5B,CAAC,gBAAgB,CAAC,EAAA,IAAA,EACrB;AACJ,wBAAA,KAAK,EAAE,YAAY;AACnB,wBAAA,0BAA0B,EAAE,WAAW;AACvC,wBAAA,iCAAiC,EAAE,gBAAgB;AACnD,wBAAA,6BAA6B,EAAE,OAAO;AACtC,wBAAA,gCAAgC,EAAE,cAAc;AAChD,wBAAA,iCAAiC,EAAE,wBAAwB;AAC3D,wBAAA,aAAa,EAAE,UAAU;AACzB,wBAAA,mBAAmB,EAAE,qBAAqB;AAC3C,qBAAA,EAAA,QAAA,EAAA,inDAAA,EAAA,MAAA,EAAA,CAAA,44hBAAA,CAAA,EAAA;0oCAkDsE,OAAO,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AEjIhF;;AAEG;;;;"}
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Test harness for one suggestion inside a `[wrMention]` panel.
|
|
11
|
+
*
|
|
12
|
+
* Reach these through {@link WrMentionHarness.getOptions}, never off the document:
|
|
13
|
+
* the panel is a portal in the shared overlay container, so a bare query answers
|
|
14
|
+
* with whichever mention field opened first.
|
|
15
|
+
*
|
|
16
|
+
* @see https://ngwr.dev/guides/testing
|
|
17
|
+
*/
|
|
18
|
+
class WrMentionOptionHarness extends ComponentHarness {
|
|
19
|
+
/** One `<li role="option">` in the panel's listbox. */
|
|
20
|
+
static hostSelector = '.wr-mention-panel__option';
|
|
21
|
+
/** Build a predicate that narrows the query. */
|
|
22
|
+
static with(options = {}) {
|
|
23
|
+
return new HarnessPredicate(WrMentionOptionHarness, options)
|
|
24
|
+
.addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))
|
|
25
|
+
.addOption('active', options.active, async (harness, active) => (await harness.isActive()) === active);
|
|
26
|
+
}
|
|
27
|
+
/** The suggestion's label — whatever `displayWith` returned for the item. */
|
|
28
|
+
async getText() {
|
|
29
|
+
return (await this.host()).text();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether the cursor is on this suggestion.
|
|
33
|
+
*
|
|
34
|
+
* Read from `aria-selected` rather than from the `--active` modifier class. The
|
|
35
|
+
* two are set from the same index, so they agree — but the ARIA state is what a
|
|
36
|
+
* screen reader is told, and the class is only what the highlight is painted
|
|
37
|
+
* with.
|
|
38
|
+
*/
|
|
39
|
+
async isActive() {
|
|
40
|
+
return (await (await this.host()).getAttribute('aria-selected')) === 'true';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The suggestion's id — the value the field names in `aria-activedescendant`.
|
|
44
|
+
*
|
|
45
|
+
* The only link between the two DOM trees: the panel is in the overlay
|
|
46
|
+
* container, not inside the field, so this id is how the reference reaches it
|
|
47
|
+
* at all.
|
|
48
|
+
*/
|
|
49
|
+
async getId() {
|
|
50
|
+
return (await this.host()).getAttribute('id');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Pick this suggestion with the mouse.
|
|
54
|
+
*
|
|
55
|
+
* Needs no layout, so it works here: the panel commits on `mousedown` —
|
|
56
|
+
* deliberately, so the field never loses focus and the caret stays put — and
|
|
57
|
+
* the CDK's no-arg click dispatches `mousedown` at the head of its sequence
|
|
58
|
+
* without any hit test.
|
|
59
|
+
*/
|
|
60
|
+
async click() {
|
|
61
|
+
return (await this.host()).click();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Move the pointer onto this suggestion, which makes it the active one.
|
|
65
|
+
*
|
|
66
|
+
* Hover and the arrow keys drive the same single cursor, so a spec can assert
|
|
67
|
+
* the mouse path through {@link WrMentionHarness.getActiveOptionLabel} exactly
|
|
68
|
+
* as it does the keyboard one.
|
|
69
|
+
*/
|
|
70
|
+
async hover() {
|
|
71
|
+
return (await this.host()).hover();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @license
|
|
77
|
+
*
|
|
78
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
79
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
80
|
+
*/
|
|
81
|
+
/** How long {@link WrMentionHarness.blur} waits for the deferred close, in ms. Generous next to the 120ms grace period. */
|
|
82
|
+
const CLOSE_TIMEOUT = 1000;
|
|
83
|
+
/** How often it re-checks, in ms. */
|
|
84
|
+
const POLL_STEP = 10;
|
|
85
|
+
/**
|
|
86
|
+
* The real `setTimeout`, captured at module load — before a spec can install fake
|
|
87
|
+
* timers.
|
|
88
|
+
*
|
|
89
|
+
* {@link WrMentionHarness.blur} has to let REAL time pass: the close on blur is a
|
|
90
|
+
* plain `setTimeout` inside the directive, and under zoneless change detection
|
|
91
|
+
* `whenStable()` resolves without waiting for a macrotask. Reading a faked global
|
|
92
|
+
* instead would mean the sleep never fires and the harness hangs where it should
|
|
93
|
+
* fail with the message it prepared.
|
|
94
|
+
*/
|
|
95
|
+
const realSetTimeout = globalThis.setTimeout;
|
|
96
|
+
function sleep(ms) {
|
|
97
|
+
return new Promise(resolve => {
|
|
98
|
+
realSetTimeout(resolve, ms);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Test harness for `[wrMention]` — the field, and the suggestion panel it opens
|
|
103
|
+
* at the caret.
|
|
104
|
+
*
|
|
105
|
+
* The harness matches the FIELD, because that is the only part of a mention in the
|
|
106
|
+
* fixture: the panel is a `ComponentPortal` in the shared overlay container, a
|
|
107
|
+
* sibling of the whole app. Suggestions are read through the document root, scoped
|
|
108
|
+
* by the id the field publishes as `aria-controls` — which is what keeps two
|
|
109
|
+
* mention fields on one page from answering with each other's suggestions.
|
|
110
|
+
*
|
|
111
|
+
* **The field is not a combobox, and it has no `aria-expanded`.** Both are
|
|
112
|
+
* deliberate (the source spells out why: it holds prose, `role="combobox"` is
|
|
113
|
+
* disallowed on `<textarea>` and would drop `aria-multiline`, and `aria-expanded`
|
|
114
|
+
* is not a supported state of `role="textbox"`), and both shape this harness.
|
|
115
|
+
* `aria-controls` is STATIC — it names the listbox even while there is no listbox —
|
|
116
|
+
* so {@link isOpen} asks whether the listbox EXISTS instead of reading a state
|
|
117
|
+
* flag, and "a list appeared" reaches a screen reader through the live region that
|
|
118
|
+
* {@link getStatusMessage} reads.
|
|
119
|
+
*
|
|
120
|
+
* **The panel's POSITION is not testable in jsdom, so nothing here reports it.**
|
|
121
|
+
* `caret.ts` places the panel by measuring a mirror `<div>` with
|
|
122
|
+
* `getBoundingClientRect`, and jsdom has no layout: every rect is zeros, so the
|
|
123
|
+
* measurement collapses to the origin. The overlay still lands on finite numbers —
|
|
124
|
+
* `line-height` computes to `normal` there, so `caret.ts` falls back to
|
|
125
|
+
* `font-size * 1.2` and the pane is offset by that alone (a measured
|
|
126
|
+
* `margin-top: 23.2px`, `margin-left: 0px` on a default fixture) — which is
|
|
127
|
+
* precisely the problem: every one of them is an artefact of the environment
|
|
128
|
+
* rather than a fact about the component. Assert placement in a real browser.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const mention = await loader.getHarness(WrMentionHarness);
|
|
133
|
+
*
|
|
134
|
+
* await mention.type('hey @al');
|
|
135
|
+
* expect(await mention.getOptionLabels()).toEqual(['Alan Turing', 'Linus Torvalds']);
|
|
136
|
+
*
|
|
137
|
+
* await mention.commit();
|
|
138
|
+
* expect(await mention.getValue()).toBe('hey @Alan Turing ');
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @see https://ngwr.dev/guides/testing
|
|
142
|
+
*/
|
|
143
|
+
class WrMentionHarness extends ComponentHarness {
|
|
144
|
+
/** The directive's host. Both tags, since the directive drives either one. */
|
|
145
|
+
static hostSelector = 'input[wrMention], textarea[wrMention]';
|
|
146
|
+
/** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
|
|
147
|
+
static with(options = {}) {
|
|
148
|
+
return new HarnessPredicate(WrMentionHarness, options)
|
|
149
|
+
.addOption('value', options.value, (harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value))
|
|
150
|
+
.addOption('placeholder', options.placeholder, (harness, placeholder) => HarnessPredicate.stringMatches(harness.getPlaceholder(), placeholder))
|
|
151
|
+
.addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);
|
|
152
|
+
}
|
|
153
|
+
/** The field's whole text, mention fragments and prose alike. */
|
|
154
|
+
async getValue() {
|
|
155
|
+
return (await this.host()).getProperty('value');
|
|
156
|
+
}
|
|
157
|
+
/** The field's placeholder, or `''` when it has none. */
|
|
158
|
+
async getPlaceholder() {
|
|
159
|
+
return (await this.host()).getProperty('placeholder');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Type into the field one keystroke at a time, APPENDING to whatever is there.
|
|
163
|
+
*
|
|
164
|
+
* The realistic path, and the one that exercises detection the way a person
|
|
165
|
+
* does: the directive re-reads the text before the caret on every `input`, so
|
|
166
|
+
* `type('hey @al')` opens the panel on the `@` and then narrows it twice.
|
|
167
|
+
*
|
|
168
|
+
* Focuses the field, which BLURS whatever was focused before — and a blurred
|
|
169
|
+
* mention field closes its own panel. Use {@link setValue} when a spec needs two
|
|
170
|
+
* fields suggesting at the same time.
|
|
171
|
+
*/
|
|
172
|
+
async type(text) {
|
|
173
|
+
return (await this.host()).sendKeys(text);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Replace the field's whole text and let the directive detect once.
|
|
177
|
+
*
|
|
178
|
+
* The `input` event is dispatched by hand rather than through keystrokes so the
|
|
179
|
+
* field is never focused — which is the only way to get two mention panels open
|
|
180
|
+
* at once, since focusing one field blurs the other and blur closes its panel.
|
|
181
|
+
* The caret lands at the end of the new text, where detection looks.
|
|
182
|
+
*/
|
|
183
|
+
async setValue(text) {
|
|
184
|
+
const host = await this.host();
|
|
185
|
+
await host.setInputValue(text);
|
|
186
|
+
await host.dispatchEvent('input');
|
|
187
|
+
}
|
|
188
|
+
/** Empty the field. The panel goes with it — there is no trigger char left to detect. */
|
|
189
|
+
async clear() {
|
|
190
|
+
return (await this.host()).clear();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Click the field.
|
|
194
|
+
*
|
|
195
|
+
* Re-opens the suggestions when the caret is still sitting inside a mention —
|
|
196
|
+
* the directive re-detects on click, which is how a user who pressed Escape (or
|
|
197
|
+
* clicked away and back) gets the panel again without retyping. Needs no layout:
|
|
198
|
+
* the CDK dispatches the event on the element rather than hit-testing a point.
|
|
199
|
+
*/
|
|
200
|
+
async click() {
|
|
201
|
+
return (await this.host()).click();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Whether the panel is offering suggestions.
|
|
205
|
+
*
|
|
206
|
+
* Asks whether this field's listbox exists, because the field publishes no
|
|
207
|
+
* open/closed state to read: `aria-expanded` is not a supported state of
|
|
208
|
+
* `role="textbox"` (axe rates it a critical violation), and `aria-controls` is
|
|
209
|
+
* static here, so its presence says nothing.
|
|
210
|
+
*/
|
|
211
|
+
async isOpen() {
|
|
212
|
+
return (await this.listboxOrNull()) !== null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The field's `aria-autocomplete` — `'list'`, permanently.
|
|
216
|
+
*
|
|
217
|
+
* A capability, not a state: ARIA says authors should not toggle it to signal
|
|
218
|
+
* that suggestions are showing, and being static is what makes the feature
|
|
219
|
+
* discoverable on focus, before a trigger char is ever typed. {@link isOpen}
|
|
220
|
+
* answers the state.
|
|
221
|
+
*/
|
|
222
|
+
async getAutocomplete() {
|
|
223
|
+
return (await this.host()).getAttribute('aria-autocomplete');
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The role the field PROMISES its popup will announce, from `aria-haspopup` —
|
|
227
|
+
* `'listbox'`. Required alongside `aria-autocomplete="list"`, and it has to name
|
|
228
|
+
* the popup's real role, which {@link getListboxRole} reads back off the panel.
|
|
229
|
+
*/
|
|
230
|
+
async getPopupRole() {
|
|
231
|
+
return (await this.host()).getAttribute('aria-haspopup');
|
|
232
|
+
}
|
|
233
|
+
/** The role the open panel announces — `'listbox'`. Throws while the panel is closed. */
|
|
234
|
+
async getListboxRole() {
|
|
235
|
+
return (await this.listbox()).getAttribute('role');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The panel's accessible name.
|
|
239
|
+
*
|
|
240
|
+
* ARIA requires a `role="listbox"` to have one, and a consumer never renders the
|
|
241
|
+
* panel themselves — so the directive supplies it from the i18n catalog
|
|
242
|
+
* (`mention.listbox`).
|
|
243
|
+
*/
|
|
244
|
+
async getListboxLabel() {
|
|
245
|
+
return (await this.listbox()).getAttribute('aria-label');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The suggestions on offer, in DOM order.
|
|
249
|
+
*
|
|
250
|
+
* The whole list a user can reach, not a window onto a longer one: the directive
|
|
251
|
+
* slices the matches at `maxResults` (8 by default) before rendering, which is
|
|
252
|
+
* why this panel needs no virtualisation.
|
|
253
|
+
*
|
|
254
|
+
* Throws while the panel is closed — the options do not exist anywhere until the
|
|
255
|
+
* portal is attached, and an empty array reads like a panel that rendered
|
|
256
|
+
* nothing.
|
|
257
|
+
*/
|
|
258
|
+
async getOptions(filters = {}) {
|
|
259
|
+
const loader = await this.listboxLoader();
|
|
260
|
+
return loader.getAllHarnesses(WrMentionOptionHarness.with(filters));
|
|
261
|
+
}
|
|
262
|
+
/** The labels of the suggestions on offer, in DOM order. */
|
|
263
|
+
async getOptionLabels() {
|
|
264
|
+
const options = await this.getOptions();
|
|
265
|
+
return Promise.all(options.map(option => option.getText()));
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The id the field names in `aria-activedescendant`, or `null` when it names
|
|
269
|
+
* nothing.
|
|
270
|
+
*
|
|
271
|
+
* The strict half of the field's two references. `aria-controls` is allowed to
|
|
272
|
+
* dangle (see {@link isOpen}); this one is not — naming an element that is not
|
|
273
|
+
* in the document is an author error no browser reports, it simply stops
|
|
274
|
+
* announcing options. So `null` is the CORRECT answer while the panel is closed,
|
|
275
|
+
* and worth asserting there.
|
|
276
|
+
*/
|
|
277
|
+
async getActiveOptionId() {
|
|
278
|
+
return (await this.host()).getAttribute('aria-activedescendant');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The label a screen reader announces as the active suggestion.
|
|
282
|
+
*
|
|
283
|
+
* Resolved through `aria-activedescendant` the way an assistive technology
|
|
284
|
+
* would, rather than by hunting for the active class — which is why it throws on
|
|
285
|
+
* a reference that resolves to nothing instead of quietly reporting what the
|
|
286
|
+
* class says.
|
|
287
|
+
*/
|
|
288
|
+
async getActiveOptionLabel() {
|
|
289
|
+
const active = await this.activeOption('getActiveOptionLabel');
|
|
290
|
+
return active.text();
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Where the active suggestion sits in {@link getOptions}, 0-based.
|
|
294
|
+
*
|
|
295
|
+
* Matched by id rather than counted by class, so the answer is the one
|
|
296
|
+
* `aria-activedescendant` actually points at.
|
|
297
|
+
*/
|
|
298
|
+
async getActiveOptionIndex() {
|
|
299
|
+
const id = await this.requireActiveOptionId('getActiveOptionIndex');
|
|
300
|
+
const options = await this.getOptions();
|
|
301
|
+
for (let index = 0; index < options.length; index++) {
|
|
302
|
+
if ((await options[index].getId()) === id)
|
|
303
|
+
return index;
|
|
304
|
+
}
|
|
305
|
+
throw new Error(`WrMentionHarness.getActiveOptionIndex(): aria-activedescendant names "${id}", which is not one of ` +
|
|
306
|
+
`the ${options.length} suggestions on offer.`);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Move the cursor down one suggestion, wrapping past the last back to the first.
|
|
310
|
+
*
|
|
311
|
+
* ArrowDown — and `aria-activedescendant` is the whole navigation model here,
|
|
312
|
+
* because the options are never focused: focus stays in the field so the user can
|
|
313
|
+
* keep typing the query.
|
|
314
|
+
*
|
|
315
|
+
* Throws while the panel is closed, where ArrowDown belongs to the field.
|
|
316
|
+
*/
|
|
317
|
+
async nextOption() {
|
|
318
|
+
await this.requireOpen('nextOption');
|
|
319
|
+
return (await this.host()).sendKeys(TestKey.DOWN_ARROW);
|
|
320
|
+
}
|
|
321
|
+
/** Move the cursor up one suggestion, wrapping past the first round to the last. */
|
|
322
|
+
async previousOption() {
|
|
323
|
+
await this.requireOpen('previousOption');
|
|
324
|
+
return (await this.host()).sendKeys(TestKey.UP_ARROW);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Insert the active suggestion with Enter.
|
|
328
|
+
*
|
|
329
|
+
* The trigger char and the query are replaced by `valueWith(item, trigger)`
|
|
330
|
+
* (`@Label` by default) plus a trailing space, the caret lands after that space,
|
|
331
|
+
* the panel closes and `(wrMentionSelected)` fires with the item, the trigger
|
|
332
|
+
* char and the query.
|
|
333
|
+
*
|
|
334
|
+
* Throws while the panel is closed — Enter is the field's own key there (a
|
|
335
|
+
* newline in a `<textarea>`), and swallowing it would be the bug.
|
|
336
|
+
*/
|
|
337
|
+
async commit() {
|
|
338
|
+
await this.requireOpen('commit');
|
|
339
|
+
return (await this.host()).sendKeys(TestKey.ENTER);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Insert the active suggestion with Tab instead.
|
|
343
|
+
*
|
|
344
|
+
* The same commit: the directive takes Tab as a second confirm key and
|
|
345
|
+
* `preventDefault`s it, so focus stays in the field rather than leaving for the
|
|
346
|
+
* next control.
|
|
347
|
+
*/
|
|
348
|
+
async commitWithTab() {
|
|
349
|
+
await this.requireOpen('commitWithTab');
|
|
350
|
+
return (await this.host()).sendKeys(TestKey.TAB);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Pick the first suggestion matching the filters, with the mouse.
|
|
354
|
+
*
|
|
355
|
+
* Opens nothing by itself — type a trigger char first. Names what the panel does
|
|
356
|
+
* offer when nothing matches, since a silent no-op here surfaces as an
|
|
357
|
+
* unexplained unchanged field further down the spec.
|
|
358
|
+
*/
|
|
359
|
+
async pick(filters) {
|
|
360
|
+
const [option] = await this.getOptions(filters);
|
|
361
|
+
if (!option) {
|
|
362
|
+
const offered = await this.getOptionLabels();
|
|
363
|
+
throw new Error(`WrMentionHarness.pick(): no suggestion matched ${JSON.stringify(filters)}. ` +
|
|
364
|
+
`The panel offers: ${offered.join(', ')}.`);
|
|
365
|
+
}
|
|
366
|
+
return option.click();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Dismiss the panel with Escape, leaving the typed text — trigger char and
|
|
370
|
+
* query included — exactly as it is. A closed panel is left alone.
|
|
371
|
+
*
|
|
372
|
+
* Sends the WHOLE keystroke, keydown and keyup, as a real Escape is. That
|
|
373
|
+
* matters here: the caret has not moved, so a keyup the directive re-detected on
|
|
374
|
+
* would reopen the panel this just closed, and a harness that dispatched only
|
|
375
|
+
* the keydown would report a dismissal the user never got.
|
|
376
|
+
*/
|
|
377
|
+
async dismiss() {
|
|
378
|
+
if (!(await this.isOpen()))
|
|
379
|
+
return;
|
|
380
|
+
return (await this.host()).sendKeys(TestKey.ESCAPE);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Blur the field and wait for the panel to go.
|
|
384
|
+
*
|
|
385
|
+
* The close is deferred by 120ms so a `mousedown` on a suggestion lands first,
|
|
386
|
+
* and that is a real `setTimeout`: under zoneless change detection nothing
|
|
387
|
+
* flushes it for us, so this waits out REAL time. A spec on fake timers should
|
|
388
|
+
* drive its own clock and read {@link isOpen} instead of calling this.
|
|
389
|
+
*/
|
|
390
|
+
async blur(timeout = CLOSE_TIMEOUT) {
|
|
391
|
+
await (await this.host()).blur();
|
|
392
|
+
for (let waited = 0; waited <= timeout; waited += POLL_STEP) {
|
|
393
|
+
if (!(await this.isOpen()))
|
|
394
|
+
return;
|
|
395
|
+
await sleep(POLL_STEP);
|
|
396
|
+
await this.forceStabilize();
|
|
397
|
+
}
|
|
398
|
+
throw new Error(`WrMentionHarness.blur(): the panel is still showing ${timeout}ms after the blur. The close is ` +
|
|
399
|
+
'deferred so that a click on a suggestion lands first, and this wait uses real time — a spec on ' +
|
|
400
|
+
'fake timers has to advance the clock itself and read isOpen().');
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* What the live region currently says.
|
|
404
|
+
*
|
|
405
|
+
* This is the announcement channel the field has INSTEAD of ARIA state, and
|
|
406
|
+
* three things reach a screen reader only through it: how many suggestions
|
|
407
|
+
* appeared, that NOTHING matched — the panel closes in that case, so there is no
|
|
408
|
+
* DOM left to inspect and no state on the field to read — and what was inserted
|
|
409
|
+
* on commit.
|
|
410
|
+
*
|
|
411
|
+
* Throws when more than one mention field is mounted. The region is parked on
|
|
412
|
+
* `<body>` rather than in the panel (which is disposed on every keystroke that
|
|
413
|
+
* leaves a mention, and a live region has to pre-exist its own text to be
|
|
414
|
+
* announced at all), and it carries no id tying it to a field — so with two
|
|
415
|
+
* fields around, the harness genuinely cannot tell which region is this one's.
|
|
416
|
+
* Answering with whichever came first would be worse than refusing: assert
|
|
417
|
+
* announcements in a fixture holding a single mention field.
|
|
418
|
+
*/
|
|
419
|
+
async getStatusMessage() {
|
|
420
|
+
const regions = await this.documentRootLocatorFactory().locatorForAll('.wr-mention__status')();
|
|
421
|
+
if (regions.length === 0) {
|
|
422
|
+
throw new Error('WrMentionHarness.getStatusMessage(): no mention live region is in the document. The directive ' +
|
|
423
|
+
'appends it to <body> in the browser only, so there is none under SSR.');
|
|
424
|
+
}
|
|
425
|
+
if (regions.length > 1) {
|
|
426
|
+
throw new Error(`WrMentionHarness.getStatusMessage(): ${regions.length} mention live regions are in the document ` +
|
|
427
|
+
'and none of them names the field it belongs to, so this cannot say which one is yours. Assert ' +
|
|
428
|
+
'the announcement in a fixture with a single mention field.');
|
|
429
|
+
}
|
|
430
|
+
return regions[0].text();
|
|
431
|
+
}
|
|
432
|
+
/** Move keyboard focus to the field. Focusing alone opens nothing — the panel needs a trigger char. */
|
|
433
|
+
async focus() {
|
|
434
|
+
return (await this.host()).focus();
|
|
435
|
+
}
|
|
436
|
+
/** Whether the field holds keyboard focus. It keeps it while the panel is open, by design. */
|
|
437
|
+
async isFocused() {
|
|
438
|
+
return (await this.host()).isFocused();
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* A loader scoped to THIS field's listbox.
|
|
442
|
+
*
|
|
443
|
+
* Scoped by the id the field publishes as `aria-controls`, never by
|
|
444
|
+
* `.wr-mention-panel`: the overlay container is shared by every mention on the
|
|
445
|
+
* page, so a class query would answer with whichever field opened first.
|
|
446
|
+
*/
|
|
447
|
+
async listboxLoader() {
|
|
448
|
+
// Resolve the element first, so a closed panel fails with the message below
|
|
449
|
+
// rather than with the CDK's generic "expected to find element" error.
|
|
450
|
+
await this.listbox();
|
|
451
|
+
return this.documentRootLocatorFactory().harnessLoaderFor(`#${await this.listboxId()}`);
|
|
452
|
+
}
|
|
453
|
+
/** This field's listbox in the overlay. Throws when the panel is closed. */
|
|
454
|
+
async listbox() {
|
|
455
|
+
const listbox = await this.listboxOrNull();
|
|
456
|
+
if (!listbox) {
|
|
457
|
+
throw new Error('WrMentionHarness: no suggestions are showing, so there is nothing inside the panel to read. It ' +
|
|
458
|
+
'exists only while the caret sits after a trigger char that matched something — type() one in ' +
|
|
459
|
+
'first. (The field publishes aria-controls whether or not the panel is up, so that attribute is ' +
|
|
460
|
+
'not the check.)');
|
|
461
|
+
}
|
|
462
|
+
return listbox;
|
|
463
|
+
}
|
|
464
|
+
/** This field's listbox, or `null` when the panel is closed. */
|
|
465
|
+
async listboxOrNull() {
|
|
466
|
+
return this.documentRootLocatorFactory().locatorForOptional(`#${await this.listboxId()}`)();
|
|
467
|
+
}
|
|
468
|
+
/** The id the field names in `aria-controls`, which its listbox carries. */
|
|
469
|
+
async listboxId() {
|
|
470
|
+
const id = await (await this.host()).getAttribute('aria-controls');
|
|
471
|
+
if (!id) {
|
|
472
|
+
throw new Error('WrMentionHarness: this field publishes no aria-controls. That is a static host binding on ' +
|
|
473
|
+
'[wrMention] — rendered even under SSR — so the element matched here is probably not a mention ' +
|
|
474
|
+
'field at all.');
|
|
475
|
+
}
|
|
476
|
+
return id;
|
|
477
|
+
}
|
|
478
|
+
/** The element `aria-activedescendant` points at, or a targeted failure. */
|
|
479
|
+
async activeOption(method) {
|
|
480
|
+
const id = await this.requireActiveOptionId(method);
|
|
481
|
+
const active = await this.documentRootLocatorFactory().locatorForOptional(`#${id}`)();
|
|
482
|
+
if (!active) {
|
|
483
|
+
throw new Error(`WrMentionHarness.${method}(): aria-activedescendant names "${id}", which is not in the document. ` +
|
|
484
|
+
'A dangling reference stops a screen reader announcing options and no browser reports it, so it ' +
|
|
485
|
+
'is a real failure rather than a harness one.');
|
|
486
|
+
}
|
|
487
|
+
return active;
|
|
488
|
+
}
|
|
489
|
+
/** The active option's id, or a targeted failure. */
|
|
490
|
+
async requireActiveOptionId(method) {
|
|
491
|
+
const id = await this.getActiveOptionId();
|
|
492
|
+
if (!id) {
|
|
493
|
+
throw new Error(`WrMentionHarness.${method}(): the field names no active suggestion. That is the correct state ` +
|
|
494
|
+
'while the panel is closed — check isOpen() first.');
|
|
495
|
+
}
|
|
496
|
+
return id;
|
|
497
|
+
}
|
|
498
|
+
/** Refuse a keystroke that would mean something else entirely with the panel closed. */
|
|
499
|
+
async requireOpen(method) {
|
|
500
|
+
if (await this.isOpen())
|
|
501
|
+
return;
|
|
502
|
+
throw new Error(`WrMentionHarness.${method}(): no suggestions are showing, so this key belongs to the field itself ` +
|
|
503
|
+
'— Enter is a newline, Tab leaves for the next control, and the arrows move the caret. Type a ' +
|
|
504
|
+
'trigger char first.');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Generated bundle index. Do not edit.
|
|
510
|
+
*/
|
|
511
|
+
|
|
512
|
+
export { WrMentionHarness, WrMentionOptionHarness };
|
|
513
|
+
//# sourceMappingURL=ngwr-mention-testing.mjs.map
|