ngwr 14.7.0 → 14.9.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 +66 -332
- package/alert/styles/_index.scss +10 -0
- package/avatar/styles/_index.scss +18 -7
- package/badge/styles/_index.scss +7 -0
- package/badge/styles/_tag.scss +7 -1
- package/bar-chart/styles/_index.scss +1 -0
- package/button/styles/_index.scss +6 -0
- package/calendar-heatmap/styles/_index.scss +1 -0
- package/cascader/styles/_rules.scss +87 -15
- package/checkbox/styles/_index.scss +4 -0
- package/command-palette/styles/_index.scss +1 -1
- package/date-picker/styles/_index.scss +30 -0
- package/density/styles/_index.scss +4 -2
- package/divider/styles/_index.scss +2 -0
- package/donut-chart/styles/_index.scss +10 -0
- package/dropdown/styles/_index.scss +67 -5
- package/editor/styles/_index.scss +441 -0
- package/event-calendar/styles/_index.scss +209 -35
- package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -1
- package/fesm2022/ngwr-action-sheet.mjs +3 -3
- package/fesm2022/ngwr-action-sheet.mjs.map +1 -1
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-affix.mjs.map +1 -1
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs.map +1 -1
- package/fesm2022/ngwr-aurora.mjs +3 -3
- package/fesm2022/ngwr-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-avatar.mjs +3 -3
- package/fesm2022/ngwr-avatar.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +3 -3
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-badge.mjs +28 -13
- package/fesm2022/ngwr-badge.mjs.map +1 -1
- package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bar-chart.mjs +31 -6
- package/fesm2022/ngwr-bar-chart.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +3 -3
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +3 -3
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +6 -6
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-burger.mjs.map +1 -1
- package/fesm2022/ngwr-button-testing.mjs.map +1 -1
- package/fesm2022/ngwr-button.mjs +6 -6
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap.mjs +61 -6
- package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +3 -3
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +9 -9
- package/fesm2022/ngwr-card.mjs.map +1 -1
- package/fesm2022/ngwr-carousel-testing.mjs.map +1 -1
- package/fesm2022/ngwr-carousel.mjs +6 -6
- package/fesm2022/ngwr-carousel.mjs.map +1 -1
- package/fesm2022/ngwr-cascader-testing.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +4 -4
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox-testing.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +18 -8
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +3 -3
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-click-spark.mjs +3 -3
- package/fesm2022/ngwr-click-spark.mjs.map +1 -1
- package/fesm2022/ngwr-clipboard.mjs +3 -3
- package/fesm2022/ngwr-clipboard.mjs.map +1 -1
- package/fesm2022/ngwr-collapse-testing.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs +9 -9
- package/fesm2022/ngwr-collapse.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +6 -6
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette-testing.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +3 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-compare.mjs +3 -3
- package/fesm2022/ngwr-compare.mjs.map +1 -1
- package/fesm2022/ngwr-confetti.mjs +3 -3
- package/fesm2022/ngwr-confetti.mjs.map +1 -1
- package/fesm2022/ngwr-config.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu-testing.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +12 -12
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-cookie.mjs +3 -3
- package/fesm2022/ngwr-cookie.mjs.map +1 -1
- package/fesm2022/ngwr-counter.mjs +6 -6
- package/fesm2022/ngwr-counter.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-fns.mjs +3 -3
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
- package/fesm2022/ngwr-date-adapters-luxon.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +721 -62
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-date.mjs +3 -3
- package/fesm2022/ngwr-date.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text.mjs +3 -3
- package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
- package/fesm2022/ngwr-density.mjs +6 -6
- package/fesm2022/ngwr-density.mjs.map +1 -1
- package/fesm2022/ngwr-descriptions.mjs +6 -6
- package/fesm2022/ngwr-descriptions.mjs.map +1 -1
- package/fesm2022/ngwr-dialog-testing.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +15 -15
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +12 -12
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-divider.mjs +3 -3
- package/fesm2022/ngwr-divider.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart.mjs +48 -6
- package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +6 -6
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer-testing.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +18 -18
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown-testing.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +174 -25
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-editor-testing.mjs +568 -0
- package/fesm2022/ngwr-editor-testing.mjs.map +1 -0
- package/fesm2022/ngwr-editor.mjs +2601 -0
- package/fesm2022/ngwr-editor.mjs.map +1 -0
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-empty.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +14 -7
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text.mjs +3 -3
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload-testing.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-form-testing.mjs.map +1 -1
- package/fesm2022/ngwr-form.mjs +61 -10
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gauge-testing.mjs.map +1 -1
- package/fesm2022/ngwr-gauge.mjs +35 -6
- package/fesm2022/ngwr-gauge.mjs.map +1 -1
- package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-glitch-text.mjs +3 -3
- package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +3 -3
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-graph-testing.mjs.map +1 -1
- package/fesm2022/ngwr-graph.mjs +6 -6
- package/fesm2022/ngwr-graph.mjs.map +1 -1
- package/fesm2022/ngwr-hotkey.mjs +6 -6
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ar.mjs +31 -0
- package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-cs.mjs +31 -0
- package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-de.mjs +31 -0
- package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +39 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-es.mjs +31 -0
- package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-fr.mjs +31 -0
- package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-he.mjs +31 -0
- package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-hi.mjs +31 -0
- package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-id.mjs +31 -0
- package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-it.mjs +31 -0
- package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ja.mjs +31 -0
- package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ko.mjs +31 -0
- package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-nl.mjs +31 -0
- package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pl.mjs +31 -0
- package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pt.mjs +31 -0
- package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +31 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-sv.mjs +31 -0
- package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-tr.mjs +31 -0
- package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-uk.mjs +31 -0
- package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-vi.mjs +31 -0
- package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh-TW.mjs +31 -0
- package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh.mjs +31 -0
- package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +9 -9
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +6 -6
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +3 -3
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +15 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +3 -3
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +15 -15
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-keyboard.mjs +3 -3
- package/fesm2022/ngwr-keyboard.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +3 -3
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-layout.mjs +15 -15
- package/fesm2022/ngwr-layout.mjs.map +1 -1
- package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -1
- package/fesm2022/ngwr-lightbox.mjs +3 -3
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +18 -7
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-list.mjs +6 -6
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +6 -6
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown-testing.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +798 -17
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-marquee-testing.mjs.map +1 -1
- package/fesm2022/ngwr-marquee.mjs +3 -3
- package/fesm2022/ngwr-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-media.mjs +3 -3
- package/fesm2022/ngwr-media.mjs.map +1 -1
- package/fesm2022/ngwr-mention-testing.mjs.map +1 -1
- package/fesm2022/ngwr-mention.mjs +6 -6
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-meta.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group-testing.mjs +7 -3
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group.mjs +26 -6
- package/fesm2022/ngwr-meter-group.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +6 -6
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-page-header.mjs +13 -5
- package/fesm2022/ngwr-page-header.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +3 -3
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +21 -21
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +9 -9
- package/fesm2022/ngwr-platform.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm-testing.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +578 -16
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-progress.mjs.map +1 -1
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-radio-testing.mjs.map +1 -1
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-radio.mjs.map +1 -1
- package/fesm2022/ngwr-rating-testing.mjs.map +1 -1
- package/fesm2022/ngwr-rating.mjs +3 -3
- package/fesm2022/ngwr-rating.mjs.map +1 -1
- package/fesm2022/ngwr-result.mjs +12 -12
- package/fesm2022/ngwr-result.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text.mjs +3 -3
- package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +3 -3
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-segmented-testing.mjs.map +1 -1
- package/fesm2022/ngwr-segmented.mjs +3 -3
- package/fesm2022/ngwr-segmented.mjs.map +1 -1
- package/fesm2022/ngwr-select-testing.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +12 -12
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +6 -6
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +10 -3
- package/fesm2022/ngwr-sidebar.mjs.map +1 -1
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-skeleton.mjs.map +1 -1
- package/fesm2022/ngwr-slider-testing.mjs.map +1 -1
- package/fesm2022/ngwr-slider.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline.mjs +84 -10
- package/fesm2022/ngwr-sparkline.mjs.map +1 -1
- package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -1
- package/fesm2022/ngwr-speed-dial.mjs +3 -3
- package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-spinner.mjs.map +1 -1
- package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -1
- package/fesm2022/ngwr-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-split-text-testing.mjs.map +1 -1
- package/fesm2022/ngwr-split-text.mjs +3 -3
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-splitter.mjs.map +1 -1
- package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -1
- package/fesm2022/ngwr-spotlight-card.mjs +6 -6
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +6 -6
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-star-border-testing.mjs.map +1 -1
- package/fesm2022/ngwr-star-border.mjs +3 -3
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-stepper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-stepper.mjs +6 -6
- package/fesm2022/ngwr-stepper.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +3 -3
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-switch-testing.mjs.map +1 -1
- package/fesm2022/ngwr-switch.mjs +3 -3
- package/fesm2022/ngwr-switch.mjs.map +1 -1
- package/fesm2022/ngwr-table-testing.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +18 -18
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-router.mjs +3 -3
- package/fesm2022/ngwr-tabs-router.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +6 -6
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea-testing.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +3 -3
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-theme.mjs +3 -3
- package/fesm2022/ngwr-theme.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card.mjs +6 -6
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-timeline.mjs.map +1 -1
- package/fesm2022/ngwr-toast-testing.mjs.map +1 -1
- package/fesm2022/ngwr-toast.mjs +9 -9
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-toolbar.mjs +10 -3
- package/fesm2022/ngwr-toolbar.mjs.map +1 -1
- package/fesm2022/ngwr-tour-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +6 -6
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer-testing.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +3 -3
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +3 -3
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter.mjs +3 -3
- package/fesm2022/ngwr-typewriter.mjs.map +1 -1
- package/fesm2022/ngwr-typography.mjs +7 -3
- package/fesm2022/ngwr-typography.mjs.map +1 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-validators.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 +3 -3
- package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-waves-testing.mjs.map +1 -1
- package/fesm2022/ngwr-waves.mjs +3 -3
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +12 -12
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/form/styles/_index.scss +16 -1
- package/gauge/styles/_index.scss +1 -0
- package/i18n/ar.json +32 -1
- package/i18n/cs.json +32 -1
- package/i18n/de.json +32 -1
- package/i18n/en.json +32 -1
- package/i18n/es.json +32 -1
- package/i18n/fr.json +32 -1
- package/i18n/he.json +32 -1
- package/i18n/hi.json +32 -1
- package/i18n/id.json +32 -1
- package/i18n/it.json +32 -1
- package/i18n/ja.json +32 -1
- package/i18n/ko.json +32 -1
- package/i18n/nl.json +32 -1
- package/i18n/pl.json +32 -1
- package/i18n/pt.json +32 -1
- package/i18n/ru.json +32 -1
- package/i18n/sv.json +32 -1
- package/i18n/tr.json +32 -1
- package/i18n/uk.json +32 -1
- package/i18n/vi.json +32 -1
- package/i18n/zh-TW.json +32 -1
- package/i18n/zh.json +32 -1
- package/input/styles/_index.scss +24 -1
- package/input-number/styles/_index.scss +69 -0
- package/input-otp/styles/_index.scss +5 -5
- package/line-chart/styles/_index.scss +30 -2
- package/llms-full.txt +14 -4
- package/llms.txt +188 -132
- package/mcp/providers.js +2 -2
- package/mcp/server.js +1 -1
- package/meter-group/styles/_index.scss +1 -0
- package/overlay/styles/_arrow.scss +30 -2
- package/package.json +43 -2
- package/page-header/styles/_index.scss +37 -1
- package/pagination/styles/_index.scss +9 -5
- package/popover/styles/_index.scss +73 -5
- package/progress/styles/_index.scss +4 -0
- package/radio/styles/_index.scss +4 -1
- package/schematics/use/symbol-map.json +1 -0
- package/select/styles/_index.scss +51 -12
- package/sidebar/styles/_index.scss +44 -2
- package/skeleton/styles/_index.scss +3 -0
- package/skills/ngwr/SKILL.md +5 -5
- package/skills/ngwr/references/catalog.md +2 -0
- package/skills/ngwr/references/setup.md +3 -3
- package/sparkline/styles/_index.scss +19 -0
- package/styles.scss +1 -0
- package/table/styles/_index.scss +7 -0
- package/textarea/styles/_index.scss +12 -3
- package/theme/styles/_colors.scss +39 -13
- package/theme/styles/_contrast.scss +9 -7
- package/toast/styles/_index.scss +4 -0
- package/toolbar/styles/_index.scss +35 -1
- package/tree/styles/_index.scss +46 -7
- package/types/ngwr-badge.d.ts +22 -7
- package/types/ngwr-bar-chart.d.ts +19 -1
- package/types/ngwr-calendar-heatmap.d.ts +29 -1
- package/types/ngwr-checkbox.d.ts +2 -0
- package/types/ngwr-config.d.ts +12 -0
- package/types/ngwr-date-picker.d.ts +308 -43
- package/types/ngwr-donut-chart.d.ts +24 -1
- package/types/ngwr-dropdown.d.ts +22 -2
- package/types/ngwr-editor-testing.d.ts +378 -0
- package/types/ngwr-editor.d.ts +449 -0
- package/types/ngwr-event-calendar.d.ts +8 -0
- package/types/ngwr-form.d.ts +35 -5
- package/types/ngwr-gauge.d.ts +20 -1
- package/types/ngwr-input-number.d.ts +7 -0
- package/types/ngwr-line-chart.d.ts +9 -1
- package/types/ngwr-markdown.d.ts +61 -2
- package/types/ngwr-meter-group-testing.d.ts +3 -2
- package/types/ngwr-meter-group.d.ts +14 -1
- package/types/ngwr-page-header.d.ts +10 -2
- package/types/ngwr-popover.d.ts +154 -3
- package/types/ngwr-sidebar.d.ts +7 -0
- package/types/ngwr-sparkline.d.ts +43 -1
- package/types/ngwr-toolbar.d.ts +7 -0
- package/types/ngwr-typography.d.ts +4 -0
- package/types/ngwr-version.d.ts +1 -1
- package/typography/styles/_index.scss +16 -0
|
@@ -119,8 +119,8 @@ class WrTabsRouting {
|
|
|
119
119
|
const segments = typeof commands === 'string' ? [commands] : [...commands];
|
|
120
120
|
return this.router.createUrlTree(segments, { relativeTo: this.route });
|
|
121
121
|
}
|
|
122
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
123
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.
|
|
122
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTabsRouting, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
123
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: WrTabsRouting, isStandalone: true, selector: "[wrTabsRouting]", providers: [
|
|
124
124
|
{
|
|
125
125
|
provide: WR_TABS_ROUTING,
|
|
126
126
|
// eslint-disable-next-line @angular-eslint/no-forward-ref
|
|
@@ -128,7 +128,7 @@ class WrTabsRouting {
|
|
|
128
128
|
},
|
|
129
129
|
], exportAs: ["wrTabsRouting"], ngImport: i0 });
|
|
130
130
|
}
|
|
131
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
131
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTabsRouting, decorators: [{
|
|
132
132
|
type: Directive,
|
|
133
133
|
args: [{
|
|
134
134
|
selector: '[wrTabsRouting]',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tabs-router.mjs","sources":["../../../projects/lib/tabs/router/tabs-routing.ts","../../../projects/lib/tabs/router/public-api.ts","../../../projects/lib/tabs/router/ngwr-tabs-router.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 { LocationStrategy } from '@angular/common';\nimport { Directive, forwardRef, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute, type IsActiveMatchOptions, NavigationEnd, Router, type UrlTree } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { WR_TABS_ROUTING, type WrTabsRoutingAdapter } from 'ngwr/tabs';\n\n/**\n * How `routerLinkActive` matches when no `[routerLinkActiveOptions]` is given:\n * a path PREFIX, so `/settings/profile` still selects a `/settings` tab, and a\n * fragment or a matrix param never unselects one.\n */\nconst MATCH: IsActiveMatchOptions = {\n paths: 'subset',\n queryParams: 'subset',\n fragment: 'ignored',\n matrixParams: 'ignored',\n};\n\n/**\n * Router support for `<wr-tabs>`. Add it to the `imports` of the component that\n * declares a strip whose `<wr-tab>`s carry a `routerLink`:\n *\n * @example\n * ```ts\n * import { WrTab, WrTabs } from 'ngwr/tabs';\n * import { WrTabsRouting } from 'ngwr/tabs/router';\n *\n * @Component({ imports: [WrTabs, WrTab, WrTabsRouting], template: `\n * <wr-tabs wrTabsRouting>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ` })\n * export class SettingsPage {}\n * ```\n *\n * Two mechanical edits per strip, both codemoddable: the symbol in `imports`,\n * and `wrTabsRouting` on the element. An attribute rather than a selector on\n * `wr-tabs` itself, for two reasons — where the router enters a template should\n * be visible in that template, and the sandbox's selector map keys a `wr-` tag\n * even when it is qualified, so `wr-tabs[wrTabsRouting]` would be a second claim\n * on the `wr-tabs` tag. Putting it on an ANCESTOR of the strip also works: the\n * token is read through the element injector, which walks up.\n *\n * **Why it is a separate entry point.** A directive listed in `imports: []`\n * lands in the compiled component's `dependencies` whether or not the branch of\n * the template that matches it ever runs, so `RouterLink` + `RouterLinkActive`\n * on the strip charged every CONTENT tab strip 73.9 kB of `@angular/router` —\n * 51% of what `ngwr/tabs` cost in an app that declares no routes. Measured with\n * source-map attribution against a routerless baseline. `@angular/router` is\n * already an optional peer dependency of the package; this is what makes that\n * true in bytes.\n *\n * **Why a directive rather than a provider function.** A relative `routerLink`\n * (`routerLink=\"overview\"`) resolves against the `ActivatedRoute` of the\n * component that declares the strip. A root-level provider would resolve every\n * one of them against the root route instead, and quietly build the wrong\n * hrefs; a directive on the element reads the same `ActivatedRoute` the old\n * `RouterLink` inside the strip's template did.\n */\n@Directive({\n selector: '[wrTabsRouting]',\n exportAs: 'wrTabsRouting',\n providers: [\n {\n provide: WR_TABS_ROUTING,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabsRouting),\n },\n ],\n})\nexport class WrTabsRouting implements WrTabsRoutingAdapter {\n private readonly router = inject(Router);\n private readonly locationStrategy = inject(LocationStrategy);\n private readonly route = inject(ActivatedRoute);\n\n /**\n * The current URL, as a signal, so `href()` and `isActive()` are reactive to\n * navigation when read from a computed. `isActive` obviously has to be; so\n * does `href`, because a RELATIVE link is resolved against the active route\n * and its target moves when the route does — which is why `RouterLink`\n * recomputes its own href on every navigation too.\n */\n private readonly url = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n href(commands: string | readonly string[]): string {\n // Read for the dependency, the same reason `isActive` does: a RELATIVE link\n // resolves against the active route, so its target moves when the route\n // does. Without this a strip inside `/users/:id` kept rendering\n // `/users/1/profile` after navigating to user 2 — the click still routed,\n // because `navigate()` rebuilds the tree, but the visible href pointed at\n // the previous user. The docblock above already promised this; the code did\n // not do it.\n this.url();\n\n // `prepareExternalUrl`, not a bare `serializeUrl`, because that is what\n // `RouterLink` writes and the href exists precisely so the BROWSER can use\n // it. Under `withHashLocation()` a serialized `/one` renders as an href the\n // app does not serve: a plain click survives (`navigateByUrl` ignores the\n // strategy and `preventDefault` stops the bad navigation) but cmd-click,\n // middle-click, \"open in new tab\" and \"copy link address\" all take the href\n // at face value — the four things rendering one is for. A `--base-href` is\n // the same defect by another door.\n return this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.tree(commands)));\n }\n\n isActive(commands: string | readonly string[]): boolean {\n // Read for the dependency, not for the value: `isActive` asks the router,\n // which is not a signal, so without this the computed that calls it would\n // never recompute and the strip would announce its first selection forever.\n this.url();\n return this.router.isActive(this.tree(commands), MATCH);\n }\n\n navigate(commands: string | readonly string[], event: MouseEvent): void {\n // Exactly the guard `RouterLink` applies: a middle click, or any click with\n // a modifier held, belongs to the browser — preventing it would make a tab\n // the one link on the page that cannot be opened in a new window.\n if (event.button !== 0 || event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) return;\n event.preventDefault();\n void this.router.navigateByUrl(this.tree(commands));\n }\n\n private tree(commands: string | readonly string[]): UrlTree {\n // A bare string is one command, the way `[routerLink]=\"'overview'\"` is —\n // spread as characters it would navigate to `/o/v/e/r/v/i/e/w`.\n const segments = typeof commands === 'string' ? [commands] : [...commands];\n return this.router.createUrlTree(segments, { relativeTo: this.route });\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\nexport { WrTabsRouting } from './tabs-routing';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;AAAA;;;;;AAKG;AAWH;;;;AAIG;AACH,MAAM,KAAK,GAAyB;AAClC,IAAA,KAAK,EAAE,QAAQ;AACf,IAAA,WAAW,EAAE,QAAQ;AACrB,IAAA,QAAQ,EAAE,SAAS;AACnB,IAAA,YAAY,EAAE,SAAS;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CG;MAYU,aAAa,CAAA;AACP,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AACvB,IAAA,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC3C,IAAA,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC;AAE/C;;;;;;AAMG;IACc,GAAG,GAAG,QAAQ,CAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CACrB,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,YAAY,aAAa,CAAC,EAC7D,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAC7B,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAC3B,EACD,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAClC;AAED,IAAA,IAAI,CAAC,QAAoC,EAAA;;;;;;;;QAQvC,IAAI,CAAC,GAAG,EAAE;;;;;;;;;QAUV,OAAO,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChG;AAEA,IAAA,QAAQ,CAAC,QAAoC,EAAA;;;;QAI3C,IAAI,CAAC,GAAG,EAAE;AACV,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IACzD;IAEA,QAAQ,CAAC,QAAoC,EAAE,KAAiB,EAAA;;;;QAI9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO;YAAE;QAC5F,KAAK,CAAC,cAAc,EAAE;AACtB,QAAA,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD;AAEQ,IAAA,IAAI,CAAC,QAAoC,EAAA;;;AAG/C,QAAA,MAAM,QAAQ,GAAG,OAAO,QAAQ,KAAK,QAAQ,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;AAC1E,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACxE;uGAhEW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,SAAA,EARb;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,eAAe;;AAExB,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,aAAa,CAAC;AAC7C,aAAA;AACF,SAAA,EAAA,QAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAEU,aAAa,EAAA,UAAA,EAAA,CAAA;kBAXzB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,QAAQ,EAAE,eAAe;AACzB,oBAAA,SAAS,EAAE;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,eAAe;;AAExB,4BAAA,WAAW,EAAE,UAAU,CAAC,mBAAmB,CAAC;AAC7C,yBAAA;AACF,qBAAA;AACF,iBAAA;;;ACjFD;;;;;AAKG;;ACLH;;AAEG
|
|
1
|
+
{"version":3,"file":"ngwr-tabs-router.mjs","sources":["../../../projects/lib/tabs/router/tabs-routing.ts","../../../projects/lib/tabs/router/public-api.ts","../../../projects/lib/tabs/router/ngwr-tabs-router.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 { LocationStrategy } from '@angular/common';\nimport { Directive, forwardRef, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute, type IsActiveMatchOptions, NavigationEnd, Router, type UrlTree } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { WR_TABS_ROUTING, type WrTabsRoutingAdapter } from 'ngwr/tabs';\n\n/**\n * How `routerLinkActive` matches when no `[routerLinkActiveOptions]` is given:\n * a path PREFIX, so `/settings/profile` still selects a `/settings` tab, and a\n * fragment or a matrix param never unselects one.\n */\nconst MATCH: IsActiveMatchOptions = {\n paths: 'subset',\n queryParams: 'subset',\n fragment: 'ignored',\n matrixParams: 'ignored',\n};\n\n/**\n * Router support for `<wr-tabs>`. Add it to the `imports` of the component that\n * declares a strip whose `<wr-tab>`s carry a `routerLink`:\n *\n * @example\n * ```ts\n * import { WrTab, WrTabs } from 'ngwr/tabs';\n * import { WrTabsRouting } from 'ngwr/tabs/router';\n *\n * @Component({ imports: [WrTabs, WrTab, WrTabsRouting], template: `\n * <wr-tabs wrTabsRouting>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ` })\n * export class SettingsPage {}\n * ```\n *\n * Two mechanical edits per strip, both codemoddable: the symbol in `imports`,\n * and `wrTabsRouting` on the element. An attribute rather than a selector on\n * `wr-tabs` itself, for two reasons — where the router enters a template should\n * be visible in that template, and the sandbox's selector map keys a `wr-` tag\n * even when it is qualified, so `wr-tabs[wrTabsRouting]` would be a second claim\n * on the `wr-tabs` tag. Putting it on an ANCESTOR of the strip also works: the\n * token is read through the element injector, which walks up.\n *\n * **Why it is a separate entry point.** A directive listed in `imports: []`\n * lands in the compiled component's `dependencies` whether or not the branch of\n * the template that matches it ever runs, so `RouterLink` + `RouterLinkActive`\n * on the strip charged every CONTENT tab strip 73.9 kB of `@angular/router` —\n * 51% of what `ngwr/tabs` cost in an app that declares no routes. Measured with\n * source-map attribution against a routerless baseline. `@angular/router` is\n * already an optional peer dependency of the package; this is what makes that\n * true in bytes.\n *\n * **Why a directive rather than a provider function.** A relative `routerLink`\n * (`routerLink=\"overview\"`) resolves against the `ActivatedRoute` of the\n * component that declares the strip. A root-level provider would resolve every\n * one of them against the root route instead, and quietly build the wrong\n * hrefs; a directive on the element reads the same `ActivatedRoute` the old\n * `RouterLink` inside the strip's template did.\n */\n@Directive({\n selector: '[wrTabsRouting]',\n exportAs: 'wrTabsRouting',\n providers: [\n {\n provide: WR_TABS_ROUTING,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabsRouting),\n },\n ],\n})\nexport class WrTabsRouting implements WrTabsRoutingAdapter {\n private readonly router = inject(Router);\n private readonly locationStrategy = inject(LocationStrategy);\n private readonly route = inject(ActivatedRoute);\n\n /**\n * The current URL, as a signal, so `href()` and `isActive()` are reactive to\n * navigation when read from a computed. `isActive` obviously has to be; so\n * does `href`, because a RELATIVE link is resolved against the active route\n * and its target moves when the route does — which is why `RouterLink`\n * recomputes its own href on every navigation too.\n */\n private readonly url = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n href(commands: string | readonly string[]): string {\n // Read for the dependency, the same reason `isActive` does: a RELATIVE link\n // resolves against the active route, so its target moves when the route\n // does. Without this a strip inside `/users/:id` kept rendering\n // `/users/1/profile` after navigating to user 2 — the click still routed,\n // because `navigate()` rebuilds the tree, but the visible href pointed at\n // the previous user. The docblock above already promised this; the code did\n // not do it.\n this.url();\n\n // `prepareExternalUrl`, not a bare `serializeUrl`, because that is what\n // `RouterLink` writes and the href exists precisely so the BROWSER can use\n // it. Under `withHashLocation()` a serialized `/one` renders as an href the\n // app does not serve: a plain click survives (`navigateByUrl` ignores the\n // strategy and `preventDefault` stops the bad navigation) but cmd-click,\n // middle-click, \"open in new tab\" and \"copy link address\" all take the href\n // at face value — the four things rendering one is for. A `--base-href` is\n // the same defect by another door.\n return this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.tree(commands)));\n }\n\n isActive(commands: string | readonly string[]): boolean {\n // Read for the dependency, not for the value: `isActive` asks the router,\n // which is not a signal, so without this the computed that calls it would\n // never recompute and the strip would announce its first selection forever.\n this.url();\n return this.router.isActive(this.tree(commands), MATCH);\n }\n\n navigate(commands: string | readonly string[], event: MouseEvent): void {\n // Exactly the guard `RouterLink` applies: a middle click, or any click with\n // a modifier held, belongs to the browser — preventing it would make a tab\n // the one link on the page that cannot be opened in a new window.\n if (event.button !== 0 || event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) return;\n event.preventDefault();\n void this.router.navigateByUrl(this.tree(commands));\n }\n\n private tree(commands: string | readonly string[]): UrlTree {\n // A bare string is one command, the way `[routerLink]=\"'overview'\"` is —\n // spread as characters it would navigate to `/o/v/e/r/v/i/e/w`.\n const segments = typeof commands === 'string' ? [commands] : [...commands];\n return this.router.createUrlTree(segments, { relativeTo: this.route });\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\nexport { WrTabsRouting } from './tabs-routing';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tabs-testing.mjs","sources":["../../../projects/lib/tabs/testing/wr-tab-harness.ts","../../../projects/lib/tabs/testing/wr-tabs-harness.ts","../../../projects/lib/tabs/testing/ngwr-tabs-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 {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrTabHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one tab of a `<wr-tabs>` strip — the header, and the panel it\n * controls.\n *\n * Its host is the RENDERED HEADER, not the `<wr-tab>` element. A `<wr-tab>` is\n * `display:none` and holds nothing but a `<ng-template>`: the label, the selection\n * state, the tab stop and the click target all live on the element the parent\n * renders for it, which is a `<button role=\"tab\">` in content mode and an\n * `<a role=\"tab\">` in router mode. A harness hosted on `wr-tab` would answer every\n * question with nothing.\n *\n * Also a CONTENT CONTAINER, scoped to the panel this tab controls, so\n * `tab.getHarness(WrButtonHarness…)` reaches the consumer's own components inside\n * the tab's content. The panel is not a descendant of the header — it is a sibling\n * of the whole strip — so it is resolved through the `aria-controls` id, which is\n * also the pairing {@link isPanelBound} checks. Only the SELECTED tab has a panel\n * to reach: `<wr-tabs>` renders one panel and destroys it on every switch.\n *\n * @example\n * ```ts\n * const tabs = await loader.getHarness(WrTabsHarness);\n * const [first] = await tabs.getTabs();\n *\n * expect(await first.isSelected()).toBe(true);\n * expect(await first.isPanelBound()).toBe(true);\n * await (await first.getHarness(WrButtonHarness.with({ text: 'Save' }))).click();\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTabHarness extends ContentContainerComponentHarness {\n /** The header the strip renders for a `<wr-tab>` — a `<button>`, or an `<a>` in router mode. */\n static hostSelector = '.wr-tabs__tab';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTabHarnessFilters = {}): HarnessPredicate<WrTabHarness> {\n return new HarnessPredicate(WrTabHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('selected', options.selected, async (harness, selected) => (await harness.isSelected()) === selected)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The tab's visible label, trimmed — the `<wr-tab>`'s `title`. */\n async getLabel(): Promise<string> {\n return (await this.host()).text();\n }\n\n /**\n * The header's own id, which is what the panel points back at with\n * `aria-labelledby` — the tab is the panel's accessible name.\n *\n * Generated from the tab's `key`, so two strips on one page whose tabs share keys\n * publish the same ids and each panel then names whichever header came first.\n */\n async getId(): Promise<string | null> {\n return (await this.host()).getAttribute('id');\n }\n\n /**\n * Whether this is the strip's selected tab.\n *\n * `aria-selected` first, because that is the state a screen reader acts on and\n * the `--active` class is only what it looks like. Both shapes carry it now — a\n * router tab publishes it off the same `routerActive()` that paints the class —\n * so the class is a fallback for a tab that has neither yet, not a second reading\n * of the same thing. The order is not interchangeable: reading the class first\n * would report a tab as selected while `aria-selected=\"false\"` told assistive tech\n * otherwise.\n */\n async isSelected(): Promise<boolean> {\n const host = await this.host();\n const selected = await host.getAttribute('aria-selected');\n if (selected !== null) return selected === 'true';\n return host.hasClass('wr-tabs__tab--active');\n }\n\n /**\n * Whether the tab refuses activation.\n *\n * The two shapes say it differently and both are read: a content tab is a real\n * `<button>` and carries the native `disabled`, a router tab is an `<a>` — which\n * cannot be disabled natively — and carries `aria-disabled` with `tabindex=\"-1\"`.\n * The `--disabled` class is on both and is deliberately not the source: a class\n * says how the tab looks, these two say what it refuses.\n */\n async isDisabled(): Promise<boolean> {\n const host = await this.host();\n if ((await host.getAttribute('aria-disabled')) === 'true') return true;\n return (await host.getProperty<boolean | undefined>('disabled')) === true;\n }\n\n /** Whether this tab is a link — the router shape, where selection follows the route. */\n async isLink(): Promise<boolean> {\n return (await this.host()).matchesSelector('a');\n }\n\n /**\n * The resolved href of a router tab, or `null` for a content tab.\n *\n * `null` is the answer for a `<button>` rather than a gap — a content tab\n * navigates nowhere, it swaps a panel.\n */\n async getHref(): Promise<string | null> {\n return (await this.host()).getAttribute('href');\n }\n\n /**\n * Whether a Tab press can land on this header.\n *\n * A content strip is ONE tab stop and hands it to the selected tab; a router\n * strip makes every enabled tab a stop of its own. See\n * `WrTabsHarness.getTabStopLabels()` — the difference is a mode difference, not\n * a rounding error.\n *\n * A non-negative `tabindex` is not enough on its own, which is why `disabled` is\n * read too: `<wr-tabs>` gives the roving `tabindex=\"0\"` to whatever `active`\n * names without checking whether that tab is disabled, so a host binding `active`\n * to a disabled tab's key renders a `<button disabled tabindex=\"0\">` — which no\n * Tab press reaches. jsdom will focus such a button anyway, so this is the only\n * place a unit test can tell the difference.\n */\n async isTabStop(): Promise<boolean> {\n if (await this.isDisabled()) return false;\n const tabIndex = await (await this.host()).getAttribute('tabindex');\n return Number.parseInt(tabIndex ?? '-1', 10) >= 0;\n }\n\n /**\n * The id of the panel this tab controls, or `null` when it controls none.\n *\n * `null` for a router tab — the strip renders no panel in that mode, the consumer\n * drops a `<router-outlet>` after it — and that missing `aria-controls` is the\n * honest report: there is nothing for the header to point at.\n */\n async getPanelId(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-controls');\n }\n\n /** Whether this tab's panel is in the DOM right now — true only for the selected tab. */\n async hasPanel(): Promise<boolean> {\n return (await this.panelOrNull()) !== null;\n }\n\n /**\n * Whether the panel and this header really name each other.\n *\n * Both halves, because each covers a different failure: the header's\n * `aria-controls` has to reach the panel that is showing, and the panel's\n * `aria-labelledby` has to come back to THIS header. A panel wired to the wrong\n * tab looks perfect on screen — the right content is under the right header — and\n * announces the wrong name, which is the whole of a tabbed interface for anyone\n * not looking at it. It also has to be a `tabpanel`: the reference resolving to\n * some other element is a broken pairing, not a styling detail.\n *\n * Throws when there is no panel at all, which is a different fact from a broken\n * pairing — see {@link getPanelText}.\n */\n async isPanelBound(): Promise<boolean> {\n const panel = await this.panelOrThrow('isPanelBound');\n if ((await panel.getAttribute('role')) !== 'tabpanel') return false;\n return (await panel.getAttribute('aria-labelledby')) === (await this.getId());\n }\n\n /**\n * The text of this tab's panel, trimmed.\n *\n * Throws rather than answering `''` for a tab whose panel is not rendered, and\n * the message says which of the two reasons it is: `<wr-tabs>` keeps only the\n * selected tab's panel in the DOM (the content of every other tab sits in an\n * unrendered `<ng-template>`, so switching tabs destroys a panel and builds the\n * next one), and a router strip renders no panel at all.\n */\n async getPanelText(): Promise<string> {\n return (await this.panelOrThrow('getPanelText')).text();\n }\n\n /**\n * Activate this tab the way a pointer does.\n *\n * A disabled CONTENT tab is left alone: it is a real `<button disabled>`, which\n * fires no click at all. A disabled ROUTER tab is an `<a>`, where `disabled` is\n * not a thing — it carries `aria-disabled=\"true\"` and `tabindex=\"-1\"` to stay off\n * the keyboard path, renders NO href, and the component drops the click rather\n * than routing it. So the click lands and nothing moves, which is why a router\n * strip's expectations belong on the resolved route rather than on the click.\n */\n async click(): Promise<void> {\n return (await this.host()).click();\n }\n\n /** Move keyboard focus onto this header. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Take focus off this header. */\n async blur(): Promise<void> {\n return (await this.host()).blur();\n }\n\n /** Whether this header holds keyboard focus — the roving cursor, not the selection. */\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\n }\n\n /** `getHarness(…)` searches this tab's PANEL, which is where its content is. */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n // Both checks, in this order, so the failure names the reason — no panel in this\n // mode, or a panel that is not the one showing — instead of the CDK's generic\n // \"no element matching selector\".\n const id = await this.panelIdOrThrow('getHarness');\n await this.panelOrThrow('getHarness');\n return this.documentRootLocatorFactory().harnessLoaderFor(WrTabHarness.byId(id));\n }\n\n /** The panel element, or `null` when this tab has none rendered. */\n private async panelOrNull(): Promise<TestElement | null> {\n const id = await this.getPanelId();\n if (id === null) return null;\n // From the document root: the panel is a sibling of the strip, not a descendant\n // of the header this harness is hosted on.\n return this.documentRootLocatorFactory().locatorForOptional(WrTabHarness.byId(id))();\n }\n\n /** The panel element, or a failure naming which of the two reasons there isn't one. */\n private async panelOrThrow(method: string): Promise<TestElement> {\n await this.panelIdOrThrow(method);\n const panel = await this.panelOrNull();\n if (!panel) {\n throw new Error(\n `WrTabHarness.${method}(): the panel for \"${await this.getLabel()}\" is not rendered. <wr-tabs> keeps ` +\n 'only the SELECTED panel in the DOM — the content of every other tab waits in an unrendered ' +\n '<ng-template> — so select the tab first.'\n );\n }\n return panel;\n }\n\n /** This tab's `aria-controls`, or a failure saying a router strip has no panels. */\n private async panelIdOrThrow(method: string): Promise<string> {\n const id = await this.getPanelId();\n if (id === null) {\n throw new Error(\n `WrTabHarness.${method}(): \"${await this.getLabel()}\" controls no panel. A router strip renders none ` +\n '— its tabs navigate and the consumer drops a <router-outlet> after the strip — so assert the ' +\n 'routed content instead.'\n );\n }\n return id;\n }\n\n /**\n * An id selector that survives the id it is given.\n *\n * `[id=\"…\"]` rather than `#…`: the ids here are built from the consumer's `key`,\n * and a key holding a dot, a colon or a leading digit turns `#id` into a selector\n * that either throws or matches something else entirely.\n */\n private static byId(id: string): string {\n return `[id=\"${id}\"]`;\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 {\n ComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n TestKey,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrTabHarnessFilters, WrTabsHarnessFilters, WrTabsHarnessSize } from './interfaces';\nimport { WrTabHarness } from './wr-tab-harness';\n\n/**\n * Test harness for `<wr-tabs>`, with {@link WrTabHarness} for one tab and the panel\n * it controls.\n *\n * The component has two modes and they answer \"which tab is active\" differently,\n * which is most of what this harness exists to keep straight:\n *\n * - **Content** — the tabs are `<button role=\"tab\">`s, the strip is ONE tab stop\n * (the roving `tabindex` sits on the selected tab), and the arrow keys ACTIVATE\n * the tab they move to, the APG's automatic-activation pattern. `<wr-tabs>`\n * renders exactly one panel: the selected tab's.\n * - **Router** — one `routerLink` on any child switches the whole strip over. The\n * tabs become `<a role=\"tab\">`s whose `aria-selected` follows the resolved route\n * (the same `routerActive()` that paints `--active`), the strip renders NO\n * panel, and it stops being one tab stop — every enabled tab is a Tab stop of its\n * own. The arrow keys still move focus, but they select nothing: the route does\n * that.\n *\n * So {@link getSelectedLabel} and {@link getFocusedLabel} are two different\n * questions, and {@link getTabStopLabels} is a third. They coincide in content mode\n * and come apart in router mode; a spec that asserts one and means another passes\n * through the bug.\n *\n * Everything here is driven from the KEYBOARD rather than by clicking a coordinate:\n * a unit test has no layout, so a hit test would answer with nothing, and the\n * keyboard is the accessible path anyway. {@link select} is the exception — a\n * no-arg click needs no coordinate.\n *\n * @example\n * ```ts\n * const tabs = await loader.getHarness(WrTabsHarness);\n *\n * expect(await tabs.getTabLabels()).toEqual(['One', 'Two', 'Three']);\n * await tabs.select({ label: 'Two' });\n * expect(await tabs.getPanelText()).toContain('Second panel');\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTabsHarness extends ComponentHarness {\n static hostSelector = 'wr-tabs';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTabsHarnessFilters = {}): HarnessPredicate<WrTabsHarness> {\n return new HarnessPredicate(WrTabsHarness, options)\n .addOption('tabLabel', options.tabLabel, async (harness, label) => {\n for (const tab of await harness.getTabLabels()) {\n if (await HarnessPredicate.stringMatches(tab, label)) return true;\n }\n return false;\n })\n .addOption('selectedLabel', options.selectedLabel, async (harness, label) => {\n const selected = await harness.getSelectedLabel();\n return selected === null ? false : HarnessPredicate.stringMatches(selected, label);\n })\n .addOption('size', options.size, async (harness, size) => (await harness.getSize()) === size)\n .addOption('router', options.router, async (harness, router) => (await harness.isRouterMode()) === router);\n }\n\n /**\n * The role the strip announces — `tablist`.\n *\n * Read off the strip, not the host: `<wr-tabs>` carries the `wr-tabs` class and no\n * role of its own, and the `tablist` is the scrolling `.wr-tabs__strip` inside it.\n * Without that role the headers are a row of buttons: nothing tells a screen\n * reader how many tabs there are or which one of them is showing.\n */\n async getRole(): Promise<string | null> {\n return (await this.strip()).getAttribute('role');\n }\n\n /**\n * The axis the strip announces, which decides which arrow keys a screen-reader\n * user is told to press.\n *\n * `<wr-tabs>` ships one orientation — the strip is a horizontal scroller and hard\n * codes `aria-orientation=\"horizontal\"` — so today this always answers\n * `'horizontal'`. It is read from the DOM and normalized the way ARIA reads it:\n * anything that is not `vertical`, a missing attribute included, is `'horizontal'`,\n * the default for a `tablist`.\n *\n * Which means an assertion here catches a strip that starts ANNOUNCING `vertical`\n * while its arrow keys still walk the horizontal axis — and cannot catch the\n * mirror image, a vertical mode that forgets to update the attribute: that one\n * answers `'horizontal'` and looks unchanged. One attribute is all this reads.\n */\n async getOrientation(): Promise<'horizontal' | 'vertical'> {\n return (await (await this.strip()).getAttribute('aria-orientation')) === 'vertical' ? 'vertical' : 'horizontal';\n }\n\n /**\n * The size the strip renders at, from the host modifier.\n *\n * `md` is the default and paints no modifier of its own, so its absence is the\n * answer rather than a missing one.\n */\n async getSize(): Promise<WrTabsHarnessSize> {\n const host = await this.host();\n if (await host.hasClass('wr-tabs--sm')) return 'sm';\n if (await host.hasClass('wr-tabs--lg')) return 'lg';\n return 'md';\n }\n\n /**\n * Whether this strip navigates instead of swapping panels.\n *\n * Derived from what got RENDERED — a router tab is an `<a>` — rather than from the\n * `routerLink` inputs, because the switch is all-or-nothing: one `routerLink`\n * anywhere in the strip turns every tab into a link and drops the panel. A strip\n * with no tabs at all is content mode, which is what the component does with it.\n */\n async isRouterMode(): Promise<boolean> {\n const tabs = await this.getTabs();\n for (const tab of tabs) {\n if (await tab.isLink()) return true;\n }\n return false;\n }\n\n /**\n * This strip's tabs, in render order.\n *\n * Scoped to this strip rather than to the host, which is what keeps a `<wr-tabs>`\n * NESTED in a panel from answering for its parent: the inner strip is a descendant\n * of the outer host, so a host-wide query would hand back both sets of tabs as one\n * flat list.\n */\n async getTabs(filters: WrTabHarnessFilters = {}): Promise<WrTabHarness[]> {\n return (await this.stripLoader()).getAllHarnesses(WrTabHarness.with(filters));\n }\n\n /** The label of every tab, in render order. */\n async getTabLabels(): Promise<string[]> {\n const tabs = await this.getTabs();\n return Promise.all(tabs.map(tab => tab.getLabel()));\n }\n\n /**\n * The selected tab, or `null` when none is.\n *\n * A content strip always has one — it falls back to the first tab when `active`\n * matches nothing — so `null` means a router strip whose current route matches no\n * tab, or a strip with no tabs.\n */\n async getSelectedTab(): Promise<WrTabHarness | null> {\n const [selected] = await this.getTabs({ selected: true });\n return selected ?? null;\n }\n\n /** The label of the selected tab, or `null` when none is selected. */\n async getSelectedLabel(): Promise<string | null> {\n const selected = await this.getSelectedTab();\n return selected ? selected.getLabel() : null;\n }\n\n /**\n * Select the first tab matching the filters, the way a pointer does.\n *\n * Throws when nothing matches, naming the tabs that do exist, and throws again\n * when the tab is still unselected afterwards — with both causes named, because a\n * click that quietly did nothing surfaces as an unrelated assertion three lines\n * later. Those causes are a disabled CONTENT tab (a `<button disabled>` never sees\n * the click at all) and a router strip whose navigation did not land on this tab.\n *\n * A router strip is otherwise fine here: the click starts a route change, the\n * harness stabilizes the pending navigation before it re-reads the strip, and\n * `routerActive()` has painted the selection by then — so this resolves. It is a\n * guard, a redirect or an unmatched path that leaves the tab unselected. A\n * DISABLED router tab is a different shape and no longer navigates: it renders\n * no href and the component drops the click, so the click lands and nothing\n * moves — see {@link WrTabHarness.click}.\n */\n async select(filters: WrTabHarnessFilters): Promise<void> {\n const [tab] = await this.getTabs(filters);\n if (!tab) {\n const labels = await this.getTabLabels();\n const offered =\n labels.length > 0 ? `This strip offers: ${labels.join(', ')}.` : 'This strip renders no tabs at all.';\n throw new Error(`WrTabsHarness.select(): no tab matched ${JSON.stringify(filters)}. ${offered}`);\n }\n await this.activate(tab, 'select');\n }\n\n /**\n * Select the tab at this position in render order, counting from 0.\n *\n * Throws on an index the strip does not have, naming its length: a silent no-op\n * there reads as \"the tab did not react\" when the tab was never addressed.\n */\n async selectByIndex(index: number): Promise<void> {\n const tabs = await this.getTabs();\n const tab = tabs[index];\n if (!tab) {\n throw new Error(\n `WrTabsHarness.selectByIndex(): this strip has ${tabs.length} tab(s), so index ${index} is out of ` +\n 'range — they are numbered from 0 in render order.'\n );\n }\n await this.activate(tab, 'selectByIndex');\n }\n\n /**\n * The labels of every tab a Tab press can land on, in render order.\n *\n * A LIST rather than one label, because the count is the mode: a content strip\n * roves a single `tabindex=\"0\"` and answers with exactly one label — the selected\n * tab — while a router strip makes every enabled tab its own stop and answers with\n * all of them. Either way a disabled tab is never in here.\n */\n async getTabStopLabels(): Promise<string[]> {\n const labels: string[] = [];\n for (const tab of await this.getTabs()) {\n if (await tab.isTabStop()) labels.push(await tab.getLabel());\n }\n return labels;\n }\n\n /**\n * Whether the strip is a single tab stop — the roving-`tabindex` pattern the APG\n * asks a `tablist` for.\n *\n * True for a content strip, false for a router one, which puts every enabled tab\n * in the page tab order. A strip whose every tab is disabled has no stop at all\n * and answers `true`: nothing to rove, and Tab skips it.\n */\n async isRoving(): Promise<boolean> {\n return (await this.getTabStopLabels()).length <= 1;\n }\n\n /**\n * The label of the tab holding keyboard focus, or `null` when focus is elsewhere.\n *\n * The other answer to \"which tab is active\", and not the same as\n * {@link getSelectedLabel}: the arrow keys move this. In content mode they also\n * select what they land on, so the two stay together; in router mode focus walks\n * the strip while the selection stays on whatever the route resolved to.\n */\n async getFocusedLabel(): Promise<string | null> {\n for (const tab of await this.getTabs()) {\n if (await tab.isFocused()) return tab.getLabel();\n }\n return null;\n }\n\n /**\n * Put focus where a Tab press would put it — the first tab stop in render order.\n *\n * That is the selected tab in content mode, and the first enabled tab in a router\n * strip. Throws when the strip has no stop at all, because Tab then skips past it\n * rather than entering it — a content strip whose selected tab is disabled has\n * none, and neither has one whose every tab is disabled, or one with no tabs.\n */\n async focusTabStop(): Promise<void> {\n for (const tab of await this.getTabs()) {\n if (await tab.isTabStop()) {\n await tab.focus();\n return;\n }\n }\n\n throw new Error(\n 'WrTabsHarness.focusTabStop(): this strip has no tab stop — its selected tab is disabled, every tab ' +\n 'is disabled, or it renders no tabs — so Tab skips it rather than entering it.'\n );\n }\n\n /**\n * ArrowRight on the strip — one tab toward the visual RIGHT, wrapping at the end\n * and skipping disabled tabs.\n *\n * The physical key, because that is what a keyboard has. The component follows\n * visual order per the APG, so under `dir=\"rtl\"` this moves to the PREVIOUS tab —\n * a spec exercising a mirrored strip swaps this and {@link pressArrowLeft}.\n *\n * In content mode the tab it lands on is also SELECTED (automatic activation); in\n * router mode only focus moves.\n */\n async pressArrowRight(): Promise<void> {\n await this.press(TestKey.RIGHT_ARROW);\n }\n\n /** ArrowLeft on the strip — one tab toward the visual LEFT, mirrored under `dir=\"rtl\"`. */\n async pressArrowLeft(): Promise<void> {\n await this.press(TestKey.LEFT_ARROW);\n }\n\n /**\n * Home on the strip — the FIRST enabled tab.\n *\n * Home and End name a position in the tab list rather than an edge of the strip,\n * so unlike the arrows they read the same in both reading directions.\n */\n async pressHome(): Promise<void> {\n await this.press(TestKey.HOME);\n }\n\n /** End on the strip — the LAST enabled tab, in both reading directions. */\n async pressEnd(): Promise<void> {\n await this.press(TestKey.END);\n }\n\n /** Whether this strip is showing a panel at all — false for a router strip. */\n async hasPanel(): Promise<boolean> {\n const selected = await this.getSelectedTab();\n return selected === null ? false : selected.hasPanel();\n }\n\n /**\n * The text of the panel that is showing, trimmed — the selected tab's content, and\n * the only tab content in the DOM.\n *\n * Throws when nothing is selected. Delegates to the selected tab, so the panel it\n * reads is the one that tab's `aria-controls` points at rather than \"the first\n * `.wr-tabs__panel` around\" — the two are only the same page when the wiring is\n * right, and {@link WrTabHarness.isPanelBound} is what says whether it is.\n */\n async getPanelText(): Promise<string> {\n const selected = await this.getSelectedTab();\n if (!selected) {\n throw new Error(\n 'WrTabsHarness.getPanelText(): no tab is selected, so no panel is rendered. A router strip whose ' +\n 'route matches none of its tabs looks like this, as does a strip with no tabs.'\n );\n }\n return selected.getPanelText();\n }\n\n /**\n * Which edges of the strip are faded out, from the public `--fade-start` /\n * `--fade-end` host modifiers — the cue that there are more tabs than fit, on the\n * side they are on.\n *\n * The component computes these from the strip's scroll metrics, so in a unit test\n * both are always `false`: jsdom lays nothing out, every element measures 0x0, and\n * a strip that never overflows never fades. Assert this in a real browser, or\n * declare the metrics and dispatch a `scroll` the way the component's own spec\n * does.\n */\n async getFades(): Promise<{ start: boolean; end: boolean }> {\n const host = await this.host();\n return {\n start: await host.hasClass('wr-tabs--fade-start'),\n end: await host.hasClass('wr-tabs--fade-end'),\n };\n }\n\n /** Click a tab and check it took, or say which of the two reasons it did not. */\n private async activate(tab: WrTabHarness, method: string): Promise<void> {\n await tab.click();\n if (await tab.isSelected()) return;\n\n throw new Error(\n `WrTabsHarness.${method}(): \"${await tab.getLabel()}\" is still unselected after being clicked — ` +\n 'either it is a content tab carrying `disabled`, which fires no click at all, or this is a router ' +\n 'strip whose navigation did not land on this tab: a guard, a redirect or an unmatched path leaves ' +\n '`routerActive()` painting some other one.'\n );\n }\n\n /**\n * Send a key to the `role=\"tablist\"` strip.\n *\n * The strip owns the keydown handler, and a real user's keypress on the focused\n * header bubbles to exactly here — so this is the honest target, and the one that\n * still works when jsdom has left real focus on `<body>`. It is also the target\n * that leaves the headers alone: the CDK types into whatever element it is handed,\n * which on a `<button>` means writing a `value` attribute onto a tab.\n */\n private async press(key: TestKey): Promise<void> {\n await (await this.strip()).sendKeys(key);\n }\n\n /** This strip's own `role=\"tablist\"` element — the first one under the host. */\n private async strip(): Promise<TestElement> {\n return this.locatorFor('.wr-tabs__strip')();\n }\n\n /**\n * A loader rooted at this strip, so tab queries cannot reach a nested strip's tabs:\n * a nested `<wr-tabs>` lives in the PANEL, which is outside this element.\n */\n private async stripLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor('.wr-tabs__strip');\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-tabs-testing.mjs","sources":["../../../projects/lib/tabs/testing/wr-tab-harness.ts","../../../projects/lib/tabs/testing/wr-tabs-harness.ts","../../../projects/lib/tabs/testing/ngwr-tabs-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 {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrTabHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one tab of a `<wr-tabs>` strip — the header, and the panel it\n * controls.\n *\n * Its host is the RENDERED HEADER, not the `<wr-tab>` element. A `<wr-tab>` is\n * `display:none` and holds nothing but a `<ng-template>`: the label, the selection\n * state, the tab stop and the click target all live on the element the parent\n * renders for it, which is a `<button role=\"tab\">` in content mode and an\n * `<a role=\"tab\">` in router mode. A harness hosted on `wr-tab` would answer every\n * question with nothing.\n *\n * Also a CONTENT CONTAINER, scoped to the panel this tab controls, so\n * `tab.getHarness(WrButtonHarness…)` reaches the consumer's own components inside\n * the tab's content. The panel is not a descendant of the header — it is a sibling\n * of the whole strip — so it is resolved through the `aria-controls` id, which is\n * also the pairing {@link isPanelBound} checks. Only the SELECTED tab has a panel\n * to reach: `<wr-tabs>` renders one panel and destroys it on every switch.\n *\n * @example\n * ```ts\n * const tabs = await loader.getHarness(WrTabsHarness);\n * const [first] = await tabs.getTabs();\n *\n * expect(await first.isSelected()).toBe(true);\n * expect(await first.isPanelBound()).toBe(true);\n * await (await first.getHarness(WrButtonHarness.with({ text: 'Save' }))).click();\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTabHarness extends ContentContainerComponentHarness {\n /** The header the strip renders for a `<wr-tab>` — a `<button>`, or an `<a>` in router mode. */\n static hostSelector = '.wr-tabs__tab';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTabHarnessFilters = {}): HarnessPredicate<WrTabHarness> {\n return new HarnessPredicate(WrTabHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('selected', options.selected, async (harness, selected) => (await harness.isSelected()) === selected)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /** The tab's visible label, trimmed — the `<wr-tab>`'s `title`. */\n async getLabel(): Promise<string> {\n return (await this.host()).text();\n }\n\n /**\n * The header's own id, which is what the panel points back at with\n * `aria-labelledby` — the tab is the panel's accessible name.\n *\n * Generated from the tab's `key`, so two strips on one page whose tabs share keys\n * publish the same ids and each panel then names whichever header came first.\n */\n async getId(): Promise<string | null> {\n return (await this.host()).getAttribute('id');\n }\n\n /**\n * Whether this is the strip's selected tab.\n *\n * `aria-selected` first, because that is the state a screen reader acts on and\n * the `--active` class is only what it looks like. Both shapes carry it now — a\n * router tab publishes it off the same `routerActive()` that paints the class —\n * so the class is a fallback for a tab that has neither yet, not a second reading\n * of the same thing. The order is not interchangeable: reading the class first\n * would report a tab as selected while `aria-selected=\"false\"` told assistive tech\n * otherwise.\n */\n async isSelected(): Promise<boolean> {\n const host = await this.host();\n const selected = await host.getAttribute('aria-selected');\n if (selected !== null) return selected === 'true';\n return host.hasClass('wr-tabs__tab--active');\n }\n\n /**\n * Whether the tab refuses activation.\n *\n * The two shapes say it differently and both are read: a content tab is a real\n * `<button>` and carries the native `disabled`, a router tab is an `<a>` — which\n * cannot be disabled natively — and carries `aria-disabled` with `tabindex=\"-1\"`.\n * The `--disabled` class is on both and is deliberately not the source: a class\n * says how the tab looks, these two say what it refuses.\n */\n async isDisabled(): Promise<boolean> {\n const host = await this.host();\n if ((await host.getAttribute('aria-disabled')) === 'true') return true;\n return (await host.getProperty<boolean | undefined>('disabled')) === true;\n }\n\n /** Whether this tab is a link — the router shape, where selection follows the route. */\n async isLink(): Promise<boolean> {\n return (await this.host()).matchesSelector('a');\n }\n\n /**\n * The resolved href of a router tab, or `null` for a content tab.\n *\n * `null` is the answer for a `<button>` rather than a gap — a content tab\n * navigates nowhere, it swaps a panel.\n */\n async getHref(): Promise<string | null> {\n return (await this.host()).getAttribute('href');\n }\n\n /**\n * Whether a Tab press can land on this header.\n *\n * A content strip is ONE tab stop and hands it to the selected tab; a router\n * strip makes every enabled tab a stop of its own. See\n * `WrTabsHarness.getTabStopLabels()` — the difference is a mode difference, not\n * a rounding error.\n *\n * A non-negative `tabindex` is not enough on its own, which is why `disabled` is\n * read too: `<wr-tabs>` gives the roving `tabindex=\"0\"` to whatever `active`\n * names without checking whether that tab is disabled, so a host binding `active`\n * to a disabled tab's key renders a `<button disabled tabindex=\"0\">` — which no\n * Tab press reaches. jsdom will focus such a button anyway, so this is the only\n * place a unit test can tell the difference.\n */\n async isTabStop(): Promise<boolean> {\n if (await this.isDisabled()) return false;\n const tabIndex = await (await this.host()).getAttribute('tabindex');\n return Number.parseInt(tabIndex ?? '-1', 10) >= 0;\n }\n\n /**\n * The id of the panel this tab controls, or `null` when it controls none.\n *\n * `null` for a router tab — the strip renders no panel in that mode, the consumer\n * drops a `<router-outlet>` after it — and that missing `aria-controls` is the\n * honest report: there is nothing for the header to point at.\n */\n async getPanelId(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-controls');\n }\n\n /** Whether this tab's panel is in the DOM right now — true only for the selected tab. */\n async hasPanel(): Promise<boolean> {\n return (await this.panelOrNull()) !== null;\n }\n\n /**\n * Whether the panel and this header really name each other.\n *\n * Both halves, because each covers a different failure: the header's\n * `aria-controls` has to reach the panel that is showing, and the panel's\n * `aria-labelledby` has to come back to THIS header. A panel wired to the wrong\n * tab looks perfect on screen — the right content is under the right header — and\n * announces the wrong name, which is the whole of a tabbed interface for anyone\n * not looking at it. It also has to be a `tabpanel`: the reference resolving to\n * some other element is a broken pairing, not a styling detail.\n *\n * Throws when there is no panel at all, which is a different fact from a broken\n * pairing — see {@link getPanelText}.\n */\n async isPanelBound(): Promise<boolean> {\n const panel = await this.panelOrThrow('isPanelBound');\n if ((await panel.getAttribute('role')) !== 'tabpanel') return false;\n return (await panel.getAttribute('aria-labelledby')) === (await this.getId());\n }\n\n /**\n * The text of this tab's panel, trimmed.\n *\n * Throws rather than answering `''` for a tab whose panel is not rendered, and\n * the message says which of the two reasons it is: `<wr-tabs>` keeps only the\n * selected tab's panel in the DOM (the content of every other tab sits in an\n * unrendered `<ng-template>`, so switching tabs destroys a panel and builds the\n * next one), and a router strip renders no panel at all.\n */\n async getPanelText(): Promise<string> {\n return (await this.panelOrThrow('getPanelText')).text();\n }\n\n /**\n * Activate this tab the way a pointer does.\n *\n * A disabled CONTENT tab is left alone: it is a real `<button disabled>`, which\n * fires no click at all. A disabled ROUTER tab is an `<a>`, where `disabled` is\n * not a thing — it carries `aria-disabled=\"true\"` and `tabindex=\"-1\"` to stay off\n * the keyboard path, renders NO href, and the component drops the click rather\n * than routing it. So the click lands and nothing moves, which is why a router\n * strip's expectations belong on the resolved route rather than on the click.\n */\n async click(): Promise<void> {\n return (await this.host()).click();\n }\n\n /** Move keyboard focus onto this header. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Take focus off this header. */\n async blur(): Promise<void> {\n return (await this.host()).blur();\n }\n\n /** Whether this header holds keyboard focus — the roving cursor, not the selection. */\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\n }\n\n /** `getHarness(…)` searches this tab's PANEL, which is where its content is. */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n // Both checks, in this order, so the failure names the reason — no panel in this\n // mode, or a panel that is not the one showing — instead of the CDK's generic\n // \"no element matching selector\".\n const id = await this.panelIdOrThrow('getHarness');\n await this.panelOrThrow('getHarness');\n return this.documentRootLocatorFactory().harnessLoaderFor(WrTabHarness.byId(id));\n }\n\n /** The panel element, or `null` when this tab has none rendered. */\n private async panelOrNull(): Promise<TestElement | null> {\n const id = await this.getPanelId();\n if (id === null) return null;\n // From the document root: the panel is a sibling of the strip, not a descendant\n // of the header this harness is hosted on.\n return this.documentRootLocatorFactory().locatorForOptional(WrTabHarness.byId(id))();\n }\n\n /** The panel element, or a failure naming which of the two reasons there isn't one. */\n private async panelOrThrow(method: string): Promise<TestElement> {\n await this.panelIdOrThrow(method);\n const panel = await this.panelOrNull();\n if (!panel) {\n throw new Error(\n `WrTabHarness.${method}(): the panel for \"${await this.getLabel()}\" is not rendered. <wr-tabs> keeps ` +\n 'only the SELECTED panel in the DOM — the content of every other tab waits in an unrendered ' +\n '<ng-template> — so select the tab first.'\n );\n }\n return panel;\n }\n\n /** This tab's `aria-controls`, or a failure saying a router strip has no panels. */\n private async panelIdOrThrow(method: string): Promise<string> {\n const id = await this.getPanelId();\n if (id === null) {\n throw new Error(\n `WrTabHarness.${method}(): \"${await this.getLabel()}\" controls no panel. A router strip renders none ` +\n '— its tabs navigate and the consumer drops a <router-outlet> after the strip — so assert the ' +\n 'routed content instead.'\n );\n }\n return id;\n }\n\n /**\n * An id selector that survives the id it is given.\n *\n * `[id=\"…\"]` rather than `#…`: the ids here are built from the consumer's `key`,\n * and a key holding a dot, a colon or a leading digit turns `#id` into a selector\n * that either throws or matches something else entirely.\n */\n private static byId(id: string): string {\n return `[id=\"${id}\"]`;\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 {\n ComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n TestKey,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrTabHarnessFilters, WrTabsHarnessFilters, WrTabsHarnessSize } from './interfaces';\nimport { WrTabHarness } from './wr-tab-harness';\n\n/**\n * Test harness for `<wr-tabs>`, with {@link WrTabHarness} for one tab and the panel\n * it controls.\n *\n * The component has two modes and they answer \"which tab is active\" differently,\n * which is most of what this harness exists to keep straight:\n *\n * - **Content** — the tabs are `<button role=\"tab\">`s, the strip is ONE tab stop\n * (the roving `tabindex` sits on the selected tab), and the arrow keys ACTIVATE\n * the tab they move to, the APG's automatic-activation pattern. `<wr-tabs>`\n * renders exactly one panel: the selected tab's.\n * - **Router** — one `routerLink` on any child switches the whole strip over. The\n * tabs become `<a role=\"tab\">`s whose `aria-selected` follows the resolved route\n * (the same `routerActive()` that paints `--active`), the strip renders NO\n * panel, and it stops being one tab stop — every enabled tab is a Tab stop of its\n * own. The arrow keys still move focus, but they select nothing: the route does\n * that.\n *\n * So {@link getSelectedLabel} and {@link getFocusedLabel} are two different\n * questions, and {@link getTabStopLabels} is a third. They coincide in content mode\n * and come apart in router mode; a spec that asserts one and means another passes\n * through the bug.\n *\n * Everything here is driven from the KEYBOARD rather than by clicking a coordinate:\n * a unit test has no layout, so a hit test would answer with nothing, and the\n * keyboard is the accessible path anyway. {@link select} is the exception — a\n * no-arg click needs no coordinate.\n *\n * @example\n * ```ts\n * const tabs = await loader.getHarness(WrTabsHarness);\n *\n * expect(await tabs.getTabLabels()).toEqual(['One', 'Two', 'Three']);\n * await tabs.select({ label: 'Two' });\n * expect(await tabs.getPanelText()).toContain('Second panel');\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTabsHarness extends ComponentHarness {\n static hostSelector = 'wr-tabs';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTabsHarnessFilters = {}): HarnessPredicate<WrTabsHarness> {\n return new HarnessPredicate(WrTabsHarness, options)\n .addOption('tabLabel', options.tabLabel, async (harness, label) => {\n for (const tab of await harness.getTabLabels()) {\n if (await HarnessPredicate.stringMatches(tab, label)) return true;\n }\n return false;\n })\n .addOption('selectedLabel', options.selectedLabel, async (harness, label) => {\n const selected = await harness.getSelectedLabel();\n return selected === null ? false : HarnessPredicate.stringMatches(selected, label);\n })\n .addOption('size', options.size, async (harness, size) => (await harness.getSize()) === size)\n .addOption('router', options.router, async (harness, router) => (await harness.isRouterMode()) === router);\n }\n\n /**\n * The role the strip announces — `tablist`.\n *\n * Read off the strip, not the host: `<wr-tabs>` carries the `wr-tabs` class and no\n * role of its own, and the `tablist` is the scrolling `.wr-tabs__strip` inside it.\n * Without that role the headers are a row of buttons: nothing tells a screen\n * reader how many tabs there are or which one of them is showing.\n */\n async getRole(): Promise<string | null> {\n return (await this.strip()).getAttribute('role');\n }\n\n /**\n * The axis the strip announces, which decides which arrow keys a screen-reader\n * user is told to press.\n *\n * `<wr-tabs>` ships one orientation — the strip is a horizontal scroller and hard\n * codes `aria-orientation=\"horizontal\"` — so today this always answers\n * `'horizontal'`. It is read from the DOM and normalized the way ARIA reads it:\n * anything that is not `vertical`, a missing attribute included, is `'horizontal'`,\n * the default for a `tablist`.\n *\n * Which means an assertion here catches a strip that starts ANNOUNCING `vertical`\n * while its arrow keys still walk the horizontal axis — and cannot catch the\n * mirror image, a vertical mode that forgets to update the attribute: that one\n * answers `'horizontal'` and looks unchanged. One attribute is all this reads.\n */\n async getOrientation(): Promise<'horizontal' | 'vertical'> {\n return (await (await this.strip()).getAttribute('aria-orientation')) === 'vertical' ? 'vertical' : 'horizontal';\n }\n\n /**\n * The size the strip renders at, from the host modifier.\n *\n * `md` is the default and paints no modifier of its own, so its absence is the\n * answer rather than a missing one.\n */\n async getSize(): Promise<WrTabsHarnessSize> {\n const host = await this.host();\n if (await host.hasClass('wr-tabs--sm')) return 'sm';\n if (await host.hasClass('wr-tabs--lg')) return 'lg';\n return 'md';\n }\n\n /**\n * Whether this strip navigates instead of swapping panels.\n *\n * Derived from what got RENDERED — a router tab is an `<a>` — rather than from the\n * `routerLink` inputs, because the switch is all-or-nothing: one `routerLink`\n * anywhere in the strip turns every tab into a link and drops the panel. A strip\n * with no tabs at all is content mode, which is what the component does with it.\n */\n async isRouterMode(): Promise<boolean> {\n const tabs = await this.getTabs();\n for (const tab of tabs) {\n if (await tab.isLink()) return true;\n }\n return false;\n }\n\n /**\n * This strip's tabs, in render order.\n *\n * Scoped to this strip rather than to the host, which is what keeps a `<wr-tabs>`\n * NESTED in a panel from answering for its parent: the inner strip is a descendant\n * of the outer host, so a host-wide query would hand back both sets of tabs as one\n * flat list.\n */\n async getTabs(filters: WrTabHarnessFilters = {}): Promise<WrTabHarness[]> {\n return (await this.stripLoader()).getAllHarnesses(WrTabHarness.with(filters));\n }\n\n /** The label of every tab, in render order. */\n async getTabLabels(): Promise<string[]> {\n const tabs = await this.getTabs();\n return Promise.all(tabs.map(tab => tab.getLabel()));\n }\n\n /**\n * The selected tab, or `null` when none is.\n *\n * A content strip always has one — it falls back to the first tab when `active`\n * matches nothing — so `null` means a router strip whose current route matches no\n * tab, or a strip with no tabs.\n */\n async getSelectedTab(): Promise<WrTabHarness | null> {\n const [selected] = await this.getTabs({ selected: true });\n return selected ?? null;\n }\n\n /** The label of the selected tab, or `null` when none is selected. */\n async getSelectedLabel(): Promise<string | null> {\n const selected = await this.getSelectedTab();\n return selected ? selected.getLabel() : null;\n }\n\n /**\n * Select the first tab matching the filters, the way a pointer does.\n *\n * Throws when nothing matches, naming the tabs that do exist, and throws again\n * when the tab is still unselected afterwards — with both causes named, because a\n * click that quietly did nothing surfaces as an unrelated assertion three lines\n * later. Those causes are a disabled CONTENT tab (a `<button disabled>` never sees\n * the click at all) and a router strip whose navigation did not land on this tab.\n *\n * A router strip is otherwise fine here: the click starts a route change, the\n * harness stabilizes the pending navigation before it re-reads the strip, and\n * `routerActive()` has painted the selection by then — so this resolves. It is a\n * guard, a redirect or an unmatched path that leaves the tab unselected. A\n * DISABLED router tab is a different shape and no longer navigates: it renders\n * no href and the component drops the click, so the click lands and nothing\n * moves — see {@link WrTabHarness.click}.\n */\n async select(filters: WrTabHarnessFilters): Promise<void> {\n const [tab] = await this.getTabs(filters);\n if (!tab) {\n const labels = await this.getTabLabels();\n const offered =\n labels.length > 0 ? `This strip offers: ${labels.join(', ')}.` : 'This strip renders no tabs at all.';\n throw new Error(`WrTabsHarness.select(): no tab matched ${JSON.stringify(filters)}. ${offered}`);\n }\n await this.activate(tab, 'select');\n }\n\n /**\n * Select the tab at this position in render order, counting from 0.\n *\n * Throws on an index the strip does not have, naming its length: a silent no-op\n * there reads as \"the tab did not react\" when the tab was never addressed.\n */\n async selectByIndex(index: number): Promise<void> {\n const tabs = await this.getTabs();\n const tab = tabs[index];\n if (!tab) {\n throw new Error(\n `WrTabsHarness.selectByIndex(): this strip has ${tabs.length} tab(s), so index ${index} is out of ` +\n 'range — they are numbered from 0 in render order.'\n );\n }\n await this.activate(tab, 'selectByIndex');\n }\n\n /**\n * The labels of every tab a Tab press can land on, in render order.\n *\n * A LIST rather than one label, because the count is the mode: a content strip\n * roves a single `tabindex=\"0\"` and answers with exactly one label — the selected\n * tab — while a router strip makes every enabled tab its own stop and answers with\n * all of them. Either way a disabled tab is never in here.\n */\n async getTabStopLabels(): Promise<string[]> {\n const labels: string[] = [];\n for (const tab of await this.getTabs()) {\n if (await tab.isTabStop()) labels.push(await tab.getLabel());\n }\n return labels;\n }\n\n /**\n * Whether the strip is a single tab stop — the roving-`tabindex` pattern the APG\n * asks a `tablist` for.\n *\n * True for a content strip, false for a router one, which puts every enabled tab\n * in the page tab order. A strip whose every tab is disabled has no stop at all\n * and answers `true`: nothing to rove, and Tab skips it.\n */\n async isRoving(): Promise<boolean> {\n return (await this.getTabStopLabels()).length <= 1;\n }\n\n /**\n * The label of the tab holding keyboard focus, or `null` when focus is elsewhere.\n *\n * The other answer to \"which tab is active\", and not the same as\n * {@link getSelectedLabel}: the arrow keys move this. In content mode they also\n * select what they land on, so the two stay together; in router mode focus walks\n * the strip while the selection stays on whatever the route resolved to.\n */\n async getFocusedLabel(): Promise<string | null> {\n for (const tab of await this.getTabs()) {\n if (await tab.isFocused()) return tab.getLabel();\n }\n return null;\n }\n\n /**\n * Put focus where a Tab press would put it — the first tab stop in render order.\n *\n * That is the selected tab in content mode, and the first enabled tab in a router\n * strip. Throws when the strip has no stop at all, because Tab then skips past it\n * rather than entering it — a content strip whose selected tab is disabled has\n * none, and neither has one whose every tab is disabled, or one with no tabs.\n */\n async focusTabStop(): Promise<void> {\n for (const tab of await this.getTabs()) {\n if (await tab.isTabStop()) {\n await tab.focus();\n return;\n }\n }\n\n throw new Error(\n 'WrTabsHarness.focusTabStop(): this strip has no tab stop — its selected tab is disabled, every tab ' +\n 'is disabled, or it renders no tabs — so Tab skips it rather than entering it.'\n );\n }\n\n /**\n * ArrowRight on the strip — one tab toward the visual RIGHT, wrapping at the end\n * and skipping disabled tabs.\n *\n * The physical key, because that is what a keyboard has. The component follows\n * visual order per the APG, so under `dir=\"rtl\"` this moves to the PREVIOUS tab —\n * a spec exercising a mirrored strip swaps this and {@link pressArrowLeft}.\n *\n * In content mode the tab it lands on is also SELECTED (automatic activation); in\n * router mode only focus moves.\n */\n async pressArrowRight(): Promise<void> {\n await this.press(TestKey.RIGHT_ARROW);\n }\n\n /** ArrowLeft on the strip — one tab toward the visual LEFT, mirrored under `dir=\"rtl\"`. */\n async pressArrowLeft(): Promise<void> {\n await this.press(TestKey.LEFT_ARROW);\n }\n\n /**\n * Home on the strip — the FIRST enabled tab.\n *\n * Home and End name a position in the tab list rather than an edge of the strip,\n * so unlike the arrows they read the same in both reading directions.\n */\n async pressHome(): Promise<void> {\n await this.press(TestKey.HOME);\n }\n\n /** End on the strip — the LAST enabled tab, in both reading directions. */\n async pressEnd(): Promise<void> {\n await this.press(TestKey.END);\n }\n\n /** Whether this strip is showing a panel at all — false for a router strip. */\n async hasPanel(): Promise<boolean> {\n const selected = await this.getSelectedTab();\n return selected === null ? false : selected.hasPanel();\n }\n\n /**\n * The text of the panel that is showing, trimmed — the selected tab's content, and\n * the only tab content in the DOM.\n *\n * Throws when nothing is selected. Delegates to the selected tab, so the panel it\n * reads is the one that tab's `aria-controls` points at rather than \"the first\n * `.wr-tabs__panel` around\" — the two are only the same page when the wiring is\n * right, and {@link WrTabHarness.isPanelBound} is what says whether it is.\n */\n async getPanelText(): Promise<string> {\n const selected = await this.getSelectedTab();\n if (!selected) {\n throw new Error(\n 'WrTabsHarness.getPanelText(): no tab is selected, so no panel is rendered. A router strip whose ' +\n 'route matches none of its tabs looks like this, as does a strip with no tabs.'\n );\n }\n return selected.getPanelText();\n }\n\n /**\n * Which edges of the strip are faded out, from the public `--fade-start` /\n * `--fade-end` host modifiers — the cue that there are more tabs than fit, on the\n * side they are on.\n *\n * The component computes these from the strip's scroll metrics, so in a unit test\n * both are always `false`: jsdom lays nothing out, every element measures 0x0, and\n * a strip that never overflows never fades. Assert this in a real browser, or\n * declare the metrics and dispatch a `scroll` the way the component's own spec\n * does.\n */\n async getFades(): Promise<{ start: boolean; end: boolean }> {\n const host = await this.host();\n return {\n start: await host.hasClass('wr-tabs--fade-start'),\n end: await host.hasClass('wr-tabs--fade-end'),\n };\n }\n\n /** Click a tab and check it took, or say which of the two reasons it did not. */\n private async activate(tab: WrTabHarness, method: string): Promise<void> {\n await tab.click();\n if (await tab.isSelected()) return;\n\n throw new Error(\n `WrTabsHarness.${method}(): \"${await tab.getLabel()}\" is still unselected after being clicked — ` +\n 'either it is a content tab carrying `disabled`, which fires no click at all, or this is a router ' +\n 'strip whose navigation did not land on this tab: a guard, a redirect or an unmatched path leaves ' +\n '`routerActive()` painting some other one.'\n );\n }\n\n /**\n * Send a key to the `role=\"tablist\"` strip.\n *\n * The strip owns the keydown handler, and a real user's keypress on the focused\n * header bubbles to exactly here — so this is the honest target, and the one that\n * still works when jsdom has left real focus on `<body>`. It is also the target\n * that leaves the headers alone: the CDK types into whatever element it is handed,\n * which on a `<button>` means writing a `value` attribute onto a tab.\n */\n private async press(key: TestKey): Promise<void> {\n await (await this.strip()).sendKeys(key);\n }\n\n /** This strip's own `role=\"tablist\"` element — the first one under the host. */\n private async strip(): Promise<TestElement> {\n return this.locatorFor('.wr-tabs__strip')();\n }\n\n /**\n * A loader rooted at this strip, so tab queries cannot reach a nested strip's tabs:\n * a nested `<wr-tabs>` lives in the PANEL, which is outside this element.\n */\n private async stripLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor('.wr-tabs__strip');\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-tabs.mjs
CHANGED
|
@@ -121,10 +121,10 @@ class WrTab {
|
|
|
121
121
|
this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
125
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.
|
|
124
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
125
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: WrTab, isStandalone: true, selector: "wr-tab", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.display": "'none'" } }, viewQueries: [{ propertyName: "contentTpl", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template><ng-content /></ng-template>`, isInline: true, encapsulation: i0.ViewEncapsulation.None });
|
|
126
126
|
}
|
|
127
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
127
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTab, decorators: [{
|
|
128
128
|
type: Component,
|
|
129
129
|
args: [{
|
|
130
130
|
selector: 'wr-tab',
|
|
@@ -399,8 +399,8 @@ class WrTabs {
|
|
|
399
399
|
const headerEl = this.stripRef()?.nativeElement.querySelector(`#${CSS.escape(this.headerId(next.key()))}`);
|
|
400
400
|
headerEl?.focus();
|
|
401
401
|
}
|
|
402
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
403
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
402
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
403
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: WrTabs, isStandalone: true, selector: "wr-tabs", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange" }, host: { properties: { "class.wr-tabs--fade-start": "canScrollStart()", "class.wr-tabs--fade-end": "canScrollEnd()", "class.wr-tabs--sm": "size() === 'sm'", "class.wr-tabs--lg": "size() === 'lg'" }, classAttribute: "wr-tabs" }, providers: [
|
|
404
404
|
{
|
|
405
405
|
provide: WR_TABS,
|
|
406
406
|
// eslint-disable-next-line @angular-eslint/no-forward-ref
|
|
@@ -408,7 +408,7 @@ class WrTabs {
|
|
|
408
408
|
},
|
|
409
409
|
], queries: [{ propertyName: "tabs", predicate: WrTab, isSignal: true }], viewQueries: [{ propertyName: "stripRef", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
410
410
|
}
|
|
411
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
411
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrTabs, decorators: [{
|
|
412
412
|
type: Component,
|
|
413
413
|
args: [{ selector: 'wr-tabs', encapsulation: ViewEncapsulation.None, host: {
|
|
414
414
|
class: 'wr-tabs',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tokens/tabs-routing.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\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 { InjectionToken } from '@angular/core';\n\n/**\n * What a `<wr-tabs>` needs from a router in order to render a router tab, and\n * nothing more. Three questions — the href to put on the anchor, whether the\n * current URL selects it, and what a click should do.\n *\n * It exists so that `ngwr/tabs` can name the capability without naming\n * `@angular/router`: a directive listed in `imports: []` lands in the compiled\n * component's `dependencies` whether or not its branch of the template ever\n * runs, so `RouterLink` + `RouterLinkActive` on the strip charged every content\n * tab strip the whole router — 73.9 kB of a 144.8 kB component. The\n * implementation lives at `ngwr/tabs/router`, which a consumer imports only\n * when they use router tabs.\n *\n * `href` and `isActive` are read from computeds and must both be reactive to\n * navigation: a RELATIVE `routerLink` resolves against the active route, so its\n * href changes when the route does, exactly as `RouterLink` recomputes its own.\n */\nexport interface WrTabsRoutingAdapter {\n /**\n * Serialized href for a tab's `routerLink`, or `null` when the adapter cannot\n * build one.\n */\n href(commands: string | readonly string[]): string | null;\n\n /**\n * Whether the current URL selects this tab. Matched the way `routerLinkActive`\n * matches without `[routerLinkActiveOptions]`: a path prefix, not an exact URL.\n */\n isActive(commands: string | readonly string[]): boolean;\n\n /**\n * Handle a click on a tab anchor. A modified or non-primary click is left to\n * the browser, so ctrl/cmd-click still opens the tab in a new window.\n */\n navigate(commands: string | readonly string[], event: MouseEvent): void;\n}\n\n/**\n * Token a `<wr-tabs>` reads to find out whether router tabs are usable. Provided\n * by the `WrTabsRouting` directive from `ngwr/tabs/router`; absent by default,\n * which is what keeps `@angular/router` out of a content-only strip.\n */\nexport const WR_TABS_ROUTING = new InjectionToken<WrTabsRoutingAdapter>('WR_TABS_ROUTING');\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS, WR_TABS_ROUTING } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip. Router mode needs the\n * `WrTabsRouting` directive from `ngwr/tabs/router` in the same\n * `imports: []`; without it a strip carrying a `routerLink` throws.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n // A BOUND display rather than a static `style` attribute: Angular writes a\n // binding through `style.setProperty`, which no CSP governs, while a real\n // `style=\"…\"` attribute is refused under `style-src 'self'` — and this host\n // would then lay out an empty box in the middle of the content.\n host: { '[style.display]': \"'none'\" },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * The routing adapter published by `WrTabsRouting` on the parent `<wr-tabs>`\n * element. A `<wr-tab>` is DECLARED inside `<wr-tabs>` in the consumer's\n * template — content projection moves where it renders, not where it injects\n * — so the parent's element injector is this one's, the same walk that\n * resolves `WR_TABS` above.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n /**\n * Resolved href for a router tab — `null` for a content tab, and for a\n * disabled one, which is how the anchor stops navigating at all.\n * @internal\n */\n readonly routerHref = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return null;\n return this.routing?.href(link) ?? null;\n });\n\n /**\n * Whether the current route selects this tab. The strip publishes it as BOTH\n * `aria-selected` and the `--active` class, off this one read, so the\n * announced selection and the painted one cannot drift apart.\n * @internal\n */\n readonly routerActive = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return false;\n return this.routing?.isActive(link) ?? false;\n });\n\n /** Hand a click on this tab's anchor to the routing adapter. @internal */\n navigate(event: MouseEvent): void {\n const link = this.routerLink();\n if (link === null || this.disabled()) return;\n this.routing?.navigate(link, event);\n }\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\n }\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 { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, WR_TABS_ROUTING, type WrTabsContext } from './tokens';\n\n/**\n * Instance counter for the header / panel ids. Deterministic under prerender,\n * unlike `randomId()` — the same reason `wr-sidebar` and `wr-mention` count.\n *\n * The ids used to be built from the tab key alone, so two `<wr-tabs>` on one\n * page that both name a tab `key=\"overview\"` emitted the same\n * `wr-tab-overview-header` twice, and the second group's `aria-labelledby`\n * resolved to the FIRST group's tab. A key is documented as parent-scoped, so\n * it is the parent that has to make it unique document-wide.\n */\nlet tabsUid = 0;\n\n/**\n * A tab key as an id fragment: anything outside `[A-Za-z0-9_-]` becomes `-`.\n * These ids land in `aria-controls` / `aria-labelledby`, which are\n * space-separated IDREF lists — `key=\"my tab\"` emitted\n * `aria-controls=\"wr-tab-my tab-panel\"`, two tokens, neither of which resolves.\n *\n * Two keys differing only outside that set collapse to one fragment; the\n * instance counter keeps that inside a single group, where the keys are the\n * consumer's own to keep apart.\n */\nfunction idFragment(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-');\n}\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it. That mode needs the `WrTabsRouting`\n * directive from `ngwr/tabs/router` alongside `WrTabs` in `imports: []`\n * — the strip itself names no `@angular/router` symbol, which is what\n * keeps a content-only strip free of the router.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs: imports: [WrTabs, WrTab, WrTabsRouting] -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * The routing adapter, when `WrTabsRouting` from `ngwr/tabs/router` is in the\n * consumer's `imports`. `null` is the default and the whole point: the strip\n * has no `@angular/router` import of its own, so a content-only tab strip\n * costs no router bytes.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n /**\n * When any child has a routerLink, the whole strip switches to router mode.\n *\n * Throws rather than degrading when the adapter is missing. Falling back to\n * content mode would render the tabs as `<button>`s that navigate nowhere and\n * open an empty panel — a strip that looks right and does nothing, with the\n * one missing import unnamed. The route is what selects a tab here, so there\n * is no half of this that still works.\n */\n protected readonly isRouter = computed(() => {\n const isRouter = this.tabs().some(t => t.routerLink() !== null);\n if (isRouter && !this.routing) {\n throw new Error(\n '<wr-tabs>: a <wr-tab> sets `routerLink`, but no routing adapter is provided. ' +\n \"Add `WrTabsRouting` from 'ngwr/tabs/router' to the `imports` of the component that \" +\n 'declares this strip.'\n );\n }\n return isRouter;\n });\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /**\n * Click on a router tab's anchor. The anchor carries a real `href`, so a\n * modified or middle click is left alone and the browser opens it the way it\n * opens any link; only a plain left click is turned into a navigation.\n */\n protected onRouterTabClick(tab: WrTab, event: MouseEvent): void {\n tab.navigate(event);\n }\n\n /** Per-instance prefix, so keys only have to be unique within one group. */\n private readonly uid = ++tabsUid;\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n\n // Step from where FOCUS is, not from what is active.\n //\n // In router mode nothing ever moves `active` — `onTabClick` and this\n // handler both gate `activate()` on `!isRouter()`, because the route is\n // what selects a tab there. Reading the neighbour off `activeTab()`\n // therefore recomputed the same `idx` on every press, and ArrowRight\n // walked from the first tab to the second and stayed there for the rest of\n // the session. In manual mode focus and activation move together, so the\n // two readings agree and nothing changes.\n const strip = this.stripRef()?.nativeElement;\n const focused = strip?.ownerDocument.activeElement;\n const focusedIdx =\n focused instanceof HTMLElement && strip?.contains(focused)\n ? tabs.findIndex(t => this.headerId(t.key()) === focused.id)\n : -1;\n const active = this.activeTab();\n const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\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-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tokens/tabs-routing.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\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 { InjectionToken } from '@angular/core';\n\n/**\n * What a `<wr-tabs>` needs from a router in order to render a router tab, and\n * nothing more. Three questions — the href to put on the anchor, whether the\n * current URL selects it, and what a click should do.\n *\n * It exists so that `ngwr/tabs` can name the capability without naming\n * `@angular/router`: a directive listed in `imports: []` lands in the compiled\n * component's `dependencies` whether or not its branch of the template ever\n * runs, so `RouterLink` + `RouterLinkActive` on the strip charged every content\n * tab strip the whole router — 73.9 kB of a 144.8 kB component. The\n * implementation lives at `ngwr/tabs/router`, which a consumer imports only\n * when they use router tabs.\n *\n * `href` and `isActive` are read from computeds and must both be reactive to\n * navigation: a RELATIVE `routerLink` resolves against the active route, so its\n * href changes when the route does, exactly as `RouterLink` recomputes its own.\n */\nexport interface WrTabsRoutingAdapter {\n /**\n * Serialized href for a tab's `routerLink`, or `null` when the adapter cannot\n * build one.\n */\n href(commands: string | readonly string[]): string | null;\n\n /**\n * Whether the current URL selects this tab. Matched the way `routerLinkActive`\n * matches without `[routerLinkActiveOptions]`: a path prefix, not an exact URL.\n */\n isActive(commands: string | readonly string[]): boolean;\n\n /**\n * Handle a click on a tab anchor. A modified or non-primary click is left to\n * the browser, so ctrl/cmd-click still opens the tab in a new window.\n */\n navigate(commands: string | readonly string[], event: MouseEvent): void;\n}\n\n/**\n * Token a `<wr-tabs>` reads to find out whether router tabs are usable. Provided\n * by the `WrTabsRouting` directive from `ngwr/tabs/router`; absent by default,\n * which is what keeps `@angular/router` out of a content-only strip.\n */\nexport const WR_TABS_ROUTING = new InjectionToken<WrTabsRoutingAdapter>('WR_TABS_ROUTING');\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS, WR_TABS_ROUTING } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip. Router mode needs the\n * `WrTabsRouting` directive from `ngwr/tabs/router` in the same\n * `imports: []`; without it a strip carrying a `routerLink` throws.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n // A BOUND display rather than a static `style` attribute: Angular writes a\n // binding through `style.setProperty`, which no CSP governs, while a real\n // `style=\"…\"` attribute is refused under `style-src 'self'` — and this host\n // would then lay out an empty box in the middle of the content.\n host: { '[style.display]': \"'none'\" },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * The routing adapter published by `WrTabsRouting` on the parent `<wr-tabs>`\n * element. A `<wr-tab>` is DECLARED inside `<wr-tabs>` in the consumer's\n * template — content projection moves where it renders, not where it injects\n * — so the parent's element injector is this one's, the same walk that\n * resolves `WR_TABS` above.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n /**\n * Resolved href for a router tab — `null` for a content tab, and for a\n * disabled one, which is how the anchor stops navigating at all.\n * @internal\n */\n readonly routerHref = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return null;\n return this.routing?.href(link) ?? null;\n });\n\n /**\n * Whether the current route selects this tab. The strip publishes it as BOTH\n * `aria-selected` and the `--active` class, off this one read, so the\n * announced selection and the painted one cannot drift apart.\n * @internal\n */\n readonly routerActive = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return false;\n return this.routing?.isActive(link) ?? false;\n });\n\n /** Hand a click on this tab's anchor to the routing adapter. @internal */\n navigate(event: MouseEvent): void {\n const link = this.routerLink();\n if (link === null || this.disabled()) return;\n this.routing?.navigate(link, event);\n }\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\n }\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 { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, WR_TABS_ROUTING, type WrTabsContext } from './tokens';\n\n/**\n * Instance counter for the header / panel ids. Deterministic under prerender,\n * unlike `randomId()` — the same reason `wr-sidebar` and `wr-mention` count.\n *\n * The ids used to be built from the tab key alone, so two `<wr-tabs>` on one\n * page that both name a tab `key=\"overview\"` emitted the same\n * `wr-tab-overview-header` twice, and the second group's `aria-labelledby`\n * resolved to the FIRST group's tab. A key is documented as parent-scoped, so\n * it is the parent that has to make it unique document-wide.\n */\nlet tabsUid = 0;\n\n/**\n * A tab key as an id fragment: anything outside `[A-Za-z0-9_-]` becomes `-`.\n * These ids land in `aria-controls` / `aria-labelledby`, which are\n * space-separated IDREF lists — `key=\"my tab\"` emitted\n * `aria-controls=\"wr-tab-my tab-panel\"`, two tokens, neither of which resolves.\n *\n * Two keys differing only outside that set collapse to one fragment; the\n * instance counter keeps that inside a single group, where the keys are the\n * consumer's own to keep apart.\n */\nfunction idFragment(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-');\n}\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it. That mode needs the `WrTabsRouting`\n * directive from `ngwr/tabs/router` alongside `WrTabs` in `imports: []`\n * — the strip itself names no `@angular/router` symbol, which is what\n * keeps a content-only strip free of the router.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs: imports: [WrTabs, WrTab, WrTabsRouting] -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * The routing adapter, when `WrTabsRouting` from `ngwr/tabs/router` is in the\n * consumer's `imports`. `null` is the default and the whole point: the strip\n * has no `@angular/router` import of its own, so a content-only tab strip\n * costs no router bytes.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n /**\n * When any child has a routerLink, the whole strip switches to router mode.\n *\n * Throws rather than degrading when the adapter is missing. Falling back to\n * content mode would render the tabs as `<button>`s that navigate nowhere and\n * open an empty panel — a strip that looks right and does nothing, with the\n * one missing import unnamed. The route is what selects a tab here, so there\n * is no half of this that still works.\n */\n protected readonly isRouter = computed(() => {\n const isRouter = this.tabs().some(t => t.routerLink() !== null);\n if (isRouter && !this.routing) {\n throw new Error(\n '<wr-tabs>: a <wr-tab> sets `routerLink`, but no routing adapter is provided. ' +\n \"Add `WrTabsRouting` from 'ngwr/tabs/router' to the `imports` of the component that \" +\n 'declares this strip.'\n );\n }\n return isRouter;\n });\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /**\n * Click on a router tab's anchor. The anchor carries a real `href`, so a\n * modified or middle click is left alone and the browser opens it the way it\n * opens any link; only a plain left click is turned into a navigation.\n */\n protected onRouterTabClick(tab: WrTab, event: MouseEvent): void {\n tab.navigate(event);\n }\n\n /** Per-instance prefix, so keys only have to be unique within one group. */\n private readonly uid = ++tabsUid;\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n\n // Step from where FOCUS is, not from what is active.\n //\n // In router mode nothing ever moves `active` — `onTabClick` and this\n // handler both gate `activate()` on `!isRouter()`, because the route is\n // what selects a tab there. Reading the neighbour off `activeTab()`\n // therefore recomputed the same `idx` on every press, and ArrowRight\n // walked from the first tab to the second and stayed there for the rest of\n // the session. In manual mode focus and activation move together, so the\n // two readings agree and nothing changes.\n const strip = this.stripRef()?.nativeElement;\n const focused = strip?.ownerDocument.activeElement;\n const focusedIdx =\n focused instanceof HTMLElement && strip?.contains(focused)\n ? tabs.findIndex(t => this.headerId(t.key()) === focused.id)\n : -1;\n const active = this.activeTab();\n const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;AAKG;AAoBH;;;;;AAKG;MACU,OAAO,GAAG,IAAI,cAAc,CAAgB,SAAS;;AC/BlE;;;;;AAKG;AAyCH;;;;AAIG;MACU,eAAe,GAAG,IAAI,cAAc,CAAuB,iBAAiB;;ACnDzF;;;;;AAKG;AAkBH;;;;;;;;;;AAUG;MAWU,KAAK,CAAA;;IAEP,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAU;AAEzC;;;AAGG;AACM,IAAA,GAAG,GAAG,KAAK,CAAS,QAAQ,CAAC,QAAQ,CAAC;4EAAC;;IAGvC,UAAU,GAAG,KAAK,CAAoC,IAAI;mFAAC;;IAG3D,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;;AAG7D,IAAA,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,WAAW;mFAAC;IAEpC,MAAM,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5C,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAEhD;;;;;;AAMG;IACc,OAAO,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEnD,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,KAAK,IAAI;iFAAC;AAExE;;;;AAIG;AACM,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAK;AAClC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE;AAC9B,QAAA,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE;AAAE,YAAA,OAAO,IAAI;QACjD,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI;IACzC,CAAC;mFAAC;AAEF;;;;;AAKG;AACM,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAK;AACpC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE;AAC9B,QAAA,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE;AAAE,YAAA,OAAO,KAAK;QAClD,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK;IAC9C,CAAC;qFAAC;;AAGF,IAAA,QAAQ,CAAC,KAAiB,EAAA;AACxB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE;AAC9B,QAAA,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE;QACtC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrC;AAEA,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,IAAI,CAAC,MAAM,EAAE;;;;;YAKf,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACxE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACtE;IACF;uGAxEW,KAAK,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAL,KAAK,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,QAAA,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,GAAA,EAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,UAAA,EAAA,KAAA,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,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,QAAA,EAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,YAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAiByB,WAAW,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAzB1C,CAAA,yCAAA,CAA2C,EAAA,QAAA,EAAA,IAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAQ1C,KAAK,EAAA,UAAA,EAAA,CAAA;kBAVjB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,QAAQ;AAClB,oBAAA,QAAQ,EAAE,CAAA,yCAAA,CAA2C;oBACrD,aAAa,EAAE,iBAAiB,CAAC,IAAI;;;;;AAKrC,oBAAA,IAAI,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE;AACtC,iBAAA;yeAkB0C,WAAW,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AC7DtD;;;;;AAKG;AAwBH;;;;;;;;;AASG;AACH,IAAI,OAAO,GAAG,CAAC;AAEf;;;;;;;;;AASG;AACH,SAAS,UAAU,CAAC,GAAW,EAAA;IAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;AAC5C;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;MAqBU,MAAM,CAAA;;IAER,MAAM,GAAG,KAAK,CAAgB,IAAI;+EAAC;;IAGnC,IAAI,GAAG,KAAK,CAAqB,IAAI;6EAAC;IAE5B,IAAI,GAAG,eAAe,CAAC,KAAK;6EAAC;IAE/B,GAAG,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAEjE;;;;;;AAMG;AACc,IAAA,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,KAAK;8EAAC;AAE1E;;;;;AAKG;IACc,OAAO,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAEtE;;;;;;;;AAQG;AACgB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAK;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC;AAC/D,QAAA,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;YAC7B,MAAM,IAAI,KAAK,CACb,+EAA+E;gBAC7E,qFAAqF;AACrF,gBAAA,sBAAsB,CACzB;QACH;AACA,QAAA,OAAO,QAAQ;IACjB,CAAC;iFAAC;;AAGiB,IAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;AAC3C,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE;AACzB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,IAAI;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;kFAAC;;AAIF,IAAA,QAAQ,CAAC,GAAW,EAAA;AAClB,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;IACtB;IAEA,QAAQ,GAAA;;;;;IAKR;IAEA,UAAU,GAAA;;IAEV;;IAImB,QAAQ,GAAG,SAAS,CAA0B,OAAO;iFAAC;;IAGtD,cAAc,GAAG,MAAM,CAAC,KAAK;uFAAC;IAC9B,YAAY,GAAG,MAAM,CAAC,KAAK;qFAAC;AAE/C,IAAA,WAAA,GAAA;;;;;;;QAOE,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;YACxB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI;AAAE,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC/E,QAAA,CAAC,CAAC;AAEF,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACrC,eAAe,CAAC,MAAK;YACnB,IAAI,CAAC,WAAW,EAAE;YAClB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,aAAa;AACzC,YAAA,IAAI,CAAC,EAAE,IAAI,OAAO,cAAc,KAAK,WAAW;gBAAE;;;AAGlD,YAAA,MAAM,EAAE,GAAG,IAAI,cAAc,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;AACvD,YAAA,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;YACd,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;AAC7C,QAAA,CAAC,CAAC;IACJ;;IAGU,WAAW,GAAA;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,aAAa;AACzC,QAAA,IAAI,CAAC,EAAE;YAAE;QACT,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW;;;;;;;;;;;;AAY3C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;AACrC,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,QAAQ,GAAG,GAAG,GAAG,CAAC,CAAC;IACtD;AAEU,IAAA,UAAU,CAAC,GAAU,EAAA;QAC7B,IAAI,GAAG,CAAC,QAAQ,EAAE;YAAE;AACpB,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAChD;AAEA;;;;AAIG;IACO,gBAAgB,CAAC,GAAU,EAAE,KAAiB,EAAA;AACtD,QAAA,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;IACrB;;IAGiB,GAAG,GAAG,EAAE,OAAO;;AAGhC,IAAA,QAAQ,CAAC,GAAW,EAAA;QAClB,OAAO,CAAA,QAAA,EAAW,IAAI,CAAC,GAAG,CAAA,KAAA,EAAQ,UAAU,CAAC,GAAG,CAAC,CAAA,OAAA,CAAS;IAC5D;;AAGA,IAAA,OAAO,CAAC,GAAW,EAAA;QACjB,OAAO,CAAA,QAAA,EAAW,IAAI,CAAC,GAAG,CAAA,KAAA,EAAQ,UAAU,CAAC,GAAG,CAAC,CAAA,MAAA,CAAQ;IAC3D;;AAGU,IAAA,cAAc,CAAC,KAAoB,EAAA;AAC3C,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACnD,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE;;;;;;;;;;QAWvB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,aAAa;AAC5C,QAAA,MAAM,OAAO,GAAG,KAAK,EAAE,aAAa,CAAC,aAAa;QAClD,MAAM,UAAU,GACd,OAAO,YAAY,WAAW,IAAI,KAAK,EAAE,QAAQ,CAAC,OAAO;cACrD,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,EAAE;cACzD,CAAC,CAAC;AACR,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE;QAC/B,MAAM,GAAG,GAAG,UAAU,IAAI,CAAC,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;;AAE7E,QAAA,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;AACnD,QAAA,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;AACpD,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE;AACxB,QAAA,IAAI,OAAe;AAEnB,QAAA,QAAQ,KAAK,CAAC,GAAG;;;;AAIf,YAAA,KAAK,YAAY;gBACf,OAAO,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO;gBAClC;AACF,YAAA,KAAK,WAAW;gBACd,OAAO,GAAG,GAAG,GAAG,OAAO,GAAG,QAAQ;gBAClC;;;AAGF,YAAA,KAAK,MAAM;gBACT,OAAO,GAAG,CAAC;gBACX;AACF,YAAA,KAAK,KAAK;AACR,gBAAA,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;gBACzB;AACF,YAAA;gBACE;;QAGJ,KAAK,CAAC,cAAc,EAAE;AACtB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC;AAC1B,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;;AAE/C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,aAAa,CAAC,aAAa,CAC3D,CAAA,CAAA,EAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA,CAAE,CAC5C;QACD,QAAQ,EAAE,KAAK,EAAE;IACnB;uGArNW,MAAM,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAN,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAM,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,MAAA,EAAA,cAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,2BAAA,EAAA,kBAAA,EAAA,yBAAA,EAAA,gBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,EAAA,cAAA,EAAA,SAAA,EAAA,EAAA,SAAA,EARN;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,OAAO;;AAEhB,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,MAAM,CAAC;AACtC,aAAA;AACF,SAAA,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,MAAA,EAAA,SAAA,EASyC,KAAK,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,UAAA,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,ECjHjD,s6EAmEA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,ED8BY,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;;2FASf,MAAM,EAAA,UAAA,EAAA,CAAA;kBApBlB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,SAAS,EAAA,aAAA,EAEJ,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B;AACJ,wBAAA,KAAK,EAAE,SAAS;AAChB,wBAAA,6BAA6B,EAAE,kBAAkB;AACjD,wBAAA,2BAA2B,EAAE,gBAAgB;AAC7C,wBAAA,qBAAqB,EAAE,iBAAiB;AACxC,wBAAA,qBAAqB,EAAE,iBAAiB;qBACzC,EAAA,OAAA,EACQ,CAAC,gBAAgB,CAAC,EAAA,SAAA,EAChB;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,OAAO;;AAEhB,4BAAA,WAAW,EAAE,UAAU,CAAC,YAAY,CAAC;AACtC,yBAAA;AACF,qBAAA,EAAA,QAAA,EAAA,s6EAAA,EAAA;AASyC,SAAA,CAAA,EAAA,cAAA,EAAA,MAAA,EAAA,EAAA,cAAA,EAAA,EAAA,MAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,QAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,CAAA,MAAA,EAAA,IAAA,EAAA,CAAA,cAAA,CAAA,EAAA,CAAA,EAAA,IAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,MAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,IAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,UAAA,CAAA,MAAA,KAAK,mEAqEkB,OAAO,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AEtL1E;;AAEG;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-textarea-testing.mjs","sources":["../../../projects/lib/textarea/testing/wr-textarea-harness.ts","../../../projects/lib/textarea/testing/ngwr-textarea-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, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrTextareaResize, WrTextareaSize } from 'ngwr/textarea';\n\nimport type { WrTextareaHarnessFilters } from './interfaces';\n\n/** Sizes that earn a modifier class — `md` is the default and carries none. */\nconst SIZES: readonly WrTextareaSize[] = ['sm', 'lg'];\n\n/** Directions the corner grip can advertise. It never renders for `none`. */\nconst RESIZE_DIRECTIONS: readonly WrTextareaResize[] = ['vertical', 'horizontal', 'both'];\n\n/**\n * Test harness for `<wr-textarea>` — the multi-line COMPONENT.\n *\n * Not to be confused with `WrInputHarness` (`ngwr/input/testing`), which drives\n * `[wrInput]` — the directive form, applied to a real `<textarea>` the consumer\n * wrote themselves. Pick by the markup: `<wr-textarea …>` is this harness,\n * `<textarea wrInput>` is that one. They are not interchangeable, because here\n * the native element is INSIDE the component: the value, the name and every\n * native state below are read off `.wr-textarea__native` (the host carries only\n * the `wr-textarea--*` modifiers), and the accessible name has to be forwarded\n * onto it — a `<label for>` in the consumer's template cannot reach inside.\n *\n * Two things this deliberately does NOT offer:\n *\n * - **A character counter or `maxlength`.** `<wr-textarea>` has neither — it\n * forwards only `placeholder`, `rows`, `readonly`, `disabled`, `value` and the\n * resolved `aria-label` to the native element, so there is no counter to read, and a\n * `maxlength` attribute written on `<wr-textarea>` lands on the wrapper where\n * nothing consumes it. A `getMaxLength()` could only ever answer `null`, which\n * would read as \"no limit set\" rather than \"no limit possible\". Cap the text in\n * the model instead.\n * - **A height, or a grip drag.** Autosize fits from `scrollHeight`, and the grip\n * is driven by pointer coordinates. A DOM with no layout reports 0 for both, so\n * either method would answer confidently and wrongly. What survives without\n * layout is who OWNS the height ({@link hasFittedHeight}) and which direction\n * the grip advertises ({@link getResizeDirection}); the grip is also\n * `aria-hidden` and has no keyboard path, so there is no accessible route for a\n * harness to drive it down.\n *\n * @example\n * ```ts\n * const notes = await loader.getHarness(WrTextareaHarness.with({ placeholder: 'Notes' }));\n *\n * await notes.setValue('first draft');\n * expect(await notes.getValue()).toBe('first draft');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTextareaHarness extends ComponentHarness {\n static hostSelector = 'wr-textarea';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTextareaHarnessFilters = {}): HarnessPredicate<WrTextareaHarness> {\n return new HarnessPredicate(WrTextareaHarness, options)\n .addOption('value', options.value, (harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value))\n .addOption('placeholder', options.placeholder, (harness, placeholder) =>\n HarnessPredicate.stringMatches(harness.getPlaceholder(), placeholder)\n )\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly native = this.locatorFor('textarea.wr-textarea__native');\n\n /**\n * The text currently in the field.\n *\n * The `value` PROPERTY, never the element's text: a `<textarea>`'s text content\n * is only its initial content, and this one is bound with `[value]`, so the\n * text content stays empty for the field's whole life no matter what is typed.\n */\n async getValue(): Promise<string> {\n return (await this.native()).getProperty<string>('value');\n }\n\n /**\n * Type a value in, the way a user would as far as the component can tell.\n *\n * No `change` is dispatched: `sendKeys` fires one `input` per character (plus the\n * key events around it), and `input` is the only value event the component\n * listens to. It hands the result out through the `value` model, so signal forms,\n * `[formField]`, `[(ngModel)]` and `[(value)]` all hear that single write.\n * `WrInputHarness` dispatches a `change` on top for the directive form; that is\n * belt-and-braces rather than a requirement, since Angular's own accessors for\n * text controls listen to `input` too.\n *\n * Throws on a disabled or read-only field — see {@link assertWritable}.\n */\n async setValue(value: string): Promise<void> {\n const native = await this.assertWritable('setValue');\n\n await native.clear();\n // `sendKeys('')` throws \"No keys have been specified\", so an empty write is\n // the clear on its own — which is exactly what emptying a field produces.\n if (value) await native.sendKeys(value);\n }\n\n /** Empty the field, dispatching the same `input` a user's last backspace would. */\n async clear(): Promise<void> {\n await (await this.assertWritable('clear')).clear();\n }\n\n /** The placeholder text, or `''` when the field has none. */\n async getPlaceholder(): Promise<string> {\n return (await this.native()).getProperty<string>('placeholder');\n }\n\n /**\n * The accessible name the field answers to, or `null` when it has none.\n *\n * Read from the native element's `aria-label`, which is the only place a name\n * can live for this component: the `<textarea>` is inside it, so a\n * `<label for>` outside cannot reach it and an `aria-label` on `<wr-textarea>`\n * names the wrapper rather than the control. The component forwards `ariaLabel`\n * and falls back to `placeholder` — and an empty placeholder is treated as no\n * name at all rather than an empty one, so this answers `null`, not `''`.\n */\n async getLabel(): Promise<string | null> {\n return (await this.native()).getAttribute('aria-label');\n }\n\n /** The visible row count (`rows`) — the field's height until autosize takes over. */\n async getRows(): Promise<number> {\n return (await this.native()).getProperty<number>('rows');\n }\n\n /** The control size. `md` is the default and earns no modifier, so a bare host IS `md`. */\n async getSize(): Promise<WrTextareaSize> {\n const host = await this.host();\n for (const size of SIZES) {\n if (await host.hasClass(`wr-textarea--${size}`)) return size;\n }\n return 'md';\n }\n\n /**\n * Whether the field refuses input.\n *\n * The native element's `disabled` property, not the host's `--disabled` class:\n * the property is what actually blocks typing and what the accessibility tree\n * reports, while the class only paints it.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.native()).getProperty<boolean>('disabled');\n }\n\n /** Whether the field shows its value but refuses edits (`readonly`). */\n async isReadonly(): Promise<boolean> {\n return (await this.native()).getProperty<boolean>('readOnly');\n }\n\n /**\n * Whether the field is marked invalid.\n *\n * The native element is checked first, because that is the element a screen\n * reader reports on — but `<wr-textarea>` forwards nothing here (it takes no\n * `ariaInvalid` input and, unlike `[wrInput]`, does not read the surrounding\n * `<wr-form-field>`), so a consumer marks the field by binding `aria-invalid`\n * on the host. Both are read for that reason, and a native `aria-invalid=\"false\"`\n * wins over the wrapper rather than falling through to it.\n */\n async isInvalid(): Promise<boolean> {\n const onNative = await (await this.native()).getAttribute('aria-invalid');\n if (onNative !== null) return onNative === 'true';\n\n return (await (await this.host()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * Whether the field grows with its content (`autosize`).\n *\n * Read from `wr-textarea--no-resize`, which is named for what it does to the\n * grip but is added by `autosize` and by nothing else — `[resizable]=\"false\"`\n * takes the grip away without it.\n */\n async isAutosizing(): Promise<boolean> {\n return (await this.host()).hasClass('wr-textarea--no-resize');\n }\n\n /**\n * Whether autosize currently OWNS the field's height: the inline `height` it\n * writes on the native element, and hands back when `autosize` goes off.\n *\n * The fitted number itself is not exposed — see the class docs. What is worth\n * asserting is the handover: a field left frozen at the last fitted height\n * ignores `rows` from then on, which reads as the input having done nothing.\n *\n * An inline `height` a consumer wrote themselves is indistinguishable from a\n * fitted one; pair this with {@link isAutosizing} when that matters.\n */\n async hasFittedHeight(): Promise<boolean> {\n const style = (await (await this.native()).getAttribute('style')) ?? '';\n return /(?:^|;)\\s*height\\s*:/.test(style);\n }\n\n /**\n * The direction the corner grip will drag in, or `'none'` when there is no grip\n * to drag — which is any of `[resizable]=\"false\"`, `autosize` or `disabled`.\n * {@link isAutosizing} and {@link isDisabled} tell those three apart.\n */\n async getResizeDirection(): Promise<WrTextareaResize> {\n const grip = await this.locatorForOptional('.wr-textarea__resize')();\n if (!grip) return 'none';\n\n for (const direction of RESIZE_DIRECTIONS) {\n if (await grip.hasClass(`wr-textarea__resize--${direction}`)) return direction;\n }\n\n throw new Error(\n 'WrTextareaHarness.getResizeDirection(): the grip carries no ' +\n '`wr-textarea__resize--<direction>` class. Every rendered grip sets one, so the element ' +\n 'matched is probably not an ngwr textarea grip.'\n );\n }\n\n /** Move keyboard focus onto the native element — the host itself is not focusable. */\n async focus(): Promise<void> {\n return (await this.native()).focus();\n }\n\n /** Take focus away, which is what makes the component emit `touch`. */\n async blur(): Promise<void> {\n return (await this.native()).blur();\n }\n\n async isFocused(): Promise<boolean> {\n return (await this.native()).isFocused();\n }\n\n /**\n * The native element, or a targeted failure when writing to it would prove\n * nothing.\n *\n * A browser drops every keystroke aimed at a disabled or read-only field, but a\n * DOM with no input gating accepts them and the model updates — so a harness\n * that typed anyway would hand a consumer a green spec for behaviour that\n * cannot happen in their app.\n */\n private async assertWritable(method: string): Promise<TestElement> {\n if (await this.isDisabled()) {\n throw new Error(\n `WrTextareaHarness.${method}(): the textarea is disabled, so a user could not type here. ` +\n 'Enable it first, or assert isDisabled() instead.'\n );\n }\n if (await this.isReadonly()) {\n throw new Error(\n `WrTextareaHarness.${method}(): the textarea is read-only, so a user could not type here. ` +\n 'Write through the bound model instead, or assert isReadonly().'\n );\n }\n\n return this.native();\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-textarea-testing.mjs","sources":["../../../projects/lib/textarea/testing/wr-textarea-harness.ts","../../../projects/lib/textarea/testing/ngwr-textarea-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, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrTextareaResize, WrTextareaSize } from 'ngwr/textarea';\n\nimport type { WrTextareaHarnessFilters } from './interfaces';\n\n/** Sizes that earn a modifier class — `md` is the default and carries none. */\nconst SIZES: readonly WrTextareaSize[] = ['sm', 'lg'];\n\n/** Directions the corner grip can advertise. It never renders for `none`. */\nconst RESIZE_DIRECTIONS: readonly WrTextareaResize[] = ['vertical', 'horizontal', 'both'];\n\n/**\n * Test harness for `<wr-textarea>` — the multi-line COMPONENT.\n *\n * Not to be confused with `WrInputHarness` (`ngwr/input/testing`), which drives\n * `[wrInput]` — the directive form, applied to a real `<textarea>` the consumer\n * wrote themselves. Pick by the markup: `<wr-textarea …>` is this harness,\n * `<textarea wrInput>` is that one. They are not interchangeable, because here\n * the native element is INSIDE the component: the value, the name and every\n * native state below are read off `.wr-textarea__native` (the host carries only\n * the `wr-textarea--*` modifiers), and the accessible name has to be forwarded\n * onto it — a `<label for>` in the consumer's template cannot reach inside.\n *\n * Two things this deliberately does NOT offer:\n *\n * - **A character counter or `maxlength`.** `<wr-textarea>` has neither — it\n * forwards only `placeholder`, `rows`, `readonly`, `disabled`, `value` and the\n * resolved `aria-label` to the native element, so there is no counter to read, and a\n * `maxlength` attribute written on `<wr-textarea>` lands on the wrapper where\n * nothing consumes it. A `getMaxLength()` could only ever answer `null`, which\n * would read as \"no limit set\" rather than \"no limit possible\". Cap the text in\n * the model instead.\n * - **A height, or a grip drag.** Autosize fits from `scrollHeight`, and the grip\n * is driven by pointer coordinates. A DOM with no layout reports 0 for both, so\n * either method would answer confidently and wrongly. What survives without\n * layout is who OWNS the height ({@link hasFittedHeight}) and which direction\n * the grip advertises ({@link getResizeDirection}); the grip is also\n * `aria-hidden` and has no keyboard path, so there is no accessible route for a\n * harness to drive it down.\n *\n * @example\n * ```ts\n * const notes = await loader.getHarness(WrTextareaHarness.with({ placeholder: 'Notes' }));\n *\n * await notes.setValue('first draft');\n * expect(await notes.getValue()).toBe('first draft');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTextareaHarness extends ComponentHarness {\n static hostSelector = 'wr-textarea';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTextareaHarnessFilters = {}): HarnessPredicate<WrTextareaHarness> {\n return new HarnessPredicate(WrTextareaHarness, options)\n .addOption('value', options.value, (harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value))\n .addOption('placeholder', options.placeholder, (harness, placeholder) =>\n HarnessPredicate.stringMatches(harness.getPlaceholder(), placeholder)\n )\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly native = this.locatorFor('textarea.wr-textarea__native');\n\n /**\n * The text currently in the field.\n *\n * The `value` PROPERTY, never the element's text: a `<textarea>`'s text content\n * is only its initial content, and this one is bound with `[value]`, so the\n * text content stays empty for the field's whole life no matter what is typed.\n */\n async getValue(): Promise<string> {\n return (await this.native()).getProperty<string>('value');\n }\n\n /**\n * Type a value in, the way a user would as far as the component can tell.\n *\n * No `change` is dispatched: `sendKeys` fires one `input` per character (plus the\n * key events around it), and `input` is the only value event the component\n * listens to. It hands the result out through the `value` model, so signal forms,\n * `[formField]`, `[(ngModel)]` and `[(value)]` all hear that single write.\n * `WrInputHarness` dispatches a `change` on top for the directive form; that is\n * belt-and-braces rather than a requirement, since Angular's own accessors for\n * text controls listen to `input` too.\n *\n * Throws on a disabled or read-only field — see {@link assertWritable}.\n */\n async setValue(value: string): Promise<void> {\n const native = await this.assertWritable('setValue');\n\n await native.clear();\n // `sendKeys('')` throws \"No keys have been specified\", so an empty write is\n // the clear on its own — which is exactly what emptying a field produces.\n if (value) await native.sendKeys(value);\n }\n\n /** Empty the field, dispatching the same `input` a user's last backspace would. */\n async clear(): Promise<void> {\n await (await this.assertWritable('clear')).clear();\n }\n\n /** The placeholder text, or `''` when the field has none. */\n async getPlaceholder(): Promise<string> {\n return (await this.native()).getProperty<string>('placeholder');\n }\n\n /**\n * The accessible name the field answers to, or `null` when it has none.\n *\n * Read from the native element's `aria-label`, which is the only place a name\n * can live for this component: the `<textarea>` is inside it, so a\n * `<label for>` outside cannot reach it and an `aria-label` on `<wr-textarea>`\n * names the wrapper rather than the control. The component forwards `ariaLabel`\n * and falls back to `placeholder` — and an empty placeholder is treated as no\n * name at all rather than an empty one, so this answers `null`, not `''`.\n */\n async getLabel(): Promise<string | null> {\n return (await this.native()).getAttribute('aria-label');\n }\n\n /** The visible row count (`rows`) — the field's height until autosize takes over. */\n async getRows(): Promise<number> {\n return (await this.native()).getProperty<number>('rows');\n }\n\n /** The control size. `md` is the default and earns no modifier, so a bare host IS `md`. */\n async getSize(): Promise<WrTextareaSize> {\n const host = await this.host();\n for (const size of SIZES) {\n if (await host.hasClass(`wr-textarea--${size}`)) return size;\n }\n return 'md';\n }\n\n /**\n * Whether the field refuses input.\n *\n * The native element's `disabled` property, not the host's `--disabled` class:\n * the property is what actually blocks typing and what the accessibility tree\n * reports, while the class only paints it.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.native()).getProperty<boolean>('disabled');\n }\n\n /** Whether the field shows its value but refuses edits (`readonly`). */\n async isReadonly(): Promise<boolean> {\n return (await this.native()).getProperty<boolean>('readOnly');\n }\n\n /**\n * Whether the field is marked invalid.\n *\n * The native element is checked first, because that is the element a screen\n * reader reports on — but `<wr-textarea>` forwards nothing here (it takes no\n * `ariaInvalid` input and, unlike `[wrInput]`, does not read the surrounding\n * `<wr-form-field>`), so a consumer marks the field by binding `aria-invalid`\n * on the host. Both are read for that reason, and a native `aria-invalid=\"false\"`\n * wins over the wrapper rather than falling through to it.\n */\n async isInvalid(): Promise<boolean> {\n const onNative = await (await this.native()).getAttribute('aria-invalid');\n if (onNative !== null) return onNative === 'true';\n\n return (await (await this.host()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * Whether the field grows with its content (`autosize`).\n *\n * Read from `wr-textarea--no-resize`, which is named for what it does to the\n * grip but is added by `autosize` and by nothing else — `[resizable]=\"false\"`\n * takes the grip away without it.\n */\n async isAutosizing(): Promise<boolean> {\n return (await this.host()).hasClass('wr-textarea--no-resize');\n }\n\n /**\n * Whether autosize currently OWNS the field's height: the inline `height` it\n * writes on the native element, and hands back when `autosize` goes off.\n *\n * The fitted number itself is not exposed — see the class docs. What is worth\n * asserting is the handover: a field left frozen at the last fitted height\n * ignores `rows` from then on, which reads as the input having done nothing.\n *\n * An inline `height` a consumer wrote themselves is indistinguishable from a\n * fitted one; pair this with {@link isAutosizing} when that matters.\n */\n async hasFittedHeight(): Promise<boolean> {\n const style = (await (await this.native()).getAttribute('style')) ?? '';\n return /(?:^|;)\\s*height\\s*:/.test(style);\n }\n\n /**\n * The direction the corner grip will drag in, or `'none'` when there is no grip\n * to drag — which is any of `[resizable]=\"false\"`, `autosize` or `disabled`.\n * {@link isAutosizing} and {@link isDisabled} tell those three apart.\n */\n async getResizeDirection(): Promise<WrTextareaResize> {\n const grip = await this.locatorForOptional('.wr-textarea__resize')();\n if (!grip) return 'none';\n\n for (const direction of RESIZE_DIRECTIONS) {\n if (await grip.hasClass(`wr-textarea__resize--${direction}`)) return direction;\n }\n\n throw new Error(\n 'WrTextareaHarness.getResizeDirection(): the grip carries no ' +\n '`wr-textarea__resize--<direction>` class. Every rendered grip sets one, so the element ' +\n 'matched is probably not an ngwr textarea grip.'\n );\n }\n\n /** Move keyboard focus onto the native element — the host itself is not focusable. */\n async focus(): Promise<void> {\n return (await this.native()).focus();\n }\n\n /** Take focus away, which is what makes the component emit `touch`. */\n async blur(): Promise<void> {\n return (await this.native()).blur();\n }\n\n async isFocused(): Promise<boolean> {\n return (await this.native()).isFocused();\n }\n\n /**\n * The native element, or a targeted failure when writing to it would prove\n * nothing.\n *\n * A browser drops every keystroke aimed at a disabled or read-only field, but a\n * DOM with no input gating accepts them and the model updates — so a harness\n * that typed anyway would hand a consumer a green spec for behaviour that\n * cannot happen in their app.\n */\n private async assertWritable(method: string): Promise<TestElement> {\n if (await this.isDisabled()) {\n throw new Error(\n `WrTextareaHarness.${method}(): the textarea is disabled, so a user could not type here. ` +\n 'Enable it first, or assert isDisabled() instead.'\n );\n }\n if (await this.isReadonly()) {\n throw new Error(\n `WrTextareaHarness.${method}(): the textarea is read-only, so a user could not type here. ` +\n 'Write through the bound model instead, or assert isReadonly().'\n );\n }\n\n return this.native();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAQH;AACA,MAAM,KAAK,GAA8B,CAAC,IAAI,EAAE,IAAI,CAAC;AAErD;AACA,MAAM,iBAAiB,GAAgC,CAAC,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;AACG,MAAO,iBAAkB,SAAQ,gBAAgB,CAAA;AACrD,IAAA,OAAO,YAAY,GAAG,aAAa;;AAGnC,IAAA,OAAO,IAAI,CAAC,OAAA,GAAoC,EAAE,EAAA;AAChD,QAAA,OAAO,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO;aACnD,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;aAC/G,SAAS,CAAC,aAAa,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,WAAW,KAClE,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,WAAW,CAAC;aAEtE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;aAC/G,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,MAAM,OAAO,CAAC,UAAU,EAAE,MAAM,QAAQ,CAAC;IACpH;AAEiB,IAAA,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,8BAA8B,CAAC;AAEzE;;;;;;AAMG;AACH,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAS,OAAO,CAAC;IAC3D;AAEA;;;;;;;;;;;;AAYG;IACH,MAAM,QAAQ,CAAC,KAAa,EAAA;QAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;AAEpD,QAAA,MAAM,MAAM,CAAC,KAAK,EAAE;;;AAGpB,QAAA,IAAI,KAAK;AAAE,YAAA,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;IACzC;;AAGA,IAAA,MAAM,KAAK,GAAA;AACT,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE;IACpD;;AAGA,IAAA,MAAM,cAAc,GAAA;AAClB,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAS,aAAa,CAAC;IACjE;AAEA;;;;;;;;;AASG;AACH,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC;IACzD;;AAGA,IAAA,MAAM,OAAO,GAAA;AACX,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAS,MAAM,CAAC;IAC1D;;AAGA,IAAA,MAAM,OAAO,GAAA;AACX,QAAA,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE;AAC9B,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAA,aAAA,EAAgB,IAAI,EAAE,CAAC;AAAE,gBAAA,OAAO,IAAI;QAC9D;AACA,QAAA,OAAO,IAAI;IACb;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,UAAU,GAAA;AACd,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAU,UAAU,CAAC;IAC/D;;AAGA,IAAA,MAAM,UAAU,GAAA;AACd,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAU,UAAU,CAAC;IAC/D;AAEA;;;;;;;;;AASG;AACH,IAAA,MAAM,SAAS,GAAA;AACb,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC;QACzE,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,QAAQ,KAAK,MAAM;AAEjD,QAAA,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,MAAM,MAAM;IAC5E;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,YAAY,GAAA;AAChB,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,wBAAwB,CAAC;IAC/D;AAEA;;;;;;;;;;AAUG;AACH,IAAA,MAAM,eAAe,GAAA;AACnB,QAAA,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE;AACvE,QAAA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;IAC3C;AAEA;;;;AAIG;AACH,IAAA,MAAM,kBAAkB,GAAA;QACtB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,EAAE;AACpE,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,MAAM;AAExB,QAAA,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE;YACzC,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAA,qBAAA,EAAwB,SAAS,EAAE,CAAC;AAAE,gBAAA,OAAO,SAAS;QAChF;QAEA,MAAM,IAAI,KAAK,CACb,8DAA8D;YAC5D,yFAAyF;AACzF,YAAA,gDAAgD,CACnD;IACH;;AAGA,IAAA,MAAM,KAAK,GAAA;QACT,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE;IACtC;;AAGA,IAAA,MAAM,IAAI,GAAA;QACR,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE;IACrC;AAEA,IAAA,MAAM,SAAS,GAAA;QACb,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE;IAC1C;AAEA;;;;;;;;AAQG;IACK,MAAM,cAAc,CAAC,MAAc,EAAA;AACzC,QAAA,IAAI,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE;AAC3B,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,kBAAA,EAAqB,MAAM,CAAA,6DAAA,CAA+D;AACxF,gBAAA,kDAAkD,CACrD;QACH;AACA,QAAA,IAAI,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE;AAC3B,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,kBAAA,EAAqB,MAAM,CAAA,8DAAA,CAAgE;AACzF,gBAAA,gEAAgE,CACnE;QACH;AAEA,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE;IACtB;;;ACvQF;;AAEG;;"}
|