ngwr 11.1.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/burger/styles/_index.scss +1 -1
- package/button/styles/_index.scss +2 -2
- package/calendar/styles/_index.scss +32 -12
- package/calendar-heatmap/styles/_index.scss +20 -0
- package/carousel/styles/_index.scss +23 -1
- package/cascader/styles/_index.scss +15 -3
- package/checkbox/styles/_index.scss +2 -2
- package/circular-text/styles/_index.scss +11 -0
- package/collapse/styles/_index.scss +2 -2
- package/color-picker/styles/_index.scss +3 -3
- package/command-palette/styles/_index.scss +12 -0
- package/compare/styles/_index.scss +18 -2
- package/context-menu/styles/_index.scss +21 -2
- package/date-picker/styles/_index.scss +5 -5
- package/descriptions/styles/_index.scss +1 -1
- package/drag-drop/styles/_index.scss +28 -1
- package/drawer/styles/_index.scss +37 -0
- package/dropdown/styles/_index.scss +2 -2
- package/event-calendar/styles/_index.scss +67 -10
- package/fesm2022/ngwr-action-sheet-testing.mjs +313 -0
- package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -0
- package/fesm2022/ngwr-action-sheet.mjs +3 -3
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-aurora.mjs +3 -3
- package/fesm2022/ngwr-avatar.mjs +3 -3
- package/fesm2022/ngwr-back-top.mjs +3 -3
- package/fesm2022/ngwr-badge.mjs +6 -6
- package/fesm2022/ngwr-bar-chart-testing.mjs +107 -0
- package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-bar-chart.mjs +3 -3
- package/fesm2022/ngwr-blur-text-testing.mjs +203 -0
- package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-blur-text.mjs +18 -4
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow-testing.mjs +325 -0
- package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -0
- package/fesm2022/ngwr-border-glow.mjs +4 -4
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +29 -17
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-button.mjs +6 -6
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs +95 -0
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar-heatmap.mjs +17 -5
- package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +368 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar.mjs +228 -10
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +10 -10
- package/fesm2022/ngwr-card.mjs.map +1 -1
- package/fesm2022/ngwr-carousel.mjs +124 -24
- package/fesm2022/ngwr-carousel.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +60 -5
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +14 -6
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text-testing.mjs +230 -0
- package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-circular-text.mjs +4 -4
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-click-spark.mjs +27 -7
- package/fesm2022/ngwr-click-spark.mjs.map +1 -1
- package/fesm2022/ngwr-clipboard.mjs +3 -3
- package/fesm2022/ngwr-collapse.mjs +9 -9
- package/fesm2022/ngwr-color-picker-testing.mjs +452 -0
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -0
- package/fesm2022/ngwr-color-picker.mjs +126 -10
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +14 -5
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-compare.mjs +3 -3
- package/fesm2022/ngwr-confetti.mjs +31 -6
- package/fesm2022/ngwr-confetti.mjs.map +1 -1
- package/fesm2022/ngwr-config.mjs +9 -0
- package/fesm2022/ngwr-config.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu-testing.mjs +30 -11
- package/fesm2022/ngwr-context-menu-testing.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +358 -48
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-cookie.mjs +3 -3
- package/fesm2022/ngwr-counter.mjs +25 -9
- package/fesm2022/ngwr-counter.mjs.map +1 -1
- package/fesm2022/{ngwr-date-adapter-fns.mjs → ngwr-date-adapters-fns.mjs} +37 -10
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -0
- package/fesm2022/{ngwr-date-adapter-luxon.mjs → ngwr-date-adapters-luxon.mjs} +5 -5
- package/fesm2022/ngwr-date-adapters-luxon.mjs.map +1 -0
- package/fesm2022/ngwr-date-picker-testing.mjs +21 -85
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +119 -37
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/{ngwr-date-adapter.mjs → ngwr-date.mjs} +36 -10
- package/fesm2022/ngwr-date.mjs.map +1 -0
- package/fesm2022/ngwr-decrypt-text-testing.mjs +261 -0
- package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-decrypt-text.mjs +9 -4
- package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
- package/fesm2022/ngwr-density.mjs +6 -6
- package/fesm2022/ngwr-descriptions.mjs +6 -6
- package/fesm2022/ngwr-dialog.mjs +73 -18
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +12 -12
- package/fesm2022/ngwr-divider.mjs +52 -5
- package/fesm2022/ngwr-divider.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart-testing.mjs +97 -0
- package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-donut-chart.mjs +7 -7
- package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +160 -11
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +156 -64
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +58 -12
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-event-calendar-testing.mjs +446 -0
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-event-calendar.mjs +66 -28
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text-testing.mjs +220 -0
- package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-falling-text.mjs +75 -46
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-form.mjs +32 -15
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text-testing.mjs +134 -0
- package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -11
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gauge-testing.mjs +86 -0
- package/fesm2022/ngwr-gauge-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gauge.mjs +3 -3
- package/fesm2022/ngwr-glitch-text-testing.mjs +215 -0
- package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-glitch-text.mjs +7 -7
- package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text-testing.mjs +258 -0
- package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gradient-text.mjs +4 -4
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-hotkey.mjs +24 -9
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +87 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +69 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +26 -15
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +6 -6
- package/fesm2022/ngwr-image-cropper-testing.mjs +200 -0
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -0
- package/fesm2022/ngwr-image-cropper.mjs +156 -11
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs +15 -10
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +89 -15
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp-testing.mjs +12 -6
- package/fesm2022/ngwr-input-otp-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +24 -5
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +15 -15
- package/fesm2022/ngwr-keyboard.mjs +3 -3
- package/fesm2022/ngwr-knob-testing.mjs +220 -0
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -0
- package/fesm2022/ngwr-knob.mjs +3 -3
- package/fesm2022/ngwr-layout.mjs +15 -15
- package/fesm2022/ngwr-lightbox-testing.mjs +223 -0
- package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -0
- package/fesm2022/ngwr-lightbox.mjs +11 -4
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart-testing.mjs +119 -0
- package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-line-chart.mjs +73 -26
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-list.mjs +7 -7
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +7 -7
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +66 -8
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-marquee-testing.mjs +336 -0
- package/fesm2022/ngwr-marquee-testing.mjs.map +1 -0
- package/fesm2022/ngwr-marquee.mjs +4 -4
- package/fesm2022/ngwr-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-media.mjs +3 -3
- package/fesm2022/ngwr-mention.mjs +108 -13
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-meter-group-testing.mjs +100 -0
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -0
- package/fesm2022/ngwr-meter-group.mjs +3 -3
- package/fesm2022/ngwr-overlay.mjs +34 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-page-header.mjs +3 -3
- package/fesm2022/ngwr-pagination-testing.mjs +6 -6
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +17 -13
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +39 -26
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +9 -9
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popover-testing.mjs +7 -2
- package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +169 -33
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs +42 -5
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-rating.mjs +3 -3
- package/fesm2022/ngwr-result.mjs +21 -30
- package/fesm2022/ngwr-result.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text-testing.mjs +220 -0
- package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-rotating-text.mjs +4 -4
- package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-segmented-testing.mjs +14 -1
- package/fesm2022/ngwr-segmented-testing.mjs.map +1 -1
- package/fesm2022/ngwr-segmented.mjs +29 -6
- package/fesm2022/ngwr-segmented.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +227 -43
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text-testing.mjs +237 -0
- package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-shiny-text.mjs +11 -9
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +31 -16
- package/fesm2022/ngwr-sidebar.mjs.map +1 -1
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs +143 -14
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline-testing.mjs +76 -0
- package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -0
- package/fesm2022/ngwr-sparkline.mjs +3 -3
- package/fesm2022/ngwr-speed-dial-testing.mjs +281 -0
- package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -0
- package/fesm2022/ngwr-speed-dial.mjs +27 -12
- package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-splash-cursor-testing.mjs +89 -0
- package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -0
- package/fesm2022/ngwr-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-split-text-testing.mjs +224 -0
- package/fesm2022/ngwr-split-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-split-text.mjs +31 -5
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs +245 -0
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-spotlight-card-testing.mjs +368 -0
- package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-spotlight-card.mjs +7 -7
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +6 -6
- package/fesm2022/ngwr-star-border-testing.mjs +172 -0
- package/fesm2022/ngwr-star-border-testing.mjs.map +1 -0
- package/fesm2022/ngwr-star-border.mjs +4 -4
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-stepper-testing.mjs +8 -8
- package/fesm2022/ngwr-stepper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-stepper.mjs +33 -8
- package/fesm2022/ngwr-stepper.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +17 -5
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-switch.mjs +3 -3
- package/fesm2022/ngwr-table.mjs +110 -40
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-testing.mjs +11 -10
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +51 -11
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +3 -3
- package/fesm2022/ngwr-theme.mjs +181 -4
- package/fesm2022/ngwr-theme.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card-testing.mjs +190 -0
- package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tilt-card.mjs +26 -8
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-toast.mjs +44 -15
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-toolbar.mjs +3 -3
- package/fesm2022/ngwr-tour-testing.mjs +173 -0
- package/fesm2022/ngwr-tour-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tour.mjs +34 -10
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +3 -3
- package/fesm2022/ngwr-tree.mjs +36 -11
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter-testing.mjs +242 -0
- package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-typewriter.mjs +13 -5
- package/fesm2022/ngwr-typewriter.mjs.map +1 -1
- package/fesm2022/ngwr-typography.mjs +3 -3
- package/fesm2022/ngwr-utils.mjs +5 -2
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +4 -4
- package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-waves-testing.mjs +128 -0
- package/fesm2022/ngwr-waves-testing.mjs.map +1 -0
- package/fesm2022/ngwr-waves.mjs +4 -4
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs +361 -0
- package/fesm2022/ngwr-window-testing.mjs.map +1 -0
- package/fesm2022/ngwr-window.mjs +175 -31
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/file-upload/styles/_index.scss +2 -2
- package/glitch-text/styles/_index.scss +27 -12
- package/i18n/en.json +71 -7
- package/i18n/ru.json +71 -7
- package/image-cropper/styles/_index.scss +19 -0
- package/input/styles/_index.scss +2 -2
- package/input-number/styles/_index.scss +2 -2
- package/input-otp/styles/_index.scss +9 -2
- package/knob/styles/_index.scss +1 -1
- package/lightbox/styles/_index.scss +16 -2
- package/line-chart/styles/_index.scss +28 -14
- package/list/styles/_index.scss +1 -1
- package/llms-full.txt +161 -19
- package/llms.txt +45 -10
- package/markdown/styles/_index.scss +3 -3
- package/mcp/api.js +6 -4
- package/mcp/catalog.js +40 -3
- package/mcp/providers.js +42 -0
- package/mcp/tools.js +22 -30
- package/meter-group/styles/_index.scss +1 -1
- package/overlay/styles/_index.scss +33 -1
- package/package.json +161 -12
- package/pagination/styles/_index.scss +23 -1
- package/popover/styles/_index.scss +16 -7
- package/progress/styles/_index.scss +1 -1
- package/radio/styles/_index.scss +2 -2
- package/rating/styles/_index.scss +1 -1
- package/schematics/collection.json +1 -1
- package/schematics/migrations/v12/index.js +65 -0
- package/schematics/migrations/v7/index.js +12 -1
- package/schematics/migrations/v8/index.js +10 -1
- package/schematics/migrations.json +5 -0
- package/schematics/ng-add/index.js +8 -3
- package/schematics/provider/index.js +80 -13
- package/schematics/use/index.js +9 -129
- package/schematics/use/rule.js +206 -0
- package/schematics/use/symbol-map.json +4 -4
- package/segmented/styles/_index.scss +14 -3
- package/select/styles/_index.scss +14 -2
- package/sidebar/styles/_index.scss +12 -10
- package/skills/ngwr/SKILL.md +117 -0
- package/skills/ngwr/references/catalog.md +212 -0
- package/skills/ngwr/references/setup.md +78 -0
- package/slider/styles/_index.scss +1 -1
- package/speed-dial/styles/_index.scss +1 -1
- package/stepper/styles/_index.scss +12 -3
- package/switch/styles/_index.scss +2 -2
- package/table/styles/_index.scss +61 -12
- package/tabs/styles/_index.scss +1 -1
- package/textarea/styles/_index.scss +1 -1
- package/theme/styles/_colors.scss +142 -56
- package/theme/styles/_dark.scss +46 -3
- package/theme/styles/_focus.scss +28 -5
- package/theme/styles/_mixins.scss +1 -1
- package/theme/styles/_variables.scss +67 -0
- package/transfer/styles/_index.scss +26 -3
- package/tree/styles/_index.scss +29 -6
- package/types/ngwr-action-sheet-testing.d.ts +260 -0
- package/types/ngwr-bar-chart-testing.d.ts +85 -0
- package/types/ngwr-blur-text-testing.d.ts +180 -0
- package/types/ngwr-blur-text.d.ts +20 -4
- package/types/ngwr-border-glow-testing.d.ts +219 -0
- package/types/ngwr-breadcrumbs.d.ts +17 -10
- package/types/ngwr-calendar-heatmap-testing.d.ts +79 -0
- package/types/ngwr-calendar-testing.d.ts +277 -0
- package/types/ngwr-calendar.d.ts +76 -0
- package/types/ngwr-carousel.d.ts +85 -6
- package/types/ngwr-cascader.d.ts +17 -0
- package/types/ngwr-checkbox.d.ts +13 -0
- package/types/ngwr-circular-text-testing.d.ts +196 -0
- package/types/ngwr-click-spark.d.ts +4 -0
- package/types/ngwr-color-picker-testing.d.ts +299 -0
- package/types/ngwr-color-picker.d.ts +34 -2
- package/types/ngwr-confetti.d.ts +8 -0
- package/types/ngwr-config.d.ts +10 -1
- package/types/ngwr-context-menu-testing.d.ts +24 -11
- package/types/ngwr-context-menu.d.ts +71 -1
- package/types/ngwr-counter.d.ts +1 -0
- package/types/{ngwr-date-adapter-fns.d.ts → ngwr-date-adapters-fns.d.ts} +33 -6
- package/types/{ngwr-date-adapter-luxon.d.ts → ngwr-date-adapters-luxon.d.ts} +1 -1
- package/types/ngwr-date-picker-testing.d.ts +17 -72
- package/types/ngwr-date-picker.d.ts +8 -4
- package/types/{ngwr-date-adapter.d.ts → ngwr-date.d.ts} +24 -5
- package/types/ngwr-decrypt-text-testing.d.ts +222 -0
- package/types/ngwr-dialog.d.ts +38 -1
- package/types/ngwr-divider.d.ts +25 -0
- package/types/ngwr-donut-chart-testing.d.ts +83 -0
- package/types/ngwr-donut-chart.d.ts +3 -3
- package/types/ngwr-drag-drop.d.ts +56 -2
- package/types/ngwr-drawer.d.ts +68 -5
- package/types/ngwr-dropdown.d.ts +9 -0
- package/types/ngwr-event-calendar-testing.d.ts +301 -0
- package/types/ngwr-event-calendar.d.ts +17 -8
- package/types/ngwr-falling-text-testing.d.ts +188 -0
- package/types/ngwr-falling-text.d.ts +1 -1
- package/types/ngwr-form.d.ts +12 -2
- package/types/ngwr-fuzzy-text-testing.d.ts +130 -0
- package/types/ngwr-gauge-testing.d.ts +71 -0
- package/types/ngwr-glitch-text-testing.d.ts +201 -0
- package/types/ngwr-gradient-text-testing.d.ts +186 -0
- package/types/ngwr-i18n.d.ts +13 -4
- package/types/ngwr-image-cropper-testing.d.ts +173 -0
- package/types/ngwr-image-cropper.d.ts +71 -5
- package/types/ngwr-input-number-testing.d.ts +15 -10
- package/types/ngwr-input-number.d.ts +44 -5
- package/types/ngwr-input-otp-testing.d.ts +12 -6
- package/types/ngwr-input-otp.d.ts +15 -1
- package/types/ngwr-knob-testing.d.ts +150 -0
- package/types/ngwr-lightbox-testing.d.ts +154 -0
- package/types/ngwr-lightbox.d.ts +6 -0
- package/types/ngwr-line-chart-testing.d.ts +93 -0
- package/types/ngwr-line-chart.d.ts +20 -3
- package/types/ngwr-markdown.d.ts +11 -0
- package/types/ngwr-marquee-testing.d.ts +280 -0
- package/types/ngwr-mention.d.ts +32 -0
- package/types/ngwr-meter-group-testing.d.ts +77 -0
- package/types/ngwr-overlay.d.ts +20 -1
- package/types/ngwr-pagination-testing.d.ts +4 -4
- package/types/ngwr-pipes.d.ts +1 -1
- package/types/ngwr-popover-testing.d.ts +7 -2
- package/types/ngwr-popover.d.ts +1 -0
- package/types/ngwr-result.d.ts +6 -0
- package/types/ngwr-rotating-text-testing.d.ts +191 -0
- package/types/ngwr-segmented-testing.d.ts +9 -0
- package/types/ngwr-segmented.d.ts +21 -1
- package/types/ngwr-select.d.ts +100 -6
- package/types/ngwr-shiny-text-testing.d.ts +165 -0
- package/types/ngwr-shiny-text.d.ts +4 -2
- package/types/ngwr-sidebar.d.ts +14 -5
- package/types/ngwr-slider.d.ts +84 -1
- package/types/ngwr-sparkline-testing.d.ts +69 -0
- package/types/ngwr-speed-dial-testing.d.ts +199 -0
- package/types/ngwr-speed-dial.d.ts +22 -3
- package/types/ngwr-splash-cursor-testing.d.ts +95 -0
- package/types/ngwr-split-text-testing.d.ts +209 -0
- package/types/ngwr-split-text.d.ts +21 -0
- package/types/ngwr-splitter-testing.d.ts +174 -0
- package/types/ngwr-spotlight-card-testing.d.ts +303 -0
- package/types/ngwr-star-border-testing.d.ts +135 -0
- package/types/ngwr-stepper-testing.d.ts +5 -5
- package/types/ngwr-stepper.d.ts +8 -0
- package/types/ngwr-storage.d.ts +1 -0
- package/types/ngwr-table.d.ts +114 -12
- package/types/ngwr-tabs-testing.d.ts +11 -10
- package/types/ngwr-tabs.d.ts +2 -0
- package/types/ngwr-theme.d.ts +82 -1
- package/types/ngwr-tilt-card-testing.d.ts +158 -0
- package/types/ngwr-tilt-card.d.ts +2 -0
- package/types/ngwr-toast.d.ts +14 -5
- package/types/ngwr-tour-testing.d.ts +143 -0
- package/types/ngwr-tour.d.ts +22 -4
- package/types/ngwr-tree.d.ts +12 -4
- package/types/ngwr-typewriter-testing.d.ts +211 -0
- package/types/ngwr-utils.d.ts +5 -2
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-waves-testing.d.ts +121 -0
- package/types/ngwr-window-testing.d.ts +263 -0
- package/types/ngwr-window.d.ts +66 -21
- package/typography/styles/_index.scss +1 -1
- package/window/styles/_index.scss +116 -42
- package/fesm2022/ngwr-date-adapter-fns.mjs.map +0 -1
- package/fesm2022/ngwr-date-adapter-luxon.mjs.map +0 -1
- package/fesm2022/ngwr-date-adapter.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-lightbox.mjs","sources":["../../../projects/lib/lightbox/lightbox.ts","../../../projects/lib/lightbox/lightbox.html","../../../projects/lib/lightbox/ngwr-lightbox.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 { type ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { type OverlayRef } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport type { TemplateRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n ViewContainerRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\n\n/**\n * Image with click-to-zoom lightbox. The thumbnail is a plain `<img>`\n * with whatever sizing/`object-fit` you give it; clicking opens a CDK\n * overlay showing the full image, with Escape + close button + backdrop\n * to dismiss.\n *\n * @example\n * ```html\n * <wr-lightbox\n * src=\"/photo.jpg\"\n * alt=\"Mountain\"\n * style=\"width: 16rem; height: 10rem; object-fit: cover\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/lightbox\n */\n@Component({\n selector: 'wr-lightbox',\n templateUrl: './lightbox.html',\n styleUrl: './lightbox.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.aspect-ratio]': 'aspectRatio()',\n },\n})\nexport class WrLightbox {\n /** Accessible name. Falls back to `image.close`, then `'Close preview'`. */\n /** Accessible name of the thumbnail button. Falls back to `alt`, then `image.open`. */\n readonly openLabel = input<string | null>(null);\n\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'image.close', 'Close preview');\n\n /** Image source. */\n readonly src = input.required<string>();\n\n /** Alt text. Required for a11y; falls back to an empty string. @default '' */\n readonly alt = input<string>('');\n\n /**\n * Lighter source for the THUMBNAIL — a small or blurred copy, so the grid does not\n * pull full-size images. The viewer always shows `src`; the thumbnail does not swap\n * to it later.\n */\n readonly preview = input<string | null>(null);\n\n // Both of these were hard-coded English literals in the template, next to a catalog\n // that already carried `image.open` translated and unread. `useI18nText` already\n // does the whole chain — a non-empty binding wins, then the catalog, then the\n // English default — so the alt text (which describes the actual picture) is what\n // gets forwarded, with `openLabel` ahead of it as the explicit override.\n protected readonly resolvedOpenLabel = useI18nText(\n computed(() => {\n // Not `??`: an empty `openLabel` is no label at all, so it has to fall through\n // to the alt text rather than winning as an empty string.\n const explicit = this.openLabel();\n if (explicit) return explicit;\n return this.alt();\n }),\n 'image.open',\n 'Open preview'\n );\n\n protected readonly resolvedViewerLabel = useI18nText(this.alt, 'image.viewer', 'Image preview');\n\n /** Disable the click-to-zoom lightbox. @default false */\n readonly disablePreview = input(false, { transform: coerceBooleanProperty });\n\n /** Caption shown under the full image in the lightbox. */\n readonly caption = input<string>('');\n\n /**\n * Reserve space before the image resolves by fixing the thumbnail's\n * `aspect-ratio` (e.g. `'16 / 9'`, `'4 / 3'`, or a number like `1.5`).\n * Prevents the layout jump when the intrinsic image size isn't known up\n * front — pair it with a `width` and the box keeps its height from first\n * paint. @default null\n */\n readonly aspectRatio = input<string | number | null>(null);\n\n protected readonly open = signal(false);\n protected readonly loaded = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-lightbox'];\n if (this.open()) parts.push('wr-lightbox--open');\n if (!this.loaded()) parts.push('wr-lightbox--loading');\n return parts.join(' ');\n });\n\n // Query by name — `@if` blocks register their own TemplateRefs, so an\n // unqualified TemplateRef query would grab the thumbnail branch instead.\n protected readonly panelTpl = viewChild.required<TemplateRef<unknown>>('viewerTpl');\n\n private readonly overlay = inject(WR_OVERLAY);\n private readonly vcr = inject(ViewContainerRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private focusTrap: ConfigurableFocusTrap | null = null;\n private previouslyFocused: HTMLElement | null = null;\n private overlayRef: OverlayRef | null = null;\n\n constructor() {\n // Destroying the component while the viewer is open removes the projected\n // content (the portal uses this component's ViewContainerRef) but leaves the\n // overlay pane and its full-screen backdrop behind, with page scroll still\n // blocked. Dispose the overlay explicitly.\n this.destroyRef.onDestroy(() => this.closeOverlay());\n\n effect(() => {\n if (this.open()) this.openOverlay();\n else this.closeOverlay();\n });\n }\n\n protected onClick(): void {\n if (this.disablePreview()) return;\n this.open.set(true);\n }\n\n protected onClose(): void {\n this.open.set(false);\n }\n\n // Swipe-down-to-dismiss on the open viewer. The image follows the finger and\n // fades; releasing past ~20% of the viewer height closes it.\n private viewerStartY = 0;\n private viewerSwiping = false;\n\n protected onViewerSwipeStart(event: TouchEvent): void {\n const touch = event.touches[0];\n if (!touch) return;\n this.viewerStartY = touch.clientY;\n this.viewerSwiping = true;\n }\n\n protected onViewerSwipeMove(event: TouchEvent, viewer: HTMLElement): void {\n if (!this.viewerSwiping) return;\n const touch = event.touches[0];\n if (!touch) return;\n const dy = touch.clientY - this.viewerStartY;\n if (dy <= 0) {\n viewer.style.transform = '';\n viewer.style.opacity = '';\n return;\n }\n event.preventDefault();\n viewer.style.transition = 'none';\n viewer.style.transform = `translateY(${dy}px)`;\n viewer.style.opacity = String(Math.max(0.3, 1 - dy / (viewer.offsetHeight || 1)));\n }\n\n protected onViewerSwipeEnd(event: TouchEvent, viewer: HTMLElement): void {\n if (!this.viewerSwiping) return;\n this.viewerSwiping = false;\n const touch = event.changedTouches[0];\n const dy = touch ? touch.clientY - this.viewerStartY : 0;\n if (dy > (viewer.offsetHeight || 0) * 0.2) {\n this.onClose();\n } else {\n viewer.style.transition = '';\n viewer.style.transform = '';\n viewer.style.opacity = '';\n }\n }\n\n /**\n * The thumbnail has settled, one way or the other. A failure counts: `--loading`\n * animates a shimmer AND sets `opacity: 0` on the image, so a broken `src` with no\n * error path left an invisible box shimmering for ever instead of showing its alt\n * text.\n */\n protected onSettled(): void {\n this.loaded.set(true);\n }\n\n // Overlay\n\n private openOverlay(): void {\n if (this.overlayRef) return;\n\n this.overlayRef = this.overlay.create({\n hasBackdrop: true,\n backdropClass: 'wr-lightbox-backdrop',\n positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),\n scrollStrategy: this.overlay.scrollStrategies.block(),\n panelClass: 'wr-lightbox-overlay',\n });\n\n const portal = new TemplatePortal(this.panelTpl(), this.vcr);\n this.overlayRef.attach(portal);\n\n // The viewer declares role=\"dialog\" + aria-modal=\"true\", which promises the\n // rest of the page is inert. Without a focus trap that promise is a lie:\n // Tab walks straight out of the viewer into the page behind the backdrop.\n const active = document.activeElement;\n this.previouslyFocused = active instanceof HTMLElement ? active : null;\n this.focusTrap = this.focusTrapFactory.create(this.overlayRef.overlayElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n this.overlayRef\n .backdropClick()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.open.set(false));\n\n this.overlayRef\n .keydownEvents()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n if (event.key === 'Escape') {\n event.preventDefault();\n this.open.set(false);\n }\n });\n }\n\n private closeOverlay(): void {\n if (!this.overlayRef) return;\n this.focusTrap?.destroy();\n this.focusTrap = null;\n const restore = this.previouslyFocused;\n this.previouslyFocused = null;\n this.overlayRef.dispose();\n this.overlayRef = null;\n // Send focus back where it came from, or it lands on <body> and the user\n // has to tab from the top of the page again.\n if (restore && typeof restore.focus === 'function') restore.focus();\n }\n}\n","@if (disablePreview()) {\n <img\n class=\"wr-lightbox__thumb\"\n [src]=\"preview() || src()\"\n [alt]=\"alt()\"\n (load)=\"onSettled()\"\n (error)=\"onSettled()\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n} @else {\n <button type=\"button\" class=\"wr-lightbox__trigger\" [attr.aria-label]=\"resolvedOpenLabel()\" (click)=\"onClick()\">\n <img\n class=\"wr-lightbox__thumb\"\n [src]=\"preview() || src()\"\n [alt]=\"alt()\"\n (load)=\"onSettled()\"\n (error)=\"onSettled()\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n </button>\n}\n\n<ng-template #viewerTpl>\n <div\n #viewer\n class=\"wr-lightbox-viewer\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"resolvedViewerLabel()\"\n (touchstart)=\"onViewerSwipeStart($event)\"\n (touchmove)=\"onViewerSwipeMove($event, viewer)\"\n (touchend)=\"onViewerSwipeEnd($event, viewer)\"\n (touchcancel)=\"onViewerSwipeEnd($event, viewer)\"\n >\n <button\n type=\"button\"\n class=\"wr-lightbox-viewer__close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"onClose()\"\n (keydown.enter)=\"onClose()\"\n (keydown.space)=\"onClose()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n <!--\n Styled `cursor: zoom-out`, which is a promise: clicking the image closes. A\n REDUNDANT mouse affordance — the close button, Escape, the backdrop and\n swipe-down all do the same thing and all reach a keyboard — so the image stays\n out of the tab order rather than becoming a second close stop.\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <img class=\"wr-lightbox-viewer__full\" [src]=\"src()\" [alt]=\"alt()\" (click)=\"onClose()\" />\n @if (caption()) {\n <div class=\"wr-lightbox-viewer__caption\">{{ caption() }}</div>\n }\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-lightbox.mjs","sources":["../../../projects/lib/lightbox/lightbox.ts","../../../projects/lib/lightbox/lightbox.html","../../../projects/lib/lightbox/ngwr-lightbox.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 { type ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { type OverlayRef } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport type { TemplateRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n ViewContainerRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\n\nlet viewerUid = 0;\n\n/**\n * Image with click-to-zoom lightbox. The thumbnail is a plain `<img>`\n * with whatever sizing/`object-fit` you give it; clicking opens a CDK\n * overlay showing the full image, with Escape + close button + backdrop\n * to dismiss.\n *\n * @example\n * ```html\n * <wr-lightbox\n * src=\"/photo.jpg\"\n * alt=\"Mountain\"\n * style=\"width: 16rem; height: 10rem; object-fit: cover\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/lightbox\n */\n@Component({\n selector: 'wr-lightbox',\n templateUrl: './lightbox.html',\n styleUrl: './lightbox.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.aspect-ratio]': 'aspectRatio()',\n },\n})\nexport class WrLightbox {\n /** Accessible name. Falls back to `image.close`, then `'Close preview'`. */\n /** Accessible name of the thumbnail button. Falls back to `alt`, then `image.open`. */\n readonly openLabel = input<string | null>(null);\n\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'image.close', 'Close preview');\n\n /** Image source. */\n readonly src = input.required<string>();\n\n /** Alt text. Required for a11y; falls back to an empty string. @default '' */\n readonly alt = input<string>('');\n\n /**\n * Lighter source for the THUMBNAIL — a small or blurred copy, so the grid does not\n * pull full-size images. The viewer always shows `src`; the thumbnail does not swap\n * to it later.\n */\n readonly preview = input<string | null>(null);\n\n // Both of these were hard-coded English literals in the template, next to a catalog\n // that already carried `image.open` translated and unread. `useI18nText` already\n // does the whole chain — a non-empty binding wins, then the catalog, then the\n // English default — so the alt text (which describes the actual picture) is what\n // gets forwarded, with `openLabel` ahead of it as the explicit override.\n protected readonly resolvedOpenLabel = useI18nText(\n computed(() => {\n // Not `??`: an empty `openLabel` is no label at all, so it has to fall through\n // to the alt text rather than winning as an empty string.\n const explicit = this.openLabel();\n if (explicit) return explicit;\n return this.alt();\n }),\n 'image.open',\n 'Open preview'\n );\n\n protected readonly resolvedViewerLabel = useI18nText(this.alt, 'image.viewer', 'Image preview');\n\n /** Disable the click-to-zoom lightbox. @default false */\n readonly disablePreview = input(false, { transform: coerceBooleanProperty });\n\n /** Caption shown under the full image in the lightbox. */\n readonly caption = input<string>('');\n\n /**\n * Reserve space before the image resolves by fixing the thumbnail's\n * `aspect-ratio` (e.g. `'16 / 9'`, `'4 / 3'`, or a number like `1.5`).\n * Prevents the layout jump when the intrinsic image size isn't known up\n * front — pair it with a `width` and the box keeps its height from first\n * paint. @default null\n */\n readonly aspectRatio = input<string | number | null>(null);\n\n protected readonly open = signal(false);\n protected readonly loaded = signal(false);\n\n /**\n * Id the trigger's `aria-controls` points at while the viewer is open.\n *\n * @internal Public so the host bindings can read it.\n */\n protected readonly viewerId = `wr-lightbox-viewer-${++viewerUid}`;\n\n protected readonly classes = computed(() => {\n const parts = ['wr-lightbox'];\n if (this.open()) parts.push('wr-lightbox--open');\n if (!this.loaded()) parts.push('wr-lightbox--loading');\n return parts.join(' ');\n });\n\n // Query by name — `@if` blocks register their own TemplateRefs, so an\n // unqualified TemplateRef query would grab the thumbnail branch instead.\n protected readonly panelTpl = viewChild.required<TemplateRef<unknown>>('viewerTpl');\n\n private readonly overlay = inject(WR_OVERLAY);\n private readonly vcr = inject(ViewContainerRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private focusTrap: ConfigurableFocusTrap | null = null;\n private previouslyFocused: HTMLElement | null = null;\n private overlayRef: OverlayRef | null = null;\n\n constructor() {\n // Destroying the component while the viewer is open removes the projected\n // content (the portal uses this component's ViewContainerRef) but leaves the\n // overlay pane and its full-screen backdrop behind, with page scroll still\n // blocked. Dispose the overlay explicitly.\n this.destroyRef.onDestroy(() => this.closeOverlay());\n\n effect(() => {\n if (this.open()) this.openOverlay();\n else this.closeOverlay();\n });\n }\n\n protected onClick(): void {\n if (this.disablePreview()) return;\n this.open.set(true);\n }\n\n protected onClose(): void {\n this.open.set(false);\n }\n\n // Swipe-down-to-dismiss on the open viewer. The image follows the finger and\n // fades; releasing past ~20% of the viewer height closes it.\n private viewerStartY = 0;\n private viewerSwiping = false;\n\n protected onViewerSwipeStart(event: TouchEvent): void {\n const touch = event.touches[0];\n if (!touch) return;\n this.viewerStartY = touch.clientY;\n this.viewerSwiping = true;\n }\n\n protected onViewerSwipeMove(event: TouchEvent, viewer: HTMLElement): void {\n if (!this.viewerSwiping) return;\n const touch = event.touches[0];\n if (!touch) return;\n const dy = touch.clientY - this.viewerStartY;\n if (dy <= 0) {\n viewer.style.transform = '';\n viewer.style.opacity = '';\n return;\n }\n event.preventDefault();\n viewer.style.transition = 'none';\n viewer.style.transform = `translateY(${dy}px)`;\n viewer.style.opacity = String(Math.max(0.3, 1 - dy / (viewer.offsetHeight || 1)));\n }\n\n protected onViewerSwipeEnd(event: TouchEvent, viewer: HTMLElement): void {\n if (!this.viewerSwiping) return;\n this.viewerSwiping = false;\n const touch = event.changedTouches[0];\n const dy = touch ? touch.clientY - this.viewerStartY : 0;\n if (dy > (viewer.offsetHeight || 0) * 0.2) {\n this.onClose();\n } else {\n viewer.style.transition = '';\n viewer.style.transform = '';\n viewer.style.opacity = '';\n }\n }\n\n /**\n * The thumbnail has settled, one way or the other. A failure counts: `--loading`\n * animates a shimmer AND sets `opacity: 0` on the image, so a broken `src` with no\n * error path left an invisible box shimmering for ever instead of showing its alt\n * text.\n */\n protected onSettled(): void {\n this.loaded.set(true);\n }\n\n // Overlay\n\n private openOverlay(): void {\n if (this.overlayRef) return;\n\n this.overlayRef = this.overlay.create({\n hasBackdrop: true,\n backdropClass: 'wr-lightbox-backdrop',\n positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),\n scrollStrategy: this.overlay.scrollStrategies.block(),\n panelClass: 'wr-lightbox-overlay',\n });\n\n const portal = new TemplatePortal(this.panelTpl(), this.vcr);\n this.overlayRef.attach(portal);\n\n // The viewer declares role=\"dialog\" + aria-modal=\"true\", which promises the\n // rest of the page is inert. Without a focus trap that promise is a lie:\n // Tab walks straight out of the viewer into the page behind the backdrop.\n const active = document.activeElement;\n this.previouslyFocused = active instanceof HTMLElement ? active : null;\n this.focusTrap = this.focusTrapFactory.create(this.overlayRef.overlayElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n this.overlayRef\n .backdropClick()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.open.set(false));\n\n this.overlayRef\n .keydownEvents()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n if (event.key === 'Escape') {\n event.preventDefault();\n this.open.set(false);\n }\n });\n }\n\n private closeOverlay(): void {\n if (!this.overlayRef) return;\n this.focusTrap?.destroy();\n this.focusTrap = null;\n const restore = this.previouslyFocused;\n this.previouslyFocused = null;\n this.overlayRef.dispose();\n this.overlayRef = null;\n // Send focus back where it came from, or it lands on <body> and the user\n // has to tab from the top of the page again.\n if (restore && typeof restore.focus === 'function') restore.focus();\n }\n}\n","@if (disablePreview()) {\n <img\n class=\"wr-lightbox__thumb\"\n [src]=\"preview() || src()\"\n [alt]=\"alt()\"\n (load)=\"onSettled()\"\n (error)=\"onSettled()\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n} @else {\n <!-- The trigger opens a modal dialog, and used to say so nowhere: no hint that it\n opens anything, and nothing on the page tying it to the viewer it opened. -->\n <button\n type=\"button\"\n class=\"wr-lightbox__trigger\"\n aria-haspopup=\"dialog\"\n [attr.aria-controls]=\"open() ? viewerId : null\"\n [attr.aria-label]=\"resolvedOpenLabel()\"\n (click)=\"onClick()\"\n >\n <img\n class=\"wr-lightbox__thumb\"\n [src]=\"preview() || src()\"\n [alt]=\"alt()\"\n (load)=\"onSettled()\"\n (error)=\"onSettled()\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n </button>\n}\n\n<ng-template #viewerTpl>\n <div\n #viewer\n class=\"wr-lightbox-viewer\"\n [id]=\"viewerId\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"resolvedViewerLabel()\"\n (touchstart)=\"onViewerSwipeStart($event)\"\n (touchmove)=\"onViewerSwipeMove($event, viewer)\"\n (touchend)=\"onViewerSwipeEnd($event, viewer)\"\n (touchcancel)=\"onViewerSwipeEnd($event, viewer)\"\n >\n <button\n type=\"button\"\n class=\"wr-lightbox-viewer__close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"onClose()\"\n (keydown.enter)=\"onClose()\"\n (keydown.space)=\"onClose()\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n <!--\n Styled `cursor: zoom-out`, which is a promise: clicking the image closes. A\n REDUNDANT mouse affordance — the close button, Escape, the backdrop and\n swipe-down all do the same thing and all reach a keyboard — so the image stays\n out of the tab order rather than becoming a second close stop.\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <img class=\"wr-lightbox-viewer__full\" [src]=\"src()\" [alt]=\"alt()\" (click)=\"onClose()\" />\n @if (caption()) {\n <div class=\"wr-lightbox-viewer__caption\">{{ caption() }}</div>\n }\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Test harness for `<wr-line-chart>`.
|
|
11
|
+
*
|
|
12
|
+
* **The lines are paths and the paths are not assertable.** A `d` attribute is a
|
|
13
|
+
* rendering detail — it moves with the viewBox, the padding and the value range — so
|
|
14
|
+
* this harness counts the lines and reads everything TEXTUAL instead: the legend, the
|
|
15
|
+
* axis ticks, the x labels and the tooltip. Those are what a reader gets, and they
|
|
16
|
+
* are what breaks when the data wiring does.
|
|
17
|
+
*
|
|
18
|
+
* **The tooltip cannot be opened from a spec, and the reason is worth knowing.** It
|
|
19
|
+
* follows a `pointermove` over the plot, and the component turns the cursor's x into
|
|
20
|
+
* an index by measuring the plot's box — which jsdom reports as 0×0, so every
|
|
21
|
+
* synthetic move resolves to the same index or to none at all. There is deliberately
|
|
22
|
+
* no `hoverAt()`; {@link getTooltipRows} reads the tooltip when something else has
|
|
23
|
+
* opened it, which in practice means a browser-run harness.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const chart = await loader.getHarness(WrLineChartHarness);
|
|
28
|
+
*
|
|
29
|
+
* expect(await chart.getSeriesLabels()).toEqual(['Revenue', 'Costs']);
|
|
30
|
+
* expect(await chart.getLineCount()).toBe(2);
|
|
31
|
+
* expect(await chart.getXLabels()).toEqual(['Jan', 'Feb', 'Mar']);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @see https://ngwr.dev/guides/testing
|
|
35
|
+
*/
|
|
36
|
+
class WrLineChartHarness extends ComponentHarness {
|
|
37
|
+
static hostSelector = 'wr-line-chart';
|
|
38
|
+
/** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
|
|
39
|
+
static with(options = {}) {
|
|
40
|
+
return new HarnessPredicate(WrLineChartHarness, options)
|
|
41
|
+
.addOption('ariaLabel', options.ariaLabel, (harness, label) => HarnessPredicate.stringMatches(harness.getAccessibleName(), label))
|
|
42
|
+
.addOption('seriesLabel', options.seriesLabel, async (harness, label) => {
|
|
43
|
+
for (const series of await harness.getSeriesLabels()) {
|
|
44
|
+
if (await HarnessPredicate.stringMatches(series, label))
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** The plot's accessible name — the only name the drawing has. */
|
|
51
|
+
async getAccessibleName() {
|
|
52
|
+
return (await this.locatorFor('.wr-line-chart__plot')()).getAttribute('aria-label');
|
|
53
|
+
}
|
|
54
|
+
/** Whether the legend is rendered (`showLegend`, and at least one series). */
|
|
55
|
+
async hasLegend() {
|
|
56
|
+
return (await this.locatorForOptional('.wr-line-chart__legend')()) !== null;
|
|
57
|
+
}
|
|
58
|
+
/** The series names from the legend, in order. */
|
|
59
|
+
async getSeriesLabels() {
|
|
60
|
+
const items = await this.locatorForAll('.wr-line-chart__legend-item')();
|
|
61
|
+
return Promise.all(items.map(item => item.text()));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* How many lines were drawn.
|
|
65
|
+
*
|
|
66
|
+
* Worth comparing with {@link getSeriesLabels}: the legend and the paths come from
|
|
67
|
+
* the same list, so a mismatch is a series that got filtered out of one of them.
|
|
68
|
+
*/
|
|
69
|
+
async getLineCount() {
|
|
70
|
+
return (await this.locatorForAll('.wr-line-chart__line')()).length;
|
|
71
|
+
}
|
|
72
|
+
/** How many point dots were drawn across every series (`showDots`). */
|
|
73
|
+
async getDotCount() {
|
|
74
|
+
return (await this.locatorForAll('.wr-line-chart__dot')()).length;
|
|
75
|
+
}
|
|
76
|
+
/** Whether the horizontal grid lines are drawn (`showGrid`). */
|
|
77
|
+
async hasGrid() {
|
|
78
|
+
return (await this.locatorForOptional('.wr-line-chart__grid-line')()) !== null;
|
|
79
|
+
}
|
|
80
|
+
/** The y-axis tick labels, top to bottom as drawn. */
|
|
81
|
+
async getYTicks() {
|
|
82
|
+
const ticks = await this.locatorForAll('.wr-line-chart__tick')();
|
|
83
|
+
return Promise.all(ticks.map(tick => tick.text()));
|
|
84
|
+
}
|
|
85
|
+
/** The x-axis labels under the plot, in order. */
|
|
86
|
+
async getXLabels() {
|
|
87
|
+
const labels = await this.locatorForAll('.wr-line-chart__x-label')();
|
|
88
|
+
return Promise.all(labels.map(label => label.text()));
|
|
89
|
+
}
|
|
90
|
+
/** Whether the hover tooltip is showing. */
|
|
91
|
+
async hasTooltip() {
|
|
92
|
+
return (await this.locatorForOptional('.wr-line-chart__tooltip')()) !== null;
|
|
93
|
+
}
|
|
94
|
+
/** The tooltip's heading — the x label of the hovered point — or `null` while it is closed. */
|
|
95
|
+
async getTooltipLabel() {
|
|
96
|
+
const label = await this.locatorForOptional('.wr-line-chart__tooltip-label')();
|
|
97
|
+
return label ? label.text() : null;
|
|
98
|
+
}
|
|
99
|
+
/** The tooltip's rows, one per series, or `[]` while it is closed. */
|
|
100
|
+
async getTooltipRows() {
|
|
101
|
+
const names = await this.locatorForAll('.wr-line-chart__tooltip-name')();
|
|
102
|
+
const values = await this.locatorForAll('.wr-line-chart__tooltip-value')();
|
|
103
|
+
return Promise.all(names.map(async (name, index) => ({
|
|
104
|
+
label: await name.text(),
|
|
105
|
+
value: (await values[index]?.text()) ?? '',
|
|
106
|
+
})));
|
|
107
|
+
}
|
|
108
|
+
/** The plot's height in pixels, as the component writes it inline. */
|
|
109
|
+
async getPlotHeight() {
|
|
110
|
+
return Number.parseFloat(await (await this.locatorFor('.wr-line-chart__plot')()).getCssValue('height'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generated bundle index. Do not edit.
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
export { WrLineChartHarness };
|
|
119
|
+
//# sourceMappingURL=ngwr-line-chart-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-line-chart-testing.mjs","sources":["../../../projects/lib/line-chart/testing/wr-line-chart-harness.ts","../../../projects/lib/line-chart/testing/ngwr-line-chart-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrLineChartHarnessFilters, WrLineChartTooltipRow } from './interfaces';\n\n/**\n * Test harness for `<wr-line-chart>`.\n *\n * **The lines are paths and the paths are not assertable.** A `d` attribute is a\n * rendering detail — it moves with the viewBox, the padding and the value range — so\n * this harness counts the lines and reads everything TEXTUAL instead: the legend, the\n * axis ticks, the x labels and the tooltip. Those are what a reader gets, and they\n * are what breaks when the data wiring does.\n *\n * **The tooltip cannot be opened from a spec, and the reason is worth knowing.** It\n * follows a `pointermove` over the plot, and the component turns the cursor's x into\n * an index by measuring the plot's box — which jsdom reports as 0×0, so every\n * synthetic move resolves to the same index or to none at all. There is deliberately\n * no `hoverAt()`; {@link getTooltipRows} reads the tooltip when something else has\n * opened it, which in practice means a browser-run harness.\n *\n * @example\n * ```ts\n * const chart = await loader.getHarness(WrLineChartHarness);\n *\n * expect(await chart.getSeriesLabels()).toEqual(['Revenue', 'Costs']);\n * expect(await chart.getLineCount()).toBe(2);\n * expect(await chart.getXLabels()).toEqual(['Jan', 'Feb', 'Mar']);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrLineChartHarness extends ComponentHarness {\n static hostSelector = 'wr-line-chart';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrLineChartHarnessFilters = {}): HarnessPredicate<WrLineChartHarness> {\n return new HarnessPredicate(WrLineChartHarness, options)\n .addOption('ariaLabel', options.ariaLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n )\n .addOption('seriesLabel', options.seriesLabel, async (harness, label) => {\n for (const series of await harness.getSeriesLabels()) {\n if (await HarnessPredicate.stringMatches(series, label)) return true;\n }\n return false;\n });\n }\n\n /** The plot's accessible name — the only name the drawing has. */\n async getAccessibleName(): Promise<string | null> {\n return (await this.locatorFor('.wr-line-chart__plot')()).getAttribute('aria-label');\n }\n\n /** Whether the legend is rendered (`showLegend`, and at least one series). */\n async hasLegend(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-line-chart__legend')()) !== null;\n }\n\n /** The series names from the legend, in order. */\n async getSeriesLabels(): Promise<string[]> {\n const items = await this.locatorForAll('.wr-line-chart__legend-item')();\n return Promise.all(items.map(item => item.text()));\n }\n\n /**\n * How many lines were drawn.\n *\n * Worth comparing with {@link getSeriesLabels}: the legend and the paths come from\n * the same list, so a mismatch is a series that got filtered out of one of them.\n */\n async getLineCount(): Promise<number> {\n return (await this.locatorForAll('.wr-line-chart__line')()).length;\n }\n\n /** How many point dots were drawn across every series (`showDots`). */\n async getDotCount(): Promise<number> {\n return (await this.locatorForAll('.wr-line-chart__dot')()).length;\n }\n\n /** Whether the horizontal grid lines are drawn (`showGrid`). */\n async hasGrid(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-line-chart__grid-line')()) !== null;\n }\n\n /** The y-axis tick labels, top to bottom as drawn. */\n async getYTicks(): Promise<string[]> {\n const ticks = await this.locatorForAll('.wr-line-chart__tick')();\n return Promise.all(ticks.map(tick => tick.text()));\n }\n\n /** The x-axis labels under the plot, in order. */\n async getXLabels(): Promise<string[]> {\n const labels = await this.locatorForAll('.wr-line-chart__x-label')();\n return Promise.all(labels.map(label => label.text()));\n }\n\n /** Whether the hover tooltip is showing. */\n async hasTooltip(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-line-chart__tooltip')()) !== null;\n }\n\n /** The tooltip's heading — the x label of the hovered point — or `null` while it is closed. */\n async getTooltipLabel(): Promise<string | null> {\n const label = await this.locatorForOptional('.wr-line-chart__tooltip-label')();\n return label ? label.text() : null;\n }\n\n /** The tooltip's rows, one per series, or `[]` while it is closed. */\n async getTooltipRows(): Promise<WrLineChartTooltipRow[]> {\n const names = await this.locatorForAll('.wr-line-chart__tooltip-name')();\n const values = await this.locatorForAll('.wr-line-chart__tooltip-value')();\n\n return Promise.all(\n names.map(async (name, index) => ({\n label: await name.text(),\n value: (await values[index]?.text()) ?? '',\n }))\n );\n }\n\n /** The plot's height in pixels, as the component writes it inline. */\n async getPlotHeight(): Promise<number> {\n return Number.parseFloat(await (await this.locatorFor('.wr-line-chart__plot')()).getCssValue('height'));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -65,16 +65,20 @@ class WrLineChart {
|
|
|
65
65
|
...(ngDevMode ? [{ debugName: "hoveredIndex" }] : /* istanbul ignore next */ []));
|
|
66
66
|
resolvedSeries = computed(() => this.series().map((s, i) => ({
|
|
67
67
|
label: s.label,
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
68
|
+
// A non-finite point becomes a HOLE at its own index, not a missing element.
|
|
69
|
+
// It cannot stay a number: `Math.min`/`Math.max` over the pooled data are both
|
|
70
|
+
// NaN as soon as one datum is, so every coordinate in EVERY series came out
|
|
71
|
+
// `NaN` — invalid path geometry, and the whole chart vanished rather than the
|
|
72
|
+
// one bad point. But filtering it out closed the gap and slid every LATER point
|
|
73
|
+
// one x-slot to the left, so each of them was drawn under, and reported in the
|
|
74
|
+
// tooltip as, the wrong x label. A gap is kept as a gap and the line is broken
|
|
75
|
+
// across it: interpolating would invent a reading the data never took.
|
|
76
|
+
data: s.data.map(v => (Number.isFinite(v) ? v : null)),
|
|
73
77
|
color: s.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length],
|
|
74
78
|
})), /* @ts-ignore */
|
|
75
79
|
...(ngDevMode ? [{ debugName: "resolvedSeries" }] : /* istanbul ignore next */ []));
|
|
76
80
|
bounds = computed(() => {
|
|
77
|
-
const all = this.resolvedSeries().flatMap(s => s.data);
|
|
81
|
+
const all = this.resolvedSeries().flatMap(s => s.data.filter(v => v !== null));
|
|
78
82
|
if (all.length === 0)
|
|
79
83
|
return { min: 0, max: 1 };
|
|
80
84
|
const min = Math.min(...all);
|
|
@@ -102,21 +106,41 @@ class WrLineChart {
|
|
|
102
106
|
...(ngDevMode ? [{ debugName: "yTicks" }] : /* istanbul ignore next */ []));
|
|
103
107
|
plotWidth = () => this.vbW - this.padding.left - this.padding.right;
|
|
104
108
|
plotHeight = () => this.vbH - this.padding.top - this.padding.bottom;
|
|
105
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* The SVG path for a series — one subpath per unbroken run, so a hole in the data
|
|
111
|
+
* leaves a hole in the line rather than a straight segment across it.
|
|
112
|
+
*/
|
|
106
113
|
pathFor(series) {
|
|
107
114
|
const { min, max } = this.bounds();
|
|
108
115
|
const count = this.pointCount();
|
|
109
|
-
if (count <= 1
|
|
116
|
+
if (count <= 1)
|
|
110
117
|
return '';
|
|
111
118
|
const pw = this.plotWidth();
|
|
112
119
|
const ph = this.plotHeight();
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
const commands = [];
|
|
121
|
+
let run = 0;
|
|
122
|
+
let last = '';
|
|
123
|
+
// A subpath of one `M` is not stroked at all, so a point with a gap on both sides
|
|
124
|
+
// would simply vanish when `showDots` is off. Repeating it gives the round linecap
|
|
125
|
+
// something to paint.
|
|
126
|
+
const closeRun = () => {
|
|
127
|
+
if (run === 1)
|
|
128
|
+
commands.push(`L ${last}`);
|
|
129
|
+
run = 0;
|
|
130
|
+
};
|
|
131
|
+
series.data.forEach((v, i) => {
|
|
132
|
+
if (v === null) {
|
|
133
|
+
closeRun();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
115
136
|
const x = this.padding.left + (i / (count - 1)) * pw;
|
|
116
137
|
const y = this.padding.top + ((max - v) / (max - min)) * ph;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
last = `${x.toFixed(2)} ${y.toFixed(2)}`;
|
|
139
|
+
commands.push(`${run === 0 ? 'M' : 'L'} ${last}`);
|
|
140
|
+
run++;
|
|
141
|
+
});
|
|
142
|
+
closeRun();
|
|
143
|
+
return commands.join(' ');
|
|
120
144
|
}
|
|
121
145
|
pointX(index) {
|
|
122
146
|
const count = this.pointCount();
|
|
@@ -128,19 +152,42 @@ class WrLineChart {
|
|
|
128
152
|
const { min, max } = this.bounds();
|
|
129
153
|
return this.padding.top + ((max - value) / (max - min)) * this.plotHeight();
|
|
130
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* A point's x as a percentage of the host's width. The x-label strip and the SVG share
|
|
157
|
+
* that width — the drawing stretches to it — so this is the same space the tooltip and
|
|
158
|
+
* the labels are placed in, at any size.
|
|
159
|
+
*/
|
|
160
|
+
xPercent(index) {
|
|
161
|
+
return (this.pointX(index) / this.vbW) * 100;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* How wide an x label may be, as a percentage of the host's width: its own slot — half
|
|
165
|
+
* the distance to a neighbour on each side — cut back to what is left between its point
|
|
166
|
+
* and the end of the strip. Centring a label on its point says nothing about its WIDTH,
|
|
167
|
+
* and with no bound at all twelve month names printed over each other and the last one
|
|
168
|
+
* hung outside the chart. Past this the stylesheet ellipsises.
|
|
169
|
+
*/
|
|
170
|
+
xMaxPercent(index) {
|
|
171
|
+
const count = this.pointCount();
|
|
172
|
+
// A single label has no neighbour to collide with and the whole strip to sit in.
|
|
173
|
+
if (count <= 1)
|
|
174
|
+
return 100;
|
|
175
|
+
const slot = (this.plotWidth() / (count - 1) / this.vbW) * 100;
|
|
176
|
+
const x = this.xPercent(index);
|
|
177
|
+
return Math.min(slot, x + slot / 2, 100 - x + slot / 2);
|
|
178
|
+
}
|
|
131
179
|
hoverPoints = computed(() => {
|
|
132
180
|
const i = this.hoveredIndex();
|
|
133
181
|
if (i === null)
|
|
134
182
|
return [];
|
|
135
|
-
return this.resolvedSeries()
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
value
|
|
140
|
-
|
|
141
|
-
x: this.pointX(i),
|
|
142
|
-
|
|
143
|
-
}));
|
|
183
|
+
return this.resolvedSeries().flatMap(s => {
|
|
184
|
+
// Past the end of a short series, or on a hole in a longer one — either way there
|
|
185
|
+
// is no reading at this x, so the series contributes neither a marker nor a row.
|
|
186
|
+
const value = s.data[i];
|
|
187
|
+
if (value === undefined || value === null)
|
|
188
|
+
return [];
|
|
189
|
+
return [{ label: s.label, value, color: s.color, x: this.pointX(i), y: this.pointY(value) }];
|
|
190
|
+
});
|
|
144
191
|
}, /* @ts-ignore */
|
|
145
192
|
...(ngDevMode ? [{ debugName: "hoverPoints" }] : /* istanbul ignore next */ []));
|
|
146
193
|
hoverLabel = computed(() => {
|
|
@@ -173,12 +220,12 @@ class WrLineChart {
|
|
|
173
220
|
...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
|
|
174
221
|
// Convenience accessor so the host can be referenced from the template.
|
|
175
222
|
hostEl = this.host.nativeElement;
|
|
176
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
177
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
223
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrLineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
224
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrLineChart, isStandalone: true, selector: "wr-line-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, xLabels: { classPropertyName: "xLabels", publicName: "xLabels", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showDots: { classPropertyName: "showDots", publicName: "showDots", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-line-chart" }, ngImport: i0, template: "@let hovered = hoveredIndex();\n@if (showLegend() && resolvedSeries().length > 0) {\n <ul class=\"wr-line-chart__legend\">\n @for (s of resolvedSeries(); track s.label) {\n <li class=\"wr-line-chart__legend-item\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"s.color\"></span>\n <span>{{ s.label }}</span>\n </li>\n }\n </ul>\n}\n\n<div class=\"wr-line-chart__plot\" role=\"img\" [attr.aria-label]=\"resolvedAriaLabel()\" [style.height.px]=\"height()\">\n <svg\n [attr.viewBox]=\"viewBox()\"\n preserveAspectRatio=\"none\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n @if (showGrid()) {\n @for (tick of yTicks(); track tick.value) {\n <line\n class=\"wr-line-chart__grid-line\"\n [attr.x1]=\"padding.left\"\n [attr.x2]=\"vbW - padding.right\"\n [attr.y1]=\"tick.y\"\n [attr.y2]=\"tick.y\"\n stroke=\"rgba(var(--wr-color-light-rgb), 0.7)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <text\n class=\"wr-line-chart__tick\"\n [attr.x]=\"padding.left - 6\"\n [attr.y]=\"tick.y + 4\"\n text-anchor=\"end\"\n fill=\"var(--wr-color-on-surface-muted)\"\n font-size=\"10\"\n font-family=\"var(--wr-font-family-base)\"\n >\n {{ formatTick(tick.value) }}\n </text>\n }\n }\n\n @for (s of resolvedSeries(); track s.label) {\n <path\n class=\"wr-line-chart__line\"\n [attr.d]=\"pathFor(s)\"\n fill=\"none\"\n [attr.stroke]=\"s.color\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n @if (showDots()) {\n @for (v of s.data; track $index; let i = $index) {\n <!-- A hole in the data gets no dot: the index still belongs to it, so the\n points after it stay on their own x. -->\n @if (v !== null) {\n <circle\n class=\"wr-line-chart__dot\"\n [attr.cx]=\"pointX(i)\"\n [attr.cy]=\"pointY(v)\"\n r=\"3\"\n [attr.fill]=\"s.color\"\n />\n }\n }\n }\n }\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <line\n class=\"wr-line-chart__crosshair\"\n [attr.x1]=\"pointX(hovered)\"\n [attr.x2]=\"pointX(hovered)\"\n [attr.y1]=\"padding.top\"\n [attr.y2]=\"vbH - padding.bottom\"\n stroke=\"var(--wr-color-medium)\"\n stroke-width=\"1\"\n stroke-dasharray=\"3 3\"\n vector-effect=\"non-scaling-stroke\"\n />\n @for (p of hoverPoints(); track p.label) {\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n fill=\"var(--wr-color-white)\"\n [attr.stroke]=\"p.color\"\n stroke-width=\"2\"\n />\n }\n }\n </svg>\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <div class=\"wr-line-chart__tooltip\" [style.left.%]=\"(pointX(hovered) / vbW) * 100\" [style.top.px]=\"padding.top\">\n <div class=\"wr-line-chart__tooltip-label\">{{ hoverLabel() }}</div>\n @for (p of hoverPoints(); track p.label) {\n <div class=\"wr-line-chart__tooltip-row\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"p.color\"></span>\n <span class=\"wr-line-chart__tooltip-name\">{{ p.label }}</span>\n <span class=\"wr-line-chart__tooltip-value\">{{ p.value }}</span>\n </div>\n }\n </div>\n }\n</div>\n\n@if (xLabels().length > 0) {\n <div class=\"wr-line-chart__x-labels\">\n @for (label of xLabels(); track $index) {\n <!-- Each label hands the stylesheet two numbers, both percentages of the host's\n width \u2014 the space the stretching SVG is in, so they track the drawing at any\n size. The x of the point it names, which the stylesheet centres it on; and the\n widest it may be before it reaches a neighbour or the end of the strip, past\n which the stylesheet ellipsises it. Laying the labels out as equal centred flex\n slots instead is bar-chart geometry: only the middle label landed on its dot,\n and the outer ones sat up to half a column away. -->\n <span\n class=\"wr-line-chart__x-label\"\n [style.--wr-line-chart-x]=\"xPercent($index)\"\n [style.--wr-line-chart-x-max]=\"xMaxPercent($index)\"\n >{{ label }}</span\n >\n }\n </div>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
178
225
|
}
|
|
179
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
226
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrLineChart, decorators: [{
|
|
180
227
|
type: Component,
|
|
181
|
-
args: [{ selector: 'wr-line-chart', encapsulation: ViewEncapsulation.None, host: { class: 'wr-line-chart' }, template: "@let hovered = hoveredIndex();\n@if (showLegend() && resolvedSeries().length > 0) {\n <ul class=\"wr-line-chart__legend\">\n @for (s of resolvedSeries(); track s.label) {\n <li class=\"wr-line-chart__legend-item\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"s.color\"></span>\n <span>{{ s.label }}</span>\n </li>\n }\n </ul>\n}\n\n<div class=\"wr-line-chart__plot\" role=\"img\" [attr.aria-label]=\"resolvedAriaLabel()\" [style.height.px]=\"height()\">\n <svg\n [attr.viewBox]=\"viewBox()\"\n preserveAspectRatio=\"none\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n @if (showGrid()) {\n @for (tick of yTicks(); track tick.value) {\n <line\n class=\"wr-line-chart__grid-line\"\n [attr.x1]=\"padding.left\"\n [attr.x2]=\"vbW - padding.right\"\n [attr.y1]=\"tick.y\"\n [attr.y2]=\"tick.y\"\n stroke=\"rgba(var(--wr-color-light-rgb), 0.7)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <text\n class=\"wr-line-chart__tick\"\n [attr.x]=\"padding.left - 6\"\n [attr.y]=\"tick.y + 4\"\n text-anchor=\"end\"\n fill=\"var(--wr-color-on-surface-muted)\"\n font-size=\"10\"\n font-family=\"var(--wr-font-family-base)\"\n >\n {{ formatTick(tick.value) }}\n </text>\n }\n }\n\n @for (s of resolvedSeries(); track s.label) {\n <path\n class=\"wr-line-chart__line\"\n [attr.d]=\"pathFor(s)\"\n fill=\"none\"\n [attr.stroke]=\"s.color\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n @if (showDots()) {\n @for (v of s.data; track $index; let i = $index) {\n <circle
|
|
228
|
+
args: [{ selector: 'wr-line-chart', encapsulation: ViewEncapsulation.None, host: { class: 'wr-line-chart' }, template: "@let hovered = hoveredIndex();\n@if (showLegend() && resolvedSeries().length > 0) {\n <ul class=\"wr-line-chart__legend\">\n @for (s of resolvedSeries(); track s.label) {\n <li class=\"wr-line-chart__legend-item\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"s.color\"></span>\n <span>{{ s.label }}</span>\n </li>\n }\n </ul>\n}\n\n<div class=\"wr-line-chart__plot\" role=\"img\" [attr.aria-label]=\"resolvedAriaLabel()\" [style.height.px]=\"height()\">\n <svg\n [attr.viewBox]=\"viewBox()\"\n preserveAspectRatio=\"none\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n @if (showGrid()) {\n @for (tick of yTicks(); track tick.value) {\n <line\n class=\"wr-line-chart__grid-line\"\n [attr.x1]=\"padding.left\"\n [attr.x2]=\"vbW - padding.right\"\n [attr.y1]=\"tick.y\"\n [attr.y2]=\"tick.y\"\n stroke=\"rgba(var(--wr-color-light-rgb), 0.7)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <text\n class=\"wr-line-chart__tick\"\n [attr.x]=\"padding.left - 6\"\n [attr.y]=\"tick.y + 4\"\n text-anchor=\"end\"\n fill=\"var(--wr-color-on-surface-muted)\"\n font-size=\"10\"\n font-family=\"var(--wr-font-family-base)\"\n >\n {{ formatTick(tick.value) }}\n </text>\n }\n }\n\n @for (s of resolvedSeries(); track s.label) {\n <path\n class=\"wr-line-chart__line\"\n [attr.d]=\"pathFor(s)\"\n fill=\"none\"\n [attr.stroke]=\"s.color\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n @if (showDots()) {\n @for (v of s.data; track $index; let i = $index) {\n <!-- A hole in the data gets no dot: the index still belongs to it, so the\n points after it stay on their own x. -->\n @if (v !== null) {\n <circle\n class=\"wr-line-chart__dot\"\n [attr.cx]=\"pointX(i)\"\n [attr.cy]=\"pointY(v)\"\n r=\"3\"\n [attr.fill]=\"s.color\"\n />\n }\n }\n }\n }\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <line\n class=\"wr-line-chart__crosshair\"\n [attr.x1]=\"pointX(hovered)\"\n [attr.x2]=\"pointX(hovered)\"\n [attr.y1]=\"padding.top\"\n [attr.y2]=\"vbH - padding.bottom\"\n stroke=\"var(--wr-color-medium)\"\n stroke-width=\"1\"\n stroke-dasharray=\"3 3\"\n vector-effect=\"non-scaling-stroke\"\n />\n @for (p of hoverPoints(); track p.label) {\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n fill=\"var(--wr-color-white)\"\n [attr.stroke]=\"p.color\"\n stroke-width=\"2\"\n />\n }\n }\n </svg>\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <div class=\"wr-line-chart__tooltip\" [style.left.%]=\"(pointX(hovered) / vbW) * 100\" [style.top.px]=\"padding.top\">\n <div class=\"wr-line-chart__tooltip-label\">{{ hoverLabel() }}</div>\n @for (p of hoverPoints(); track p.label) {\n <div class=\"wr-line-chart__tooltip-row\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"p.color\"></span>\n <span class=\"wr-line-chart__tooltip-name\">{{ p.label }}</span>\n <span class=\"wr-line-chart__tooltip-value\">{{ p.value }}</span>\n </div>\n }\n </div>\n }\n</div>\n\n@if (xLabels().length > 0) {\n <div class=\"wr-line-chart__x-labels\">\n @for (label of xLabels(); track $index) {\n <!-- Each label hands the stylesheet two numbers, both percentages of the host's\n width \u2014 the space the stretching SVG is in, so they track the drawing at any\n size. The x of the point it names, which the stylesheet centres it on; and the\n widest it may be before it reaches a neighbour or the end of the strip, past\n which the stylesheet ellipsises it. Laying the labels out as equal centred flex\n slots instead is bar-chart geometry: only the middle label landed on its dot,\n and the outer ones sat up to half a column away. -->\n <span\n class=\"wr-line-chart__x-label\"\n [style.--wr-line-chart-x]=\"xPercent($index)\"\n [style.--wr-line-chart-x-max]=\"xMaxPercent($index)\"\n >{{ label }}</span\n >\n }\n </div>\n}\n" }]
|
|
182
229
|
}], propDecorators: { series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], xLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "xLabels", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], showDots: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDots", required: false }] }] } });
|
|
183
230
|
|
|
184
231
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-line-chart.mjs","sources":["../../../projects/lib/line-chart/line-chart.ts","../../../projects/lib/line-chart/line-chart.html","../../../projects/lib/line-chart/ngwr-line-chart.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 { Component, ElementRef, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\n\nimport type { WrLineSeries } from './interfaces';\n\nconst FALLBACK_COLORS = [\n 'var(--wr-color-primary)',\n 'var(--wr-color-secondary)',\n 'var(--wr-color-success)',\n 'var(--wr-color-warning)',\n 'var(--wr-color-danger)',\n];\n\n/**\n * Multi-series line chart with axes, gridlines, and a hover tooltip.\n * SVG-only — no external dependency.\n *\n * @example\n * ```html\n * <wr-line-chart\n * [series]=\"[\n * { label: 'Visits', data: [12, 18, 9, 22, 30, 27, 35] },\n * { label: 'Signups', data: [3, 5, 4, 8, 11, 9, 14], color: 'var(--wr-color-success)' }\n * ]\"\n * [xLabels]=\"['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/line-chart\n */\n@Component({\n selector: 'wr-line-chart',\n templateUrl: './line-chart.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-line-chart' },\n})\nexport class WrLineChart {\n readonly series = input<readonly WrLineSeries[]>([]);\n\n /**\n * Accessible name of the chart. The legend carries the series NAMES only — the numbers\n * are nowhere in text — so without this the plot is nothing at all to a screen reader.\n * Falls back to `lineChart.label`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'lineChart.label', 'Line chart');\n\n /** Labels for the X axis (one per data point). */\n readonly xLabels = input<readonly string[]>([]);\n\n /** Chart pixel height. @default 240 */\n readonly height = input(240, { transform: (v: unknown): number => Math.max(80, coerceNumberProperty(v, 240)) });\n\n /** Show gridlines + axis ticks. @default true */\n readonly showGrid = input(true, { transform: coerceBooleanProperty });\n\n /** Show the legend above the chart. @default true */\n readonly showLegend = input(true, { transform: coerceBooleanProperty });\n\n /** Show dots at each data point. @default true */\n readonly showDots = input(true, { transform: coerceBooleanProperty });\n\n // Drawn in a 600×300 viewBox with reserved space for axis labels.\n protected readonly vbW = 600;\n protected readonly vbH = 300;\n protected readonly padding = { top: 16, right: 16, bottom: 28, left: 36 } as const;\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Hovered point index (across all series) — null when no hover. */\n protected readonly hoveredIndex = signal<number | null>(null);\n\n protected readonly resolvedSeries = computed(() =>\n this.series().map((s, i) => ({\n label: s.label,\n // Non-finite points are dropped, not scaled. `Math.min`/`Math.max` over the pooled\n // data are both NaN as soon as one datum is, so every coordinate in EVERY series\n // became `NaN` — invalid path geometry, and the whole chart vanished rather than the\n // one bad point.\n data: s.data.filter(v => Number.isFinite(v)),\n color: s.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length],\n }))\n );\n\n protected readonly bounds = computed(() => {\n const all = this.resolvedSeries().flatMap(s => s.data);\n if (all.length === 0) return { min: 0, max: 1 };\n const min = Math.min(...all);\n const max = Math.max(...all);\n if (min === max) return { min: min - 1, max: max + 1 };\n // Pad upper bound by ~10% for breathing room.\n return { min: Math.min(0, min), max: max + (max - min) * 0.1 };\n });\n\n protected readonly pointCount = computed(() => {\n const maxLen = this.resolvedSeries().reduce((acc, s) => Math.max(acc, s.data.length), 0);\n return Math.max(maxLen, this.xLabels().length);\n });\n\n /** Y-axis tick marks (5 of them). */\n protected readonly yTicks = computed(() => {\n const { min, max } = this.bounds();\n const step = (max - min) / 4;\n return [0, 1, 2, 3, 4].map(i => {\n const value = max - i * step;\n return { value, y: this.padding.top + ((max - value) / (max - min)) * this.plotHeight() };\n });\n });\n\n protected readonly plotWidth = (): number => this.vbW - this.padding.left - this.padding.right;\n protected readonly plotHeight = (): number => this.vbH - this.padding.top - this.padding.bottom;\n\n /** Get the SVG path for a series. */\n protected pathFor(series: { data: readonly number[] }): string {\n const { min, max } = this.bounds();\n const count = this.pointCount();\n if (count <= 1 || series.data.length === 0) return '';\n const pw = this.plotWidth();\n const ph = this.plotHeight();\n return series.data\n .map((v, i) => {\n const x = this.padding.left + (i / (count - 1)) * pw;\n const y = this.padding.top + ((max - v) / (max - min)) * ph;\n return `${i === 0 ? 'M' : 'L'} ${x.toFixed(2)} ${y.toFixed(2)}`;\n })\n .join(' ');\n }\n\n protected pointX(index: number): number {\n const count = this.pointCount();\n if (count <= 1) return this.padding.left;\n return this.padding.left + (index / (count - 1)) * this.plotWidth();\n }\n\n protected pointY(value: number): number {\n const { min, max } = this.bounds();\n return this.padding.top + ((max - value) / (max - min)) * this.plotHeight();\n }\n\n protected readonly hoverPoints = computed(() => {\n const i = this.hoveredIndex();\n if (i === null) return [];\n return this.resolvedSeries()\n .filter(s => i < s.data.length)\n .map(s => ({\n label: s.label,\n value: s.data[i],\n color: s.color,\n x: this.pointX(i),\n y: this.pointY(s.data[i]),\n }));\n });\n\n protected readonly hoverLabel = computed(() => {\n const i = this.hoveredIndex();\n if (i === null) return '';\n return this.xLabels()[i] ?? String(i);\n });\n\n protected onPointerMove(event: PointerEvent): void {\n const svg = (event.currentTarget as SVGElement).getBoundingClientRect();\n const ratio = (event.clientX - svg.left) / svg.width;\n const vbX = ratio * this.vbW;\n const count = this.pointCount();\n if (count === 0) return;\n const i = Math.round(((vbX - this.padding.left) / this.plotWidth()) * (count - 1));\n if (i >= 0 && i < count) this.hoveredIndex.set(i);\n }\n\n protected onPointerLeave(): void {\n this.hoveredIndex.set(null);\n }\n\n protected formatTick(v: number): string {\n if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;\n return v.toFixed(v % 1 === 0 ? 0 : 1);\n }\n\n protected readonly viewBox = computed(() => `0 0 ${this.vbW} ${this.vbH}`);\n\n // Convenience accessor so the host can be referenced from the template.\n protected readonly hostEl = this.host.nativeElement;\n}\n\nexport type { WrLineSeries } from './interfaces';\n","@let hovered = hoveredIndex();\n@if (showLegend() && resolvedSeries().length > 0) {\n <ul class=\"wr-line-chart__legend\">\n @for (s of resolvedSeries(); track s.label) {\n <li class=\"wr-line-chart__legend-item\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"s.color\"></span>\n <span>{{ s.label }}</span>\n </li>\n }\n </ul>\n}\n\n<div class=\"wr-line-chart__plot\" role=\"img\" [attr.aria-label]=\"resolvedAriaLabel()\" [style.height.px]=\"height()\">\n <svg\n [attr.viewBox]=\"viewBox()\"\n preserveAspectRatio=\"none\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n @if (showGrid()) {\n @for (tick of yTicks(); track tick.value) {\n <line\n class=\"wr-line-chart__grid-line\"\n [attr.x1]=\"padding.left\"\n [attr.x2]=\"vbW - padding.right\"\n [attr.y1]=\"tick.y\"\n [attr.y2]=\"tick.y\"\n stroke=\"rgba(var(--wr-color-light-rgb), 0.7)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <text\n class=\"wr-line-chart__tick\"\n [attr.x]=\"padding.left - 6\"\n [attr.y]=\"tick.y + 4\"\n text-anchor=\"end\"\n fill=\"var(--wr-color-on-surface-muted)\"\n font-size=\"10\"\n font-family=\"var(--wr-font-family-base)\"\n >\n {{ formatTick(tick.value) }}\n </text>\n }\n }\n\n @for (s of resolvedSeries(); track s.label) {\n <path\n class=\"wr-line-chart__line\"\n [attr.d]=\"pathFor(s)\"\n fill=\"none\"\n [attr.stroke]=\"s.color\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n @if (showDots()) {\n @for (v of s.data; track $index; let i = $index) {\n <circle class=\"wr-line-chart__dot\" [attr.cx]=\"pointX(i)\" [attr.cy]=\"pointY(v)\" r=\"3\" [attr.fill]=\"s.color\" />\n }\n }\n }\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <line\n class=\"wr-line-chart__crosshair\"\n [attr.x1]=\"pointX(hovered)\"\n [attr.x2]=\"pointX(hovered)\"\n [attr.y1]=\"padding.top\"\n [attr.y2]=\"vbH - padding.bottom\"\n stroke=\"var(--wr-color-medium)\"\n stroke-width=\"1\"\n stroke-dasharray=\"3 3\"\n vector-effect=\"non-scaling-stroke\"\n />\n @for (p of hoverPoints(); track p.label) {\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n fill=\"var(--wr-color-white)\"\n [attr.stroke]=\"p.color\"\n stroke-width=\"2\"\n />\n }\n }\n </svg>\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <div class=\"wr-line-chart__tooltip\" [style.left.%]=\"(pointX(hovered) / vbW) * 100\" [style.top.px]=\"padding.top\">\n <div class=\"wr-line-chart__tooltip-label\">{{ hoverLabel() }}</div>\n @for (p of hoverPoints(); track p.label) {\n <div class=\"wr-line-chart__tooltip-row\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"p.color\"></span>\n <span class=\"wr-line-chart__tooltip-name\">{{ p.label }}</span>\n <span class=\"wr-line-chart__tooltip-value\">{{ p.value }}</span>\n </div>\n }\n </div>\n }\n</div>\n\n@if (xLabels().length > 0) {\n <div class=\"wr-line-chart__x-labels\">\n @for (label of xLabels(); track $index) {\n <span class=\"wr-line-chart__x-label\">{{ label }}</span>\n }\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-line-chart.mjs","sources":["../../../projects/lib/line-chart/line-chart.ts","../../../projects/lib/line-chart/line-chart.html","../../../projects/lib/line-chart/ngwr-line-chart.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 { Component, ElementRef, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\n\nimport type { WrLineSeries } from './interfaces';\n\nconst FALLBACK_COLORS = [\n 'var(--wr-color-primary)',\n 'var(--wr-color-secondary)',\n 'var(--wr-color-success)',\n 'var(--wr-color-warning)',\n 'var(--wr-color-danger)',\n];\n\n/**\n * Multi-series line chart with axes, gridlines, and a hover tooltip.\n * SVG-only — no external dependency.\n *\n * @example\n * ```html\n * <wr-line-chart\n * [series]=\"[\n * { label: 'Visits', data: [12, 18, 9, 22, 30, 27, 35] },\n * { label: 'Signups', data: [3, 5, 4, 8, 11, 9, 14], color: 'var(--wr-color-success)' }\n * ]\"\n * [xLabels]=\"['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/line-chart\n */\n@Component({\n selector: 'wr-line-chart',\n templateUrl: './line-chart.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-line-chart' },\n})\nexport class WrLineChart {\n readonly series = input<readonly WrLineSeries[]>([]);\n\n /**\n * Accessible name of the chart. The legend carries the series NAMES only — the numbers\n * are nowhere in text — so without this the plot is nothing at all to a screen reader.\n * Falls back to `lineChart.label`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'lineChart.label', 'Line chart');\n\n /** Labels for the X axis (one per data point). */\n readonly xLabels = input<readonly string[]>([]);\n\n /** Chart pixel height. @default 240 */\n readonly height = input(240, { transform: (v: unknown): number => Math.max(80, coerceNumberProperty(v, 240)) });\n\n /** Show gridlines + axis ticks. @default true */\n readonly showGrid = input(true, { transform: coerceBooleanProperty });\n\n /** Show the legend above the chart. @default true */\n readonly showLegend = input(true, { transform: coerceBooleanProperty });\n\n /** Show dots at each data point. @default true */\n readonly showDots = input(true, { transform: coerceBooleanProperty });\n\n // Drawn in a 600×300 viewBox with reserved space for axis labels.\n protected readonly vbW = 600;\n protected readonly vbH = 300;\n protected readonly padding = { top: 16, right: 16, bottom: 28, left: 36 } as const;\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Hovered point index (across all series) — null when no hover. */\n protected readonly hoveredIndex = signal<number | null>(null);\n\n protected readonly resolvedSeries = computed(() =>\n this.series().map((s, i) => ({\n label: s.label,\n // A non-finite point becomes a HOLE at its own index, not a missing element.\n // It cannot stay a number: `Math.min`/`Math.max` over the pooled data are both\n // NaN as soon as one datum is, so every coordinate in EVERY series came out\n // `NaN` — invalid path geometry, and the whole chart vanished rather than the\n // one bad point. But filtering it out closed the gap and slid every LATER point\n // one x-slot to the left, so each of them was drawn under, and reported in the\n // tooltip as, the wrong x label. A gap is kept as a gap and the line is broken\n // across it: interpolating would invent a reading the data never took.\n data: s.data.map(v => (Number.isFinite(v) ? v : null)),\n color: s.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length],\n }))\n );\n\n protected readonly bounds = computed(() => {\n const all = this.resolvedSeries().flatMap(s => s.data.filter(v => v !== null));\n if (all.length === 0) return { min: 0, max: 1 };\n const min = Math.min(...all);\n const max = Math.max(...all);\n if (min === max) return { min: min - 1, max: max + 1 };\n // Pad upper bound by ~10% for breathing room.\n return { min: Math.min(0, min), max: max + (max - min) * 0.1 };\n });\n\n protected readonly pointCount = computed(() => {\n const maxLen = this.resolvedSeries().reduce((acc, s) => Math.max(acc, s.data.length), 0);\n return Math.max(maxLen, this.xLabels().length);\n });\n\n /** Y-axis tick marks (5 of them). */\n protected readonly yTicks = computed(() => {\n const { min, max } = this.bounds();\n const step = (max - min) / 4;\n return [0, 1, 2, 3, 4].map(i => {\n const value = max - i * step;\n return { value, y: this.padding.top + ((max - value) / (max - min)) * this.plotHeight() };\n });\n });\n\n protected readonly plotWidth = (): number => this.vbW - this.padding.left - this.padding.right;\n protected readonly plotHeight = (): number => this.vbH - this.padding.top - this.padding.bottom;\n\n /**\n * The SVG path for a series — one subpath per unbroken run, so a hole in the data\n * leaves a hole in the line rather than a straight segment across it.\n */\n protected pathFor(series: { data: readonly (number | null)[] }): string {\n const { min, max } = this.bounds();\n const count = this.pointCount();\n if (count <= 1) return '';\n const pw = this.plotWidth();\n const ph = this.plotHeight();\n\n const commands: string[] = [];\n let run = 0;\n let last = '';\n // A subpath of one `M` is not stroked at all, so a point with a gap on both sides\n // would simply vanish when `showDots` is off. Repeating it gives the round linecap\n // something to paint.\n const closeRun = (): void => {\n if (run === 1) commands.push(`L ${last}`);\n run = 0;\n };\n\n series.data.forEach((v, i) => {\n if (v === null) {\n closeRun();\n return;\n }\n const x = this.padding.left + (i / (count - 1)) * pw;\n const y = this.padding.top + ((max - v) / (max - min)) * ph;\n last = `${x.toFixed(2)} ${y.toFixed(2)}`;\n commands.push(`${run === 0 ? 'M' : 'L'} ${last}`);\n run++;\n });\n closeRun();\n\n return commands.join(' ');\n }\n\n protected pointX(index: number): number {\n const count = this.pointCount();\n if (count <= 1) return this.padding.left;\n return this.padding.left + (index / (count - 1)) * this.plotWidth();\n }\n\n protected pointY(value: number): number {\n const { min, max } = this.bounds();\n return this.padding.top + ((max - value) / (max - min)) * this.plotHeight();\n }\n\n /**\n * A point's x as a percentage of the host's width. The x-label strip and the SVG share\n * that width — the drawing stretches to it — so this is the same space the tooltip and\n * the labels are placed in, at any size.\n */\n protected xPercent(index: number): number {\n return (this.pointX(index) / this.vbW) * 100;\n }\n\n /**\n * How wide an x label may be, as a percentage of the host's width: its own slot — half\n * the distance to a neighbour on each side — cut back to what is left between its point\n * and the end of the strip. Centring a label on its point says nothing about its WIDTH,\n * and with no bound at all twelve month names printed over each other and the last one\n * hung outside the chart. Past this the stylesheet ellipsises.\n */\n protected xMaxPercent(index: number): number {\n const count = this.pointCount();\n // A single label has no neighbour to collide with and the whole strip to sit in.\n if (count <= 1) return 100;\n const slot = (this.plotWidth() / (count - 1) / this.vbW) * 100;\n const x = this.xPercent(index);\n return Math.min(slot, x + slot / 2, 100 - x + slot / 2);\n }\n\n protected readonly hoverPoints = computed(() => {\n const i = this.hoveredIndex();\n if (i === null) return [];\n return this.resolvedSeries().flatMap(s => {\n // Past the end of a short series, or on a hole in a longer one — either way there\n // is no reading at this x, so the series contributes neither a marker nor a row.\n const value = s.data[i];\n if (value === undefined || value === null) return [];\n return [{ label: s.label, value, color: s.color, x: this.pointX(i), y: this.pointY(value) }];\n });\n });\n\n protected readonly hoverLabel = computed(() => {\n const i = this.hoveredIndex();\n if (i === null) return '';\n return this.xLabels()[i] ?? String(i);\n });\n\n protected onPointerMove(event: PointerEvent): void {\n const svg = (event.currentTarget as SVGElement).getBoundingClientRect();\n const ratio = (event.clientX - svg.left) / svg.width;\n const vbX = ratio * this.vbW;\n const count = this.pointCount();\n if (count === 0) return;\n const i = Math.round(((vbX - this.padding.left) / this.plotWidth()) * (count - 1));\n if (i >= 0 && i < count) this.hoveredIndex.set(i);\n }\n\n protected onPointerLeave(): void {\n this.hoveredIndex.set(null);\n }\n\n protected formatTick(v: number): string {\n if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;\n return v.toFixed(v % 1 === 0 ? 0 : 1);\n }\n\n protected readonly viewBox = computed(() => `0 0 ${this.vbW} ${this.vbH}`);\n\n // Convenience accessor so the host can be referenced from the template.\n protected readonly hostEl = this.host.nativeElement;\n}\n\nexport type { WrLineSeries } from './interfaces';\n","@let hovered = hoveredIndex();\n@if (showLegend() && resolvedSeries().length > 0) {\n <ul class=\"wr-line-chart__legend\">\n @for (s of resolvedSeries(); track s.label) {\n <li class=\"wr-line-chart__legend-item\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"s.color\"></span>\n <span>{{ s.label }}</span>\n </li>\n }\n </ul>\n}\n\n<div class=\"wr-line-chart__plot\" role=\"img\" [attr.aria-label]=\"resolvedAriaLabel()\" [style.height.px]=\"height()\">\n <svg\n [attr.viewBox]=\"viewBox()\"\n preserveAspectRatio=\"none\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n @if (showGrid()) {\n @for (tick of yTicks(); track tick.value) {\n <line\n class=\"wr-line-chart__grid-line\"\n [attr.x1]=\"padding.left\"\n [attr.x2]=\"vbW - padding.right\"\n [attr.y1]=\"tick.y\"\n [attr.y2]=\"tick.y\"\n stroke=\"rgba(var(--wr-color-light-rgb), 0.7)\"\n stroke-width=\"1\"\n vector-effect=\"non-scaling-stroke\"\n />\n <text\n class=\"wr-line-chart__tick\"\n [attr.x]=\"padding.left - 6\"\n [attr.y]=\"tick.y + 4\"\n text-anchor=\"end\"\n fill=\"var(--wr-color-on-surface-muted)\"\n font-size=\"10\"\n font-family=\"var(--wr-font-family-base)\"\n >\n {{ formatTick(tick.value) }}\n </text>\n }\n }\n\n @for (s of resolvedSeries(); track s.label) {\n <path\n class=\"wr-line-chart__line\"\n [attr.d]=\"pathFor(s)\"\n fill=\"none\"\n [attr.stroke]=\"s.color\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n @if (showDots()) {\n @for (v of s.data; track $index; let i = $index) {\n <!-- A hole in the data gets no dot: the index still belongs to it, so the\n points after it stay on their own x. -->\n @if (v !== null) {\n <circle\n class=\"wr-line-chart__dot\"\n [attr.cx]=\"pointX(i)\"\n [attr.cy]=\"pointY(v)\"\n r=\"3\"\n [attr.fill]=\"s.color\"\n />\n }\n }\n }\n }\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <line\n class=\"wr-line-chart__crosshair\"\n [attr.x1]=\"pointX(hovered)\"\n [attr.x2]=\"pointX(hovered)\"\n [attr.y1]=\"padding.top\"\n [attr.y2]=\"vbH - padding.bottom\"\n stroke=\"var(--wr-color-medium)\"\n stroke-width=\"1\"\n stroke-dasharray=\"3 3\"\n vector-effect=\"non-scaling-stroke\"\n />\n @for (p of hoverPoints(); track p.label) {\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n fill=\"var(--wr-color-white)\"\n [attr.stroke]=\"p.color\"\n stroke-width=\"2\"\n />\n }\n }\n </svg>\n\n @if (hovered !== null && hoverPoints().length > 0) {\n <div class=\"wr-line-chart__tooltip\" [style.left.%]=\"(pointX(hovered) / vbW) * 100\" [style.top.px]=\"padding.top\">\n <div class=\"wr-line-chart__tooltip-label\">{{ hoverLabel() }}</div>\n @for (p of hoverPoints(); track p.label) {\n <div class=\"wr-line-chart__tooltip-row\">\n <span class=\"wr-line-chart__swatch\" [style.background]=\"p.color\"></span>\n <span class=\"wr-line-chart__tooltip-name\">{{ p.label }}</span>\n <span class=\"wr-line-chart__tooltip-value\">{{ p.value }}</span>\n </div>\n }\n </div>\n }\n</div>\n\n@if (xLabels().length > 0) {\n <div class=\"wr-line-chart__x-labels\">\n @for (label of xLabels(); track $index) {\n <!-- Each label hands the stylesheet two numbers, both percentages of the host's\n width — the space the stretching SVG is in, so they track the drawing at any\n size. The x of the point it names, which the stylesheet centres it on; and the\n widest it may be before it reaches a neighbour or the end of the strip, past\n which the stylesheet ellipsises it. Laying the labels out as equal centred flex\n slots instead is bar-chart geometry: only the middle label landed on its dot,\n and the outer ones sat up to half a column away. -->\n <span\n class=\"wr-line-chart__x-label\"\n [style.--wr-line-chart-x]=\"xPercent($index)\"\n [style.--wr-line-chart-x-max]=\"xMaxPercent($index)\"\n >{{ label }}</span\n >\n }\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|