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-falling-text.mjs","sources":["../../../projects/lib/falling-text/falling-text.ts","../../../projects/lib/falling-text/falling-text.html","../../../projects/lib/falling-text/ngwr-falling-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FallingText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/falling-text\n *\n * The reactbits version uses matter-js (~140 KB) for the physics. This\n * port ships a minimal AABB physics simulator (gravity + air drag + wall\n * / floor collision + body-vs-body resolution + cursor drag) so there's\n * no runtime dependency.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrFallingTextTrigger } from './interfaces';\n\ninterface Body {\n el: HTMLElement;\n x: number; // centre x\n y: number; // centre y\n vx: number;\n vy: number;\n angle: number;\n angVel: number;\n w: number;\n h: number;\n /** Last `transform` written to `el`. The loop parks when no body's changes. */\n transform: string;\n}\n\nconst RESTITUTION = 0.55;\nconst AIR_DRAG = 0.005;\nconst FRICTION = 0.4; // tangential damping on wall/floor hits\nconst DRAG_STIFFNESS = 0.6;\n\n/** Resolve AABB-vs-AABB collision between two bodies via min-overlap axis. */\nfunction resolveCollision(a: Body, b: Body): void {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const overlapX = a.w / 2 + b.w / 2 - Math.abs(dx);\n const overlapY = a.h / 2 + b.h / 2 - Math.abs(dy);\n if (overlapX <= 0 || overlapY <= 0) return;\n\n if (overlapX < overlapY) {\n // Resolve along x.\n const push = overlapX / 2;\n if (dx > 0) {\n a.x -= push;\n b.x += push;\n } else {\n a.x += push;\n b.x -= push;\n }\n const avg = (a.vx + b.vx) / 2;\n a.vx = avg * RESTITUTION - (a.vx - avg) * RESTITUTION;\n b.vx = avg * RESTITUTION - (b.vx - avg) * RESTITUTION;\n } else {\n const push = overlapY / 2;\n if (dy > 0) {\n a.y -= push;\n b.y += push;\n } else {\n a.y += push;\n b.y -= push;\n }\n const avg = (a.vy + b.vy) / 2;\n a.vy = avg * RESTITUTION - (a.vy - avg) * RESTITUTION;\n b.vy = avg * RESTITUTION - (b.vy - avg) * RESTITUTION;\n }\n\n // Tiny torque so stacked bodies wiggle naturally.\n a.angVel += (Math.random() - 0.5) * 0.2;\n b.angVel += (Math.random() - 0.5) * 0.2;\n}\n\n/**\n * Words fall like physical bodies — gravity, wall/floor collision, and\n * cursor drag-to-pick-up. Words start at their typeset position then\n * release into the simulator on trigger.\n *\n * Triggers:\n * - `'auto'` release immediately on mount\n * - `'scroll'` release on viewport entry\n * - `'hover'` release on first hover\n * - `'click'` release on first click\n *\n * Highlight specific words by passing prefix-matched keywords via\n * `[highlightWords]` — matched spans get `wr-falling-text__word--hl`.\n *\n * @example\n * ```html\n * <wr-falling-text\n * text=\"React-Bits is a library of animated and interactive React components.\"\n * [highlightWords]=\"['React', 'animated', 'components']\"\n * trigger=\"hover\"\n * [gravity]=\"0.6\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/falling-text\n */\n@Component({\n selector: 'wr-falling-text',\n templateUrl: './falling-text.html',\n styleUrl: './falling-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-falling-text' },\n})\nexport class WrFallingText {\n /** Text to render. Words are separated by spaces. */\n readonly text = input.required<string>();\n\n /** Prefix-match keywords; any word starting with one of these gets the highlight class. */\n readonly highlightWords = input<readonly string[]>([]);\n\n /** When to release the words into the simulator. @default 'auto' */\n readonly trigger = input<WrFallingTextTrigger>('auto');\n\n /** Gravity in pixels/sec². @default 980 */\n readonly gravity = input(980, { transform: numAttr(980) });\n\n /** Font size as a CSS length. @default '1rem' */\n readonly fontSize = input('1rem');\n\n private readonly wordsRef = viewChild.required<ElementRef<HTMLDivElement>>('wordsContainer');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private started = false;\n private teardownLoop: (() => void) | undefined;\n\n protected readonly words = computed<readonly { text: string; highlighted: boolean }[]>(() => {\n const hl = this.highlightWords();\n return this.text()\n .split(' ')\n .filter(w => w.length > 0)\n .map(text => ({ text, highlighted: hl.some(h => text.startsWith(h)) }));\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n const trigger = this.trigger();\n if (trigger === 'auto') {\n this.start();\n return;\n }\n if (trigger === 'scroll') {\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.start();\n break;\n }\n }\n },\n { threshold: 0.1 }\n );\n io.observe(this.host.nativeElement);\n this.destroyRef.onDestroy(() => io.disconnect());\n return;\n }\n // hover / click — wait for host event\n const startOnce = (): void => {\n if (this.started) return;\n this.start();\n };\n const handler = trigger === 'hover' ? 'mouseenter' : 'click';\n this.host.nativeElement.addEventListener(handler, startOnce, { once: true });\n this.destroyRef.onDestroy(() => this.host.nativeElement.removeEventListener(handler, startOnce));\n });\n\n this.destroyRef.onDestroy(() => this.teardownLoop?.());\n }\n\n // Physics\n\n private start(): void {\n if (this.started) return;\n // Reduced motion: tumbling words are pure decoration — leave the\n // text standing in its normal layout no matter the trigger.\n if (this.platform.prefersReducedMotion()) return;\n this.started = true;\n\n const hostEl = this.host.nativeElement;\n const wordsContainer = this.wordsRef().nativeElement;\n const containerRect = hostEl.getBoundingClientRect();\n const width = containerRect.width;\n const height = containerRect.height;\n if (width <= 0 || height <= 0) return;\n\n // Snapshot each word's position relative to the container, then absolutise.\n const wordEls = Array.from(wordsContainer.querySelectorAll<HTMLElement>('.wr-falling-text__word'));\n if (wordEls.length === 0) return;\n\n const bodies: Body[] = wordEls.map(el => {\n const r = el.getBoundingClientRect();\n const cx = r.left - containerRect.left + r.width / 2;\n const cy = r.top - containerRect.top + r.height / 2;\n return {\n el,\n x: cx,\n y: cy,\n vx: (Math.random() - 0.5) * 5 * 60, // px/s\n vy: 0,\n angle: 0,\n angVel: (Math.random() - 0.5) * 2,\n w: r.width,\n h: r.height,\n transform: '',\n };\n });\n\n // Absolutely position all word spans so we can move them freely. The\n // rotation origin is fixed: `w` / `h` are measured once here and never\n // reassigned, so writing it alongside every transform re-set the same\n // string sixty times a second.\n bodies.forEach(b => {\n b.el.style.position = 'absolute';\n b.el.style.left = '0';\n b.el.style.top = '0';\n b.el.style.willChange = 'transform';\n b.el.style.transformOrigin = `${b.w / 2}px ${b.h / 2}px`;\n this.commit(b);\n });\n\n let raf = 0;\n let lastTs = 0;\n const tick = (ts: number): void => {\n const dt = lastTs === 0 ? 1 / 60 : Math.min(0.05, (ts - lastTs) / 1000);\n lastTs = ts;\n\n const g = this.gravity();\n for (const b of bodies) {\n // Integrate.\n b.vy += g * dt;\n b.vx *= 1 - AIR_DRAG;\n b.vy *= 1 - AIR_DRAG;\n b.x += b.vx * dt;\n b.y += b.vy * dt;\n b.angle += b.angVel * dt;\n b.angVel *= 1 - AIR_DRAG;\n\n // Walls + floor + ceiling.\n if (b.x - b.w / 2 < 0) {\n b.x = b.w / 2;\n b.vx = Math.abs(b.vx) * RESTITUTION;\n b.angVel *= FRICTION;\n }\n if (b.x + b.w / 2 > width) {\n b.x = width - b.w / 2;\n b.vx = -Math.abs(b.vx) * RESTITUTION;\n b.angVel *= FRICTION;\n }\n if (b.y + b.h / 2 > height) {\n b.y = height - b.h / 2;\n b.vy = -Math.abs(b.vy) * RESTITUTION;\n b.vx *= FRICTION;\n b.angVel *= FRICTION;\n }\n if (b.y - b.h / 2 < 0) {\n b.y = b.h / 2;\n b.vy = Math.abs(b.vy) * RESTITUTION;\n }\n }\n\n // Body-vs-body collisions — O(n²), fine for ~30 words.\n for (let i = 0; i < bodies.length; i++) {\n for (let j = i + 1; j < bodies.length; j++) {\n resolveCollision(bodies[i], bodies[j]);\n }\n }\n\n let moved = false;\n for (const b of bodies) moved = this.commit(b) || moved;\n\n // Park once the words have come to rest. `AIR_DRAG` damps velocities\n // asymptotically so they approach zero and never reach it, and rest is\n // therefore read off the transform the frame WOULD write rather than off\n // the velocities: a word lying on the floor is re-clamped to the same `y`\n // every frame, so its string stops changing while `vy` keeps oscillating\n // around the bounce. Unparked, the integration and the O(n²) pass ran for\n // the life of the page with nothing on screen moving. Words packed\n // tightly enough to keep jostling never satisfy this and keep running —\n // correctly, they are still moving.\n if (!moved) {\n raf = 0;\n return;\n }\n raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n\n // Drag state.\n let drag: { body: Body; offX: number; offY: number } | null = null;\n\n const onPointerDown = (e: PointerEvent): void => {\n const local = this.toLocal(e, hostEl);\n // Pick the topmost (last) body whose AABB contains the pointer.\n for (let i = bodies.length - 1; i >= 0; i--) {\n const b = bodies[i];\n if (\n local.x >= b.x - b.w / 2 &&\n local.x <= b.x + b.w / 2 &&\n local.y >= b.y - b.h / 2 &&\n local.y <= b.y + b.h / 2\n ) {\n drag = { body: b, offX: local.x - b.x, offY: local.y - b.y };\n hostEl.setPointerCapture(e.pointerId);\n // A grab has to wake a parked loop, or the drag springs move a body\n // nothing ever commits — same shape as `WrClickSpark.onClick`.\n if (!raf) {\n lastTs = 0;\n raf = requestAnimationFrame(tick);\n }\n break;\n }\n }\n };\n const onPointerMove = (e: PointerEvent): void => {\n if (!drag) return;\n const local = this.toLocal(e, hostEl);\n const targetX = local.x - drag.offX;\n const targetY = local.y - drag.offY;\n // Spring toward the target — produces a satisfying tug rather than a snap.\n drag.body.vx += (targetX - drag.body.x) * DRAG_STIFFNESS;\n drag.body.vy += (targetY - drag.body.y) * DRAG_STIFFNESS;\n };\n const onPointerUp = (e: PointerEvent): void => {\n if (!drag) return;\n hostEl.releasePointerCapture(e.pointerId);\n drag = null;\n };\n hostEl.addEventListener('pointerdown', onPointerDown);\n hostEl.addEventListener('pointermove', onPointerMove);\n hostEl.addEventListener('pointerup', onPointerUp);\n hostEl.addEventListener('pointercancel', onPointerUp);\n\n this.teardownLoop = (): void => {\n cancelAnimationFrame(raf);\n hostEl.removeEventListener('pointerdown', onPointerDown);\n hostEl.removeEventListener('pointermove', onPointerMove);\n hostEl.removeEventListener('pointerup', onPointerUp);\n hostEl.removeEventListener('pointercancel', onPointerUp);\n };\n }\n\n /** Apply the body's position + angle to the DOM. `false` when it has not moved. */\n private commit(b: Body): boolean {\n const transform = `translate(${b.x - b.w / 2}px, ${b.y - b.h / 2}px) rotate(${b.angle}rad)`;\n if (transform === b.transform) return false;\n b.transform = transform;\n b.el.style.transform = transform;\n return true;\n }\n\n private toLocal(e: PointerEvent | MouseEvent, host: HTMLElement): { x: number; y: number } {\n const rect = host.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n }\n}\n\nexport type { WrFallingTextTrigger } from './interfaces';\n","<div #wordsContainer class=\"wr-falling-text__words\" [style.font-size]=\"fontSize()\">\n @for (w of words(); track $index) {\n <span class=\"wr-falling-text__word\" [class.wr-falling-text__word--hl]=\"w.highlighted\">{{ w.text }}</span>\n @if (!$last) {\n <span class=\"wr-falling-text__space\"> </span>\n }\n }\n</div>\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-falling-text.mjs","sources":["../../../projects/lib/falling-text/falling-text.ts","../../../projects/lib/falling-text/falling-text.html","../../../projects/lib/falling-text/ngwr-falling-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FallingText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/falling-text\n *\n * The reactbits version uses matter-js (~140 KB) for the physics. This\n * port ships a minimal AABB physics simulator (gravity + air drag + wall\n * / floor collision + body-vs-body resolution + cursor drag) so there's\n * no runtime dependency.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrFallingTextTrigger } from './interfaces';\n\ninterface Body {\n el: HTMLElement;\n x: number; // centre x\n y: number; // centre y\n vx: number;\n vy: number;\n angle: number;\n angVel: number;\n w: number;\n h: number;\n /** Last `transform` written to `el`. The loop parks when no body's changes. */\n transform: string;\n}\n\nconst RESTITUTION = 0.55;\nconst AIR_DRAG = 0.005;\nconst FRICTION = 0.4; // tangential damping on wall/floor hits\nconst DRAG_STIFFNESS = 0.6;\n\n/** Resolve AABB-vs-AABB collision between two bodies via min-overlap axis. */\nfunction resolveCollision(a: Body, b: Body): void {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const overlapX = a.w / 2 + b.w / 2 - Math.abs(dx);\n const overlapY = a.h / 2 + b.h / 2 - Math.abs(dy);\n if (overlapX <= 0 || overlapY <= 0) return;\n\n if (overlapX < overlapY) {\n // Resolve along x.\n const push = overlapX / 2;\n if (dx > 0) {\n a.x -= push;\n b.x += push;\n } else {\n a.x += push;\n b.x -= push;\n }\n const avg = (a.vx + b.vx) / 2;\n a.vx = avg * RESTITUTION - (a.vx - avg) * RESTITUTION;\n b.vx = avg * RESTITUTION - (b.vx - avg) * RESTITUTION;\n } else {\n const push = overlapY / 2;\n if (dy > 0) {\n a.y -= push;\n b.y += push;\n } else {\n a.y += push;\n b.y -= push;\n }\n const avg = (a.vy + b.vy) / 2;\n a.vy = avg * RESTITUTION - (a.vy - avg) * RESTITUTION;\n b.vy = avg * RESTITUTION - (b.vy - avg) * RESTITUTION;\n }\n\n // Tiny torque so stacked bodies wiggle naturally.\n a.angVel += (Math.random() - 0.5) * 0.2;\n b.angVel += (Math.random() - 0.5) * 0.2;\n}\n\n/**\n * Words fall like physical bodies — gravity, wall/floor collision, and\n * cursor drag-to-pick-up. Words start at their typeset position then\n * release into the simulator on trigger.\n *\n * Triggers:\n * - `'auto'` release immediately on mount\n * - `'scroll'` release on viewport entry\n * - `'hover'` release on first hover\n * - `'click'` release on first click\n *\n * Highlight specific words by passing prefix-matched keywords via\n * `[highlightWords]` — matched spans get `wr-falling-text__word--hl`.\n *\n * @example\n * ```html\n * <wr-falling-text\n * text=\"React-Bits is a library of animated and interactive React components.\"\n * [highlightWords]=\"['React', 'animated', 'components']\"\n * trigger=\"hover\"\n * [gravity]=\"0.6\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/falling-text\n */\n@Component({\n selector: 'wr-falling-text',\n templateUrl: './falling-text.html',\n styleUrl: './falling-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-falling-text' },\n})\nexport class WrFallingText {\n /** Text to render. Words are separated by spaces. */\n readonly text = input.required<string>();\n\n /** Prefix-match keywords; any word starting with one of these gets the highlight class. */\n readonly highlightWords = input<readonly string[]>([]);\n\n /** When to release the words into the simulator. @default 'auto' */\n readonly trigger = input<WrFallingTextTrigger>('auto');\n\n /** Gravity in pixels/sec². @default 980 */\n readonly gravity = input(980, { transform: numAttr(980) });\n\n /** Font size as a CSS length. @default '1rem' */\n readonly fontSize = input('1rem');\n\n private readonly wordsRef = viewChild.required<ElementRef<HTMLDivElement>>('wordsContainer');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private started = false;\n private teardownLoop: (() => void) | undefined;\n\n protected readonly words = computed<readonly { text: string; highlighted: boolean }[]>(() => {\n const hl = this.highlightWords();\n return this.text()\n .split(' ')\n .filter(w => w.length > 0)\n .map(text => ({ text, highlighted: hl.some(h => text.startsWith(h)) }));\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n const trigger = this.trigger();\n if (trigger === 'auto') {\n this.start();\n return;\n }\n if (trigger === 'scroll') {\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.start();\n break;\n }\n }\n },\n { threshold: 0.1 }\n );\n io.observe(this.host.nativeElement);\n this.destroyRef.onDestroy(() => io.disconnect());\n return;\n }\n // hover / click — wait for host event\n const startOnce = (): void => {\n if (this.started) return;\n this.start();\n };\n const handler = trigger === 'hover' ? 'mouseenter' : 'click';\n this.host.nativeElement.addEventListener(handler, startOnce, { once: true });\n this.destroyRef.onDestroy(() => this.host.nativeElement.removeEventListener(handler, startOnce));\n });\n\n this.destroyRef.onDestroy(() => this.teardownLoop?.());\n }\n\n // Physics\n\n private start(): void {\n if (this.started) return;\n // Reduced motion: tumbling words are pure decoration — leave the\n // text standing in its normal layout no matter the trigger.\n if (this.platform.prefersReducedMotion()) return;\n this.started = true;\n\n const hostEl = this.host.nativeElement;\n const wordsContainer = this.wordsRef().nativeElement;\n const containerRect = hostEl.getBoundingClientRect();\n const width = containerRect.width;\n const height = containerRect.height;\n if (width <= 0 || height <= 0) return;\n\n // Snapshot each word's position relative to the container, then absolutise.\n const wordEls = Array.from(wordsContainer.querySelectorAll<HTMLElement>('.wr-falling-text__word'));\n if (wordEls.length === 0) return;\n\n const bodies: Body[] = wordEls.map(el => {\n const r = el.getBoundingClientRect();\n const cx = r.left - containerRect.left + r.width / 2;\n const cy = r.top - containerRect.top + r.height / 2;\n return {\n el,\n x: cx,\n y: cy,\n vx: (Math.random() - 0.5) * 5 * 60, // px/s\n vy: 0,\n angle: 0,\n angVel: (Math.random() - 0.5) * 2,\n w: r.width,\n h: r.height,\n transform: '',\n };\n });\n\n // Absolutely position all word spans so we can move them freely. The\n // rotation origin is fixed: `w` / `h` are measured once here and never\n // reassigned, so writing it alongside every transform re-set the same\n // string sixty times a second.\n bodies.forEach(b => {\n b.el.style.position = 'absolute';\n b.el.style.left = '0';\n b.el.style.top = '0';\n b.el.style.willChange = 'transform';\n b.el.style.transformOrigin = `${b.w / 2}px ${b.h / 2}px`;\n this.commit(b);\n });\n\n let raf = 0;\n let lastTs = 0;\n const tick = (ts: number): void => {\n const dt = lastTs === 0 ? 1 / 60 : Math.min(0.05, (ts - lastTs) / 1000);\n lastTs = ts;\n\n const g = this.gravity();\n for (const b of bodies) {\n // Integrate.\n b.vy += g * dt;\n b.vx *= 1 - AIR_DRAG;\n b.vy *= 1 - AIR_DRAG;\n b.x += b.vx * dt;\n b.y += b.vy * dt;\n b.angle += b.angVel * dt;\n b.angVel *= 1 - AIR_DRAG;\n\n // Walls + floor + ceiling.\n if (b.x - b.w / 2 < 0) {\n b.x = b.w / 2;\n b.vx = Math.abs(b.vx) * RESTITUTION;\n b.angVel *= FRICTION;\n }\n if (b.x + b.w / 2 > width) {\n b.x = width - b.w / 2;\n b.vx = -Math.abs(b.vx) * RESTITUTION;\n b.angVel *= FRICTION;\n }\n if (b.y + b.h / 2 > height) {\n b.y = height - b.h / 2;\n b.vy = -Math.abs(b.vy) * RESTITUTION;\n b.vx *= FRICTION;\n b.angVel *= FRICTION;\n }\n if (b.y - b.h / 2 < 0) {\n b.y = b.h / 2;\n b.vy = Math.abs(b.vy) * RESTITUTION;\n }\n }\n\n // Body-vs-body collisions — O(n²), fine for ~30 words.\n for (let i = 0; i < bodies.length; i++) {\n for (let j = i + 1; j < bodies.length; j++) {\n resolveCollision(bodies[i], bodies[j]);\n }\n }\n\n let moved = false;\n for (const b of bodies) moved = this.commit(b) || moved;\n\n // Park once the words have come to rest. `AIR_DRAG` damps velocities\n // asymptotically so they approach zero and never reach it, and rest is\n // therefore read off the transform the frame WOULD write rather than off\n // the velocities: a word lying on the floor is re-clamped to the same `y`\n // every frame, so its string stops changing while `vy` keeps oscillating\n // around the bounce. Unparked, the integration and the O(n²) pass ran for\n // the life of the page with nothing on screen moving. Words packed\n // tightly enough to keep jostling never satisfy this and keep running —\n // correctly, they are still moving.\n if (!moved) {\n raf = 0;\n return;\n }\n raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n\n // Drag state.\n let drag: { body: Body; offX: number; offY: number } | null = null;\n\n const onPointerDown = (e: PointerEvent): void => {\n const local = this.toLocal(e, hostEl);\n // Pick the topmost (last) body whose AABB contains the pointer.\n for (let i = bodies.length - 1; i >= 0; i--) {\n const b = bodies[i];\n if (\n local.x >= b.x - b.w / 2 &&\n local.x <= b.x + b.w / 2 &&\n local.y >= b.y - b.h / 2 &&\n local.y <= b.y + b.h / 2\n ) {\n drag = { body: b, offX: local.x - b.x, offY: local.y - b.y };\n hostEl.setPointerCapture(e.pointerId);\n // A grab has to wake a parked loop, or the drag springs move a body\n // nothing ever commits — same shape as `WrClickSpark.onClick`.\n if (!raf) {\n lastTs = 0;\n raf = requestAnimationFrame(tick);\n }\n break;\n }\n }\n };\n const onPointerMove = (e: PointerEvent): void => {\n if (!drag) return;\n const local = this.toLocal(e, hostEl);\n const targetX = local.x - drag.offX;\n const targetY = local.y - drag.offY;\n // Spring toward the target — produces a satisfying tug rather than a snap.\n drag.body.vx += (targetX - drag.body.x) * DRAG_STIFFNESS;\n drag.body.vy += (targetY - drag.body.y) * DRAG_STIFFNESS;\n };\n const onPointerUp = (e: PointerEvent): void => {\n if (!drag) return;\n hostEl.releasePointerCapture(e.pointerId);\n drag = null;\n };\n hostEl.addEventListener('pointerdown', onPointerDown);\n hostEl.addEventListener('pointermove', onPointerMove);\n hostEl.addEventListener('pointerup', onPointerUp);\n hostEl.addEventListener('pointercancel', onPointerUp);\n\n this.teardownLoop = (): void => {\n cancelAnimationFrame(raf);\n hostEl.removeEventListener('pointerdown', onPointerDown);\n hostEl.removeEventListener('pointermove', onPointerMove);\n hostEl.removeEventListener('pointerup', onPointerUp);\n hostEl.removeEventListener('pointercancel', onPointerUp);\n };\n }\n\n /** Apply the body's position + angle to the DOM. `false` when it has not moved. */\n private commit(b: Body): boolean {\n const transform = `translate(${b.x - b.w / 2}px, ${b.y - b.h / 2}px) rotate(${b.angle}rad)`;\n if (transform === b.transform) return false;\n b.transform = transform;\n b.el.style.transform = transform;\n return true;\n }\n\n private toLocal(e: PointerEvent | MouseEvent, host: HTMLElement): { x: number; y: number } {\n const rect = host.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n }\n}\n\nexport type { WrFallingTextTrigger } from './interfaces';\n","<div #wordsContainer class=\"wr-falling-text__words\" [style.font-size]=\"fontSize()\">\n @for (w of words(); track $index) {\n <span class=\"wr-falling-text__word\" [class.wr-falling-text__word--hl]=\"w.highlighted\">{{ w.text }}</span>\n @if (!$last) {\n <span class=\"wr-falling-text__space\"> </span>\n }\n }\n</div>\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-file-upload-testing.mjs","sources":["../../../projects/lib/file-upload/testing/wr-file-upload-harness.ts","../../../projects/lib/file-upload/testing/ngwr-file-upload-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, type EventData, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrFileUploadHarnessFilters } from './interfaces';\n\n/**\n * A `DataTransfer` carrying `files`.\n *\n * `FileList` has no constructor anywhere — a browser mints one only as a side\n * effect of `new DataTransfer()`, and jsdom ships neither (`new FileList()` is an\n * illegal constructor there, and the `files` IDL setter refuses anything that is\n * not the genuine article). So: the real thing where it can be had, and an\n * array-like stand-in otherwise, which is all the component ever reads — it does\n * `Array.from(files)` and nothing else.\n */\nfunction dataTransferFor(files: readonly File[]): DataTransfer {\n const Transfer = (globalThis as { DataTransfer?: new () => DataTransfer }).DataTransfer;\n\n if (Transfer) {\n try {\n const transfer = new Transfer();\n for (const file of files) transfer.items.add(file);\n return transfer;\n } catch {\n // A DataTransfer that cannot take items is no use — fall through.\n }\n }\n\n const list: Record<PropertyKey, unknown> = { ...files };\n list['length'] = files.length;\n list['item'] = (index: number): File | null => files[index] ?? null;\n list[Symbol.iterator] = function* (): Generator<File> {\n yield* files;\n };\n\n return {\n dropEffect: 'none',\n effectAllowed: 'all',\n files: list,\n items: [],\n types: files.length > 0 ? ['Files'] : [],\n } as unknown as DataTransfer;\n}\n\n/**\n * The payload `TestElement.dispatchEvent` merges onto the event it fires.\n *\n * One transfer is shared by every event of a drag, the way a real one is: the\n * component writes `dropEffect` on it during `dragover` and reads `files` back\n * out on `drop`.\n */\nfunction dragPayload(transfer: DataTransfer): Record<string, EventData> {\n return { dataTransfer: transfer } as unknown as Record<string, EventData>;\n}\n\n/** The native element behind a `TestElement`, or `null` when there is no DOM to reach. */\nfunction nativeElement(element: TestElement): Element | null {\n const candidate = (element as Partial<{ element: unknown }>).element;\n return typeof Element !== 'undefined' && candidate instanceof Element ? candidate : null;\n}\n\nfunction isFileInput(element: Element | null): element is HTMLInputElement {\n return element !== null && element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'file';\n}\n\n/** Put `files` on the picker, whichever way this environment allows. */\nfunction setPickerFiles(input: HTMLInputElement, files: readonly File[]): void {\n const list = dataTransferFor(files).files;\n\n try {\n input.files = list;\n if (input.files?.length === files.length) return;\n } catch {\n // jsdom's IDL setter refuses a stand-in FileList. Shadow the accessor instead.\n }\n\n // An own data property wins over the prototype accessor, so the component still\n // reads its files off the REAL input — which matters, because the alternative\n // (forging `event.target`) would let a harness pass on a component that never\n // touches the element the user operated.\n Object.defineProperty(input, 'files', { configurable: true, value: list });\n}\n\n/**\n * Test harness for `<wr-file-upload>`.\n *\n * The control a user operates is the `.wr-file-upload__zone` — a `role=\"button\"`\n * div that is the single tab stop; the `<input type=\"file\">` behind it is\n * `aria-hidden` and `tabindex=\"-1\"`, and exists only so a click can be forwarded\n * to it. So focus, drag and disabled state are all read off the zone, plus the\n * `wr-file-upload--*` host modifiers and the rendered file list. The picker is\n * touched only where it is the one that carries the state — `accept` and\n * `multiple` land on it, and {@link selectFiles} has to hand it the files.\n *\n * What this component does NOT have, so this harness does not either:\n * - **no per-file progress or status.** `<wr-file-upload>` picks files; uploading\n * them is the app's job, and there is no bar or state to read.\n * - **no rendered error.** A file refused for its type, its size or the file count\n * leaves NOTHING in the DOM — no `aria-invalid`, no message, and a list that\n * simply did not grow. The `(rejected)` output is the only channel, so a spec\n * asserts on the host's handler. See {@link selectFiles}.\n * - **no `maxSize` / `maxFiles` readback.** Neither reaches the DOM (unlike\n * `accept` and `multiple`, which land on the picker), so they can only be\n * observed by offering a file that breaks them.\n *\n * Two traps worth knowing before writing assertions. The bound value's SHAPE\n * follows `multiple` — a bare `File` (or `null`) in single mode, a `File[]` in\n * multi — and `[showList]=\"false\"` renders no list at all, so {@link getFileNames}\n * answers `[]` for a component that is holding files. Assert the bound value in\n * that case.\n *\n * @example\n * ```ts\n * const upload = await loader.getHarness(WrFileUploadHarness.with({ label: 'Avatar' }));\n * await upload.selectFiles(new File(['…'], 'me.png', { type: 'image/png' }));\n *\n * expect(await upload.getFileNames()).toEqual(['me.png']);\n * await upload.removeFileNamed('me.png');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFileUploadHarness extends ComponentHarness {\n static hostSelector = 'wr-file-upload';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFileUploadHarnessFilters = {}): HarnessPredicate<WrFileUploadHarness> {\n return new HarnessPredicate(WrFileUploadHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('multiple', options.multiple, async (harness, multiple) => (await harness.isMultiple()) === multiple)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly zone = this.locatorFor('.wr-file-upload__zone');\n private readonly picker = this.locatorFor('input.wr-file-upload__picker');\n private readonly items = this.locatorForAll('.wr-file-upload__item');\n private readonly names = this.locatorForAll('.wr-file-upload__name');\n private readonly sizes = this.locatorForAll('.wr-file-upload__size');\n private readonly removeButtons = this.locatorForAll('.wr-file-upload__remove');\n\n /**\n * The accessible name of the trigger — what a screen-reader user is told this\n * control is.\n *\n * Read from the zone's `aria-label` (the `dropZoneLabel` input) rather than from\n * the copy inside it, because that label OVERRIDES the copy: the visible \"Click\n * to browse\" is not the accessible name, and a harness that returned it would\n * describe a control nobody is announced.\n */\n async getLabel(): Promise<string> {\n return (await (await this.zone()).getAttribute('aria-label')) ?? '';\n }\n\n /** The visible call-to-action line — the `pickLabel` input. */\n async getPickText(): Promise<string> {\n return (await this.locatorFor('.wr-file-upload__hint strong')()).text();\n }\n\n /** The visible instruction below the CTA — the `dropLabel` input. */\n async getDropText(): Promise<string> {\n return (await this.locatorFor('.wr-file-upload__hint span')()).text();\n }\n\n /** The optional `helperText` line, or `null` when the component was given none. */\n async getHelperText(): Promise<string | null> {\n const helper = await this.locatorForOptional('.wr-file-upload__hint small')();\n return helper === null ? null : helper.text();\n }\n\n /** The `accept` list the picker advertises, or `null` when it accepts anything. */\n async getAccept(): Promise<string | null> {\n return (await this.picker()).getAttribute('accept');\n }\n\n /**\n * Whether more than one file can be held.\n *\n * Read as the DOM PROPERTY: the template writes the attribute as an empty string\n * (`[attr.multiple]=\"multiple() ? '' : null\"`), and `''` is falsy — an attribute\n * read would report a multi-file upload as single.\n */\n async isMultiple(): Promise<boolean> {\n return (await this.picker()).getProperty<boolean>('multiple');\n }\n\n /**\n * Whether the control refuses interaction.\n *\n * Read from the zone's `aria-disabled`. The host also carries\n * `wr-file-upload--disabled` and the picker its own `disabled`, but the zone is\n * a `role=\"button\"` div — `aria-disabled` is the only one of the three that\n * tells a screen-reader user the trigger is inert.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.zone()).getAttribute('aria-disabled')) === 'true';\n }\n\n /**\n * Whether the zone is armed for a drop.\n *\n * The `wr-file-upload--dragging` host modifier is the whole signal — a drop\n * target has no ARIA state, so there is nothing better to read here.\n */\n async isDragging(): Promise<boolean> {\n return (await this.host()).hasClass('wr-file-upload--dragging');\n }\n\n /** How many files the list is RENDERING. Zero with `[showList]=\"false\"`, always. */\n async getFileCount(): Promise<number> {\n return (await this.items()).length;\n }\n\n /** The file names in the rendered list, in order. */\n async getFileNames(): Promise<string[]> {\n const names = await this.names();\n return Promise.all(names.map(name => name.text()));\n }\n\n /**\n * The file sizes in the rendered list, as SHOWN — `'4.9 KB'`, not `5000`.\n *\n * The component formats bytes for display and the byte count never reaches the\n * DOM, so this is the string a user reads. Assert the `File` itself if the exact\n * size matters.\n */\n async getFileSizes(): Promise<string[]> {\n const sizes = await this.sizes();\n return Promise.all(sizes.map(size => size.text()));\n }\n\n /**\n * Hand files to the picker, the way choosing them in the browser dialog does.\n *\n * This is the one method that reaches past `TestElement` to the native\n * `<input type=\"file\">`, and it has to: `TestElement` has no file API, and it\n * cannot grow one portably, because a `FileList` is not constructible and a\n * remote WebDriver session cannot be handed a `File` object at all. Everything\n * else on this harness is environment-agnostic; this needs a DOM behind it\n * (`TestbedHarnessEnvironment`), and says so with an error when it does not have\n * one. The input is found through this harness's own locator rather than a\n * `document` query, so it is still THIS component's picker that gets the files.\n *\n * Rejected files are silent: nothing appears in the DOM, so pair this with the\n * host's `(rejected)` handler when a file is meant to be refused. In single mode\n * the LAST accepted file wins, which is also how a drop of several files onto a\n * single-file zone behaves.\n *\n * @throws if called with no files, on a disabled upload, or outside a DOM\n * environment.\n */\n async selectFiles(...files: File[]): Promise<void> {\n if (files.length === 0) {\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): pass at least one file. Cancelling the browser dialog fires no ' +\n '`change` at all, so \"picked nothing\" is not a state a user can put the component in.'\n );\n }\n\n if (await this.isDisabled()) {\n // The component guards its drop handler but not its `change` handler — it\n // does not need to, since a disabled `<input type=\"file\">` cannot be\n // operated. Forcing the event through anyway would smuggle a file past the\n // disabled state and leave a consumer's spec proving something no user can do.\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): the upload is disabled, and a disabled picker cannot be opened. ' +\n 'Enable it first, or use dropFiles() to assert that a disabled zone refuses what is dropped on it.'\n );\n }\n\n const picker = await this.picker();\n const input = nativeElement(picker);\n\n if (!isFileInput(input)) {\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): no DOM element behind the harness. Files can only be handed to a ' +\n 'real <input type=\"file\">, so this method needs TestbedHarnessEnvironment; a remote WebDriver ' +\n 'environment cannot carry a File across. Every other method on this harness works in both.'\n );\n }\n\n setPickerFiles(input, files);\n await picker.dispatchEvent('change');\n }\n\n /**\n * Drag files over the zone and drop them.\n *\n * Driven by dispatching the drag sequence AT the zone rather than by coordinates:\n * nothing has layout in a unit test — `getBoundingClientRect` is all zeros — so a\n * pointer-driven drag would land nowhere. From the component's side it is the\n * same event either way; it reads `dataTransfer.files` and never a position.\n *\n * A disabled upload refuses the drop itself, so this stays a no-op there rather\n * than throwing the way {@link selectFiles} does — that refusal is the\n * component's own, and worth asserting.\n */\n async dropFiles(...files: File[]): Promise<void> {\n const zone = await this.zone();\n const payload = dragPayload(dataTransferFor(files));\n\n await zone.dispatchEvent('dragenter', payload);\n await zone.dispatchEvent('dragover', payload);\n await zone.dispatchEvent('drop', payload);\n }\n\n /** Drag over the zone without letting go — arms {@link isDragging}. */\n async dragOver(): Promise<void> {\n const zone = await this.zone();\n const payload = dragPayload(dataTransferFor([]));\n\n await zone.dispatchEvent('dragenter', payload);\n await zone.dispatchEvent('dragover', payload);\n }\n\n /**\n * Drag back out of the zone, disarming {@link isDragging}.\n *\n * Dispatched at the zone itself on purpose: the component ignores a `dragleave`\n * whose target is one of the zone's children, because crossing the icon or the\n * hint text is not leaving the zone.\n */\n async dragLeave(): Promise<void> {\n await (await this.zone()).dispatchEvent('dragleave');\n }\n\n /**\n * Remove the file at `index` from the rendered list.\n *\n * On a disabled upload the remove buttons are disabled too, so this is a no-op\n * there — assert the result rather than assuming it.\n */\n async removeFile(index: number): Promise<void> {\n const buttons = await this.removeButtons();\n\n if (index < 0 || index >= buttons.length) {\n throw new Error(\n `WrFileUploadHarness.removeFile(${index}): the rendered list holds ${buttons.length} file(s). ` +\n 'With [showList]=\"false\" it holds none no matter what is selected.'\n );\n }\n\n await buttons[index].click();\n }\n\n /** Remove the file with this name. Names come from {@link getFileNames}. */\n async removeFileNamed(name: string): Promise<void> {\n const names = await this.getFileNames();\n const index = names.indexOf(name);\n\n if (index === -1) {\n const holding = names.length > 0 ? names.map(n => JSON.stringify(n)).join(', ') : 'no files';\n throw new Error(\n `WrFileUploadHarness.removeFileNamed(${JSON.stringify(name)}): the rendered list holds ${holding}.`\n );\n }\n\n await this.removeFile(index);\n }\n\n /** Move keyboard focus to the zone — the control's only tab stop. */\n async focus(): Promise<void> {\n return (await this.zone()).focus();\n }\n\n /** Whether the zone has focus. */\n async isFocused(): Promise<boolean> {\n return (await this.zone()).isFocused();\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-file-upload-testing.mjs","sources":["../../../projects/lib/file-upload/testing/wr-file-upload-harness.ts","../../../projects/lib/file-upload/testing/ngwr-file-upload-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, type EventData, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrFileUploadHarnessFilters } from './interfaces';\n\n/**\n * A `DataTransfer` carrying `files`.\n *\n * `FileList` has no constructor anywhere — a browser mints one only as a side\n * effect of `new DataTransfer()`, and jsdom ships neither (`new FileList()` is an\n * illegal constructor there, and the `files` IDL setter refuses anything that is\n * not the genuine article). So: the real thing where it can be had, and an\n * array-like stand-in otherwise, which is all the component ever reads — it does\n * `Array.from(files)` and nothing else.\n */\nfunction dataTransferFor(files: readonly File[]): DataTransfer {\n const Transfer = (globalThis as { DataTransfer?: new () => DataTransfer }).DataTransfer;\n\n if (Transfer) {\n try {\n const transfer = new Transfer();\n for (const file of files) transfer.items.add(file);\n return transfer;\n } catch {\n // A DataTransfer that cannot take items is no use — fall through.\n }\n }\n\n const list: Record<PropertyKey, unknown> = { ...files };\n list['length'] = files.length;\n list['item'] = (index: number): File | null => files[index] ?? null;\n list[Symbol.iterator] = function* (): Generator<File> {\n yield* files;\n };\n\n return {\n dropEffect: 'none',\n effectAllowed: 'all',\n files: list,\n items: [],\n types: files.length > 0 ? ['Files'] : [],\n } as unknown as DataTransfer;\n}\n\n/**\n * The payload `TestElement.dispatchEvent` merges onto the event it fires.\n *\n * One transfer is shared by every event of a drag, the way a real one is: the\n * component writes `dropEffect` on it during `dragover` and reads `files` back\n * out on `drop`.\n */\nfunction dragPayload(transfer: DataTransfer): Record<string, EventData> {\n return { dataTransfer: transfer } as unknown as Record<string, EventData>;\n}\n\n/** The native element behind a `TestElement`, or `null` when there is no DOM to reach. */\nfunction nativeElement(element: TestElement): Element | null {\n const candidate = (element as Partial<{ element: unknown }>).element;\n return typeof Element !== 'undefined' && candidate instanceof Element ? candidate : null;\n}\n\nfunction isFileInput(element: Element | null): element is HTMLInputElement {\n return element !== null && element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'file';\n}\n\n/** Put `files` on the picker, whichever way this environment allows. */\nfunction setPickerFiles(input: HTMLInputElement, files: readonly File[]): void {\n const list = dataTransferFor(files).files;\n\n try {\n input.files = list;\n if (input.files?.length === files.length) return;\n } catch {\n // jsdom's IDL setter refuses a stand-in FileList. Shadow the accessor instead.\n }\n\n // An own data property wins over the prototype accessor, so the component still\n // reads its files off the REAL input — which matters, because the alternative\n // (forging `event.target`) would let a harness pass on a component that never\n // touches the element the user operated.\n Object.defineProperty(input, 'files', { configurable: true, value: list });\n}\n\n/**\n * Test harness for `<wr-file-upload>`.\n *\n * The control a user operates is the `.wr-file-upload__zone` — a `role=\"button\"`\n * div that is the single tab stop; the `<input type=\"file\">` behind it is\n * `aria-hidden` and `tabindex=\"-1\"`, and exists only so a click can be forwarded\n * to it. So focus, drag and disabled state are all read off the zone, plus the\n * `wr-file-upload--*` host modifiers and the rendered file list. The picker is\n * touched only where it is the one that carries the state — `accept` and\n * `multiple` land on it, and {@link selectFiles} has to hand it the files.\n *\n * What this component does NOT have, so this harness does not either:\n * - **no per-file progress or status.** `<wr-file-upload>` picks files; uploading\n * them is the app's job, and there is no bar or state to read.\n * - **no rendered error.** A file refused for its type, its size or the file count\n * leaves NOTHING in the DOM — no `aria-invalid`, no message, and a list that\n * simply did not grow. The `(rejected)` output is the only channel, so a spec\n * asserts on the host's handler. See {@link selectFiles}.\n * - **no `maxSize` / `maxFiles` readback.** Neither reaches the DOM (unlike\n * `accept` and `multiple`, which land on the picker), so they can only be\n * observed by offering a file that breaks them.\n *\n * Two traps worth knowing before writing assertions. The bound value's SHAPE\n * follows `multiple` — a bare `File` (or `null`) in single mode, a `File[]` in\n * multi — and `[showList]=\"false\"` renders no list at all, so {@link getFileNames}\n * answers `[]` for a component that is holding files. Assert the bound value in\n * that case.\n *\n * @example\n * ```ts\n * const upload = await loader.getHarness(WrFileUploadHarness.with({ label: 'Avatar' }));\n * await upload.selectFiles(new File(['…'], 'me.png', { type: 'image/png' }));\n *\n * expect(await upload.getFileNames()).toEqual(['me.png']);\n * await upload.removeFileNamed('me.png');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFileUploadHarness extends ComponentHarness {\n static hostSelector = 'wr-file-upload';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFileUploadHarnessFilters = {}): HarnessPredicate<WrFileUploadHarness> {\n return new HarnessPredicate(WrFileUploadHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('multiple', options.multiple, async (harness, multiple) => (await harness.isMultiple()) === multiple)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly zone = this.locatorFor('.wr-file-upload__zone');\n private readonly picker = this.locatorFor('input.wr-file-upload__picker');\n private readonly items = this.locatorForAll('.wr-file-upload__item');\n private readonly names = this.locatorForAll('.wr-file-upload__name');\n private readonly sizes = this.locatorForAll('.wr-file-upload__size');\n private readonly removeButtons = this.locatorForAll('.wr-file-upload__remove');\n\n /**\n * The accessible name of the trigger — what a screen-reader user is told this\n * control is.\n *\n * Read from the zone's `aria-label` (the `dropZoneLabel` input) rather than from\n * the copy inside it, because that label OVERRIDES the copy: the visible \"Click\n * to browse\" is not the accessible name, and a harness that returned it would\n * describe a control nobody is announced.\n */\n async getLabel(): Promise<string> {\n return (await (await this.zone()).getAttribute('aria-label')) ?? '';\n }\n\n /** The visible call-to-action line — the `pickLabel` input. */\n async getPickText(): Promise<string> {\n return (await this.locatorFor('.wr-file-upload__hint strong')()).text();\n }\n\n /** The visible instruction below the CTA — the `dropLabel` input. */\n async getDropText(): Promise<string> {\n return (await this.locatorFor('.wr-file-upload__hint span')()).text();\n }\n\n /** The optional `helperText` line, or `null` when the component was given none. */\n async getHelperText(): Promise<string | null> {\n const helper = await this.locatorForOptional('.wr-file-upload__hint small')();\n return helper === null ? null : helper.text();\n }\n\n /** The `accept` list the picker advertises, or `null` when it accepts anything. */\n async getAccept(): Promise<string | null> {\n return (await this.picker()).getAttribute('accept');\n }\n\n /**\n * Whether more than one file can be held.\n *\n * Read as the DOM PROPERTY: the template writes the attribute as an empty string\n * (`[attr.multiple]=\"multiple() ? '' : null\"`), and `''` is falsy — an attribute\n * read would report a multi-file upload as single.\n */\n async isMultiple(): Promise<boolean> {\n return (await this.picker()).getProperty<boolean>('multiple');\n }\n\n /**\n * Whether the control refuses interaction.\n *\n * Read from the zone's `aria-disabled`. The host also carries\n * `wr-file-upload--disabled` and the picker its own `disabled`, but the zone is\n * a `role=\"button\"` div — `aria-disabled` is the only one of the three that\n * tells a screen-reader user the trigger is inert.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.zone()).getAttribute('aria-disabled')) === 'true';\n }\n\n /**\n * Whether the zone is armed for a drop.\n *\n * The `wr-file-upload--dragging` host modifier is the whole signal — a drop\n * target has no ARIA state, so there is nothing better to read here.\n */\n async isDragging(): Promise<boolean> {\n return (await this.host()).hasClass('wr-file-upload--dragging');\n }\n\n /** How many files the list is RENDERING. Zero with `[showList]=\"false\"`, always. */\n async getFileCount(): Promise<number> {\n return (await this.items()).length;\n }\n\n /** The file names in the rendered list, in order. */\n async getFileNames(): Promise<string[]> {\n const names = await this.names();\n return Promise.all(names.map(name => name.text()));\n }\n\n /**\n * The file sizes in the rendered list, as SHOWN — `'4.9 KB'`, not `5000`.\n *\n * The component formats bytes for display and the byte count never reaches the\n * DOM, so this is the string a user reads. Assert the `File` itself if the exact\n * size matters.\n */\n async getFileSizes(): Promise<string[]> {\n const sizes = await this.sizes();\n return Promise.all(sizes.map(size => size.text()));\n }\n\n /**\n * Hand files to the picker, the way choosing them in the browser dialog does.\n *\n * This is the one method that reaches past `TestElement` to the native\n * `<input type=\"file\">`, and it has to: `TestElement` has no file API, and it\n * cannot grow one portably, because a `FileList` is not constructible and a\n * remote WebDriver session cannot be handed a `File` object at all. Everything\n * else on this harness is environment-agnostic; this needs a DOM behind it\n * (`TestbedHarnessEnvironment`), and says so with an error when it does not have\n * one. The input is found through this harness's own locator rather than a\n * `document` query, so it is still THIS component's picker that gets the files.\n *\n * Rejected files are silent: nothing appears in the DOM, so pair this with the\n * host's `(rejected)` handler when a file is meant to be refused. In single mode\n * the LAST accepted file wins, which is also how a drop of several files onto a\n * single-file zone behaves.\n *\n * @throws if called with no files, on a disabled upload, or outside a DOM\n * environment.\n */\n async selectFiles(...files: File[]): Promise<void> {\n if (files.length === 0) {\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): pass at least one file. Cancelling the browser dialog fires no ' +\n '`change` at all, so \"picked nothing\" is not a state a user can put the component in.'\n );\n }\n\n if (await this.isDisabled()) {\n // The component guards its drop handler but not its `change` handler — it\n // does not need to, since a disabled `<input type=\"file\">` cannot be\n // operated. Forcing the event through anyway would smuggle a file past the\n // disabled state and leave a consumer's spec proving something no user can do.\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): the upload is disabled, and a disabled picker cannot be opened. ' +\n 'Enable it first, or use dropFiles() to assert that a disabled zone refuses what is dropped on it.'\n );\n }\n\n const picker = await this.picker();\n const input = nativeElement(picker);\n\n if (!isFileInput(input)) {\n throw new Error(\n 'WrFileUploadHarness.selectFiles(): no DOM element behind the harness. Files can only be handed to a ' +\n 'real <input type=\"file\">, so this method needs TestbedHarnessEnvironment; a remote WebDriver ' +\n 'environment cannot carry a File across. Every other method on this harness works in both.'\n );\n }\n\n setPickerFiles(input, files);\n await picker.dispatchEvent('change');\n }\n\n /**\n * Drag files over the zone and drop them.\n *\n * Driven by dispatching the drag sequence AT the zone rather than by coordinates:\n * nothing has layout in a unit test — `getBoundingClientRect` is all zeros — so a\n * pointer-driven drag would land nowhere. From the component's side it is the\n * same event either way; it reads `dataTransfer.files` and never a position.\n *\n * A disabled upload refuses the drop itself, so this stays a no-op there rather\n * than throwing the way {@link selectFiles} does — that refusal is the\n * component's own, and worth asserting.\n */\n async dropFiles(...files: File[]): Promise<void> {\n const zone = await this.zone();\n const payload = dragPayload(dataTransferFor(files));\n\n await zone.dispatchEvent('dragenter', payload);\n await zone.dispatchEvent('dragover', payload);\n await zone.dispatchEvent('drop', payload);\n }\n\n /** Drag over the zone without letting go — arms {@link isDragging}. */\n async dragOver(): Promise<void> {\n const zone = await this.zone();\n const payload = dragPayload(dataTransferFor([]));\n\n await zone.dispatchEvent('dragenter', payload);\n await zone.dispatchEvent('dragover', payload);\n }\n\n /**\n * Drag back out of the zone, disarming {@link isDragging}.\n *\n * Dispatched at the zone itself on purpose: the component ignores a `dragleave`\n * whose target is one of the zone's children, because crossing the icon or the\n * hint text is not leaving the zone.\n */\n async dragLeave(): Promise<void> {\n await (await this.zone()).dispatchEvent('dragleave');\n }\n\n /**\n * Remove the file at `index` from the rendered list.\n *\n * On a disabled upload the remove buttons are disabled too, so this is a no-op\n * there — assert the result rather than assuming it.\n */\n async removeFile(index: number): Promise<void> {\n const buttons = await this.removeButtons();\n\n if (index < 0 || index >= buttons.length) {\n throw new Error(\n `WrFileUploadHarness.removeFile(${index}): the rendered list holds ${buttons.length} file(s). ` +\n 'With [showList]=\"false\" it holds none no matter what is selected.'\n );\n }\n\n await buttons[index].click();\n }\n\n /** Remove the file with this name. Names come from {@link getFileNames}. */\n async removeFileNamed(name: string): Promise<void> {\n const names = await this.getFileNames();\n const index = names.indexOf(name);\n\n if (index === -1) {\n const holding = names.length > 0 ? names.map(n => JSON.stringify(n)).join(', ') : 'no files';\n throw new Error(\n `WrFileUploadHarness.removeFileNamed(${JSON.stringify(name)}): the rendered list holds ${holding}.`\n );\n }\n\n await this.removeFile(index);\n }\n\n /** Move keyboard focus to the zone — the control's only tab stop. */\n async focus(): Promise<void> {\n return (await this.zone()).focus();\n }\n\n /** Whether the zone has focus. */\n async isFocused(): Promise<boolean> {\n return (await this.zone()).isFocused();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -316,10 +316,10 @@ class WrFileUpload {
|
|
|
316
316
|
this.value.set(list.length > 0 ? list[0] : null);
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
320
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
319
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrFileUpload, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
320
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: WrFileUpload, isStandalone: true, selector: "wr-file-upload", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, showList: { classPropertyName: "showList", publicName: "showList", 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 }, pickLabel: { classPropertyName: "pickLabel", publicName: "pickLabel", isSignal: true, isRequired: false, transformFunction: null }, dropLabel: { classPropertyName: "dropLabel", publicName: "dropLabel", isSignal: true, isRequired: false, transformFunction: null }, dropZoneLabel: { classPropertyName: "dropZoneLabel", publicName: "dropZoneLabel", isSignal: true, isRequired: false, transformFunction: null }, removeFileLabel: { classPropertyName: "removeFileLabel", publicName: "removeFileLabel", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", rejected: "rejected", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"zoneName()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly \u2014 the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
321
321
|
}
|
|
322
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
322
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: WrFileUpload, decorators: [{
|
|
323
323
|
type: Component,
|
|
324
324
|
args: [{ selector: 'wr-file-upload', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [], template: "<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"zoneName()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly \u2014 the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\n}\n" }]
|
|
325
325
|
}], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], showList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showList", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], pickLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickLabel", required: false }] }], dropLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropLabel", required: false }] }], dropZoneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropZoneLabel", required: false }] }], removeFileLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeFileLabel", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], touch: [{ type: i0.Output, args: ["touch"] }], picker: [{ type: i0.ViewChild, args: ['picker', { isSignal: true }] }] } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-file-upload.mjs","sources":["../../../projects/lib/file-upload/file-upload.ts","../../../projects/lib/file-upload/file-upload.html","../../../projects/lib/file-upload/ngwr-file-upload.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 { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n type ElementRef,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useFormFieldAria } from 'ngwr/form';\nimport { readI18nText, useI18nFormatter, useI18nText } from 'ngwr/i18n';\n\nimport type { WrFileUploadRejection, WrFileUploadRejectionReason } from './interfaces';\n\n/** Does `file` match the `accept` attribute (extensions or MIME globs)? */\nfunction matchesAccept(file: File, accept: string): boolean {\n if (!accept) return true;\n const tokens = accept\n .split(',')\n .map(t => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n const name = file.name.toLowerCase();\n const mime = file.type.toLowerCase();\n return tokens.some(token => {\n if (token.startsWith('.')) return name.endsWith(token);\n if (token.endsWith('/*')) return mime.startsWith(token.slice(0, -1));\n return mime === token;\n });\n}\n\n/**\n * Drag-and-drop file upload zone. Click to browse, drag-and-drop to add,\n * with an optional file list + remove buttons.\n *\n * A signal-forms native control: it implements\n * `FormValueControl<File | readonly File[] | null>`, so `[formField]` binds\n * straight to its `value` model — no `ControlValueAccessor` in between.\n * `[(value)]` works standalone. The value shape follows `multiple`: a single\n * `File` (or `null`), or a `File[]` array.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-file-upload [formField]=\"form.files\" [multiple]=\"true\" accept=\"image/*\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-file-upload [(value)]=\"avatar\" accept=\".png,.jpg\" [maxSize]=\"2 * 1024 * 1024\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/file-upload\n */\n@Component({\n selector: 'wr-file-upload',\n templateUrl: './file-upload.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n imports: [],\n})\nexport class WrFileUpload implements FormValueControl<File | readonly File[] | null> {\n /** Allow multiple files. Single mode replaces on each add. @default false */\n readonly multiple = input(false, { transform: coerceBooleanProperty });\n\n /** `accept` attribute — comma-separated MIME types or extensions. @default '' */\n readonly accept = input<string>('');\n\n /** Max bytes per file. `0` disables the check. @default 0 */\n readonly maxSize = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Max files (multi mode only). `0` disables the check. @default 0 */\n readonly maxFiles = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Render the picked-files list below the zone. @default true */\n readonly showList = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the selection while the zone stays focusable and the files\n * still submit. Bound automatically from the field's readonly state when used\n * with `[formField]`.\n *\n * The zone stops opening the picker, a drop is refused and the per-file remove\n * buttons go inert — but the list stays readable, which is the point. No\n * `aria-readonly`: the zone is a `role=\"button\"`, and ARIA does not define the\n * state for that role, so `aria-disabled` would be the only mirror available\n * and it would say the wrong thing.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n /**\n * The field's label names the drop zone, when there is one.\n *\n * The zone is a `div[role=\"button\"]`, so `<wr-form-field>`'s `<label for>`\n * cannot reach it and named nothing at all — the zone announced only its own\n * generic \"drop files here\" copy. `aria-labelledby` is the direction that\n * works for a role. The zone copy stays as the label when no field wraps it.\n */\n protected readonly labelledBy = computed(() => this.fieldAria.labelledBy());\n\n /** `null` while the field's label carries the name, or both are announced. */\n protected readonly zoneName = computed(() => (this.labelledBy() ? null : this.resolvedDropZone()));\n\n /** Primary call-to-action label. Falls back to `fileUpload.browse`. */\n readonly pickLabel = input<string | null>(null);\n\n /** Secondary instruction below the CTA. Falls back to `fileUpload.dropZone`. */\n readonly dropLabel = input<string | null>(null);\n\n /** Drop-zone host aria-label. Falls back to `fileUpload.dropZoneLabel`. */\n readonly dropZoneLabel = input<string | null>(null);\n\n /** Remove-file button aria-label. Falls back to `fileUpload.removeFile`. */\n readonly removeFileLabel = input<string | null>(null);\n\n protected readonly resolvedPick = useI18nText(this.pickLabel, 'fileUpload.browse', 'Click to browse');\n protected readonly resolvedDrop = useI18nText(this.dropLabel, 'fileUpload.dropZone', 'or drop files here');\n protected readonly resolvedDropZone = useI18nText(\n this.dropZoneLabel,\n 'fileUpload.dropZoneLabel',\n 'File upload drop zone — click or drop files'\n );\n protected readonly resolvedRemove = useI18nText(this.removeFileLabel, 'fileUpload.removeFile', 'Remove file');\n\n /** Optional helper text shown below the labels (e.g. accepted formats). */\n readonly helperText = input<string>('');\n\n /**\n * Selected file(s). Bound by `[formField]`, or two-way via `[(value)]`. Shape\n * follows `multiple`: a single `File` (or `null`), or a `File[]` array.\n */\n readonly value = model<File | readonly File[] | null>(null);\n\n /** Emitted when files are rejected for type / size / count reasons. */\n readonly rejected = output<readonly WrFileUploadRejection[]>();\n\n /** Emitted on commit so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly picker = viewChild.required<ElementRef<HTMLInputElement>>('picker');\n\n protected readonly files = signal<readonly File[]>([]);\n protected readonly dragging = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-file-upload'];\n if (this.dragging()) parts.push('wr-file-upload--dragging');\n if (this.disabled()) parts.push('wr-file-upload--disabled');\n else if (this.readonly()) parts.push('wr-file-upload--readonly');\n return parts.join(' ');\n });\n\n /**\n * Human-readable file size (e.g. `1.2 MB`).\n *\n * Every piece of this used to be English and unreachable: a module-level unit\n * table with no catalog key, and `toFixed()`, which writes an ASCII decimal\n * point whatever `LOCALE_ID` says. A Russian page showed translated drop-zone\n * copy above `3.4 KB`. The number now goes through `Intl.NumberFormat` and the\n * unit through the catalog, joined by a template the catalog also owns — so a\n * locale controls the separator, the digits and whether a space sits before\n * the unit.\n *\n * Not a `computed`: it is called per file, with the file's size as its\n * argument. It still re-runs on a locale switch, because the formatters it\n * calls are signals read during the same change detection that renders the row.\n */\n protected formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return this.sizeText({ value: this.number(0, 0), unit: this.unitTexts[0]() });\n }\n const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), this.unitTexts.length - 1);\n const v = bytes / 1024 ** i;\n const digits = v >= 100 || i === 0 ? 0 : 1;\n return this.sizeText({ value: this.number(v, digits), unit: this.unitTexts[i]() });\n }\n\n /** `{{value}} {{unit}}` — the join, so a locale owns the space too. */\n private readonly sizeText = useI18nFormatter('fileUpload.size', '{{value}} {{unit}}');\n\n /**\n * One resolved signal per binary unit, indexed by the power of 1024 the\n * arithmetic lands on.\n *\n * Spelled out rather than built from a key list: `i18n.spec.ts` reads every\n * catalog key the library asks for out of the SOURCE, by matching a quoted\n * dotted string inside the call, so a key built by interpolation is invisible\n * to it. That spec fails rather than passing quietly, but either way the key\n * would stop being checked.\n */\n private readonly unitTexts = [\n readI18nText('fileUpload.unitByte', 'B'),\n readI18nText('fileUpload.unitKb', 'KB'),\n readI18nText('fileUpload.unitMb', 'MB'),\n readI18nText('fileUpload.unitGb', 'GB'),\n readI18nText('fileUpload.unitTb', 'TB'),\n ] as const;\n\n private readonly locale = inject(LOCALE_ID);\n\n private number(value: number, digits: number): string {\n return new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n }).format(value);\n }\n\n constructor() {\n // Rebuild the internal file list from an external `value` write (mirrors the\n // old `writeValue`). Skipped when the value is merely the echo of an\n // in-component edit, so re-syncing can never clobber a live selection.\n effect(() => {\n const v = this.value();\n untracked(() => {\n // Coerce null/undefined to `[]` — a classic-forms binding can write\n // null, which the old `writeValue(v: … | null)` accepted too.\n const next: readonly File[] = v === null || v === undefined ? [] : Array.isArray(v) ? v : [v as File];\n const current = this.files();\n if (current.length === next.length && current.every((file, i) => file === next[i])) return;\n this.files.set(next);\n });\n });\n }\n\n // Template handlers\n\n protected openPicker(): void {\n if (this.disabled() || this.readonly()) return;\n this.picker().nativeElement.click();\n }\n\n protected onZoneKey(event: KeyboardEvent): void {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.openPicker();\n }\n }\n\n protected onPickerChange(event: Event): void {\n const target = event.target as HTMLInputElement;\n if (target.files) this.acceptFiles(Array.from(target.files));\n // Reset so picking the same file twice re-fires `change`.\n target.value = '';\n }\n\n protected onDragEnter(event: DragEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n this.dragging.set(true);\n }\n\n protected onDragOver(event: DragEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';\n }\n\n protected onDragLeave(event: DragEvent): void {\n // Only clear when leaving the zone itself, not internal children.\n if (event.currentTarget === event.target) this.dragging.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n this.dragging.set(false);\n if (this.disabled() || this.readonly()) return;\n const dropped = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];\n if (dropped.length > 0) this.acceptFiles(dropped);\n }\n\n protected removeAt(index: number): void {\n if (this.disabled() || this.readonly()) return;\n const next = this.files().filter((_, i) => i !== index);\n this.files.set(next);\n this.emit();\n }\n\n // Internals\n\n private acceptFiles(incoming: readonly File[]): void {\n if (this.disabled() || this.readonly()) return;\n const accept = this.accept();\n const maxSize = this.maxSize();\n const rejections: WrFileUploadRejection[] = [];\n const accepted: File[] = [];\n\n for (const file of incoming) {\n const reason = this.reject(file, accept, maxSize);\n if (reason) rejections.push({ file, reason });\n else accepted.push(file);\n }\n\n let next: File[];\n if (this.multiple()) {\n next = [...this.files(), ...accepted];\n const maxFiles = this.maxFiles();\n if (maxFiles > 0 && next.length > maxFiles) {\n const overflow = next.splice(maxFiles);\n for (const file of overflow) rejections.push({ file, reason: 'count' });\n }\n } else {\n // Single mode — last accepted wins, prior selection cleared.\n next = accepted.length > 0 ? [accepted[accepted.length - 1]] : [...this.files()];\n }\n\n this.files.set(next);\n this.touch.emit();\n this.emit();\n if (rejections.length > 0) this.rejected.emit(rejections);\n }\n\n private reject(file: File, accept: string, maxSize: number): WrFileUploadRejectionReason | null {\n if (accept && !matchesAccept(file, accept)) return 'type';\n if (maxSize > 0 && file.size > maxSize) return 'size';\n return null;\n }\n\n private emit(): void {\n const list = this.files();\n if (this.multiple()) {\n this.value.set(list);\n } else {\n this.value.set(list.length > 0 ? list[0] : null);\n }\n }\n}\n","<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"zoneName()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly — the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\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-file-upload.mjs","sources":["../../../projects/lib/file-upload/file-upload.ts","../../../projects/lib/file-upload/file-upload.html","../../../projects/lib/file-upload/ngwr-file-upload.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 { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n type ElementRef,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useFormFieldAria } from 'ngwr/form';\nimport { readI18nText, useI18nFormatter, useI18nText } from 'ngwr/i18n';\n\nimport type { WrFileUploadRejection, WrFileUploadRejectionReason } from './interfaces';\n\n/** Does `file` match the `accept` attribute (extensions or MIME globs)? */\nfunction matchesAccept(file: File, accept: string): boolean {\n if (!accept) return true;\n const tokens = accept\n .split(',')\n .map(t => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n const name = file.name.toLowerCase();\n const mime = file.type.toLowerCase();\n return tokens.some(token => {\n if (token.startsWith('.')) return name.endsWith(token);\n if (token.endsWith('/*')) return mime.startsWith(token.slice(0, -1));\n return mime === token;\n });\n}\n\n/**\n * Drag-and-drop file upload zone. Click to browse, drag-and-drop to add,\n * with an optional file list + remove buttons.\n *\n * A signal-forms native control: it implements\n * `FormValueControl<File | readonly File[] | null>`, so `[formField]` binds\n * straight to its `value` model — no `ControlValueAccessor` in between.\n * `[(value)]` works standalone. The value shape follows `multiple`: a single\n * `File` (or `null`), or a `File[]` array.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-file-upload [formField]=\"form.files\" [multiple]=\"true\" accept=\"image/*\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-file-upload [(value)]=\"avatar\" accept=\".png,.jpg\" [maxSize]=\"2 * 1024 * 1024\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/file-upload\n */\n@Component({\n selector: 'wr-file-upload',\n templateUrl: './file-upload.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n imports: [],\n})\nexport class WrFileUpload implements FormValueControl<File | readonly File[] | null> {\n /** Allow multiple files. Single mode replaces on each add. @default false */\n readonly multiple = input(false, { transform: coerceBooleanProperty });\n\n /** `accept` attribute — comma-separated MIME types or extensions. @default '' */\n readonly accept = input<string>('');\n\n /** Max bytes per file. `0` disables the check. @default 0 */\n readonly maxSize = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Max files (multi mode only). `0` disables the check. @default 0 */\n readonly maxFiles = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Render the picked-files list below the zone. @default true */\n readonly showList = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the selection while the zone stays focusable and the files\n * still submit. Bound automatically from the field's readonly state when used\n * with `[formField]`.\n *\n * The zone stops opening the picker, a drop is refused and the per-file remove\n * buttons go inert — but the list stays readable, which is the point. No\n * `aria-readonly`: the zone is a `role=\"button\"`, and ARIA does not define the\n * state for that role, so `aria-disabled` would be the only mirror available\n * and it would say the wrong thing.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n /**\n * The field's label names the drop zone, when there is one.\n *\n * The zone is a `div[role=\"button\"]`, so `<wr-form-field>`'s `<label for>`\n * cannot reach it and named nothing at all — the zone announced only its own\n * generic \"drop files here\" copy. `aria-labelledby` is the direction that\n * works for a role. The zone copy stays as the label when no field wraps it.\n */\n protected readonly labelledBy = computed(() => this.fieldAria.labelledBy());\n\n /** `null` while the field's label carries the name, or both are announced. */\n protected readonly zoneName = computed(() => (this.labelledBy() ? null : this.resolvedDropZone()));\n\n /** Primary call-to-action label. Falls back to `fileUpload.browse`. */\n readonly pickLabel = input<string | null>(null);\n\n /** Secondary instruction below the CTA. Falls back to `fileUpload.dropZone`. */\n readonly dropLabel = input<string | null>(null);\n\n /** Drop-zone host aria-label. Falls back to `fileUpload.dropZoneLabel`. */\n readonly dropZoneLabel = input<string | null>(null);\n\n /** Remove-file button aria-label. Falls back to `fileUpload.removeFile`. */\n readonly removeFileLabel = input<string | null>(null);\n\n protected readonly resolvedPick = useI18nText(this.pickLabel, 'fileUpload.browse', 'Click to browse');\n protected readonly resolvedDrop = useI18nText(this.dropLabel, 'fileUpload.dropZone', 'or drop files here');\n protected readonly resolvedDropZone = useI18nText(\n this.dropZoneLabel,\n 'fileUpload.dropZoneLabel',\n 'File upload drop zone — click or drop files'\n );\n protected readonly resolvedRemove = useI18nText(this.removeFileLabel, 'fileUpload.removeFile', 'Remove file');\n\n /** Optional helper text shown below the labels (e.g. accepted formats). */\n readonly helperText = input<string>('');\n\n /**\n * Selected file(s). Bound by `[formField]`, or two-way via `[(value)]`. Shape\n * follows `multiple`: a single `File` (or `null`), or a `File[]` array.\n */\n readonly value = model<File | readonly File[] | null>(null);\n\n /** Emitted when files are rejected for type / size / count reasons. */\n readonly rejected = output<readonly WrFileUploadRejection[]>();\n\n /** Emitted on commit so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly picker = viewChild.required<ElementRef<HTMLInputElement>>('picker');\n\n protected readonly files = signal<readonly File[]>([]);\n protected readonly dragging = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-file-upload'];\n if (this.dragging()) parts.push('wr-file-upload--dragging');\n if (this.disabled()) parts.push('wr-file-upload--disabled');\n else if (this.readonly()) parts.push('wr-file-upload--readonly');\n return parts.join(' ');\n });\n\n /**\n * Human-readable file size (e.g. `1.2 MB`).\n *\n * Every piece of this used to be English and unreachable: a module-level unit\n * table with no catalog key, and `toFixed()`, which writes an ASCII decimal\n * point whatever `LOCALE_ID` says. A Russian page showed translated drop-zone\n * copy above `3.4 KB`. The number now goes through `Intl.NumberFormat` and the\n * unit through the catalog, joined by a template the catalog also owns — so a\n * locale controls the separator, the digits and whether a space sits before\n * the unit.\n *\n * Not a `computed`: it is called per file, with the file's size as its\n * argument. It still re-runs on a locale switch, because the formatters it\n * calls are signals read during the same change detection that renders the row.\n */\n protected formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return this.sizeText({ value: this.number(0, 0), unit: this.unitTexts[0]() });\n }\n const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), this.unitTexts.length - 1);\n const v = bytes / 1024 ** i;\n const digits = v >= 100 || i === 0 ? 0 : 1;\n return this.sizeText({ value: this.number(v, digits), unit: this.unitTexts[i]() });\n }\n\n /** `{{value}} {{unit}}` — the join, so a locale owns the space too. */\n private readonly sizeText = useI18nFormatter('fileUpload.size', '{{value}} {{unit}}');\n\n /**\n * One resolved signal per binary unit, indexed by the power of 1024 the\n * arithmetic lands on.\n *\n * Spelled out rather than built from a key list: `i18n.spec.ts` reads every\n * catalog key the library asks for out of the SOURCE, by matching a quoted\n * dotted string inside the call, so a key built by interpolation is invisible\n * to it. That spec fails rather than passing quietly, but either way the key\n * would stop being checked.\n */\n private readonly unitTexts = [\n readI18nText('fileUpload.unitByte', 'B'),\n readI18nText('fileUpload.unitKb', 'KB'),\n readI18nText('fileUpload.unitMb', 'MB'),\n readI18nText('fileUpload.unitGb', 'GB'),\n readI18nText('fileUpload.unitTb', 'TB'),\n ] as const;\n\n private readonly locale = inject(LOCALE_ID);\n\n private number(value: number, digits: number): string {\n return new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n }).format(value);\n }\n\n constructor() {\n // Rebuild the internal file list from an external `value` write (mirrors the\n // old `writeValue`). Skipped when the value is merely the echo of an\n // in-component edit, so re-syncing can never clobber a live selection.\n effect(() => {\n const v = this.value();\n untracked(() => {\n // Coerce null/undefined to `[]` — a classic-forms binding can write\n // null, which the old `writeValue(v: … | null)` accepted too.\n const next: readonly File[] = v === null || v === undefined ? [] : Array.isArray(v) ? v : [v as File];\n const current = this.files();\n if (current.length === next.length && current.every((file, i) => file === next[i])) return;\n this.files.set(next);\n });\n });\n }\n\n // Template handlers\n\n protected openPicker(): void {\n if (this.disabled() || this.readonly()) return;\n this.picker().nativeElement.click();\n }\n\n protected onZoneKey(event: KeyboardEvent): void {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.openPicker();\n }\n }\n\n protected onPickerChange(event: Event): void {\n const target = event.target as HTMLInputElement;\n if (target.files) this.acceptFiles(Array.from(target.files));\n // Reset so picking the same file twice re-fires `change`.\n target.value = '';\n }\n\n protected onDragEnter(event: DragEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n this.dragging.set(true);\n }\n\n protected onDragOver(event: DragEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';\n }\n\n protected onDragLeave(event: DragEvent): void {\n // Only clear when leaving the zone itself, not internal children.\n if (event.currentTarget === event.target) this.dragging.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n this.dragging.set(false);\n if (this.disabled() || this.readonly()) return;\n const dropped = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];\n if (dropped.length > 0) this.acceptFiles(dropped);\n }\n\n protected removeAt(index: number): void {\n if (this.disabled() || this.readonly()) return;\n const next = this.files().filter((_, i) => i !== index);\n this.files.set(next);\n this.emit();\n }\n\n // Internals\n\n private acceptFiles(incoming: readonly File[]): void {\n if (this.disabled() || this.readonly()) return;\n const accept = this.accept();\n const maxSize = this.maxSize();\n const rejections: WrFileUploadRejection[] = [];\n const accepted: File[] = [];\n\n for (const file of incoming) {\n const reason = this.reject(file, accept, maxSize);\n if (reason) rejections.push({ file, reason });\n else accepted.push(file);\n }\n\n let next: File[];\n if (this.multiple()) {\n next = [...this.files(), ...accepted];\n const maxFiles = this.maxFiles();\n if (maxFiles > 0 && next.length > maxFiles) {\n const overflow = next.splice(maxFiles);\n for (const file of overflow) rejections.push({ file, reason: 'count' });\n }\n } else {\n // Single mode — last accepted wins, prior selection cleared.\n next = accepted.length > 0 ? [accepted[accepted.length - 1]] : [...this.files()];\n }\n\n this.files.set(next);\n this.touch.emit();\n this.emit();\n if (rejections.length > 0) this.rejected.emit(rejections);\n }\n\n private reject(file: File, accept: string, maxSize: number): WrFileUploadRejectionReason | null {\n if (accept && !matchesAccept(file, accept)) return 'type';\n if (maxSize > 0 && file.size > maxSize) return 'size';\n return null;\n }\n\n private emit(): void {\n const list = this.files();\n if (this.multiple()) {\n this.value.set(list);\n } else {\n this.value.set(list.length > 0 ? list[0] : null);\n }\n }\n}\n","<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"zoneName()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly — the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\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-form-testing.mjs","sources":["../../../projects/lib/form/testing/wr-form-field-harness.ts","../../../projects/lib/form/testing/wr-form-item-harness.ts","../../../projects/lib/form/testing/ngwr-form-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrFormFieldError, WrFormFieldHarnessFilters } from './interfaces';\n\n/**\n * The messages this field is showing, or the ones it is holding back.\n *\n * Anchored with `:scope >` because the field's own parts are all direct children\n * of its host: a `<wr-form-field>` nested in another one's control slot otherwise\n * has its error block matched by the OUTER field's query too, and the outer\n * answers with a sentence that is not its own.\n */\nconst MESSAGE = ':scope > .wr-form-field__errors > .wr-form-field__error';\nconst VISIBLE = `${MESSAGE}:not(.wr-form-field__error--hidden)`;\nconst SUPPRESSED = `${MESSAGE}.wr-form-field__error--hidden`;\n\n/** The control slot — every native control lookup is scoped through this. */\nconst SLOT = ':scope > .wr-form-field__control';\n\n/** The label and the hint, anchored for the same reason as {@link MESSAGE}. */\nconst LABEL = ':scope > .wr-form-field__label';\nconst HINT = ':scope > .wr-form-field__hint';\n\n/**\n * Test harness for `<wr-form-field>` — the label + control + hint + validation\n * copy block.\n *\n * The field's real job is the COPY CONTRACT, and this harness is built around it.\n * A message reaches the DOM one of two ways, and they are distinguishable there:\n *\n * - a `<wr-form-error key=\"…\">` the consumer wrote, which renders only while that\n * key is failing — the others stay in the DOM carrying\n * `wr-form-field__error--hidden`, which is why {@link getErrors} filters and\n * {@link getSuppressedErrorKeys} exists. A raw `.wr-form-error` query answers\n * with the held-back copy too, and reads green on the wrong sentence.\n * - a message the field RESOLVED for an error no markup claimed:\n * `provideWrFormErrors()`, then the `ngwr/i18n` `validation.*` catalog, then a\n * built-in English sentence. All three render the identical `<div>`, so\n * `source` is `'resolved'` for every one of them — the DOM genuinely cannot say\n * which link answered, and this harness does not pretend otherwise.\n *\n * Nothing renders at all until the projected control is touched or dirty, so a\n * spec has to reach that gate first: {@link blurControl} is the shortest way\n * (blur is what marks a bound control touched), and typing through the control's\n * own harness is the other.\n *\n * The field publishes no `aria-label` anywhere. Its `<label for>` pointing at the\n * projected control IS the naming story, and a control that does not adopt the\n * id leaves the field silently unlabelled — {@link isLabelLinkedToControl} is\n * what catches that. The other half is the reverse wiring: the control reads back\n * `aria-describedby` and `aria-invalid`, which is the only reason a screen reader\n * hears the message ({@link getAnnouncedDescription}).\n *\n * Harnesses loaded through this one resolve INSIDE the control slot, so the\n * control's own harness composes without a selector:\n *\n * @example\n * ```ts\n * const email = await loader.getHarness(WrFormFieldHarness.with({ label: 'Email' }));\n *\n * await (await email.getHarness(WrInputHarness)).setValue('nope');\n * await email.blurControl();\n *\n * expect(await email.getErrorTexts()).toEqual([\"That isn't a valid email.\"]);\n * expect(await email.getAnnouncedDescription()).toBe(\"That isn't a valid email.\");\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormFieldHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-field';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormFieldHarnessFilters = {}): HarnessPredicate<WrFormFieldHarness> {\n return new HarnessPredicate(WrFormFieldHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('hint', options.hint, (harness, hint) => HarnessPredicate.stringMatches(harness.getHint(), hint))\n .addOption('required', options.required, async (harness, required) => (await harness.isRequired()) === required)\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The label text, or `null` when the field renders no label.\n *\n * The `*` and `(optional)` markers ride INSIDE the `<label>`, so they are\n * excluded here — otherwise this answers `'Email *'` and every equality\n * assertion has to know which marker the field happens to carry.\n *\n * `null` is not \"the consumer forgot\": `label=\"\"` renders no `<label>` element\n * at all, which also means there is nothing naming the control.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.text({ exclude: '.wr-form-field__required, .wr-form-field__optional' }) : null;\n }\n\n /**\n * Whether the field is marked required.\n *\n * Read from the host's `wr-form-field--required` modifier, not from the `*`:\n * that marker is `aria-hidden=\"true\"` decoration, and it is only rendered when\n * there is a label to hang it on — the modifier is set either way. Note what\n * this does NOT mean: the field forwards nothing to the projected control, so\n * `required` here is the field's own visual state. What announces the constraint\n * is the forms layer — a `required` rule writes the native `required` attribute\n * onto the control — and the two are set independently, so a field can carry the\n * `*` with no validator behind it.\n */\n async isRequired(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--required');\n }\n\n /**\n * Whether the label carries the `(optional)` marker.\n *\n * Read from the marker itself, because — unlike `required` — `optional` sets no\n * host modifier. It is also the marker a screen reader actually reads: this one\n * is not `aria-hidden`. `required` wins when both inputs are set, so a required\n * field is never optional here however it was bound.\n */\n async isOptional(): Promise<boolean> {\n return (await this.locatorForOptional(`${LABEL} .wr-form-field__optional`)()) !== null;\n }\n\n /**\n * The hint text, or `null` when none is showing.\n *\n * `null` does not mean the consumer set no hint: the error block takes the\n * hint's slot outright, so a field with a visible message has no hint in the DOM\n * at all — which is also why the hint's id stops being published at the same\n * moment. Whether the CONTROL announces it is a separate question, and one this\n * harness answers through {@link getAnnouncedDescription}.\n */\n async getHint(): Promise<string | null> {\n const hint = await this.locatorForOptional(HINT)();\n return hint ? hint.text() : null;\n }\n\n /**\n * Whether the field is painting the error state.\n *\n * The host's `wr-form-field--invalid` modifier — what the user sees. The\n * screen-reader half lives on the control as `aria-invalid`, and the two are\n * separate elements rather than two readings of one: {@link isControlInvalid}.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--invalid');\n }\n\n /**\n * Every message the field is SHOWING, in DOM order — the consumer's markup\n * first, then whatever the field resolved for the keys that markup left over.\n *\n * Only THIS field's own error block is read — its direct child, holding its\n * direct children — so a `<wr-form-field>` nested in another one's control slot\n * contributes nothing to the outer field's answer. Without that anchoring the\n * outer field reports the inner one's copy as its own, which is the same class\n * of break as reading a sibling field's messages.\n */\n async getErrors(): Promise<WrFormFieldError[]> {\n const elements = await this.locatorForAll(VISIBLE)();\n\n return Promise.all(\n elements.map(async element => ({\n key: await element.getAttribute('data-key'),\n text: await element.text(),\n // The consumer's copy is its own element; a resolved message is a plain\n // `<div>` the field stamped out. That tag name is the whole of what the\n // DOM knows about provenance.\n source:\n (await element.getProperty<string>('tagName')).toLowerCase() === 'wr-form-error'\n ? ('projected' as const)\n : ('resolved' as const),\n }))\n );\n }\n\n /** The visible messages as text, in DOM order. */\n async getErrorTexts(): Promise<string[]> {\n return (await this.getErrors()).map(error => error.text);\n }\n\n /**\n * The visible message for one validator key.\n *\n * Throws rather than answering `null` when that key has no visible message: the\n * two reasons are worth failing on and the message names both — the key is not\n * in error at all, or its copy resolved to nothing.\n */\n async getErrorText(key: string): Promise<string> {\n const errors = await this.getErrors();\n const match = errors.find(error => error.key === key);\n if (match) return match.text;\n\n const shown = errors.map(error => error.key ?? '(keyless)');\n const detail =\n shown.length > 0\n ? `Showing: ${shown.join(', ')}.`\n : 'The field is showing nothing — it is either valid, or the control is still untouched and pristine.';\n\n throw new Error(\n `WrFormFieldHarness.getErrorText(${JSON.stringify(key)}): no visible message for that key. ${detail}`\n );\n }\n\n /**\n * The keys of the projected messages the field is holding BACK — copy that is in\n * the DOM but hidden because its key is not among the failing ones.\n *\n * This is the gating contract seen from outside, and the reason a spec must not\n * query `.wr-form-error` itself: the suppressed sentences sit right next to the\n * live one.\n */\n async getSuppressedErrorKeys(): Promise<string[]> {\n const hidden = await this.locatorForAll(SUPPRESSED)();\n const keys = await Promise.all(hidden.map(element => element.getAttribute('data-key')));\n return keys.filter((key): key is string => key !== null);\n }\n\n /**\n * Whether the field is announcing an error it has no words for: invalid, with an\n * error block that renders nothing.\n *\n * Worth a named check because it is invisible in review — the field still goes\n * red, and `aria-describedby` still points at the block, so a screen reader is\n * sent to an empty element. It happens when no link in the copy chain answers\n * the key (`[autoErrors]=\"false\"` with no markup, or a key nothing has copy for\n * — Signal Forms reports `minLength`, while the chain only knows Angular's\n * lowercase `minlength`).\n */\n async hasEmptyErrorBlock(): Promise<boolean> {\n if (!(await this.isInvalid())) return false;\n return (await this.getErrors()).every(error => error.text.length === 0);\n }\n\n /** The id the `<label for>` points at, or `null` when there is no label. */\n async getLabelFor(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.getAttribute('for') : null;\n }\n\n /** The `id` the projected control actually answers to, or `null` when it has none. */\n async getControlId(): Promise<string | null> {\n return (await this.control()).getAttribute('id');\n }\n\n /**\n * Whether the `<label for>` resolves to an element inside THIS field's control\n * slot — the only thing that makes the field nameable.\n *\n * Scoped to the slot on purpose: ids are document-global, so a check that went\n * through `getElementById` would also pass for a label pointing at a DIFFERENT\n * field's control, and for a control that has been moved out of the field\n * entirely. The failure this catches is quiet — a projected control that does\n * not adopt the field's `controlId` (a bare `<input>` with no `[wrInput]`, say)\n * leaves a `for` naming nothing, the field renders exactly as it always did, and\n * nothing is labelled.\n */\n async isLabelLinkedToControl(): Promise<boolean> {\n const id = await this.getLabelFor();\n if (!id) return false;\n return (await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)()) !== null;\n }\n\n /**\n * The ids the projected control names in `aria-describedby`, in order.\n *\n * Which id that is depends on the CONTROL, not on the field, and the answer is\n * worth knowing before reading a result. A control that composes the pair with\n * `useFormFieldAria()` names the error block while one is showing and the hint\n * otherwise. A control that reads the field's `describedBy` on its own — the\n * `[wrInput]` directive, `wr-segmented` and `wr-slider` — names the error block\n * and nothing else, so an empty list beside a visible hint is that control's\n * answer rather than a broken field.\n *\n * The two are separate because folding the hint into the one signal announced\n * every hinted field as invalid: those three derive `aria-invalid` from it.\n */\n async getDescribedByIds(): Promise<string[]> {\n const value = await (await this.control()).getAttribute('aria-describedby');\n return value ? value.split(/\\s+/).filter(Boolean) : [];\n }\n\n /**\n * What a screen reader reads as the control's description: the text of the\n * elements `aria-describedby` names, joined by a space. `null` when the control\n * describes nothing.\n *\n * `''` is a real answer and the interesting one — the reference resolves to an\n * element with no text in it, which is {@link hasEmptyErrorBlock}. Ids naming\n * something outside this field are skipped (a consumer's own description is out\n * of a field harness's reach); an id naming nothing anywhere throws, because a\n * dangling reference is the bug this method exists to surface.\n *\n * Suppressed copy is excluded explicitly rather than left to CSS: a browser drops\n * `display: none` subtrees from the accessibility tree, but a unit test loads no\n * stylesheet at all, so the referenced block's raw `textContent` runs every\n * held-back sentence together with the live one and this would answer with copy\n * nobody can read.\n */\n async getAnnouncedDescription(): Promise<string | null> {\n const ids = await this.getDescribedByIds();\n if (ids.length === 0) return null;\n\n const parts: string[] = [];\n for (const id of ids) {\n const inField = await this.locatorForOptional(`[id=\"${id}\"]`)();\n if (inField) {\n parts.push(await inField.text({ exclude: '.wr-form-field__error--hidden' }));\n continue;\n }\n if ((await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"]`)()) === null) {\n throw new Error(\n `WrFormFieldHarness.getAnnouncedDescription(): the control's aria-describedby names \"${id}\", which is not ` +\n 'an element anywhere in the document — a screen reader is pointed at nothing.'\n );\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Whether the control announces itself invalid.\n *\n * `aria-invalid` on the projected control, which is what assistive technology\n * is told — the field's own red styling is {@link isInvalid}. Both are here\n * because they live on different elements and travel in opposite directions: the\n * field renders the message, the control reads back that there is one. A field\n * that paints red while the control says nothing is a real (and silent) break.\n */\n async isControlInvalid(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-invalid')) === 'true';\n }\n\n /** Move focus to the projected control. */\n async focusControl(): Promise<void> {\n return (await this.control()).focus();\n }\n\n /**\n * Blur the projected control, which is usually how a spec gets any message to\n * appear at all: a bound control marks itself touched on blur, and the field\n * renders nothing while the control is untouched AND pristine.\n */\n async blurControl(): Promise<void> {\n return (await this.control()).blur();\n }\n\n /** The rendered `<label>`, or `null` when `label` is empty. */\n private async labelElement(): Promise<TestElement | null> {\n return this.locatorForOptional(LABEL)();\n }\n\n /**\n * The control this field wraps.\n *\n * The element the label points at, when that resolves inside the slot — which\n * covers a control nested inside a component (`<wr-input-number>`'s own\n * `<input>` adopts the field's id, so the label names it, not the component\n * element). Otherwise the first native control in the slot, so a field with no\n * label still answers.\n */\n private async control(): Promise<TestElement> {\n const id = await this.getLabelFor();\n if (id) {\n const labelled = await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)();\n if (labelled) return labelled;\n }\n\n const native = await this.locatorForOptional(`${SLOT} input, ${SLOT} select, ${SLOT} textarea`)();\n if (native) return native;\n\n throw new Error(\n 'WrFormFieldHarness: this field wraps no control. Nothing in `.wr-form-field__control` answers to the ' +\n \"label's `for`, and there is no <input> / <select> / <textarea> to fall back to.\"\n );\n }\n\n /**\n * The content loader every `HarnessLoader` method on this class runs through,\n * scoped to the control slot.\n *\n * So `field.getHarness(WrInputHarness)` finds the control this field wraps and\n * not a sibling field's — the loader is per-instance by construction, which is\n * what a document-wide query for the same selector would lose.\n */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor(SLOT);\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 { ContentContainerComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrFormItemHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-form-item>` — the layout-only field row.\n *\n * Deliberately a much smaller surface than {@link WrFormFieldHarness}, because\n * the component is a much smaller thing: it discovers no control, generates no\n * id, wires no `for` and no `aria-describedby`, and reads no validity. It paints\n * the error state it is TOLD to paint, via `[hasError]`, and everything else in\n * it belongs to the consumer — including the `<label>`, which is named here\n * rather than linked (nothing wires it; the consumer's own `for` is the only\n * thing that can).\n *\n * The one behavioural trap it is worth a harness for: a `<wr-form-error>` inside\n * a form-item renders UNCONDITIONALLY. `key` has no effect at all — the gating\n * lives in the surrounding `<wr-form-field>`, and with no field to ask, every\n * message decides it is the one to show. Markup moved from a field into an item\n * therefore starts printing every sentence at once, and {@link getErrorTexts}\n * is what says so.\n *\n * @example\n * ```ts\n * const item = await loader.getHarness(WrFormItemHarness.with({ label: 'Email' }));\n *\n * expect(await item.isInvalid()).toBe(true);\n * expect(await item.getErrorTexts()).toEqual(['Invalid email']);\n * await (await item.getHarness(WrInputHarness)).setValue('ada@example.test');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormItemHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormItemHarnessFilters = {}): HarnessPredicate<WrFormItemHarness> {\n return new HarnessPredicate(WrFormItemHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The text of the item's own `<label>` child, or `null` when it has none.\n *\n * Direct child only — that is what the item styles (`> label` is what turns red\n * in the error state), so a label nested deeper is the consumer's own business\n * and answering with it would describe something the item never touches.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.locatorForOptional(':scope > label')();\n return label ? label.text() : null;\n }\n\n /**\n * Whether the item is painting the error state.\n *\n * The `wr-form-item--error` modifier, which is driven entirely by the\n * consumer's `[hasError]` — the item runs no validity check of its own, so this\n * answers what it was told, not what the control thinks.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-item--error');\n }\n\n /**\n * Every `<wr-form-error>` in the item, as text, in DOM order.\n *\n * All of them: nothing is gated here, `key` or no `key`. Nothing is ever\n * hidden either, so there is no suppressed-message counterpart to this — see\n * the class docs.\n */\n async getErrorTexts(): Promise<string[]> {\n const errors = await this.locatorForAll('wr-form-error')();\n return Promise.all(errors.map(error => error.text()));\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-form-testing.mjs","sources":["../../../projects/lib/form/testing/wr-form-field-harness.ts","../../../projects/lib/form/testing/wr-form-item-harness.ts","../../../projects/lib/form/testing/ngwr-form-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrFormFieldError, WrFormFieldHarnessFilters } from './interfaces';\n\n/**\n * The messages this field is showing, or the ones it is holding back.\n *\n * Anchored with `:scope >` because the field's own parts are all direct children\n * of its host: a `<wr-form-field>` nested in another one's control slot otherwise\n * has its error block matched by the OUTER field's query too, and the outer\n * answers with a sentence that is not its own.\n */\nconst MESSAGE = ':scope > .wr-form-field__errors > .wr-form-field__error';\nconst VISIBLE = `${MESSAGE}:not(.wr-form-field__error--hidden)`;\nconst SUPPRESSED = `${MESSAGE}.wr-form-field__error--hidden`;\n\n/** The control slot — every native control lookup is scoped through this. */\nconst SLOT = ':scope > .wr-form-field__control';\n\n/** The label and the hint, anchored for the same reason as {@link MESSAGE}. */\nconst LABEL = ':scope > .wr-form-field__label';\nconst HINT = ':scope > .wr-form-field__hint';\n\n/**\n * Test harness for `<wr-form-field>` — the label + control + hint + validation\n * copy block.\n *\n * The field's real job is the COPY CONTRACT, and this harness is built around it.\n * A message reaches the DOM one of two ways, and they are distinguishable there:\n *\n * - a `<wr-form-error key=\"…\">` the consumer wrote, which renders only while that\n * key is failing — the others stay in the DOM carrying\n * `wr-form-field__error--hidden`, which is why {@link getErrors} filters and\n * {@link getSuppressedErrorKeys} exists. A raw `.wr-form-error` query answers\n * with the held-back copy too, and reads green on the wrong sentence.\n * - a message the field RESOLVED for an error no markup claimed:\n * `provideWrFormErrors()`, then the `ngwr/i18n` `validation.*` catalog, then a\n * built-in English sentence. All three render the identical `<div>`, so\n * `source` is `'resolved'` for every one of them — the DOM genuinely cannot say\n * which link answered, and this harness does not pretend otherwise.\n *\n * Nothing renders at all until the projected control is touched or dirty, so a\n * spec has to reach that gate first: {@link blurControl} is the shortest way\n * (blur is what marks a bound control touched), and typing through the control's\n * own harness is the other.\n *\n * The field publishes no `aria-label` anywhere. Its `<label for>` pointing at the\n * projected control IS the naming story, and a control that does not adopt the\n * id leaves the field silently unlabelled — {@link isLabelLinkedToControl} is\n * what catches that. The other half is the reverse wiring: the control reads back\n * `aria-describedby` and `aria-invalid`, which is the only reason a screen reader\n * hears the message ({@link getAnnouncedDescription}).\n *\n * Harnesses loaded through this one resolve INSIDE the control slot, so the\n * control's own harness composes without a selector:\n *\n * @example\n * ```ts\n * const email = await loader.getHarness(WrFormFieldHarness.with({ label: 'Email' }));\n *\n * await (await email.getHarness(WrInputHarness)).setValue('nope');\n * await email.blurControl();\n *\n * expect(await email.getErrorTexts()).toEqual([\"That isn't a valid email.\"]);\n * expect(await email.getAnnouncedDescription()).toBe(\"That isn't a valid email.\");\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormFieldHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-field';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormFieldHarnessFilters = {}): HarnessPredicate<WrFormFieldHarness> {\n return new HarnessPredicate(WrFormFieldHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('hint', options.hint, (harness, hint) => HarnessPredicate.stringMatches(harness.getHint(), hint))\n .addOption('required', options.required, async (harness, required) => (await harness.isRequired()) === required)\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The label text, or `null` when the field renders no label.\n *\n * The `*` and `(optional)` markers ride INSIDE the `<label>`, so they are\n * excluded here — otherwise this answers `'Email *'` and every equality\n * assertion has to know which marker the field happens to carry.\n *\n * `null` is not \"the consumer forgot\": `label=\"\"` renders no `<label>` element\n * at all, which also means there is nothing naming the control.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.text({ exclude: '.wr-form-field__required, .wr-form-field__optional' }) : null;\n }\n\n /**\n * Whether the field is marked required.\n *\n * Read from the host's `wr-form-field--required` modifier, not from the `*`:\n * that marker is `aria-hidden=\"true\"` decoration, and it is only rendered when\n * there is a label to hang it on — the modifier is set either way. Note what\n * this does NOT mean: the field forwards nothing to the projected control, so\n * `required` here is the field's own visual state. What announces the constraint\n * is the forms layer — a `required` rule writes the native `required` attribute\n * onto the control — and the two are set independently, so a field can carry the\n * `*` with no validator behind it.\n */\n async isRequired(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--required');\n }\n\n /**\n * Whether the label carries the `(optional)` marker.\n *\n * Read from the marker itself, because — unlike `required` — `optional` sets no\n * host modifier. It is also the marker a screen reader actually reads: this one\n * is not `aria-hidden`. `required` wins when both inputs are set, so a required\n * field is never optional here however it was bound.\n */\n async isOptional(): Promise<boolean> {\n return (await this.locatorForOptional(`${LABEL} .wr-form-field__optional`)()) !== null;\n }\n\n /**\n * The hint text, or `null` when none is showing.\n *\n * `null` does not mean the consumer set no hint: the error block takes the\n * hint's slot outright, so a field with a visible message has no hint in the DOM\n * at all — which is also why the hint's id stops being published at the same\n * moment. Whether the CONTROL announces it is a separate question, and one this\n * harness answers through {@link getAnnouncedDescription}.\n */\n async getHint(): Promise<string | null> {\n const hint = await this.locatorForOptional(HINT)();\n return hint ? hint.text() : null;\n }\n\n /**\n * Whether the field is painting the error state.\n *\n * The host's `wr-form-field--invalid` modifier — what the user sees. The\n * screen-reader half lives on the control as `aria-invalid`, and the two are\n * separate elements rather than two readings of one: {@link isControlInvalid}.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--invalid');\n }\n\n /**\n * Every message the field is SHOWING, in DOM order — the consumer's markup\n * first, then whatever the field resolved for the keys that markup left over.\n *\n * Only THIS field's own error block is read — its direct child, holding its\n * direct children — so a `<wr-form-field>` nested in another one's control slot\n * contributes nothing to the outer field's answer. Without that anchoring the\n * outer field reports the inner one's copy as its own, which is the same class\n * of break as reading a sibling field's messages.\n */\n async getErrors(): Promise<WrFormFieldError[]> {\n const elements = await this.locatorForAll(VISIBLE)();\n\n return Promise.all(\n elements.map(async element => ({\n key: await element.getAttribute('data-key'),\n text: await element.text(),\n // The consumer's copy is its own element; a resolved message is a plain\n // `<div>` the field stamped out. That tag name is the whole of what the\n // DOM knows about provenance.\n source:\n (await element.getProperty<string>('tagName')).toLowerCase() === 'wr-form-error'\n ? ('projected' as const)\n : ('resolved' as const),\n }))\n );\n }\n\n /** The visible messages as text, in DOM order. */\n async getErrorTexts(): Promise<string[]> {\n return (await this.getErrors()).map(error => error.text);\n }\n\n /**\n * The visible message for one validator key.\n *\n * Throws rather than answering `null` when that key has no visible message: the\n * two reasons are worth failing on and the message names both — the key is not\n * in error at all, or its copy resolved to nothing.\n */\n async getErrorText(key: string): Promise<string> {\n const errors = await this.getErrors();\n const match = errors.find(error => error.key === key);\n if (match) return match.text;\n\n const shown = errors.map(error => error.key ?? '(keyless)');\n const detail =\n shown.length > 0\n ? `Showing: ${shown.join(', ')}.`\n : 'The field is showing nothing — it is either valid, or the control is still untouched and pristine.';\n\n throw new Error(\n `WrFormFieldHarness.getErrorText(${JSON.stringify(key)}): no visible message for that key. ${detail}`\n );\n }\n\n /**\n * The keys of the projected messages the field is holding BACK — copy that is in\n * the DOM but hidden because its key is not among the failing ones.\n *\n * This is the gating contract seen from outside, and the reason a spec must not\n * query `.wr-form-error` itself: the suppressed sentences sit right next to the\n * live one.\n */\n async getSuppressedErrorKeys(): Promise<string[]> {\n const hidden = await this.locatorForAll(SUPPRESSED)();\n const keys = await Promise.all(hidden.map(element => element.getAttribute('data-key')));\n return keys.filter((key): key is string => key !== null);\n }\n\n /**\n * Whether the field is announcing an error it has no words for: invalid, with an\n * error block that renders nothing.\n *\n * Worth a named check because it is invisible in review — the field still goes\n * red, and `aria-describedby` still points at the block, so a screen reader is\n * sent to an empty element. It happens when no link in the copy chain answers\n * the key (`[autoErrors]=\"false\"` with no markup, or a key nothing has copy for\n * — Signal Forms reports `minLength`, while the chain only knows Angular's\n * lowercase `minlength`).\n */\n async hasEmptyErrorBlock(): Promise<boolean> {\n if (!(await this.isInvalid())) return false;\n return (await this.getErrors()).every(error => error.text.length === 0);\n }\n\n /** The id the `<label for>` points at, or `null` when there is no label. */\n async getLabelFor(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.getAttribute('for') : null;\n }\n\n /** The `id` the projected control actually answers to, or `null` when it has none. */\n async getControlId(): Promise<string | null> {\n return (await this.control()).getAttribute('id');\n }\n\n /**\n * Whether the `<label for>` resolves to an element inside THIS field's control\n * slot — the only thing that makes the field nameable.\n *\n * Scoped to the slot on purpose: ids are document-global, so a check that went\n * through `getElementById` would also pass for a label pointing at a DIFFERENT\n * field's control, and for a control that has been moved out of the field\n * entirely. The failure this catches is quiet — a projected control that does\n * not adopt the field's `controlId` (a bare `<input>` with no `[wrInput]`, say)\n * leaves a `for` naming nothing, the field renders exactly as it always did, and\n * nothing is labelled.\n */\n async isLabelLinkedToControl(): Promise<boolean> {\n const id = await this.getLabelFor();\n if (!id) return false;\n return (await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)()) !== null;\n }\n\n /**\n * The ids the projected control names in `aria-describedby`, in order.\n *\n * Which id that is depends on the CONTROL, not on the field, and the answer is\n * worth knowing before reading a result. A control that composes the pair with\n * `useFormFieldAria()` names the error block while one is showing and the hint\n * otherwise. A control that reads the field's `describedBy` on its own — the\n * `[wrInput]` directive, `wr-segmented` and `wr-slider` — names the error block\n * and nothing else, so an empty list beside a visible hint is that control's\n * answer rather than a broken field.\n *\n * The two are separate because folding the hint into the one signal announced\n * every hinted field as invalid: those three derive `aria-invalid` from it.\n */\n async getDescribedByIds(): Promise<string[]> {\n const value = await (await this.control()).getAttribute('aria-describedby');\n return value ? value.split(/\\s+/).filter(Boolean) : [];\n }\n\n /**\n * What a screen reader reads as the control's description: the text of the\n * elements `aria-describedby` names, joined by a space. `null` when the control\n * describes nothing.\n *\n * `''` is a real answer and the interesting one — the reference resolves to an\n * element with no text in it, which is {@link hasEmptyErrorBlock}. Ids naming\n * something outside this field are skipped (a consumer's own description is out\n * of a field harness's reach); an id naming nothing anywhere throws, because a\n * dangling reference is the bug this method exists to surface.\n *\n * Suppressed copy is excluded explicitly rather than left to CSS: a browser drops\n * `display: none` subtrees from the accessibility tree, but a unit test loads no\n * stylesheet at all, so the referenced block's raw `textContent` runs every\n * held-back sentence together with the live one and this would answer with copy\n * nobody can read.\n */\n async getAnnouncedDescription(): Promise<string | null> {\n const ids = await this.getDescribedByIds();\n if (ids.length === 0) return null;\n\n const parts: string[] = [];\n for (const id of ids) {\n const inField = await this.locatorForOptional(`[id=\"${id}\"]`)();\n if (inField) {\n parts.push(await inField.text({ exclude: '.wr-form-field__error--hidden' }));\n continue;\n }\n if ((await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"]`)()) === null) {\n throw new Error(\n `WrFormFieldHarness.getAnnouncedDescription(): the control's aria-describedby names \"${id}\", which is not ` +\n 'an element anywhere in the document — a screen reader is pointed at nothing.'\n );\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Whether the control announces itself invalid.\n *\n * `aria-invalid` on the projected control, which is what assistive technology\n * is told — the field's own red styling is {@link isInvalid}. Both are here\n * because they live on different elements and travel in opposite directions: the\n * field renders the message, the control reads back that there is one. A field\n * that paints red while the control says nothing is a real (and silent) break.\n */\n async isControlInvalid(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-invalid')) === 'true';\n }\n\n /** Move focus to the projected control. */\n async focusControl(): Promise<void> {\n return (await this.control()).focus();\n }\n\n /**\n * Blur the projected control, which is usually how a spec gets any message to\n * appear at all: a bound control marks itself touched on blur, and the field\n * renders nothing while the control is untouched AND pristine.\n */\n async blurControl(): Promise<void> {\n return (await this.control()).blur();\n }\n\n /** The rendered `<label>`, or `null` when `label` is empty. */\n private async labelElement(): Promise<TestElement | null> {\n return this.locatorForOptional(LABEL)();\n }\n\n /**\n * The control this field wraps.\n *\n * The element the label points at, when that resolves inside the slot — which\n * covers a control nested inside a component (`<wr-input-number>`'s own\n * `<input>` adopts the field's id, so the label names it, not the component\n * element). Otherwise the first native control in the slot, so a field with no\n * label still answers.\n */\n private async control(): Promise<TestElement> {\n const id = await this.getLabelFor();\n if (id) {\n const labelled = await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)();\n if (labelled) return labelled;\n }\n\n const native = await this.locatorForOptional(`${SLOT} input, ${SLOT} select, ${SLOT} textarea`)();\n if (native) return native;\n\n throw new Error(\n 'WrFormFieldHarness: this field wraps no control. Nothing in `.wr-form-field__control` answers to the ' +\n \"label's `for`, and there is no <input> / <select> / <textarea> to fall back to.\"\n );\n }\n\n /**\n * The content loader every `HarnessLoader` method on this class runs through,\n * scoped to the control slot.\n *\n * So `field.getHarness(WrInputHarness)` finds the control this field wraps and\n * not a sibling field's — the loader is per-instance by construction, which is\n * what a document-wide query for the same selector would lose.\n */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor(SLOT);\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 { ContentContainerComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrFormItemHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-form-item>` — the layout-only field row.\n *\n * Deliberately a much smaller surface than {@link WrFormFieldHarness}, because\n * the component is a much smaller thing: it discovers no control, generates no\n * id, wires no `for` and no `aria-describedby`, and reads no validity. It paints\n * the error state it is TOLD to paint, via `[hasError]`, and everything else in\n * it belongs to the consumer — including the `<label>`, which is named here\n * rather than linked (nothing wires it; the consumer's own `for` is the only\n * thing that can).\n *\n * The one behavioural trap it is worth a harness for: a `<wr-form-error>` inside\n * a form-item renders UNCONDITIONALLY. `key` has no effect at all — the gating\n * lives in the surrounding `<wr-form-field>`, and with no field to ask, every\n * message decides it is the one to show. Markup moved from a field into an item\n * therefore starts printing every sentence at once, and {@link getErrorTexts}\n * is what says so.\n *\n * @example\n * ```ts\n * const item = await loader.getHarness(WrFormItemHarness.with({ label: 'Email' }));\n *\n * expect(await item.isInvalid()).toBe(true);\n * expect(await item.getErrorTexts()).toEqual(['Invalid email']);\n * await (await item.getHarness(WrInputHarness)).setValue('ada@example.test');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormItemHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormItemHarnessFilters = {}): HarnessPredicate<WrFormItemHarness> {\n return new HarnessPredicate(WrFormItemHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The text of the item's own `<label>` child, or `null` when it has none.\n *\n * Direct child only — that is what the item styles (`> label` is what turns red\n * in the error state), so a label nested deeper is the consumer's own business\n * and answering with it would describe something the item never touches.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.locatorForOptional(':scope > label')();\n return label ? label.text() : null;\n }\n\n /**\n * Whether the item is painting the error state.\n *\n * The `wr-form-item--error` modifier, which is driven entirely by the\n * consumer's `[hasError]` — the item runs no validity check of its own, so this\n * answers what it was told, not what the control thinks.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-item--error');\n }\n\n /**\n * Every `<wr-form-error>` in the item, as text, in DOM order.\n *\n * All of them: nothing is gated here, `key` or no `key`. Nothing is ever\n * hidden either, so there is no suppressed-message counterpart to this — see\n * the class docs.\n */\n async getErrorTexts(): Promise<string[]> {\n const errors = await this.locatorForAll('wr-form-error')();\n return Promise.all(errors.map(error => error.text()));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|