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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-segmented-testing.mjs","sources":["../../../projects/lib/segmented/testing/wr-segmented-option-harness.ts","../../../projects/lib/segmented/testing/wr-segmented-harness.ts","../../../projects/lib/segmented/testing/ngwr-segmented-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSegmentedOptionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one segment of a `<wr-segmented>` — an option, not a control.\n *\n * A segment is a real `<button type=\"button\">` carrying `aria-pressed`, so it is a\n * TOGGLE BUTTON to a screen reader: it announces its own pressed state and its own\n * name, and it is its own tab stop. That is why this exists as a harness of its own,\n * and why the questions below are worth asking per segment rather than in bulk on\n * `WrSegmentedHarness` — which is where the SELECTION lives.\n *\n * Three name-shaped methods, because a segment has three different names and only\n * one of them is what a person hears:\n *\n * - {@link getText} — the visible label, `null` for an icon-only segment.\n * - {@link getAccessibleName} — what is announced, `null` when nothing names it.\n * - {@link getLabel} — the same, but it refuses to answer `null`, so a spec\n * addressing segments by label fails where the mistake is rather than three\n * lines later.\n *\n * @example\n * ```ts\n * const segmented = await loader.getHarness(WrSegmentedHarness);\n * const [, week] = await segmented.getOptions();\n *\n * await week.select();\n * expect(await week.isSelected()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSegmentedOptionHarness extends ComponentHarness {\n /** The segment is a plain `<button>` — the BEM class is what identifies it. */\n static hostSelector = '.wr-segmented__option';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSegmentedOptionHarnessFilters = {}): HarnessPredicate<WrSegmentedOptionHarness> {\n return (\n new HarnessPredicate(WrSegmentedOptionHarness, options)\n // Matched on the nullable name, not on `getLabel()`: a predicate runs against\n // EVERY segment, so one unnamed sibling would otherwise throw a query for the\n // others out of the water.\n .addOption('label', options.label, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\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\n private readonly labelText = this.locatorForOptional('.wr-segmented__label');\n private readonly icon = this.locatorForOptional('.wr-segmented__icon');\n\n /**\n * The visible label, or `null` for an icon-only segment.\n *\n * `null` rather than `''`: the label element is only rendered when the option\n * carries a `label`, and \"no text at all\" and \"an empty label\" are different\n * mistakes to be looking at.\n */\n async getText(): Promise<string | null> {\n const label = await this.labelText();\n return label ? label.text() : null;\n }\n\n /**\n * The name a screen reader announces for this segment, or `null` when nothing\n * names it.\n *\n * The component's own fallback chain, read back off the page: a labelled segment\n * is named by its text and carries no `aria-label`; an icon-only one is named by\n * `aria-label`, which the template fills from the option's `ariaLabel` or — as a\n * last resort — from its stringified `value`.\n *\n * `null` is a finding, not a detail: an icon-only segment whose option supplied\n * neither an `ariaLabel` nor a stringifiable value is announced as a bare unnamed\n * button.\n */\n async getAccessibleName(): Promise<string | null> {\n const text = await this.getText();\n if (text !== null) return text;\n return (await this.host()).getAttribute('aria-label');\n }\n\n /**\n * The segment's accessible name, for addressing it — and a targeted failure when\n * it has none.\n *\n * {@link getAccessibleName} is the same answer without the refusal; use that one\n * when `null` is what a spec is checking for.\n */\n async getLabel(): Promise<string> {\n const name = await this.getAccessibleName();\n if (name === null) {\n throw new Error(\n 'WrSegmentedOptionHarness.getLabel(): this segment has no name — it renders no label and carries no ' +\n '`aria-label`, so a screen reader announces an unnamed button. Give the option a `label`, or an ' +\n '`ariaLabel` when it is icon-only.'\n );\n }\n return name;\n }\n\n /**\n * Whether this segment is the control's current answer.\n *\n * From `aria-pressed`, in preference to the `wr-segmented__option--selected`\n * class: both are public and they move together, but the class only recolours the\n * label while the attribute is the entire state a screen reader is given.\n */\n async isSelected(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-pressed')) === 'true';\n }\n\n /**\n * Whether the segment refuses selection — for either reason.\n *\n * The native `disabled` PROPERTY, which is what actually blocks activation. The\n * option's own `disabled` and the whole control's land on it identically, and from\n * outside they are the same thing: this segment cannot be picked. Ask\n * `WrSegmentedHarness.isDisabled()` for which of the two it was.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).getProperty<boolean>('disabled');\n }\n\n /** Whether the segment renders an icon beside (or instead of) its label. */\n async hasIcon(): Promise<boolean> {\n return (await this.icon()) !== null;\n }\n\n /**\n * The name of the segment's icon, or `null` when it renders none.\n *\n * From `data-icon`, which `<wr-icon>` reflects. The glyph itself is markup the\n * icon registry writes into the element, and an UNREGISTERED name leaves that\n * empty — so the attribute is the only place the wiring survives, and it is the\n * one that tells \"no icon asked for\" from \"icon asked for, never registered\".\n */\n async getIconName(): Promise<string | null> {\n const icon = await this.icon();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /**\n * Pick this segment.\n *\n * A click, and deliberately not a keypress: activating a `<button>` from the\n * keyboard is the BROWSER's default action for Enter / Space — the component\n * implements no key handling of its own — and jsdom implements none of it either,\n * so there is nothing here for a key to drive. A no-arg CDK click needs no\n * layout, which is the other half of why this is the drivable path.\n *\n * Nothing happens when the segment is already selected (a segmented control has no\n * untoggle), and nothing happens when it is disabled — but not for the reason the\n * `pointer-events: none` on a whole disabled control suggests, which is CSS a unit\n * test never applies. The CDK's own `click()` reads the native `disabled` PROPERTY\n * and dispatches the mouse sequence WITHOUT the final `click` event, so on a\n * disabled segment the component's handler is never reached at all (measured: zero\n * click events). Either way the value does not move and this resolves quietly, so\n * assert the result rather than assume it, or reach for\n * `WrSegmentedHarness.select()`, which says which of the two it was.\n */\n async select(): Promise<void> {\n return (await this.host()).click();\n }\n\n /** Move keyboard focus to the segment. Each segment is its own tab stop. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Take focus off the segment. */\n async blur(): Promise<void> {\n return (await this.host()).blur();\n }\n\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\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 { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSegmentedSize } from 'ngwr/segmented';\n\nimport type { WrSegmentedHarnessFilters, WrSegmentedOptionHarnessFilters } from './interfaces';\nimport { WrSegmentedOptionHarness } from './wr-segmented-option-harness';\n\n/** Sizes that earn a modifier class — `md` is the default and carries none. */\nconst SIZES: readonly WrSegmentedSize[] = ['sm', 'lg'];\n\n/**\n * Test harness for `<wr-segmented>` — the root of a two-class family with\n * {@link WrSegmentedOptionHarness}.\n *\n * **What the control actually is**, because it decides everything below: not a\n * `radiogroup`, not a `tablist` and not native radios. The host is a plain\n * `role=\"group\"` and each segment is a `<button aria-pressed>` — a row of toggle\n * buttons over one value. Three consequences a spec has to know about:\n *\n * - The selection is `aria-pressed`, on exactly one segment. There is no\n * `aria-checked`, no `aria-selected`, and no \"2 of 3\" position announced.\n * - **There is no roving tab stop.** The component authors no `tabindex` and no\n * arrow-key handler, so every enabled segment is its OWN tab stop —\n * {@link getTabStopLabels} returns a list where a radiogroup or a tablist would\n * have one entry. Focus and selection are therefore independent here:\n * {@link getFocusedLabel} implies nothing about {@link getSelectedIndex}, and\n * arrowing along the strip moves neither.\n * - Activation is the browser's own default action on a button, which jsdom does not\n * implement, so {@link select} clicks — see {@link WrSegmentedOptionHarness.select}.\n *\n * **It is a signal-forms control**, which adds two things to read here and one that\n * cannot be read at all. `[formField]` binds the component's own `value` model, and a\n * surrounding `<wr-form-field>` publishes its state onto the GROUP —\n * {@link isInvalid} and {@link getDescriptionText} answer that, on the host rather\n * than per segment, because the strip is one field and a description repeated on\n * every segment would be announced once per Tab. What is NOT here is the field's\n * `touched` flag: the component only emits `touch`, and whether anything listened is\n * a fact about the form, not about this DOM — assert it on the field.\n * {@link blur} is how a spec gets there, and it is the ONLY route: a click does not\n * mark a field touched, and neither does moving from one segment to the next.\n *\n * **There is deliberately no `getValue()` and no selection by value.** `options` is\n * a bound array of `{ value, label, … }` and the value is a generic `T` the component\n * writes nowhere — not on the host, not on a segment. The single exception is an\n * accident rather than an addressing scheme: an icon-only option with no `ariaLabel`\n * has its value stringified into the segment's `aria-label`, which for an object\n * value reads `[object Object]`. So address segments by their label\n * ({@link select}) or by position ({@link selectAt}), and assert the picked value on\n * the host's own model — the only place it exists, and the assertion that would have\n * caught the bug anyway.\n *\n * The sliding thumb is decoration (`aria-hidden`), and the four methods that mention\n * it read what the component PUBLISHES for the stylesheet — two custom properties and\n * two modifier classes — never painted geometry, which a DOM without layout does not\n * have. {@link getThumbIndex} in particular is not the selection; it is the trap that\n * method documents.\n *\n * @example\n * ```ts\n * const range = await loader.getHarness(WrSegmentedHarness.with({ label: 'Range' }));\n *\n * expect(await range.getOptionLabels()).toEqual(['Day', 'Week', 'Month']);\n * await range.select({ label: 'Week' });\n * expect(await range.getSelectedIndex()).toBe(1);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSegmentedHarness extends ComponentHarness {\n static hostSelector = 'wr-segmented';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSegmentedHarnessFilters = {}): HarnessPredicate<WrSegmentedHarness> {\n return new HarnessPredicate(WrSegmentedHarness, options)\n .addOption('label', options.label, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\n .addOption('size', options.size, async (harness, size) => (await harness.getSize()) === size)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /**\n * The role the host announces — `group`.\n *\n * Worth pinning precisely because it is the weaker of the shapes this component\n * could have had: a `group` of toggle buttons tells a screen reader that the\n * segments belong together, and nothing more. Were this to become a `radiogroup` or\n * a `tablist`, the keyboard contract would change with it (one tab stop, arrow keys\n * moving the selection), and every spec written against {@link getTabStopLabels}\n * would need to change too.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /**\n * The control's accessible name, resolved the way a screen reader resolves it: the\n * text of every element `aria-labelledby` points at, and `aria-label` only when\n * that named nothing. `null` when the consumer wired neither.\n *\n * The order is not interchangeable — `aria-labelledby` WINS in the accessible-name\n * computation — so a control carrying both is announced by the heading it points at,\n * and reporting the `aria-label` would describe a name nobody hears.\n *\n * The component ships no label input on purpose (the question is the consumer's own\n * heading), so this is the only way to check that wiring held, and a `null` on a\n * group of three unrelated-sounding buttons is a real finding.\n */\n async getAccessibleName(): Promise<string | null> {\n const host = await this.host();\n\n const labelledBy = await host.getAttribute('aria-labelledby');\n if (labelledBy !== null) {\n // Resolved from the document root: `aria-labelledby` may point anywhere on the\n // page, and the heading above a control is normally outside it.\n const root = this.documentRootLocatorFactory();\n const texts: string[] = [];\n for (const id of labelledBy.split(/\\s+/).filter(Boolean)) {\n const target = await root.locatorForOptional(`#${id}`)();\n if (target) texts.push(await target.text());\n }\n\n // A reference that resolves to nothing names nothing, and the computation falls\n // through to `aria-label` exactly as it does in a browser.\n if (texts.length) return texts.join(' ');\n }\n\n return host.getAttribute('aria-label');\n }\n\n /** This control's own segments, in DOM order. A sibling control's are not here. */\n async getOptions(filters: WrSegmentedOptionHarnessFilters = {}): Promise<WrSegmentedOptionHarness[]> {\n return this.locatorForAll(WrSegmentedOptionHarness.with(filters))();\n }\n\n /**\n * The accessible name of every segment, in DOM order — the visible labels, with an\n * icon-only segment answering with the `aria-label` that stands in for its text.\n *\n * Throws when a segment has no name at all, rather than leaving a hole in the list:\n * see {@link WrSegmentedOptionHarness.getLabel}.\n */\n async getOptionLabels(): Promise<string[]> {\n const options = await this.getOptions();\n return Promise.all(options.map(option => option.getLabel()));\n }\n\n /** The pressed segment, or `null` when nothing is selected. */\n async getSelectedOption(): Promise<WrSegmentedOptionHarness | null> {\n const [selected] = await this.getOptions({ selected: true });\n return selected ?? null;\n }\n\n /**\n * The accessible name of the pressed segment, or `null` when nothing is selected.\n *\n * `null` is also the answer for a bound value that matches no option — the control\n * holds it, no segment is pressed, and the page shows an unanswered control. See\n * {@link getThumbIndex}, which does NOT answer `null` in that state.\n */\n async getSelectedLabel(): Promise<string | null> {\n const selected = await this.getSelectedOption();\n return selected ? selected.getLabel() : null;\n }\n\n /**\n * The position of the pressed segment, or `-1` when nothing is selected.\n *\n * Derived from `aria-pressed`, which is the selection. The other index on the page —\n * the thumb's — is not; it clamps at 0 (see {@link getThumbIndex}).\n */\n async getSelectedIndex(): Promise<number> {\n const options = await this.getOptions();\n for (let index = 0; index < options.length; index++) {\n if (await options[index].isSelected()) return index;\n }\n return -1;\n }\n\n /**\n * Pick the first segment matching the filters — this is how the control's value\n * changes.\n *\n * Throws when nothing matches, naming the segments that do exist, and throws again\n * when the segment is still unpressed afterwards. That second failure has exactly\n * two causes and reports both, because a click that quietly did nothing surfaces as\n * an unrelated assertion three lines later.\n */\n async select(filters: WrSegmentedOptionHarnessFilters): Promise<void> {\n const [option] = await this.getOptions(filters);\n if (!option) {\n throw new Error(\n `WrSegmentedHarness.select(): no segment matched ${JSON.stringify(filters)}. This control offers: ` +\n `${(await this.describeOptions()).join(', ')}.`\n );\n }\n await this.pick(option, 'select');\n }\n\n /**\n * Pick the segment at a position, counting from zero — the addressing route for an\n * icon-only strip, or for one whose labels a spec would rather not hard-code.\n *\n * Throws on an index outside the control, saying how many segments there are: an\n * off-by-one that resolved to nothing would otherwise read as \"the click did not\n * take\".\n */\n async selectAt(index: number): Promise<void> {\n const options = await this.getOptions();\n const option = options[index];\n if (!option) {\n throw new Error(\n `WrSegmentedHarness.selectAt(): index ${index} is outside this control, which has ${options.length} ` +\n `segment(s): ${(await this.describeOptions()).join(', ')}.`\n );\n }\n await this.pick(option, 'selectAt');\n }\n\n /**\n * Whether the control was disabled AS A WHOLE, from the `wr-segmented--disabled`\n * modifier.\n *\n * Deliberately not \"every segment is disabled\": a control whose options each carry\n * their own `disabled` looks identical to a user, and the modifier is the only thing\n * on the page that tells the two apart. What actually blocks a click is the native\n * `disabled` each button gets either way — ask a segment\n * ({@link WrSegmentedOptionHarness.isDisabled}) for that.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-segmented--disabled');\n }\n\n /**\n * The rendered control size.\n *\n * `md` is the component's own default and earns NO modifier class, so a bare host\n * IS `md` — the absence is the answer rather than a missing one. Read from the\n * class instead of the input, which is also why this reports what was rendered:\n * unlike the form controls, `<wr-segmented>` does not resolve its size through\n * `provideWrConfig()`, so an app-wide default set there does not reach it and this\n * will say so.\n */\n async getSize(): Promise<WrSegmentedSize> {\n const host = await this.host();\n for (const size of SIZES) {\n if (await host.hasClass(`wr-segmented--${size}`)) return size;\n }\n return 'md';\n }\n\n /**\n * The accessible name of every segment a Tab press can reach, in tab order.\n *\n * A LIST, not one label, and that is the shape worth pinning: the component authors\n * no roving `tabindex`, so each enabled segment is its own tab stop and reaching the\n * third one takes three Tabs. An all-disabled control is skipped over entirely and\n * answers `[]`.\n *\n * Unnamed segments are reported as `(unnamed)` rather than throwing — a tab order is\n * still a fact worth reading on a page that has that bug in it.\n */\n async getTabStopLabels(): Promise<string[]> {\n const enabled = await this.getOptions({ disabled: false });\n return this.describe(enabled);\n }\n\n /**\n * The accessible name of the segment that currently holds focus, or `null` when\n * focus is elsewhere.\n *\n * Independent of the selection here — see the note on the class. Reported as\n * `(unnamed)` for a segment with no name, for the reason\n * {@link getTabStopLabels} gives.\n */\n async getFocusedLabel(): Promise<string | null> {\n for (const option of await this.getOptions()) {\n if (await option.isFocused()) {\n return (await option.getAccessibleName()) ?? '(unnamed)';\n }\n }\n return null;\n }\n\n /**\n * Whether the surrounding `<wr-form-field>` is reporting this control invalid, from\n * `aria-invalid` on the host.\n *\n * `false` for a control with no field around it, and `false` for a field that is\n * invalid but has not shown its copy yet: the component keys the attribute on the\n * MESSAGE existing rather than on the error, because announcing \"invalid\" while\n * pointing at nothing is worse than staying quiet. So this and\n * {@link getDescriptionText} move together, and a `true` with no description would\n * be the finding.\n */\n async isInvalid(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * The text `aria-describedby` points at — the validation copy a screen reader is\n * given for this control — or `null` when the host describes nothing.\n *\n * Resolved from the document root, because the element named may sit anywhere on\n * the page, and THROWS when the reference resolves to no element: a dangling\n * `aria-describedby` renders exactly like a working one and is announced as\n * nothing, so returning `null` for it would report the bug as the healthy state.\n */\n async getDescriptionText(): Promise<string | null> {\n const describedBy = await (await this.host()).getAttribute('aria-describedby');\n if (describedBy === null) return null;\n\n const root = this.documentRootLocatorFactory();\n const texts: string[] = [];\n for (const id of describedBy.split(/\\s+/).filter(Boolean)) {\n // `CSS.escape`, because `WrFormField.controlId` is a public input: a\n // consumer passing `controlId=\"user.email\"` makes `#user.email-errors`\n // read as \"id `user` with class `email-errors`\", which matches nothing —\n // and this method THROWS on no match, so an unescaped selector reports a\n // healthy field as the exact bug the throw exists to catch.\n const target = await root.locatorForOptional(`#${CSS.escape(id)}`)();\n if (!target) {\n throw new Error(\n `WrSegmentedHarness.getDescriptionText(): \\`aria-describedby\\` names \"${id}\", which is on no element ` +\n 'in the document — a screen reader announces nothing for it.'\n );\n }\n texts.push(await target.text());\n }\n return texts.join(' ');\n }\n\n /**\n * Take focus off the strip entirely — what marks a bound field touched, and the\n * only thing that does.\n *\n * The component emits `touch` from a `focusout` that leaves the host, so a click\n * does not reach it and neither does Tab from one segment to the next: a strip a\n * user tabbed straight past has to end up untouched, which is the case `touched`\n * exists for. Resolves quietly when nothing inside was focused, since that is\n * already the state this asks for.\n */\n async blur(): Promise<void> {\n for (const option of await this.getOptions()) {\n if (await option.isFocused()) return option.blur();\n }\n }\n\n /**\n * The index the component publishes for the thumb, from the\n * `--wr-segmented-thumb-index` custom property it sets inline.\n *\n * **Not the selection, and this is the difference that bites.** The property is\n * `Math.max(0, selectedIndex)`, so a control holding a value that matches no option\n * publishes `0` — the thumb parks under the FIRST segment and is hidden by\n * {@link isThumbVisible} instead of being moved out of the way. A harness that read\n * this to answer \"which segment is selected\" would report segment one for a control\n * with no selection at all. {@link getSelectedIndex} is the selection.\n *\n * It is also not where the thumb IS: the stylesheet turns this into a `translateX`,\n * and no measurement of that survives a DOM without layout. This is the input to\n * that, which is the part a unit test can honestly check.\n */\n async getThumbIndex(): Promise<number> {\n return this.thumbVar('--wr-segmented-thumb-index');\n }\n\n /**\n * The segment count the component publishes for the thumb, from\n * `--wr-segmented-thumb-count` — the divisor the stylesheet computes the thumb's\n * width with.\n *\n * Should always equal the number of segments, and the reason to ask is that nothing\n * else notices when it does not: a count that fell out of step with the options\n * leaves the thumb the wrong width and sliding to the wrong place, while every ARIA\n * state on the page stays correct. It is clamped to a minimum of `1`, so an empty\n * control reports `1` rather than dividing by zero.\n */\n async getThumbCount(): Promise<number> {\n return this.thumbVar('--wr-segmented-thumb-count');\n }\n\n /**\n * Whether the thumb is being shown at all.\n *\n * Read from the ABSENCE of the `wr-segmented--unselected` modifier, which is the\n * hook the stylesheet zeroes the thumb's opacity through. The computed opacity is\n * not the answer here — a unit test applies no stylesheet — so the public modifier\n * is the contract, and it is what tells \"parked under segment one\" from \"hidden\n * because nothing is selected\".\n */\n async isThumbVisible(): Promise<boolean> {\n return !(await (await this.host()).hasClass('wr-segmented--unselected'));\n }\n\n /**\n * Whether the thumb will animate its next move, from the `wr-segmented--mounted`\n * modifier that gates the transition.\n *\n * Not \"the thumb is animating now\": nothing about a running animation is visible to\n * a unit test. What this pins is the deliberate asymmetry — the class is added in\n * `afterNextRender`, so the FIRST snap to the initial value is instant and every\n * user-driven move after it slides. Deferred work like that only lands once the\n * fixture is stable, which every harness read already waits for.\n */\n async isThumbTransitionEnabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-segmented--mounted');\n }\n\n /** Click a segment and turn a click that did nothing into a failure that says why. */\n private async pick(option: WrSegmentedOptionHarness, caller: 'select' | 'selectAt'): Promise<void> {\n await option.select();\n\n if (!(await option.isSelected())) {\n const name = (await option.getAccessibleName()) ?? '(unnamed)';\n throw new Error(\n `WrSegmentedHarness.${caller}(): \"${name}\" is still unpressed after being clicked — either that ` +\n 'option carries `disabled` or the whole control does.'\n );\n }\n }\n\n /** Segment names for an error message, which must not throw on its way out. */\n private async describeOptions(): Promise<string[]> {\n return this.describe(await this.getOptions());\n }\n\n private async describe(options: readonly WrSegmentedOptionHarness[]): Promise<string[]> {\n return Promise.all(options.map(async option => (await option.getAccessibleName()) ?? '(unnamed)'));\n }\n\n /**\n * One custom property off the host's `style` ATTRIBUTE.\n *\n * Not `getCssValue()`, which is `getComputedStyle`: with the entry point's own\n * stylesheet loaded that resolves the sheet's declaration, so the method\n * answers plausibly at exactly the moment the host binding — the thing under\n * test — is what broke. The attribute holds only what the component wrote.\n */\n private async inlineVar(property: string): Promise<string> {\n const attr = (await (await this.host()).getAttribute('style')) ?? '';\n const found = new RegExp(`(?:^|;)\\\\s*${property}\\\\s*:\\\\s*([^;]+)`).exec(attr);\n return (found?.[1] ?? '').trim();\n }\n\n /** One of the thumb's inline custom properties, as a number. */\n private async thumbVar(property: string): Promise<number> {\n const raw = await this.inlineVar(property);\n const value = Number.parseInt(raw, 10);\n if (Number.isNaN(value)) {\n throw new Error(\n `WrSegmentedHarness: could not read \\`${property}\\`, which computed to \"${raw}\". The component sets it ` +\n 'inline on the host; an environment that drops custom properties cannot answer this.'\n );\n }\n return value;\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-segmented-testing.mjs","sources":["../../../projects/lib/segmented/testing/wr-segmented-option-harness.ts","../../../projects/lib/segmented/testing/wr-segmented-harness.ts","../../../projects/lib/segmented/testing/ngwr-segmented-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSegmentedOptionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one segment of a `<wr-segmented>` — an option, not a control.\n *\n * A segment is a real `<button type=\"button\">` carrying `aria-pressed`, so it is a\n * TOGGLE BUTTON to a screen reader: it announces its own pressed state and its own\n * name, and it is its own tab stop. That is why this exists as a harness of its own,\n * and why the questions below are worth asking per segment rather than in bulk on\n * `WrSegmentedHarness` — which is where the SELECTION lives.\n *\n * Three name-shaped methods, because a segment has three different names and only\n * one of them is what a person hears:\n *\n * - {@link getText} — the visible label, `null` for an icon-only segment.\n * - {@link getAccessibleName} — what is announced, `null` when nothing names it.\n * - {@link getLabel} — the same, but it refuses to answer `null`, so a spec\n * addressing segments by label fails where the mistake is rather than three\n * lines later.\n *\n * @example\n * ```ts\n * const segmented = await loader.getHarness(WrSegmentedHarness);\n * const [, week] = await segmented.getOptions();\n *\n * await week.select();\n * expect(await week.isSelected()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSegmentedOptionHarness extends ComponentHarness {\n /** The segment is a plain `<button>` — the BEM class is what identifies it. */\n static hostSelector = '.wr-segmented__option';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSegmentedOptionHarnessFilters = {}): HarnessPredicate<WrSegmentedOptionHarness> {\n return (\n new HarnessPredicate(WrSegmentedOptionHarness, options)\n // Matched on the nullable name, not on `getLabel()`: a predicate runs against\n // EVERY segment, so one unnamed sibling would otherwise throw a query for the\n // others out of the water.\n .addOption('label', options.label, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\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\n private readonly labelText = this.locatorForOptional('.wr-segmented__label');\n private readonly icon = this.locatorForOptional('.wr-segmented__icon');\n\n /**\n * The visible label, or `null` for an icon-only segment.\n *\n * `null` rather than `''`: the label element is only rendered when the option\n * carries a `label`, and \"no text at all\" and \"an empty label\" are different\n * mistakes to be looking at.\n */\n async getText(): Promise<string | null> {\n const label = await this.labelText();\n return label ? label.text() : null;\n }\n\n /**\n * The name a screen reader announces for this segment, or `null` when nothing\n * names it.\n *\n * The component's own fallback chain, read back off the page: a labelled segment\n * is named by its text and carries no `aria-label`; an icon-only one is named by\n * `aria-label`, which the template fills from the option's `ariaLabel` or — as a\n * last resort — from its stringified `value`.\n *\n * `null` is a finding, not a detail: an icon-only segment whose option supplied\n * neither an `ariaLabel` nor a stringifiable value is announced as a bare unnamed\n * button.\n */\n async getAccessibleName(): Promise<string | null> {\n const text = await this.getText();\n if (text !== null) return text;\n return (await this.host()).getAttribute('aria-label');\n }\n\n /**\n * The segment's accessible name, for addressing it — and a targeted failure when\n * it has none.\n *\n * {@link getAccessibleName} is the same answer without the refusal; use that one\n * when `null` is what a spec is checking for.\n */\n async getLabel(): Promise<string> {\n const name = await this.getAccessibleName();\n if (name === null) {\n throw new Error(\n 'WrSegmentedOptionHarness.getLabel(): this segment has no name — it renders no label and carries no ' +\n '`aria-label`, so a screen reader announces an unnamed button. Give the option a `label`, or an ' +\n '`ariaLabel` when it is icon-only.'\n );\n }\n return name;\n }\n\n /**\n * Whether this segment is the control's current answer.\n *\n * From `aria-pressed`, in preference to the `wr-segmented__option--selected`\n * class: both are public and they move together, but the class only recolours the\n * label while the attribute is the entire state a screen reader is given.\n */\n async isSelected(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-pressed')) === 'true';\n }\n\n /**\n * Whether the segment refuses selection — for either reason.\n *\n * The native `disabled` PROPERTY, which is what actually blocks activation. The\n * option's own `disabled` and the whole control's land on it identically, and from\n * outside they are the same thing: this segment cannot be picked. Ask\n * `WrSegmentedHarness.isDisabled()` for which of the two it was.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).getProperty<boolean>('disabled');\n }\n\n /** Whether the segment renders an icon beside (or instead of) its label. */\n async hasIcon(): Promise<boolean> {\n return (await this.icon()) !== null;\n }\n\n /**\n * The name of the segment's icon, or `null` when it renders none.\n *\n * From `data-icon`, which `<wr-icon>` reflects. The glyph itself is markup the\n * icon registry writes into the element, and an UNREGISTERED name leaves that\n * empty — so the attribute is the only place the wiring survives, and it is the\n * one that tells \"no icon asked for\" from \"icon asked for, never registered\".\n */\n async getIconName(): Promise<string | null> {\n const icon = await this.icon();\n return icon ? icon.getAttribute('data-icon') : null;\n }\n\n /**\n * Pick this segment.\n *\n * A click, and deliberately not a keypress: activating a `<button>` from the\n * keyboard is the BROWSER's default action for Enter / Space — the component\n * implements no key handling of its own — and jsdom implements none of it either,\n * so there is nothing here for a key to drive. A no-arg CDK click needs no\n * layout, which is the other half of why this is the drivable path.\n *\n * Nothing happens when the segment is already selected (a segmented control has no\n * untoggle), and nothing happens when it is disabled — but not for the reason the\n * `pointer-events: none` on a whole disabled control suggests, which is CSS a unit\n * test never applies. The CDK's own `click()` reads the native `disabled` PROPERTY\n * and dispatches the mouse sequence WITHOUT the final `click` event, so on a\n * disabled segment the component's handler is never reached at all (measured: zero\n * click events). Either way the value does not move and this resolves quietly, so\n * assert the result rather than assume it, or reach for\n * `WrSegmentedHarness.select()`, which says which of the two it was.\n */\n async select(): Promise<void> {\n return (await this.host()).click();\n }\n\n /** Move keyboard focus to the segment. Each segment is its own tab stop. */\n async focus(): Promise<void> {\n return (await this.host()).focus();\n }\n\n /** Take focus off the segment. */\n async blur(): Promise<void> {\n return (await this.host()).blur();\n }\n\n async isFocused(): Promise<boolean> {\n return (await this.host()).isFocused();\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 { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrSegmentedSize } from 'ngwr/segmented';\n\nimport type { WrSegmentedHarnessFilters, WrSegmentedOptionHarnessFilters } from './interfaces';\nimport { WrSegmentedOptionHarness } from './wr-segmented-option-harness';\n\n/** Sizes that earn a modifier class — `md` is the default and carries none. */\nconst SIZES: readonly WrSegmentedSize[] = ['sm', 'lg'];\n\n/**\n * Test harness for `<wr-segmented>` — the root of a two-class family with\n * {@link WrSegmentedOptionHarness}.\n *\n * **What the control actually is**, because it decides everything below: not a\n * `radiogroup`, not a `tablist` and not native radios. The host is a plain\n * `role=\"group\"` and each segment is a `<button aria-pressed>` — a row of toggle\n * buttons over one value. Three consequences a spec has to know about:\n *\n * - The selection is `aria-pressed`, on exactly one segment. There is no\n * `aria-checked`, no `aria-selected`, and no \"2 of 3\" position announced.\n * - **There is no roving tab stop.** The component authors no `tabindex` and no\n * arrow-key handler, so every enabled segment is its OWN tab stop —\n * {@link getTabStopLabels} returns a list where a radiogroup or a tablist would\n * have one entry. Focus and selection are therefore independent here:\n * {@link getFocusedLabel} implies nothing about {@link getSelectedIndex}, and\n * arrowing along the strip moves neither.\n * - Activation is the browser's own default action on a button, which jsdom does not\n * implement, so {@link select} clicks — see {@link WrSegmentedOptionHarness.select}.\n *\n * **It is a signal-forms control**, which adds two things to read here and one that\n * cannot be read at all. `[formField]` binds the component's own `value` model, and a\n * surrounding `<wr-form-field>` publishes its state onto the GROUP —\n * {@link isInvalid} and {@link getDescriptionText} answer that, on the host rather\n * than per segment, because the strip is one field and a description repeated on\n * every segment would be announced once per Tab. What is NOT here is the field's\n * `touched` flag: the component only emits `touch`, and whether anything listened is\n * a fact about the form, not about this DOM — assert it on the field.\n * {@link blur} is how a spec gets there, and it is the ONLY route: a click does not\n * mark a field touched, and neither does moving from one segment to the next.\n *\n * **There is deliberately no `getValue()` and no selection by value.** `options` is\n * a bound array of `{ value, label, … }` and the value is a generic `T` the component\n * writes nowhere — not on the host, not on a segment. The single exception is an\n * accident rather than an addressing scheme: an icon-only option with no `ariaLabel`\n * has its value stringified into the segment's `aria-label`, which for an object\n * value reads `[object Object]`. So address segments by their label\n * ({@link select}) or by position ({@link selectAt}), and assert the picked value on\n * the host's own model — the only place it exists, and the assertion that would have\n * caught the bug anyway.\n *\n * The sliding thumb is decoration (`aria-hidden`), and the four methods that mention\n * it read what the component PUBLISHES for the stylesheet — two custom properties and\n * two modifier classes — never painted geometry, which a DOM without layout does not\n * have. {@link getThumbIndex} in particular is not the selection; it is the trap that\n * method documents.\n *\n * @example\n * ```ts\n * const range = await loader.getHarness(WrSegmentedHarness.with({ label: 'Range' }));\n *\n * expect(await range.getOptionLabels()).toEqual(['Day', 'Week', 'Month']);\n * await range.select({ label: 'Week' });\n * expect(await range.getSelectedIndex()).toBe(1);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSegmentedHarness extends ComponentHarness {\n static hostSelector = 'wr-segmented';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrSegmentedHarnessFilters = {}): HarnessPredicate<WrSegmentedHarness> {\n return new HarnessPredicate(WrSegmentedHarness, options)\n .addOption('label', options.label, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\n .addOption('size', options.size, async (harness, size) => (await harness.getSize()) === size)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n /**\n * The role the host announces — `group`.\n *\n * Worth pinning precisely because it is the weaker of the shapes this component\n * could have had: a `group` of toggle buttons tells a screen reader that the\n * segments belong together, and nothing more. Were this to become a `radiogroup` or\n * a `tablist`, the keyboard contract would change with it (one tab stop, arrow keys\n * moving the selection), and every spec written against {@link getTabStopLabels}\n * would need to change too.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /**\n * The control's accessible name, resolved the way a screen reader resolves it: the\n * text of every element `aria-labelledby` points at, and `aria-label` only when\n * that named nothing. `null` when the consumer wired neither.\n *\n * The order is not interchangeable — `aria-labelledby` WINS in the accessible-name\n * computation — so a control carrying both is announced by the heading it points at,\n * and reporting the `aria-label` would describe a name nobody hears.\n *\n * The component ships no label input on purpose (the question is the consumer's own\n * heading), so this is the only way to check that wiring held, and a `null` on a\n * group of three unrelated-sounding buttons is a real finding.\n */\n async getAccessibleName(): Promise<string | null> {\n const host = await this.host();\n\n const labelledBy = await host.getAttribute('aria-labelledby');\n if (labelledBy !== null) {\n // Resolved from the document root: `aria-labelledby` may point anywhere on the\n // page, and the heading above a control is normally outside it.\n const root = this.documentRootLocatorFactory();\n const texts: string[] = [];\n for (const id of labelledBy.split(/\\s+/).filter(Boolean)) {\n const target = await root.locatorForOptional(`#${id}`)();\n if (target) texts.push(await target.text());\n }\n\n // A reference that resolves to nothing names nothing, and the computation falls\n // through to `aria-label` exactly as it does in a browser.\n if (texts.length) return texts.join(' ');\n }\n\n return host.getAttribute('aria-label');\n }\n\n /** This control's own segments, in DOM order. A sibling control's are not here. */\n async getOptions(filters: WrSegmentedOptionHarnessFilters = {}): Promise<WrSegmentedOptionHarness[]> {\n return this.locatorForAll(WrSegmentedOptionHarness.with(filters))();\n }\n\n /**\n * The accessible name of every segment, in DOM order — the visible labels, with an\n * icon-only segment answering with the `aria-label` that stands in for its text.\n *\n * Throws when a segment has no name at all, rather than leaving a hole in the list:\n * see {@link WrSegmentedOptionHarness.getLabel}.\n */\n async getOptionLabels(): Promise<string[]> {\n const options = await this.getOptions();\n return Promise.all(options.map(option => option.getLabel()));\n }\n\n /** The pressed segment, or `null` when nothing is selected. */\n async getSelectedOption(): Promise<WrSegmentedOptionHarness | null> {\n const [selected] = await this.getOptions({ selected: true });\n return selected ?? null;\n }\n\n /**\n * The accessible name of the pressed segment, or `null` when nothing is selected.\n *\n * `null` is also the answer for a bound value that matches no option — the control\n * holds it, no segment is pressed, and the page shows an unanswered control. See\n * {@link getThumbIndex}, which does NOT answer `null` in that state.\n */\n async getSelectedLabel(): Promise<string | null> {\n const selected = await this.getSelectedOption();\n return selected ? selected.getLabel() : null;\n }\n\n /**\n * The position of the pressed segment, or `-1` when nothing is selected.\n *\n * Derived from `aria-pressed`, which is the selection. The other index on the page —\n * the thumb's — is not; it clamps at 0 (see {@link getThumbIndex}).\n */\n async getSelectedIndex(): Promise<number> {\n const options = await this.getOptions();\n for (let index = 0; index < options.length; index++) {\n if (await options[index].isSelected()) return index;\n }\n return -1;\n }\n\n /**\n * Pick the first segment matching the filters — this is how the control's value\n * changes.\n *\n * Throws when nothing matches, naming the segments that do exist, and throws again\n * when the segment is still unpressed afterwards. That second failure has exactly\n * two causes and reports both, because a click that quietly did nothing surfaces as\n * an unrelated assertion three lines later.\n */\n async select(filters: WrSegmentedOptionHarnessFilters): Promise<void> {\n const [option] = await this.getOptions(filters);\n if (!option) {\n throw new Error(\n `WrSegmentedHarness.select(): no segment matched ${JSON.stringify(filters)}. This control offers: ` +\n `${(await this.describeOptions()).join(', ')}.`\n );\n }\n await this.pick(option, 'select');\n }\n\n /**\n * Pick the segment at a position, counting from zero — the addressing route for an\n * icon-only strip, or for one whose labels a spec would rather not hard-code.\n *\n * Throws on an index outside the control, saying how many segments there are: an\n * off-by-one that resolved to nothing would otherwise read as \"the click did not\n * take\".\n */\n async selectAt(index: number): Promise<void> {\n const options = await this.getOptions();\n const option = options[index];\n if (!option) {\n throw new Error(\n `WrSegmentedHarness.selectAt(): index ${index} is outside this control, which has ${options.length} ` +\n `segment(s): ${(await this.describeOptions()).join(', ')}.`\n );\n }\n await this.pick(option, 'selectAt');\n }\n\n /**\n * Whether the control was disabled AS A WHOLE, from the `wr-segmented--disabled`\n * modifier.\n *\n * Deliberately not \"every segment is disabled\": a control whose options each carry\n * their own `disabled` looks identical to a user, and the modifier is the only thing\n * on the page that tells the two apart. What actually blocks a click is the native\n * `disabled` each button gets either way — ask a segment\n * ({@link WrSegmentedOptionHarness.isDisabled}) for that.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-segmented--disabled');\n }\n\n /**\n * The rendered control size.\n *\n * `md` is the component's own default and earns NO modifier class, so a bare host\n * IS `md` — the absence is the answer rather than a missing one. Read from the\n * class instead of the input, which is also why this reports what was rendered:\n * unlike the form controls, `<wr-segmented>` does not resolve its size through\n * `provideWrConfig()`, so an app-wide default set there does not reach it and this\n * will say so.\n */\n async getSize(): Promise<WrSegmentedSize> {\n const host = await this.host();\n for (const size of SIZES) {\n if (await host.hasClass(`wr-segmented--${size}`)) return size;\n }\n return 'md';\n }\n\n /**\n * The accessible name of every segment a Tab press can reach, in tab order.\n *\n * A LIST, not one label, and that is the shape worth pinning: the component authors\n * no roving `tabindex`, so each enabled segment is its own tab stop and reaching the\n * third one takes three Tabs. An all-disabled control is skipped over entirely and\n * answers `[]`.\n *\n * Unnamed segments are reported as `(unnamed)` rather than throwing — a tab order is\n * still a fact worth reading on a page that has that bug in it.\n */\n async getTabStopLabels(): Promise<string[]> {\n const enabled = await this.getOptions({ disabled: false });\n return this.describe(enabled);\n }\n\n /**\n * The accessible name of the segment that currently holds focus, or `null` when\n * focus is elsewhere.\n *\n * Independent of the selection here — see the note on the class. Reported as\n * `(unnamed)` for a segment with no name, for the reason\n * {@link getTabStopLabels} gives.\n */\n async getFocusedLabel(): Promise<string | null> {\n for (const option of await this.getOptions()) {\n if (await option.isFocused()) {\n return (await option.getAccessibleName()) ?? '(unnamed)';\n }\n }\n return null;\n }\n\n /**\n * Whether the surrounding `<wr-form-field>` is reporting this control invalid, from\n * `aria-invalid` on the host.\n *\n * `false` for a control with no field around it, and `false` for a field that is\n * invalid but has not shown its copy yet: the component keys the attribute on the\n * MESSAGE existing rather than on the error, because announcing \"invalid\" while\n * pointing at nothing is worse than staying quiet. So this and\n * {@link getDescriptionText} move together, and a `true` with no description would\n * be the finding.\n */\n async isInvalid(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * The text `aria-describedby` points at — the validation copy a screen reader is\n * given for this control — or `null` when the host describes nothing.\n *\n * Resolved from the document root, because the element named may sit anywhere on\n * the page, and THROWS when the reference resolves to no element: a dangling\n * `aria-describedby` renders exactly like a working one and is announced as\n * nothing, so returning `null` for it would report the bug as the healthy state.\n */\n async getDescriptionText(): Promise<string | null> {\n const describedBy = await (await this.host()).getAttribute('aria-describedby');\n if (describedBy === null) return null;\n\n const root = this.documentRootLocatorFactory();\n const texts: string[] = [];\n for (const id of describedBy.split(/\\s+/).filter(Boolean)) {\n // `CSS.escape`, because `WrFormField.controlId` is a public input: a\n // consumer passing `controlId=\"user.email\"` makes `#user.email-errors`\n // read as \"id `user` with class `email-errors`\", which matches nothing —\n // and this method THROWS on no match, so an unescaped selector reports a\n // healthy field as the exact bug the throw exists to catch.\n const target = await root.locatorForOptional(`#${CSS.escape(id)}`)();\n if (!target) {\n throw new Error(\n `WrSegmentedHarness.getDescriptionText(): \\`aria-describedby\\` names \"${id}\", which is on no element ` +\n 'in the document — a screen reader announces nothing for it.'\n );\n }\n texts.push(await target.text());\n }\n return texts.join(' ');\n }\n\n /**\n * Take focus off the strip entirely — what marks a bound field touched, and the\n * only thing that does.\n *\n * The component emits `touch` from a `focusout` that leaves the host, so a click\n * does not reach it and neither does Tab from one segment to the next: a strip a\n * user tabbed straight past has to end up untouched, which is the case `touched`\n * exists for. Resolves quietly when nothing inside was focused, since that is\n * already the state this asks for.\n */\n async blur(): Promise<void> {\n for (const option of await this.getOptions()) {\n if (await option.isFocused()) return option.blur();\n }\n }\n\n /**\n * The index the component publishes for the thumb, from the\n * `--wr-segmented-thumb-index` custom property it sets inline.\n *\n * **Not the selection, and this is the difference that bites.** The property is\n * `Math.max(0, selectedIndex)`, so a control holding a value that matches no option\n * publishes `0` — the thumb parks under the FIRST segment and is hidden by\n * {@link isThumbVisible} instead of being moved out of the way. A harness that read\n * this to answer \"which segment is selected\" would report segment one for a control\n * with no selection at all. {@link getSelectedIndex} is the selection.\n *\n * It is also not where the thumb IS: the stylesheet turns this into a `translateX`,\n * and no measurement of that survives a DOM without layout. This is the input to\n * that, which is the part a unit test can honestly check.\n */\n async getThumbIndex(): Promise<number> {\n return this.thumbVar('--wr-segmented-thumb-index');\n }\n\n /**\n * The segment count the component publishes for the thumb, from\n * `--wr-segmented-thumb-count` — the divisor the stylesheet computes the thumb's\n * width with.\n *\n * Should always equal the number of segments, and the reason to ask is that nothing\n * else notices when it does not: a count that fell out of step with the options\n * leaves the thumb the wrong width and sliding to the wrong place, while every ARIA\n * state on the page stays correct. It is clamped to a minimum of `1`, so an empty\n * control reports `1` rather than dividing by zero.\n */\n async getThumbCount(): Promise<number> {\n return this.thumbVar('--wr-segmented-thumb-count');\n }\n\n /**\n * Whether the thumb is being shown at all.\n *\n * Read from the ABSENCE of the `wr-segmented--unselected` modifier, which is the\n * hook the stylesheet zeroes the thumb's opacity through. The computed opacity is\n * not the answer here — a unit test applies no stylesheet — so the public modifier\n * is the contract, and it is what tells \"parked under segment one\" from \"hidden\n * because nothing is selected\".\n */\n async isThumbVisible(): Promise<boolean> {\n return !(await (await this.host()).hasClass('wr-segmented--unselected'));\n }\n\n /**\n * Whether the thumb will animate its next move, from the `wr-segmented--mounted`\n * modifier that gates the transition.\n *\n * Not \"the thumb is animating now\": nothing about a running animation is visible to\n * a unit test. What this pins is the deliberate asymmetry — the class is added in\n * `afterNextRender`, so the FIRST snap to the initial value is instant and every\n * user-driven move after it slides. Deferred work like that only lands once the\n * fixture is stable, which every harness read already waits for.\n */\n async isThumbTransitionEnabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-segmented--mounted');\n }\n\n /** Click a segment and turn a click that did nothing into a failure that says why. */\n private async pick(option: WrSegmentedOptionHarness, caller: 'select' | 'selectAt'): Promise<void> {\n await option.select();\n\n if (!(await option.isSelected())) {\n const name = (await option.getAccessibleName()) ?? '(unnamed)';\n throw new Error(\n `WrSegmentedHarness.${caller}(): \"${name}\" is still unpressed after being clicked — either that ` +\n 'option carries `disabled` or the whole control does.'\n );\n }\n }\n\n /** Segment names for an error message, which must not throw on its way out. */\n private async describeOptions(): Promise<string[]> {\n return this.describe(await this.getOptions());\n }\n\n private async describe(options: readonly WrSegmentedOptionHarness[]): Promise<string[]> {\n return Promise.all(options.map(async option => (await option.getAccessibleName()) ?? '(unnamed)'));\n }\n\n /**\n * One custom property off the host's `style` ATTRIBUTE.\n *\n * Not `getCssValue()`, which is `getComputedStyle`: with the entry point's own\n * stylesheet loaded that resolves the sheet's declaration, so the method\n * answers plausibly at exactly the moment the host binding — the thing under\n * test — is what broke. The attribute holds only what the component wrote.\n */\n private async inlineVar(property: string): Promise<string> {\n const attr = (await (await this.host()).getAttribute('style')) ?? '';\n const found = new RegExp(`(?:^|;)\\\\s*${property}\\\\s*:\\\\s*([^;]+)`).exec(attr);\n return (found?.[1] ?? '').trim();\n }\n\n /** One of the thumb's inline custom properties, as a number. */\n private async thumbVar(property: string): Promise<number> {\n const raw = await this.inlineVar(property);\n const value = Number.parseInt(raw, 10);\n if (Number.isNaN(value)) {\n throw new Error(\n `WrSegmentedHarness: could not read \\`${property}\\`, which computed to \"${raw}\". The component sets it ` +\n 'inline on the host; an environment that drops custom properties cannot answer this.'\n );\n }\n return value;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -230,10 +230,10 @@ class WrSegmented {
|
|
|
230
230
|
return;
|
|
231
231
|
this.touch.emit();
|
|
232
232
|
}
|
|
233
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
234
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
233
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
234
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: WrSegmented, isStandalone: true, selector: "wr-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, listeners: { "focusout": "onFocusOut($event)" }, properties: { "class": "classes()", "style": "thumbStyle()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "ariaInvalid()" } }, ngImport: i0, template: "<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element \u2014 see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n", dependencies: [{ kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
235
235
|
}
|
|
236
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
236
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrSegmented, decorators: [{
|
|
237
237
|
type: Component,
|
|
238
238
|
args: [{ selector: 'wr-segmented', encapsulation: ViewEncapsulation.None, host: {
|
|
239
239
|
'[class]': 'classes()',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-segmented.mjs","sources":["../../../projects/lib/segmented/segmented.ts","../../../projects/lib/segmented/segmented.html","../../../projects/lib/segmented/ngwr-segmented.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 { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSegmentedOption } from './interfaces';\n\n/**\n * Single-choice picker rendered as a segmented control. Two-way binds\n * the selected `value`.\n *\n * A signal-forms native control: it implements `FormValueControl<T | null>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone, and classic\n * `[(ngModel)]` / reactive forms keep working through Angular's bridge.\n *\n * It stays a `role=\"group\"` of `aria-pressed` toggle buttons rather than\n * becoming a `radiogroup`: being a form control changes what the value is bound\n * to, not what the widget is, and a radiogroup would owe a roving tab stop and\n * arrow-key selection — a different keyboard contract for every consumer who\n * already ships this. The group is what the field describes (`aria-describedby`\n * / `aria-invalid` land on the host), while the `<label for>` has to name a\n * labelable element, so the id goes on the first segment.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-segmented [options]=\"ranges\" [formField]=\"form.range\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-segmented\n * [options]=\"[\n * { value: 'day', label: 'Day' },\n * { value: 'week', label: 'Week' },\n * { value: 'month', label: 'Month' },\n * ]\"\n * [(value)]=\"range\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/segmented\n */\nexport type WrSegmentedSize = 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'wr-segmented',\n templateUrl: './segmented.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style]': 'thumbStyle()',\n role: 'group',\n '[attr.aria-describedby]': 'describedBy()',\n '[attr.aria-invalid]': 'ariaInvalid()',\n '(focusout)': 'onFocusOut($event)',\n },\n imports: [WrIcon],\n})\nexport class WrSegmented<T = unknown> implements FormValueControl<T | null> {\n /** The segments to render. */\n readonly options = input.required<readonly WrSegmentedOption<T>[]>();\n\n // The model keeps the component's own `T`, and the contract is satisfied as\n // `FormValueControl<T | null>` — legal because `FormField<T>` infers its type from\n // the FIELD it is handed and never from the host component, so the interface's\n // parameter describes this model rather than constraining the binding.\n // `wr-cascader` and `wr-tree` widen theirs to `unknown` because their value SHAPE\n // changes with a mode input (`TId | null` in single mode, a `readonly TId[]` in\n // multi) and no one type is true of both. A segmented control has one shape, so\n // widening here would only throw away what `[options]` already proved: every\n // `(valueChange)=\"pick($event)\"` in a typed app would start receiving `unknown`.\n // With the default type parameter this degenerates to exactly the catalog's\n // `FormValueControl<unknown>`.\n /**\n * The picked segment's `value`, `null` when nothing is selected. Bound by\n * `[formField]`, or two-way via `[(value)]`.\n */\n readonly value = model<T | null>(null);\n\n /** Emitted when focus leaves the strip, so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n /**\n * Disable the whole control. Bound automatically from the field's disabled\n * state when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes while every segment stays focusable and the value still\n * submits. Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * NOTHING is mirrored into ARIA here, deliberately: the strip is a\n * `role=\"group\"` of `aria-pressed` buttons, and ARIA defines `aria-readonly`\n * for neither role — it is a state of `checkbox` / `radiogroup` / `textbox`\n * and their kin, and it is not global. `aria-disabled` is the only attribute\n * that would apply to a button, and it says the wrong thing: these segments\n * are still focusable and the value still submits. So the state is honest in\n * the DOM (`wr-segmented--readonly`) and silent in the accessibility tree\n * rather than announced with a word that means something else.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Control size — shares the `--wr-control-*` contract. @default 'md' */\n readonly size = input<WrSegmentedSize>('md');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Id the surrounding `<wr-form-field>`'s `<label for>` points at, stamped on the\n * FIRST segment.\n *\n * The field renders its label before it can see what was projected into it, so the\n * id travels the other way and the control adopts it, exactly as `[wrInput]`,\n * `wr-slider` and `wr-select` do. Without this the `for` named an element that was\n * nowhere in the document: clicking the label did nothing.\n *\n * It cannot go on the host — only a labelable element can be a label's target, and\n * `<wr-segmented>` is not one — and there is no single control to put it on, so it\n * goes where clicking the label should land a user: the first segment, which is\n * also the strip's first tab stop. The same choice `wr-slider` makes with its low\n * thumb.\n *\n * What this does NOT change is that segment's NAME: it is named by its own label,\n * and a `<label for>` cannot outrank text content in the accname order anyway.\n *\n * The one shape this reads wrong is a strip NESTED inside another control that is\n * itself the field — `<wr-color-picker>`'s own format tabs are the only case in\n * the library — where the id lands on a tab instead of on the picker. That is the\n * same exposure `[wrInput]`, `wr-slider` and `wr-select` all carry, and the fix\n * belongs to the outer control (shield its subtree by providing `WR_FORM_FIELD`\n * as `null`), not to a strip that cannot tell whose field it is in.\n */\n protected readonly controlId = computed(() => this.field?.controlId() ?? null);\n\n /**\n * The field's error message, wired to the GROUP rather than to a segment.\n *\n * A segmented control is one field made of several buttons, so the description\n * belongs to the thing the field describes — repeated on every segment it would be\n * announced once per Tab. `role=\"group\"` supports neither state as a role-specific\n * attribute, but both are ARIA globals, so this is valid where an `aria-checked`\n * would not be.\n *\n * `aria-invalid` is keyed on the message EXISTING rather than on `errorKeys()`,\n * because the field only publishes an id once it is showing something — announcing\n * \"invalid\" while pointing at nothing is worse than staying quiet.\n */\n // Through the shared helper, not a local pair: the hint is described by\n // `hintId`, which only `useFormFieldAria()` composes, and `aria-invalid` keys\n // on the error COUNT rather than on \"something is described\". Hand-rolled here,\n // these three announced the error and never the hint — and `<input wrInput>` is\n // the control the report was filed against.\n private readonly fieldAria = useFormFieldAria();\n protected readonly describedBy = this.fieldAria.describedBy;\n protected readonly ariaInvalid = this.fieldAria.ariaInvalid;\n\n /** Index of the selected option, or `-1` when nothing is selected. */\n protected readonly selectedIndex = computed(() => {\n const v = this.value();\n return this.options().findIndex(o => o.value === v);\n });\n\n /** Flips true after the first paint so the thumb only animates user-driven changes, not the initial snap. */\n private readonly mounted = signal(false);\n\n /**\n * True for the tick in which the reading direction changed.\n *\n * Mirroring moves the thumb's SLOT by the whole width of the strip (see\n * `thumbStyle`), and animating that is a quarter-second of the pill travelling\n * across options it does not mark — caught under the wrong label every time the\n * docs site's LTR / RTL switch was used, which is a strip inside an overlay that\n * mirrors underneath it. The selection did not move, so neither should the pill:\n * the stylesheet drops its transition while this is set, and the new slot lands\n * in the same pass that removes it.\n *\n * Cleared from a task rather than `afterNextRender`, which runs inside the very\n * change detection that set this and would put the transition back before the\n * new position is painted.\n */\n private readonly flipping = signal(false);\n\n constructor() {\n afterNextRender(() => this.mounted.set(true));\n\n // `null` until the first run: an effect always runs once, and that run is not\n // a flip.\n let previous: boolean | null = null;\n effect(() => {\n const rtl = this.isRtl();\n if (previous === rtl) return;\n const flipped = previous !== null;\n previous = rtl;\n if (!flipped || typeof setTimeout !== 'function') return;\n\n untracked(() => this.flipping.set(true));\n setTimeout(() => this.flipping.set(false));\n });\n }\n\n protected readonly classes = computed(() => {\n const parts = ['wr-segmented'];\n const size = this.size();\n if (size !== 'md') parts.push(`wr-segmented--${size}`);\n if (this.disabled()) parts.push('wr-segmented--disabled');\n else if (this.readonly()) parts.push('wr-segmented--readonly');\n if (this.selectedIndex() < 0) parts.push('wr-segmented--unselected');\n if (this.mounted()) parts.push('wr-segmented--mounted');\n if (this.flipping()) parts.push('wr-segmented--flipping');\n return parts.join(' ');\n });\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. A subtree that overrides the direction does it with\n * the CDK's `Dir` directive, which writes `valueSignal`, so `thumbStyle`\n * re-renders instead of leaving the thumb parked on the old segment.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * Inline CSS vars driving the sliding thumb position.\n *\n * `--wr-segmented-thumb-index` is the SLOT the thumb parks in, counted from the\n * physical left, which is the selected index only in LTR. The stylesheet anchors\n * the thumb with a physical `left` and slides it with `translateX` — neither has\n * a logical form — while the options are a grid that mirrors, so under `dir=\"rtl\"`\n * the segment at logical index `i` occupies slot `count - 1 - i`. Left unsigned,\n * the pill sat under a different label than the one it marks (the whole strip's\n * worth of offset at the far end), which is the carousel's `trackStyle` problem\n * one component over.\n */\n protected readonly thumbStyle = computed<Record<string, string>>(() => {\n const i = Math.max(0, this.selectedIndex());\n const count = Math.max(1, this.options().length);\n const slot = this.isRtl() ? count - 1 - i : i;\n return {\n '--wr-segmented-thumb-index': `${slot}`,\n '--wr-segmented-thumb-count': `${count}`,\n };\n });\n\n protected isSelected(option: WrSegmentedOption<T>): boolean {\n return this.value() === option.value;\n }\n\n protected select(option: WrSegmentedOption<T>): void {\n if (this.disabled() || this.readonly() || option.disabled || this.isSelected(option)) return;\n this.value.set(option.value);\n }\n\n protected trackByValue(_: number, option: WrSegmentedOption<T>): unknown {\n return option.value;\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and a click\n * cannot be the source: a strip tabbed into and out of without a pick left the\n * field untouched forever — which is exactly the case `touched` exists for.\n *\n * `wr-knob` and `wr-rating` bind `(blur)` on the one element they own; this\n * component owns one button per option, so it listens for the bubbling `focusout`\n * on its host instead — and ignores the one that only moves focus from one segment\n * to the next, the way `wr-slider` ignores the hop between its two thumbs.\n */\n protected onFocusOut(event: FocusEvent): void {\n if (this.host.nativeElement.contains(event.relatedTarget as Node | null)) return;\n this.touch.emit();\n }\n}\n","<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element — see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\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-segmented.mjs","sources":["../../../projects/lib/segmented/segmented.ts","../../../projects/lib/segmented/segmented.html","../../../projects/lib/segmented/ngwr-segmented.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 { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSegmentedOption } from './interfaces';\n\n/**\n * Single-choice picker rendered as a segmented control. Two-way binds\n * the selected `value`.\n *\n * A signal-forms native control: it implements `FormValueControl<T | null>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone, and classic\n * `[(ngModel)]` / reactive forms keep working through Angular's bridge.\n *\n * It stays a `role=\"group\"` of `aria-pressed` toggle buttons rather than\n * becoming a `radiogroup`: being a form control changes what the value is bound\n * to, not what the widget is, and a radiogroup would owe a roving tab stop and\n * arrow-key selection — a different keyboard contract for every consumer who\n * already ships this. The group is what the field describes (`aria-describedby`\n * / `aria-invalid` land on the host), while the `<label for>` has to name a\n * labelable element, so the id goes on the first segment.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-segmented [options]=\"ranges\" [formField]=\"form.range\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-segmented\n * [options]=\"[\n * { value: 'day', label: 'Day' },\n * { value: 'week', label: 'Week' },\n * { value: 'month', label: 'Month' },\n * ]\"\n * [(value)]=\"range\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/segmented\n */\nexport type WrSegmentedSize = 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'wr-segmented',\n templateUrl: './segmented.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style]': 'thumbStyle()',\n role: 'group',\n '[attr.aria-describedby]': 'describedBy()',\n '[attr.aria-invalid]': 'ariaInvalid()',\n '(focusout)': 'onFocusOut($event)',\n },\n imports: [WrIcon],\n})\nexport class WrSegmented<T = unknown> implements FormValueControl<T | null> {\n /** The segments to render. */\n readonly options = input.required<readonly WrSegmentedOption<T>[]>();\n\n // The model keeps the component's own `T`, and the contract is satisfied as\n // `FormValueControl<T | null>` — legal because `FormField<T>` infers its type from\n // the FIELD it is handed and never from the host component, so the interface's\n // parameter describes this model rather than constraining the binding.\n // `wr-cascader` and `wr-tree` widen theirs to `unknown` because their value SHAPE\n // changes with a mode input (`TId | null` in single mode, a `readonly TId[]` in\n // multi) and no one type is true of both. A segmented control has one shape, so\n // widening here would only throw away what `[options]` already proved: every\n // `(valueChange)=\"pick($event)\"` in a typed app would start receiving `unknown`.\n // With the default type parameter this degenerates to exactly the catalog's\n // `FormValueControl<unknown>`.\n /**\n * The picked segment's `value`, `null` when nothing is selected. Bound by\n * `[formField]`, or two-way via `[(value)]`.\n */\n readonly value = model<T | null>(null);\n\n /** Emitted when focus leaves the strip, so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n /**\n * Disable the whole control. Bound automatically from the field's disabled\n * state when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes while every segment stays focusable and the value still\n * submits. Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * NOTHING is mirrored into ARIA here, deliberately: the strip is a\n * `role=\"group\"` of `aria-pressed` buttons, and ARIA defines `aria-readonly`\n * for neither role — it is a state of `checkbox` / `radiogroup` / `textbox`\n * and their kin, and it is not global. `aria-disabled` is the only attribute\n * that would apply to a button, and it says the wrong thing: these segments\n * are still focusable and the value still submits. So the state is honest in\n * the DOM (`wr-segmented--readonly`) and silent in the accessibility tree\n * rather than announced with a word that means something else.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Control size — shares the `--wr-control-*` contract. @default 'md' */\n readonly size = input<WrSegmentedSize>('md');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Id the surrounding `<wr-form-field>`'s `<label for>` points at, stamped on the\n * FIRST segment.\n *\n * The field renders its label before it can see what was projected into it, so the\n * id travels the other way and the control adopts it, exactly as `[wrInput]`,\n * `wr-slider` and `wr-select` do. Without this the `for` named an element that was\n * nowhere in the document: clicking the label did nothing.\n *\n * It cannot go on the host — only a labelable element can be a label's target, and\n * `<wr-segmented>` is not one — and there is no single control to put it on, so it\n * goes where clicking the label should land a user: the first segment, which is\n * also the strip's first tab stop. The same choice `wr-slider` makes with its low\n * thumb.\n *\n * What this does NOT change is that segment's NAME: it is named by its own label,\n * and a `<label for>` cannot outrank text content in the accname order anyway.\n *\n * The one shape this reads wrong is a strip NESTED inside another control that is\n * itself the field — `<wr-color-picker>`'s own format tabs are the only case in\n * the library — where the id lands on a tab instead of on the picker. That is the\n * same exposure `[wrInput]`, `wr-slider` and `wr-select` all carry, and the fix\n * belongs to the outer control (shield its subtree by providing `WR_FORM_FIELD`\n * as `null`), not to a strip that cannot tell whose field it is in.\n */\n protected readonly controlId = computed(() => this.field?.controlId() ?? null);\n\n /**\n * The field's error message, wired to the GROUP rather than to a segment.\n *\n * A segmented control is one field made of several buttons, so the description\n * belongs to the thing the field describes — repeated on every segment it would be\n * announced once per Tab. `role=\"group\"` supports neither state as a role-specific\n * attribute, but both are ARIA globals, so this is valid where an `aria-checked`\n * would not be.\n *\n * `aria-invalid` is keyed on the message EXISTING rather than on `errorKeys()`,\n * because the field only publishes an id once it is showing something — announcing\n * \"invalid\" while pointing at nothing is worse than staying quiet.\n */\n // Through the shared helper, not a local pair: the hint is described by\n // `hintId`, which only `useFormFieldAria()` composes, and `aria-invalid` keys\n // on the error COUNT rather than on \"something is described\". Hand-rolled here,\n // these three announced the error and never the hint — and `<input wrInput>` is\n // the control the report was filed against.\n private readonly fieldAria = useFormFieldAria();\n protected readonly describedBy = this.fieldAria.describedBy;\n protected readonly ariaInvalid = this.fieldAria.ariaInvalid;\n\n /** Index of the selected option, or `-1` when nothing is selected. */\n protected readonly selectedIndex = computed(() => {\n const v = this.value();\n return this.options().findIndex(o => o.value === v);\n });\n\n /** Flips true after the first paint so the thumb only animates user-driven changes, not the initial snap. */\n private readonly mounted = signal(false);\n\n /**\n * True for the tick in which the reading direction changed.\n *\n * Mirroring moves the thumb's SLOT by the whole width of the strip (see\n * `thumbStyle`), and animating that is a quarter-second of the pill travelling\n * across options it does not mark — caught under the wrong label every time the\n * docs site's LTR / RTL switch was used, which is a strip inside an overlay that\n * mirrors underneath it. The selection did not move, so neither should the pill:\n * the stylesheet drops its transition while this is set, and the new slot lands\n * in the same pass that removes it.\n *\n * Cleared from a task rather than `afterNextRender`, which runs inside the very\n * change detection that set this and would put the transition back before the\n * new position is painted.\n */\n private readonly flipping = signal(false);\n\n constructor() {\n afterNextRender(() => this.mounted.set(true));\n\n // `null` until the first run: an effect always runs once, and that run is not\n // a flip.\n let previous: boolean | null = null;\n effect(() => {\n const rtl = this.isRtl();\n if (previous === rtl) return;\n const flipped = previous !== null;\n previous = rtl;\n if (!flipped || typeof setTimeout !== 'function') return;\n\n untracked(() => this.flipping.set(true));\n setTimeout(() => this.flipping.set(false));\n });\n }\n\n protected readonly classes = computed(() => {\n const parts = ['wr-segmented'];\n const size = this.size();\n if (size !== 'md') parts.push(`wr-segmented--${size}`);\n if (this.disabled()) parts.push('wr-segmented--disabled');\n else if (this.readonly()) parts.push('wr-segmented--readonly');\n if (this.selectedIndex() < 0) parts.push('wr-segmented--unselected');\n if (this.mounted()) parts.push('wr-segmented--mounted');\n if (this.flipping()) parts.push('wr-segmented--flipping');\n return parts.join(' ');\n });\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. A subtree that overrides the direction does it with\n * the CDK's `Dir` directive, which writes `valueSignal`, so `thumbStyle`\n * re-renders instead of leaving the thumb parked on the old segment.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * Inline CSS vars driving the sliding thumb position.\n *\n * `--wr-segmented-thumb-index` is the SLOT the thumb parks in, counted from the\n * physical left, which is the selected index only in LTR. The stylesheet anchors\n * the thumb with a physical `left` and slides it with `translateX` — neither has\n * a logical form — while the options are a grid that mirrors, so under `dir=\"rtl\"`\n * the segment at logical index `i` occupies slot `count - 1 - i`. Left unsigned,\n * the pill sat under a different label than the one it marks (the whole strip's\n * worth of offset at the far end), which is the carousel's `trackStyle` problem\n * one component over.\n */\n protected readonly thumbStyle = computed<Record<string, string>>(() => {\n const i = Math.max(0, this.selectedIndex());\n const count = Math.max(1, this.options().length);\n const slot = this.isRtl() ? count - 1 - i : i;\n return {\n '--wr-segmented-thumb-index': `${slot}`,\n '--wr-segmented-thumb-count': `${count}`,\n };\n });\n\n protected isSelected(option: WrSegmentedOption<T>): boolean {\n return this.value() === option.value;\n }\n\n protected select(option: WrSegmentedOption<T>): void {\n if (this.disabled() || this.readonly() || option.disabled || this.isSelected(option)) return;\n this.value.set(option.value);\n }\n\n protected trackByValue(_: number, option: WrSegmentedOption<T>): unknown {\n return option.value;\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and a click\n * cannot be the source: a strip tabbed into and out of without a pick left the\n * field untouched forever — which is exactly the case `touched` exists for.\n *\n * `wr-knob` and `wr-rating` bind `(blur)` on the one element they own; this\n * component owns one button per option, so it listens for the bubbling `focusout`\n * on its host instead — and ignores the one that only moves focus from one segment\n * to the next, the way `wr-slider` ignores the hop between its two thumbs.\n */\n protected onFocusOut(event: FocusEvent): void {\n if (this.host.nativeElement.contains(event.relatedTarget as Node | null)) return;\n this.touch.emit();\n }\n}\n","<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element — see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\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-select-testing.mjs","sources":["../../../projects/lib/select/testing/wr-option-harness.ts","../../../projects/lib/select/testing/wr-select-harness.ts","../../../projects/lib/select/testing/ngwr-select-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrOptionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one option inside a `<wr-select>` panel.\n *\n * The selector is the CLASS, not the `wr-option` element: a select rendering a\n * virtualized list emits plain `<div role=\"option\" class=\"wr-option\">` rows\n * instead of components, and a spec should not have to care which one it got.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrOptionHarness extends ComponentHarness {\n /** The projected component and the virtualized row both carry this class. */\n static hostSelector = '.wr-option';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrOptionHarnessFilters = {}): HarnessPredicate<WrOptionHarness> {\n return new HarnessPredicate(WrOptionHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\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 /**\n * The option's text as drawn, trimmed — without its leading visual.\n *\n * A `wrOptionLeading` template renders INSIDE the row, so reading the whole\n * host would hand back an avatar's initials as the first word of the name. It\n * is excluded here for the same reason the select excludes it from the label.\n *\n * This is the drawn text, not the `label` input: an option whose `label` is\n * shorter than what it draws still answers with what it draws. What the select\n * REPORTS for it is on the select harness, as `getValueText()` or\n * `getChipLabels()`.\n */\n async getText(): Promise<string> {\n return (await this.host()).text({ exclude: '.wr-option__leading' });\n }\n\n /**\n * Whether the row is drawing a leading visual right now. Reads the slot the\n * select renders the template into, so it answers `false` for an option that\n * declares a template while the panel is closed — which is the contract.\n */\n async hasLeading(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-option__leading')()) !== null;\n }\n\n /** Whether the option is currently selected. */\n async isSelected(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-selected')) === 'true';\n }\n\n /** Whether the option refuses selection. */\n async isDisabled(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-disabled')) === 'true';\n }\n\n /** Whether the option is the keyboard cursor target. */\n async isActive(): Promise<boolean> {\n return (await this.host()).hasClass('wr-option--active');\n }\n\n /**\n * Whether a search query has filtered this option out.\n *\n * Filtered options stay in the DOM — registration order has to survive a\n * query — and collapse via CSS, so a spec that counts options while a search\n * is active gets them back unless it asks. `getOptions()` on the select\n * harness already drops them.\n */\n async isHidden(): Promise<boolean> {\n return (await this.host()).hasClass('wr-option--hidden');\n }\n\n /** Click the option. */\n async click(): Promise<void> {\n return (await this.host()).click();\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 { WrOptionHarnessFilters, WrSelectHarnessFilters } from './interfaces';\nimport { WrOptionHarness } from './wr-option-harness';\n\n/**\n * Test harness for `<wr-select>` — single, multi, tag and search modes.\n *\n * The panel is NOT inside the select: it is a template portal in the overlay\n * container, a sibling of the whole app. So the options are reached through the\n * document root, scoped by the `aria-controls` id the trigger already publishes\n * — which is what keeps two selects on one page from reading each other's\n * options.\n *\n * @example\n * ```ts\n * const select = await loader.getHarness(WrSelectHarness);\n *\n * await select.open();\n * await select.selectOption({ text: 'Medium' });\n *\n * expect(await select.getValueText()).toBe('Medium');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSelectHarness extends ComponentHarness {\n static hostSelector = 'wr-select';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrSelectHarnessFilters = {}): HarnessPredicate<WrSelectHarness> {\n return new HarnessPredicate(WrSelectHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getValueText(), text))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n /** Whether the panel is showing. */\n async isOpen(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--open');\n }\n\n /** Whether the select refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--disabled');\n }\n\n /** Whether the select takes more than one value (multi or tag mode). */\n async isMultiple(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--multi');\n }\n\n /**\n * What the trigger currently shows as the selection: the chip labels joined by\n * `', '` in multi and tag modes, the label in single mode, the input's text in\n * search mode, and `''` when nothing is selected (the placeholder is showing).\n */\n async getValueText(): Promise<string> {\n const chips = await this.getChipLabels();\n if (chips.length > 0) return chips.join(', ');\n\n const value = await this.locatorForOptional('.wr-select__value')();\n if (value) return value.text();\n\n // Search mode keeps the display text in the input's value, not in the DOM.\n const search = await this.locatorForOptional('.wr-select__search-input')();\n return search ? search.getProperty<string>('value') : '';\n }\n\n /** The placeholder, or `null` when a selection is hiding it. */\n async getPlaceholder(): Promise<string | null> {\n const placeholder = await this.locatorForOptional('.wr-select__placeholder')();\n if (placeholder) return placeholder.text();\n\n const input = await this.searchInput();\n return input ? input.getProperty<string>('placeholder') : null;\n }\n\n /** The visible chip labels, in order. The `+N more` overflow chip is not one. */\n async getChipLabels(): Promise<string[]> {\n const labels = await this.locatorForAll('.wr-select__chip-label')();\n return Promise.all(labels.map(label => label.text()));\n }\n\n /**\n * Open the panel.\n *\n * Throws rather than resolving quietly when the panel does not appear, because\n * the two reasons it would not are both worth failing on: a tag-mode select\n * has no panel at all, and a searchable one with `minChars` opens on the query\n * rather than on focus.\n */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n\n await (await this.locatorFor('.wr-select__trigger')()).click();\n if (await this.isOpen()) return;\n\n throw new Error(\n 'WrSelectHarness.open(): the panel did not open. A tag-mode select has no panel, and a ' +\n 'searchable select with `minChars` opens on the query — call typeSearch() instead.'\n );\n }\n\n /** Close the panel. A closed select is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n // Escape reaches the select through the CDK's keyboard dispatcher, so it\n // works for the button trigger and the search input alike.\n await (await this.locatorFor('.wr-select__trigger')()).sendKeys(TestKey.ESCAPE);\n }\n\n /**\n * The options currently offered, in DOM order.\n *\n * Filtered-out options are dropped: a client-side search leaves them in the\n * DOM (registration order has to survive a query) and collapses them with CSS,\n * so counting raw elements would answer with the unfiltered list.\n */\n async getOptions(filters: WrOptionHarnessFilters = {}): Promise<WrOptionHarness[]> {\n const loader = await this.panelLoader();\n const all = await loader.getAllHarnesses(WrOptionHarness.with(filters));\n\n const visible: WrOptionHarness[] = [];\n for (const option of all) {\n if (!(await option.isHidden())) visible.push(option);\n }\n return visible;\n }\n\n /** The labels of the options currently offered, in DOM order. */\n async getOptionLabels(): Promise<string[]> {\n const options = await this.getOptions();\n return Promise.all(options.map(option => option.getText()));\n }\n\n /** Open the panel if needed, then click the first option matching the filters. */\n async selectOption(filters: WrOptionHarnessFilters): Promise<void> {\n await this.open();\n\n const [option] = await this.getOptions(filters);\n if (!option) throw new Error(`WrSelectHarness.selectOption(): no option matched ${JSON.stringify(filters)}.`);\n\n await option.click();\n }\n\n /** Type into a search or tag select's input, replacing whatever is there. */\n async typeSearch(query: string): Promise<void> {\n const input = await this.searchInput();\n if (!input) throw new Error('WrSelectHarness.typeSearch(): this select has no text input.');\n\n await input.clear();\n if (query) await input.sendKeys(query);\n }\n\n /** Click the clear (×) control. It only exists on a `clearable` select with a selection. */\n async clear(): Promise<void> {\n await (await this.locatorFor('.wr-select__clear')()).click();\n }\n\n /** Remove one chip by its label. */\n async removeChip(label: string): Promise<void> {\n const labels = await this.getChipLabels();\n const index = labels.indexOf(label);\n if (index < 0) throw new Error(`WrSelectHarness.removeChip(): no chip labelled \"${label}\".`);\n\n // Label and remove control are emitted once per chip by the same loop, so\n // the two lists line up by index.\n const removes = await this.locatorForAll('.wr-select__chip-remove')();\n await removes[index].click();\n }\n\n /** The \"no results\" message, or `null` when the panel is showing options. */\n async getNoResultsText(): Promise<string | null> {\n const empty = await this.inPanel('.wr-select-panel__empty');\n return empty ? empty.text() : null;\n }\n\n /** Whether the panel is showing its async loading row. */\n async isLoading(): Promise<boolean> {\n return (await this.inPanel('.wr-select-panel__loading')) !== null;\n }\n\n /** Move keyboard focus to the select. */\n async focus(): Promise<void> {\n const input = await this.searchInput();\n return (input ?? (await this.locatorFor('.wr-select__trigger')())).focus();\n }\n\n /** The text input of a search or tag select, or `null` for the button trigger. */\n private async searchInput(): Promise<TestElement | null> {\n return this.locatorForOptional('.wr-select__search-input, .wr-select__tag-input')();\n }\n\n /** One element inside THIS select's panel, or `null`. */\n private async inPanel(selector: string): Promise<TestElement | null> {\n return this.documentRootLocatorFactory().locatorForOptional(`#${await this.panelId()} ${selector}`)();\n }\n\n /**\n * A loader scoped to THIS select's panel.\n *\n * Scoped by the `aria-controls` id rather than by `.wr-select-panel`: the\n * overlay container is shared, so a bare class query would happily return the\n * options of a different select that happens to be open.\n */\n private async panelLoader(): Promise<HarnessLoader> {\n return this.documentRootLocatorFactory().harnessLoaderFor(`#${await this.panelId()}`);\n }\n\n /** The id the trigger publishes as `aria-controls`, which the panel carries. */\n private async panelId(): Promise<string> {\n const combobox = await this.locatorForOptional('[role=\"combobox\"]')();\n const id = combobox ? await combobox.getAttribute('aria-controls') : null;\n\n if (!id) {\n throw new Error('WrSelectHarness: this select has no panel — tag mode renders its options inline.');\n }\n if (!(await this.isOpen())) {\n throw new Error('WrSelectHarness: the panel is closed — call open() first.');\n }\n\n return id;\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-select-testing.mjs","sources":["../../../projects/lib/select/testing/wr-option-harness.ts","../../../projects/lib/select/testing/wr-select-harness.ts","../../../projects/lib/select/testing/ngwr-select-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrOptionHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one option inside a `<wr-select>` panel.\n *\n * The selector is the CLASS, not the `wr-option` element: a select rendering a\n * virtualized list emits plain `<div role=\"option\" class=\"wr-option\">` rows\n * instead of components, and a spec should not have to care which one it got.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrOptionHarness extends ComponentHarness {\n /** The projected component and the virtualized row both carry this class. */\n static hostSelector = '.wr-option';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrOptionHarnessFilters = {}): HarnessPredicate<WrOptionHarness> {\n return new HarnessPredicate(WrOptionHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\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 /**\n * The option's text as drawn, trimmed — without its leading visual.\n *\n * A `wrOptionLeading` template renders INSIDE the row, so reading the whole\n * host would hand back an avatar's initials as the first word of the name. It\n * is excluded here for the same reason the select excludes it from the label.\n *\n * This is the drawn text, not the `label` input: an option whose `label` is\n * shorter than what it draws still answers with what it draws. What the select\n * REPORTS for it is on the select harness, as `getValueText()` or\n * `getChipLabels()`.\n */\n async getText(): Promise<string> {\n return (await this.host()).text({ exclude: '.wr-option__leading' });\n }\n\n /**\n * Whether the row is drawing a leading visual right now. Reads the slot the\n * select renders the template into, so it answers `false` for an option that\n * declares a template while the panel is closed — which is the contract.\n */\n async hasLeading(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-option__leading')()) !== null;\n }\n\n /** Whether the option is currently selected. */\n async isSelected(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-selected')) === 'true';\n }\n\n /** Whether the option refuses selection. */\n async isDisabled(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-disabled')) === 'true';\n }\n\n /** Whether the option is the keyboard cursor target. */\n async isActive(): Promise<boolean> {\n return (await this.host()).hasClass('wr-option--active');\n }\n\n /**\n * Whether a search query has filtered this option out.\n *\n * Filtered options stay in the DOM — registration order has to survive a\n * query — and collapse via CSS, so a spec that counts options while a search\n * is active gets them back unless it asks. `getOptions()` on the select\n * harness already drops them.\n */\n async isHidden(): Promise<boolean> {\n return (await this.host()).hasClass('wr-option--hidden');\n }\n\n /** Click the option. */\n async click(): Promise<void> {\n return (await this.host()).click();\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 { WrOptionHarnessFilters, WrSelectHarnessFilters } from './interfaces';\nimport { WrOptionHarness } from './wr-option-harness';\n\n/**\n * Test harness for `<wr-select>` — single, multi, tag and search modes.\n *\n * The panel is NOT inside the select: it is a template portal in the overlay\n * container, a sibling of the whole app. So the options are reached through the\n * document root, scoped by the `aria-controls` id the trigger already publishes\n * — which is what keeps two selects on one page from reading each other's\n * options.\n *\n * @example\n * ```ts\n * const select = await loader.getHarness(WrSelectHarness);\n *\n * await select.open();\n * await select.selectOption({ text: 'Medium' });\n *\n * expect(await select.getValueText()).toBe('Medium');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrSelectHarness extends ComponentHarness {\n static hostSelector = 'wr-select';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrSelectHarnessFilters = {}): HarnessPredicate<WrSelectHarness> {\n return new HarnessPredicate(WrSelectHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getValueText(), text))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n /** Whether the panel is showing. */\n async isOpen(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--open');\n }\n\n /** Whether the select refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--disabled');\n }\n\n /** Whether the select takes more than one value (multi or tag mode). */\n async isMultiple(): Promise<boolean> {\n return (await this.host()).hasClass('wr-select--multi');\n }\n\n /**\n * What the trigger currently shows as the selection: the chip labels joined by\n * `', '` in multi and tag modes, the label in single mode, the input's text in\n * search mode, and `''` when nothing is selected (the placeholder is showing).\n */\n async getValueText(): Promise<string> {\n const chips = await this.getChipLabels();\n if (chips.length > 0) return chips.join(', ');\n\n const value = await this.locatorForOptional('.wr-select__value')();\n if (value) return value.text();\n\n // Search mode keeps the display text in the input's value, not in the DOM.\n const search = await this.locatorForOptional('.wr-select__search-input')();\n return search ? search.getProperty<string>('value') : '';\n }\n\n /** The placeholder, or `null` when a selection is hiding it. */\n async getPlaceholder(): Promise<string | null> {\n const placeholder = await this.locatorForOptional('.wr-select__placeholder')();\n if (placeholder) return placeholder.text();\n\n const input = await this.searchInput();\n return input ? input.getProperty<string>('placeholder') : null;\n }\n\n /** The visible chip labels, in order. The `+N more` overflow chip is not one. */\n async getChipLabels(): Promise<string[]> {\n const labels = await this.locatorForAll('.wr-select__chip-label')();\n return Promise.all(labels.map(label => label.text()));\n }\n\n /**\n * Open the panel.\n *\n * Throws rather than resolving quietly when the panel does not appear, because\n * the two reasons it would not are both worth failing on: a tag-mode select\n * has no panel at all, and a searchable one with `minChars` opens on the query\n * rather than on focus.\n */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n\n await (await this.locatorFor('.wr-select__trigger')()).click();\n if (await this.isOpen()) return;\n\n throw new Error(\n 'WrSelectHarness.open(): the panel did not open. A tag-mode select has no panel, and a ' +\n 'searchable select with `minChars` opens on the query — call typeSearch() instead.'\n );\n }\n\n /** Close the panel. A closed select is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n // Escape reaches the select through the CDK's keyboard dispatcher, so it\n // works for the button trigger and the search input alike.\n await (await this.locatorFor('.wr-select__trigger')()).sendKeys(TestKey.ESCAPE);\n }\n\n /**\n * The options currently offered, in DOM order.\n *\n * Filtered-out options are dropped: a client-side search leaves them in the\n * DOM (registration order has to survive a query) and collapses them with CSS,\n * so counting raw elements would answer with the unfiltered list.\n */\n async getOptions(filters: WrOptionHarnessFilters = {}): Promise<WrOptionHarness[]> {\n const loader = await this.panelLoader();\n const all = await loader.getAllHarnesses(WrOptionHarness.with(filters));\n\n const visible: WrOptionHarness[] = [];\n for (const option of all) {\n if (!(await option.isHidden())) visible.push(option);\n }\n return visible;\n }\n\n /** The labels of the options currently offered, in DOM order. */\n async getOptionLabels(): Promise<string[]> {\n const options = await this.getOptions();\n return Promise.all(options.map(option => option.getText()));\n }\n\n /** Open the panel if needed, then click the first option matching the filters. */\n async selectOption(filters: WrOptionHarnessFilters): Promise<void> {\n await this.open();\n\n const [option] = await this.getOptions(filters);\n if (!option) throw new Error(`WrSelectHarness.selectOption(): no option matched ${JSON.stringify(filters)}.`);\n\n await option.click();\n }\n\n /** Type into a search or tag select's input, replacing whatever is there. */\n async typeSearch(query: string): Promise<void> {\n const input = await this.searchInput();\n if (!input) throw new Error('WrSelectHarness.typeSearch(): this select has no text input.');\n\n await input.clear();\n if (query) await input.sendKeys(query);\n }\n\n /** Click the clear (×) control. It only exists on a `clearable` select with a selection. */\n async clear(): Promise<void> {\n await (await this.locatorFor('.wr-select__clear')()).click();\n }\n\n /** Remove one chip by its label. */\n async removeChip(label: string): Promise<void> {\n const labels = await this.getChipLabels();\n const index = labels.indexOf(label);\n if (index < 0) throw new Error(`WrSelectHarness.removeChip(): no chip labelled \"${label}\".`);\n\n // Label and remove control are emitted once per chip by the same loop, so\n // the two lists line up by index.\n const removes = await this.locatorForAll('.wr-select__chip-remove')();\n await removes[index].click();\n }\n\n /** The \"no results\" message, or `null` when the panel is showing options. */\n async getNoResultsText(): Promise<string | null> {\n const empty = await this.inPanel('.wr-select-panel__empty');\n return empty ? empty.text() : null;\n }\n\n /** Whether the panel is showing its async loading row. */\n async isLoading(): Promise<boolean> {\n return (await this.inPanel('.wr-select-panel__loading')) !== null;\n }\n\n /** Move keyboard focus to the select. */\n async focus(): Promise<void> {\n const input = await this.searchInput();\n return (input ?? (await this.locatorFor('.wr-select__trigger')())).focus();\n }\n\n /** The text input of a search or tag select, or `null` for the button trigger. */\n private async searchInput(): Promise<TestElement | null> {\n return this.locatorForOptional('.wr-select__search-input, .wr-select__tag-input')();\n }\n\n /** One element inside THIS select's panel, or `null`. */\n private async inPanel(selector: string): Promise<TestElement | null> {\n return this.documentRootLocatorFactory().locatorForOptional(`#${await this.panelId()} ${selector}`)();\n }\n\n /**\n * A loader scoped to THIS select's panel.\n *\n * Scoped by the `aria-controls` id rather than by `.wr-select-panel`: the\n * overlay container is shared, so a bare class query would happily return the\n * options of a different select that happens to be open.\n */\n private async panelLoader(): Promise<HarnessLoader> {\n return this.documentRootLocatorFactory().harnessLoaderFor(`#${await this.panelId()}`);\n }\n\n /** The id the trigger publishes as `aria-controls`, which the panel carries. */\n private async panelId(): Promise<string> {\n const combobox = await this.locatorForOptional('[role=\"combobox\"]')();\n const id = combobox ? await combobox.getAttribute('aria-controls') : null;\n\n if (!id) {\n throw new Error('WrSelectHarness: this select has no panel — tag mode renders its options inline.');\n }\n if (!(await this.isOpen())) {\n throw new Error('WrSelectHarness: the panel is closed — call open() first.');\n }\n\n return id;\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-select.mjs
CHANGED
|
@@ -76,10 +76,10 @@ class WrOptionLeading {
|
|
|
76
76
|
static ngTemplateContextGuard(_dir, _ctx) {
|
|
77
77
|
return true;
|
|
78
78
|
}
|
|
79
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
80
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.
|
|
79
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOptionLeading, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
80
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: WrOptionLeading, isStandalone: true, selector: "ng-template[wrOptionLeading]", ngImport: i0 });
|
|
81
81
|
}
|
|
82
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
82
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOptionLeading, decorators: [{
|
|
83
83
|
type: Directive,
|
|
84
84
|
args: [{
|
|
85
85
|
selector: 'ng-template[wrOptionLeading]',
|
|
@@ -311,10 +311,10 @@ class WrOption {
|
|
|
311
311
|
return;
|
|
312
312
|
this.parent.selectOption(this.value());
|
|
313
313
|
}
|
|
314
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
315
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
314
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOption, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
315
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: WrOption, isStandalone: true, selector: "wr-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "click": "onClick()" }, properties: { "attr.id": "id", "class": "classes()", "attr.aria-selected": "selected()", "attr.aria-disabled": "disabled() ? true : null" } }, queries: [{ propertyName: "ownLeading", first: true, predicate: WrOptionLeading, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "leadingSlot", first: true, predicate: ["leadingSlot"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- The leading visual is a sibling of the projected text, never a wrapper\n around it: `.wr-option` is a flex row, and wrapping the content would fold\n a consumer's own icon and label into one flex item. `aria-hidden` keeps it\n out of the option's accessible name; `syncLabel` skips it by reference. -->\n@if (rowLeading(); as leading) {\n <span #leadingSlot class=\"wr-option__leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), placement: 'option' }\"\n />\n </span>\n}\n<ng-content />\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
316
316
|
}
|
|
317
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOption, decorators: [{
|
|
318
318
|
type: Component,
|
|
319
319
|
args: [{ selector: 'wr-option', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet], host: {
|
|
320
320
|
role: 'option',
|
|
@@ -2046,8 +2046,8 @@ class WrSelect {
|
|
|
2046
2046
|
this.overlayRef.dispose();
|
|
2047
2047
|
this.overlayRef = null;
|
|
2048
2048
|
}
|
|
2049
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
2050
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
2049
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2050
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: WrSelect, isStandalone: true, selector: "wr-select", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loader: { classPropertyName: "loader", publicName: "loader", isSignal: true, isRequired: false, transformFunction: null }, serverSearch: { classPropertyName: "serverSearch", publicName: "serverSearch", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, freeText: { classPropertyName: "freeText", publicName: "freeText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxItems: { classPropertyName: "maxItems", publicName: "maxItems", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchQuery: "searchQueryChange", searchChange: "searchChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, providers: [
|
|
2051
2051
|
{
|
|
2052
2052
|
provide: WR_SELECT,
|
|
2053
2053
|
// eslint-disable-next-line @angular-eslint/no-forward-ref
|
|
@@ -2055,7 +2055,7 @@ class WrSelect {
|
|
|
2055
2055
|
},
|
|
2056
2056
|
], queries: [{ propertyName: "defaultLeading", first: true, predicate: WrOptionLeading, isSignal: true }, { propertyName: "projectedOptions", predicate: WrOption, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- The selected option's leading visual, before the field \u2014 the same class\n and the same `placement: 'value'` the button trigger uses, because it is\n the same surface and a consumer who styled one should not have to learn\n a second name. `searchLeading()` draws it only while the field is\n showing the VALUE, so it never stands in front of a query or a bare\n placeholder, and it sits OUTSIDE the input for the obvious reason: an\n `<input>` holds a string. `aria-hidden` keeps it out of the combobox's\n name, which is `resolvedAriaLabel()`, and out of its value, which is the\n input's own text. -->\n @if (searchLeading(); as leading) {\n <span class=\"wr-select__value-leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), placement: 'value' }\"\n />\n </span>\n }\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here.\n `aria-label` is the trigger's name and belongs on the same element for\n the same reason: this input carries `role=\"combobox\"`, and the four\n other trigger shapes all bind `resolvedAriaLabel()`. Without it a\n search select with no placeholder rendered a combobox with no name at\n all \u2014 the one shape where the documented `ariaLabel` input reached\n nothing. -->\n <!-- `aria-autocomplete` is static, and `list` rather than `both`: the panel\n is the only place a suggestion appears, nothing is written back into\n the field for the user. It describes a permanent CAPABILITY, which is\n why it does not track `open()` \u2014 the same reasoning `wr-mention`\n records at its own host binding. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [readonly]=\"isReadonly()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n @if (chip.leading; as leading) {\n <span class=\"wr-select__chip-leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: chip.value, placement: 'chip' }\"\n />\n </span>\n }\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <!-- `aria-autocomplete` is conditional here, unlike the search input's:\n this field is only a combobox when a panel backs it. In plain tag\n mode there is no popup to suggest from, and claiming otherwise\n describes a control that does not exist. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isReadonly() || (isTag() && atCapacity())\"\n [attr.aria-readonly]=\"hasChipSearch() && isReadonly() ? true : null\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-autocomplete]=\"hasChipSearch() ? 'list' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <!-- A selected chip draws its option's leading visual \u2014 one copy per\n VISIBLE chip, so `maxTagCount` caps these too. -->\n @if (chip.leading; as leading) {\n <span class=\"wr-select__chip-leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: chip.value, placement: 'chip' }\"\n />\n </span>\n }\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <!-- Beside the label, not inside it: the harness and the accessible name\n both read `.wr-select__value`, and the visual says nothing either should. -->\n @if (selectedLeading(); as leading) {\n <span class=\"wr-select__value-leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), placement: 'value' }\"\n />\n </span>\n }\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n <!-- Only the select-wide template reaches a virtual row \u2014 there is no\n `<wr-option>` here to declare one \u2014 and only rows inside the\n rendered window instantiate it. -->\n @if (optionLeading(); as leading) {\n <span class=\"wr-option__leading\" aria-hidden=\"true\">\n <ng-container\n [ngTemplateOutlet]=\"leading\"\n [ngTemplateOutletContext]=\"{ $implicit: entry.item, placement: 'option' }\"\n />\n </span>\n }\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled", "label"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
2057
2057
|
}
|
|
2058
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
2058
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrSelect, decorators: [{
|
|
2059
2059
|
type: Component,
|
|
2060
2060
|
args: [{ selector: 'wr-select', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrOption, NgTemplateOutlet], providers: [
|
|
2061
2061
|
{
|
|
@@ -2092,10 +2092,10 @@ class WrOptionGroup {
|
|
|
2092
2092
|
* be wired to it for the name to reach a screen reader.
|
|
2093
2093
|
*/
|
|
2094
2094
|
labelId = randomId('wr-option-group-label');
|
|
2095
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
2096
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.
|
|
2095
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOptionGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2096
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: WrOptionGroup, isStandalone: true, selector: "wr-option-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId" }, classAttribute: "wr-option-group" }, ngImport: i0, template: "<div class=\"wr-option-group__label\" [id]=\"labelId\">{{ label() }}</div>\n<ng-content />\n", encapsulation: i0.ViewEncapsulation.None });
|
|
2097
2097
|
}
|
|
2098
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
2098
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrOptionGroup, decorators: [{
|
|
2099
2099
|
type: Component,
|
|
2100
2100
|
args: [{ selector: 'wr-option-group', encapsulation: ViewEncapsulation.None, host: { class: 'wr-option-group', role: 'group', '[attr.aria-labelledby]': 'labelId' }, template: "<div class=\"wr-option-group__label\" [id]=\"labelId\">{{ label() }}</div>\n<ng-content />\n" }]
|
|
2101
2101
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }] } });
|