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
package/fesm2022/ngwr-theme.mjs
CHANGED
|
@@ -42,6 +42,183 @@ const WR_COLORS = [
|
|
|
42
42
|
'dark',
|
|
43
43
|
];
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @license
|
|
47
|
+
*
|
|
48
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
49
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* The palette recipe, in TypeScript.
|
|
53
|
+
*
|
|
54
|
+
* `theme/styles/_colors.scss` derives seven tokens per intent from one hex, and
|
|
55
|
+
* until now that arithmetic existed only in Sass — so a theme picked at RUNTIME
|
|
56
|
+
* (a builder, a tenant colour from an API, a preset fetched from a registry)
|
|
57
|
+
* could not reproduce it. The showcase had half of it copied by hand for its
|
|
58
|
+
* own primary-colour switcher, which is the copy that drifts.
|
|
59
|
+
*
|
|
60
|
+
* **Seven tokens, not twelve, and that is not an omission.** The tint and ink
|
|
61
|
+
* layer is written in terms of `var()`:
|
|
62
|
+
*
|
|
63
|
+
* ```css
|
|
64
|
+
* --wr-color-primary-soft: rgba(var(--wr-color-primary-rgb), 0.12);
|
|
65
|
+
* --wr-color-primary-ink: color-mix(in srgb, var(--wr-color-primary) 78%, var(--wr-color-dark));
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* so redefining the base and its channels re-resolves `-soft`, `-soft-border`,
|
|
69
|
+
* `-soft-contrast`, `-active` and `-ink` for free. A preset that also emitted
|
|
70
|
+
* those would be freezing values the stylesheet is meant to keep deriving.
|
|
71
|
+
*
|
|
72
|
+
* **`-contrast` IS re-derived, and must be.** It picks whichever of black and
|
|
73
|
+
* white scores higher against the fill, so a re-tuned intent keeps the label of
|
|
74
|
+
* the colour it replaced unless something recomputes it — that is how the dark
|
|
75
|
+
* theme once put white text on its lightened primary at 3.36:1.
|
|
76
|
+
*
|
|
77
|
+
* The LIGHT recipe only. `_dark.scss` is hand-tuned rather than derived — its
|
|
78
|
+
* `dark-light` uses `color.scale(…, 70%)` where the light block uses
|
|
79
|
+
* `color.adjust(…, 5%)`, and `light-light` goes -2.5% — so a generator claiming
|
|
80
|
+
* to reproduce it would be lying. Feed dark seeds through the same recipe if you
|
|
81
|
+
* want a derived dark theme; you will get a consistent one, not the shipped one.
|
|
82
|
+
*
|
|
83
|
+
* `pnpm check:theme` compiles nothing and trusts nothing: it reads the BUILT
|
|
84
|
+
* stylesheet and asserts this module reproduces every one of those tokens for
|
|
85
|
+
* the nine shipped intents.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* import { wrThemeTokens } from 'ngwr/theme';
|
|
90
|
+
*
|
|
91
|
+
* const tokens = wrThemeTokens({ primary: '#41598f' });
|
|
92
|
+
* for (const [name, value] of Object.entries(tokens)) {
|
|
93
|
+
* document.documentElement.style.setProperty(name, value);
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
/** Lightness deltas, in PERCENTAGE POINTS, matching `color.adjust` in the SCSS. */
|
|
98
|
+
const SHADES = [
|
|
99
|
+
['dark', -5],
|
|
100
|
+
['darker', -10],
|
|
101
|
+
['light', 5],
|
|
102
|
+
['lighter', 10],
|
|
103
|
+
];
|
|
104
|
+
/** `#rgb` / `#rrggbb` → channels. Returns `null` for anything else. */
|
|
105
|
+
function parseHex(hex) {
|
|
106
|
+
const raw = hex.trim().replace(/^#/, '');
|
|
107
|
+
const full = raw.length === 3 ? [...raw].map(c => c + c).join('') : raw;
|
|
108
|
+
if (!/^[0-9a-fA-F]{6}$/.test(full))
|
|
109
|
+
return null;
|
|
110
|
+
const n = Number.parseInt(full, 16);
|
|
111
|
+
/* eslint-disable no-bitwise -- unpacking a packed 24-bit colour */
|
|
112
|
+
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
113
|
+
/* eslint-enable no-bitwise */
|
|
114
|
+
}
|
|
115
|
+
const toHex = ({ r, g, b }) => `#${[r, g, b].map(c => Math.round(c).toString(16).padStart(2, '0')).join('')}`;
|
|
116
|
+
function rgbToHsl({ r, g, b }) {
|
|
117
|
+
const rn = r / 255;
|
|
118
|
+
const gn = g / 255;
|
|
119
|
+
const bn = b / 255;
|
|
120
|
+
const max = Math.max(rn, gn, bn);
|
|
121
|
+
const min = Math.min(rn, gn, bn);
|
|
122
|
+
const d = max - min;
|
|
123
|
+
const l = (max + min) / 2;
|
|
124
|
+
if (d === 0)
|
|
125
|
+
return { h: 0, s: 0, l };
|
|
126
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
127
|
+
let h;
|
|
128
|
+
if (max === rn)
|
|
129
|
+
h = (gn - bn) / d + (gn < bn ? 6 : 0);
|
|
130
|
+
else if (max === gn)
|
|
131
|
+
h = (bn - rn) / d + 2;
|
|
132
|
+
else
|
|
133
|
+
h = (rn - gn) / d + 4;
|
|
134
|
+
return { h: h * 60, s, l };
|
|
135
|
+
}
|
|
136
|
+
function hslToRgb(h, s, l) {
|
|
137
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
138
|
+
// Two modulos, not one. `((h % 360) + 360) / 60` reads as normalisation and
|
|
139
|
+
// is not: it maps 222° to sector 9.7 instead of 3.7, so every hue landed in
|
|
140
|
+
// the wrong arm of the sextant table and only greys (c = x = 0) survived.
|
|
141
|
+
// The parity gate caught it on the first run — `dark`'s navy came back red.
|
|
142
|
+
const hp = (((h % 360) + 360) % 360) / 60;
|
|
143
|
+
const x = c * (1 - Math.abs((hp % 2) - 1));
|
|
144
|
+
const [rp, gp, bp] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
|
|
145
|
+
const m = l - c / 2;
|
|
146
|
+
return { r: (rp + m) * 255, g: (gp + m) * 255, b: (bp + m) * 255 };
|
|
147
|
+
}
|
|
148
|
+
/** One channel, linearised per WCAG 2.x — the SCSS `_linear`. */
|
|
149
|
+
function linear(channel) {
|
|
150
|
+
const s = channel / 255;
|
|
151
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
152
|
+
}
|
|
153
|
+
/** WCAG relative luminance. */
|
|
154
|
+
const luminance = ({ r, g, b }) => 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
|
|
155
|
+
/** WCAG contrast ratio between two colours. */
|
|
156
|
+
function ratio(a, b) {
|
|
157
|
+
const la = luminance(a);
|
|
158
|
+
const lb = luminance(b);
|
|
159
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
160
|
+
}
|
|
161
|
+
const BLACK = { r: 0, g: 0, b: 0 };
|
|
162
|
+
const WHITE = { r: 255, g: 255, b: 255 };
|
|
163
|
+
/**
|
|
164
|
+
* The readable label ON a filled intent — whichever of black and white scores
|
|
165
|
+
* higher, the SCSS `_contrast()`.
|
|
166
|
+
*
|
|
167
|
+
* It PICKS, it does not blend, so those two values are the ceiling for every
|
|
168
|
+
* intent. The comparison is `>=`, so a colour exactly at the flip point
|
|
169
|
+
* (√21 ≈ 4.58 against both) takes the dark label, matching the SCSS.
|
|
170
|
+
*/
|
|
171
|
+
function wrContrastFor(hex) {
|
|
172
|
+
const rgb = parseHex(hex);
|
|
173
|
+
if (!rgb)
|
|
174
|
+
return '#000000';
|
|
175
|
+
return ratio(rgb, BLACK) >= ratio(rgb, WHITE) ? '#000000' : '#ffffff';
|
|
176
|
+
}
|
|
177
|
+
/** Shift HSL lightness by `deltaPct` PERCENTAGE POINTS, clamped — `color.adjust`. */
|
|
178
|
+
function adjustLightness(rgb, deltaPct) {
|
|
179
|
+
const { h, s, l } = rgbToHsl(rgb);
|
|
180
|
+
return hslToRgb(h, s, Math.min(1, Math.max(0, l + deltaPct / 100)));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The seven `--wr-color-<name>*` tokens one seed produces.
|
|
184
|
+
*
|
|
185
|
+
* Returns an empty record for a hex it cannot parse rather than a palette built
|
|
186
|
+
* on black — a silently black theme is harder to notice than a missing one.
|
|
187
|
+
*
|
|
188
|
+
* `name` is a shipped intent rather than any string: a token layer the
|
|
189
|
+
* stylesheet never declares has nothing reading it, and a typo would otherwise
|
|
190
|
+
* emit `--wr-color-primry-*` and paint nothing.
|
|
191
|
+
*/
|
|
192
|
+
function wrIntentTokens(name, seed) {
|
|
193
|
+
const rgb = parseHex(seed);
|
|
194
|
+
if (!rgb)
|
|
195
|
+
return {};
|
|
196
|
+
const out = {
|
|
197
|
+
[`--wr-color-${name}`]: toHex(rgb),
|
|
198
|
+
[`--wr-color-${name}-rgb`]: `${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)}`,
|
|
199
|
+
[`--wr-color-${name}-contrast`]: wrContrastFor(toHex(rgb)),
|
|
200
|
+
};
|
|
201
|
+
for (const [suffix, delta] of SHADES) {
|
|
202
|
+
out[`--wr-color-${name}-${suffix}`] = toHex(adjustLightness(rgb, delta));
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Every token a theme preset needs, for as many intents as it re-tunes.
|
|
208
|
+
*
|
|
209
|
+
* Intents left out keep the values the stylesheet compiled, so a preset that
|
|
210
|
+
* only moves `primary` writes seven declarations and nothing else.
|
|
211
|
+
*/
|
|
212
|
+
function wrThemeTokens(seeds) {
|
|
213
|
+
const out = {};
|
|
214
|
+
for (const name of WR_COLORS) {
|
|
215
|
+
const seed = seeds[name];
|
|
216
|
+
if (seed)
|
|
217
|
+
Object.assign(out, wrIntentTokens(name, seed));
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
45
222
|
/**
|
|
46
223
|
* @license
|
|
47
224
|
*
|
|
@@ -133,10 +310,10 @@ class WrTheme {
|
|
|
133
310
|
return;
|
|
134
311
|
this.storage.set(this.config.storageKey, mode);
|
|
135
312
|
}
|
|
136
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
137
|
-
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.
|
|
313
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrTheme, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
314
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.2", ngImport: i0, type: WrTheme });
|
|
138
315
|
}
|
|
139
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
316
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrTheme, decorators: [{
|
|
140
317
|
type: Service
|
|
141
318
|
}], ctorParameters: () => [] });
|
|
142
319
|
|
|
@@ -173,5 +350,5 @@ function provideWrTheme(config = {}) {
|
|
|
173
350
|
* Generated bundle index. Do not edit.
|
|
174
351
|
*/
|
|
175
352
|
|
|
176
|
-
export { DEFAULT_WR_THEME_CONFIG, WR_COLORS, WR_THEME_CONFIG, WrTheme, provideWrTheme };
|
|
353
|
+
export { DEFAULT_WR_THEME_CONFIG, WR_COLORS, WR_THEME_CONFIG, WrTheme, provideWrTheme, wrContrastFor, wrIntentTokens, wrThemeTokens };
|
|
177
354
|
//# sourceMappingURL=ngwr-theme.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-theme.mjs","sources":["../../../projects/lib/theme/colors.ts","../../../projects/lib/theme/wr-theme-config.ts","../../../projects/lib/theme/services/wr-theme.ts","../../../projects/lib/theme/provide-wr-theme.ts","../../../projects/lib/theme/ngwr-theme.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\n/**\n * Built-in color variants available to NGWR components.\n *\n * Kept in sync manually with the SCSS `$base-colors` map in\n * `theme/styles/_colors.scss`. Both sources must stay aligned —\n * adding a color requires updating both this array and the SCSS map.\n * Nothing enforces that, and it has already drifted once: `info` landed\n * in the map without landing here, so v8.0.0 shipped every\n * `--wr-color-info*` token — plus an `--info` modifier from each of the\n * five stylesheets that iterate the map — while this type refused the\n * value. Order matches the map so the two read as one list.\n *\n * @example\n * ```ts\n * import { WR_COLORS } from 'ngwr/theme';\n *\n * // Iterate all colors for demos or documentation\n * WR_COLORS.forEach(color => console.log(color));\n * ```\n */\nexport const WR_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'light',\n 'medium',\n 'dark',\n] as const;\n\n/**\n * A color variant name.\n *\n * Use as an input type on components that accept a color:\n *\n * @example\n * ```ts\n * import type { WrColor } from 'ngwr/theme';\n *\n * @Component({...})\n * export class MyComponent {\n * readonly color = input<WrColor>('primary');\n * }\n * ```\n */\nexport type WrColor = (typeof WR_COLORS)[number];\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\n/** Mode the user explicitly selected. `auto` follows `prefers-color-scheme`. */\nexport type WrThemeMode = 'light' | 'dark' | 'auto';\n\n/** Resolved theme — what's actually applied to the DOM. */\nexport type WrResolvedTheme = 'light' | 'dark';\n\n/** Configuration for {@link WrTheme}. */\nexport interface WrThemeConfig {\n /** Initial mode used when no persisted value is present. @default 'auto' */\n readonly defaultMode: WrThemeMode;\n /** localStorage key for persistence. Set to `null` to disable persistence. @default 'wr-theme' */\n readonly storageKey: string | null;\n /** Attribute applied to `<html>` to expose the resolved theme. @default 'data-theme' */\n readonly attribute: string;\n}\n\nexport const DEFAULT_WR_THEME_CONFIG: WrThemeConfig = {\n defaultMode: 'auto',\n storageKey: 'wr-theme',\n attribute: 'data-theme',\n};\n\nexport const WR_THEME_CONFIG = new InjectionToken<WrThemeConfig>('WR_THEME_CONFIG', {\n factory: (): WrThemeConfig => DEFAULT_WR_THEME_CONFIG,\n});\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { DOCUMENT } from '@angular/common';\nimport { Service, type Signal, computed, effect, inject, signal } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrStorage } from 'ngwr/storage';\n\nimport { WR_THEME_CONFIG, type WrResolvedTheme, type WrThemeMode } from '../wr-theme-config';\n\nconst VALID_MODES: readonly WrThemeMode[] = ['light', 'dark', 'auto'];\n\nfunction isThemeMode(v: unknown): v is WrThemeMode {\n return typeof v === 'string' && (VALID_MODES as readonly string[]).includes(v);\n}\n\n/**\n * Light / dark theme manager.\n *\n * - `mode` is what the user picked (`light` | `dark` | `auto`).\n * - `resolved` is what's actually applied — `auto` resolves through\n * {@link WrPlatform.prefersDark}.\n * - Writes `[data-theme]` (or your configured attribute) on `<html>`.\n * - Persists the selected mode via {@link WrStorage} (defaults to\n * `localStorage`; swap the storage engine globally to change where).\n *\n * @example\n * ```ts\n * const theme = inject(WrTheme);\n * theme.set('dark');\n * theme.toggle();\n * theme.resolved(); // 'dark'\n * ```\n *\n * @see https://ngwr.dev/reference/services/theme\n */\n@Service()\nexport class WrTheme {\n private readonly doc = inject(DOCUMENT);\n private readonly platform = inject(WrPlatform);\n private readonly storage = inject(WrStorage);\n private readonly config = inject(WR_THEME_CONFIG);\n\n /** User-selected mode. */\n readonly mode = signal<WrThemeMode>(this.readPersisted() ?? this.config.defaultMode);\n\n /** Resolved theme actually applied to the DOM. */\n readonly resolved: Signal<WrResolvedTheme> = computed(() => {\n const m = this.mode();\n if (m === 'auto') return this.platform.prefersDark() ? 'dark' : 'light';\n return m;\n });\n\n constructor() {\n // Whenever the resolved theme changes, mirror it to the DOM + persist mode.\n effect(() => {\n const value = this.resolved();\n const html = this.doc.documentElement;\n if (html) html.setAttribute(this.config.attribute, value);\n this.persist(this.mode());\n });\n }\n\n /** Switch to a specific mode. */\n set(mode: WrThemeMode): void {\n this.mode.set(mode);\n }\n\n /** Cycle: light → dark → light (skips auto). */\n toggle(): void {\n this.mode.set(this.resolved() === 'dark' ? 'light' : 'dark');\n }\n\n // Persistence\n\n private readPersisted(): WrThemeMode | null {\n if (!this.config.storageKey) return null;\n const raw = this.storage.get<WrThemeMode>(this.config.storageKey);\n return isThemeMode(raw) ? raw : null;\n }\n\n private persist(mode: WrThemeMode): void {\n if (!this.config.storageKey) return;\n this.storage.set(this.config.storageKey, mode);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n type EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n} from '@angular/core';\n\nimport { WrTheme } from './services/wr-theme';\nimport { DEFAULT_WR_THEME_CONFIG, WR_THEME_CONFIG, type WrThemeConfig } from './wr-theme-config';\n\n/**\n * Configure and activate {@link WrTheme}. Config is partial — merged with\n * defaults.\n *\n * Calling this is all it takes: the initializer instantiates {@link WrTheme},\n * whose constructor effect mirrors the resolved theme onto `<html>`. Without\n * that, the service (a tree-shakable `@Service()`) would only be created once\n * something injected it, so `[data-theme]` was never written and an app that\n * followed the docs silently ignored `defaultMode: 'auto'`.\n *\n * @example\n * ```ts\n * provideWrTheme({ defaultMode: 'dark', storageKey: 'my-app-theme' })\n * ```\n */\nexport function provideWrTheme(config: Partial<WrThemeConfig> = {}): EnvironmentProviders {\n const merged: WrThemeConfig = { ...DEFAULT_WR_THEME_CONFIG, ...config };\n return makeEnvironmentProviders([\n { provide: WR_THEME_CONFIG, useValue: merged },\n provideEnvironmentInitializer(() => void inject(WrTheme)),\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-theme.mjs","sources":["../../../projects/lib/theme/colors.ts","../../../projects/lib/theme/palette.ts","../../../projects/lib/theme/wr-theme-config.ts","../../../projects/lib/theme/services/wr-theme.ts","../../../projects/lib/theme/provide-wr-theme.ts","../../../projects/lib/theme/ngwr-theme.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\n/**\n * Built-in color variants available to NGWR components.\n *\n * Kept in sync manually with the SCSS `$base-colors` map in\n * `theme/styles/_colors.scss`. Both sources must stay aligned —\n * adding a color requires updating both this array and the SCSS map.\n * Nothing enforces that, and it has already drifted once: `info` landed\n * in the map without landing here, so v8.0.0 shipped every\n * `--wr-color-info*` token — plus an `--info` modifier from each of the\n * five stylesheets that iterate the map — while this type refused the\n * value. Order matches the map so the two read as one list.\n *\n * @example\n * ```ts\n * import { WR_COLORS } from 'ngwr/theme';\n *\n * // Iterate all colors for demos or documentation\n * WR_COLORS.forEach(color => console.log(color));\n * ```\n */\nexport const WR_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'light',\n 'medium',\n 'dark',\n] as const;\n\n/**\n * A color variant name.\n *\n * Use as an input type on components that accept a color:\n *\n * @example\n * ```ts\n * import type { WrColor } from 'ngwr/theme';\n *\n * @Component({...})\n * export class MyComponent {\n * readonly color = input<WrColor>('primary');\n * }\n * ```\n */\nexport type WrColor = (typeof WR_COLORS)[number];\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The palette recipe, in TypeScript.\n *\n * `theme/styles/_colors.scss` derives seven tokens per intent from one hex, and\n * until now that arithmetic existed only in Sass — so a theme picked at RUNTIME\n * (a builder, a tenant colour from an API, a preset fetched from a registry)\n * could not reproduce it. The showcase had half of it copied by hand for its\n * own primary-colour switcher, which is the copy that drifts.\n *\n * **Seven tokens, not twelve, and that is not an omission.** The tint and ink\n * layer is written in terms of `var()`:\n *\n * ```css\n * --wr-color-primary-soft: rgba(var(--wr-color-primary-rgb), 0.12);\n * --wr-color-primary-ink: color-mix(in srgb, var(--wr-color-primary) 78%, var(--wr-color-dark));\n * ```\n *\n * so redefining the base and its channels re-resolves `-soft`, `-soft-border`,\n * `-soft-contrast`, `-active` and `-ink` for free. A preset that also emitted\n * those would be freezing values the stylesheet is meant to keep deriving.\n *\n * **`-contrast` IS re-derived, and must be.** It picks whichever of black and\n * white scores higher against the fill, so a re-tuned intent keeps the label of\n * the colour it replaced unless something recomputes it — that is how the dark\n * theme once put white text on its lightened primary at 3.36:1.\n *\n * The LIGHT recipe only. `_dark.scss` is hand-tuned rather than derived — its\n * `dark-light` uses `color.scale(…, 70%)` where the light block uses\n * `color.adjust(…, 5%)`, and `light-light` goes -2.5% — so a generator claiming\n * to reproduce it would be lying. Feed dark seeds through the same recipe if you\n * want a derived dark theme; you will get a consistent one, not the shipped one.\n *\n * `pnpm check:theme` compiles nothing and trusts nothing: it reads the BUILT\n * stylesheet and asserts this module reproduces every one of those tokens for\n * the nine shipped intents.\n *\n * @example\n * ```ts\n * import { wrThemeTokens } from 'ngwr/theme';\n *\n * const tokens = wrThemeTokens({ primary: '#41598f' });\n * for (const [name, value] of Object.entries(tokens)) {\n * document.documentElement.style.setProperty(name, value);\n * }\n * ```\n */\n\nimport { WR_COLORS, type WrColor } from './colors';\n\n/** Lightness deltas, in PERCENTAGE POINTS, matching `color.adjust` in the SCSS. */\nconst SHADES = [\n ['dark', -5],\n ['darker', -10],\n ['light', 5],\n ['lighter', 10],\n] as const;\n\ninterface Rgb {\n readonly r: number;\n readonly g: number;\n readonly b: number;\n}\n\n/** `#rgb` / `#rrggbb` → channels. Returns `null` for anything else. */\nfunction parseHex(hex: string): Rgb | null {\n const raw = hex.trim().replace(/^#/, '');\n const full = raw.length === 3 ? [...raw].map(c => c + c).join('') : raw;\n if (!/^[0-9a-fA-F]{6}$/.test(full)) return null;\n const n = Number.parseInt(full, 16);\n /* eslint-disable no-bitwise -- unpacking a packed 24-bit colour */\n return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };\n /* eslint-enable no-bitwise */\n}\n\nconst toHex = ({ r, g, b }: Rgb): string =>\n `#${[r, g, b].map(c => Math.round(c).toString(16).padStart(2, '0')).join('')}`;\n\nfunction rgbToHsl({ r, g, b }: Rgb): { h: number; s: number; l: number } {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const d = max - min;\n const l = (max + min) / 2;\n if (d === 0) return { h: 0, s: 0, l };\n\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === rn) h = (gn - bn) / d + (gn < bn ? 6 : 0);\n else if (max === gn) h = (bn - rn) / d + 2;\n else h = (rn - gn) / d + 4;\n return { h: h * 60, s, l };\n}\n\nfunction hslToRgb(h: number, s: number, l: number): Rgb {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n // Two modulos, not one. `((h % 360) + 360) / 60` reads as normalisation and\n // is not: it maps 222° to sector 9.7 instead of 3.7, so every hue landed in\n // the wrong arm of the sextant table and only greys (c = x = 0) survived.\n // The parity gate caught it on the first run — `dark`'s navy came back red.\n const hp = (((h % 360) + 360) % 360) / 60;\n const x = c * (1 - Math.abs((hp % 2) - 1));\n const [rp, gp, bp] =\n hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];\n const m = l - c / 2;\n return { r: (rp + m) * 255, g: (gp + m) * 255, b: (bp + m) * 255 };\n}\n\n/** One channel, linearised per WCAG 2.x — the SCSS `_linear`. */\nfunction linear(channel: number): number {\n const s = channel / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n}\n\n/** WCAG relative luminance. */\nconst luminance = ({ r, g, b }: Rgb): number => 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n\n/** WCAG contrast ratio between two colours. */\nfunction ratio(a: Rgb, b: Rgb): number {\n const la = luminance(a);\n const lb = luminance(b);\n return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);\n}\n\nconst BLACK: Rgb = { r: 0, g: 0, b: 0 };\nconst WHITE: Rgb = { r: 255, g: 255, b: 255 };\n\n/**\n * The readable label ON a filled intent — whichever of black and white scores\n * higher, the SCSS `_contrast()`.\n *\n * It PICKS, it does not blend, so those two values are the ceiling for every\n * intent. The comparison is `>=`, so a colour exactly at the flip point\n * (√21 ≈ 4.58 against both) takes the dark label, matching the SCSS.\n */\nfunction wrContrastFor(hex: string): string {\n const rgb = parseHex(hex);\n if (!rgb) return '#000000';\n return ratio(rgb, BLACK) >= ratio(rgb, WHITE) ? '#000000' : '#ffffff';\n}\n\n/** Shift HSL lightness by `deltaPct` PERCENTAGE POINTS, clamped — `color.adjust`. */\nfunction adjustLightness(rgb: Rgb, deltaPct: number): Rgb {\n const { h, s, l } = rgbToHsl(rgb);\n return hslToRgb(h, s, Math.min(1, Math.max(0, l + deltaPct / 100)));\n}\n\n/**\n * The seven `--wr-color-<name>*` tokens one seed produces.\n *\n * Returns an empty record for a hex it cannot parse rather than a palette built\n * on black — a silently black theme is harder to notice than a missing one.\n *\n * `name` is a shipped intent rather than any string: a token layer the\n * stylesheet never declares has nothing reading it, and a typo would otherwise\n * emit `--wr-color-primry-*` and paint nothing.\n */\nfunction wrIntentTokens(name: WrColor, seed: string): Record<string, string> {\n const rgb = parseHex(seed);\n if (!rgb) return {};\n\n const out: Record<string, string> = {\n [`--wr-color-${name}`]: toHex(rgb),\n [`--wr-color-${name}-rgb`]: `${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)}`,\n [`--wr-color-${name}-contrast`]: wrContrastFor(toHex(rgb)),\n };\n for (const [suffix, delta] of SHADES) {\n out[`--wr-color-${name}-${suffix}`] = toHex(adjustLightness(rgb, delta));\n }\n return out;\n}\n\n/**\n * Every token a theme preset needs, for as many intents as it re-tunes.\n *\n * Intents left out keep the values the stylesheet compiled, so a preset that\n * only moves `primary` writes seven declarations and nothing else.\n */\nfunction wrThemeTokens(seeds: Partial<Record<WrColor, string>>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of WR_COLORS) {\n const seed = seeds[name];\n if (seed) Object.assign(out, wrIntentTokens(name, seed));\n }\n return out;\n}\n\nexport { wrContrastFor, wrIntentTokens, wrThemeTokens };\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\n/** Mode the user explicitly selected. `auto` follows `prefers-color-scheme`. */\nexport type WrThemeMode = 'light' | 'dark' | 'auto';\n\n/** Resolved theme — what's actually applied to the DOM. */\nexport type WrResolvedTheme = 'light' | 'dark';\n\n/** Configuration for {@link WrTheme}. */\nexport interface WrThemeConfig {\n /** Initial mode used when no persisted value is present. @default 'auto' */\n readonly defaultMode: WrThemeMode;\n /** localStorage key for persistence. Set to `null` to disable persistence. @default 'wr-theme' */\n readonly storageKey: string | null;\n /** Attribute applied to `<html>` to expose the resolved theme. @default 'data-theme' */\n readonly attribute: string;\n}\n\nexport const DEFAULT_WR_THEME_CONFIG: WrThemeConfig = {\n defaultMode: 'auto',\n storageKey: 'wr-theme',\n attribute: 'data-theme',\n};\n\nexport const WR_THEME_CONFIG = new InjectionToken<WrThemeConfig>('WR_THEME_CONFIG', {\n factory: (): WrThemeConfig => DEFAULT_WR_THEME_CONFIG,\n});\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { DOCUMENT } from '@angular/common';\nimport { Service, type Signal, computed, effect, inject, signal } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrStorage } from 'ngwr/storage';\n\nimport { WR_THEME_CONFIG, type WrResolvedTheme, type WrThemeMode } from '../wr-theme-config';\n\nconst VALID_MODES: readonly WrThemeMode[] = ['light', 'dark', 'auto'];\n\nfunction isThemeMode(v: unknown): v is WrThemeMode {\n return typeof v === 'string' && (VALID_MODES as readonly string[]).includes(v);\n}\n\n/**\n * Light / dark theme manager.\n *\n * - `mode` is what the user picked (`light` | `dark` | `auto`).\n * - `resolved` is what's actually applied — `auto` resolves through\n * {@link WrPlatform.prefersDark}.\n * - Writes `[data-theme]` (or your configured attribute) on `<html>`.\n * - Persists the selected mode via {@link WrStorage} (defaults to\n * `localStorage`; swap the storage engine globally to change where).\n *\n * @example\n * ```ts\n * const theme = inject(WrTheme);\n * theme.set('dark');\n * theme.toggle();\n * theme.resolved(); // 'dark'\n * ```\n *\n * @see https://ngwr.dev/reference/services/theme\n */\n@Service()\nexport class WrTheme {\n private readonly doc = inject(DOCUMENT);\n private readonly platform = inject(WrPlatform);\n private readonly storage = inject(WrStorage);\n private readonly config = inject(WR_THEME_CONFIG);\n\n /** User-selected mode. */\n readonly mode = signal<WrThemeMode>(this.readPersisted() ?? this.config.defaultMode);\n\n /** Resolved theme actually applied to the DOM. */\n readonly resolved: Signal<WrResolvedTheme> = computed(() => {\n const m = this.mode();\n if (m === 'auto') return this.platform.prefersDark() ? 'dark' : 'light';\n return m;\n });\n\n constructor() {\n // Whenever the resolved theme changes, mirror it to the DOM + persist mode.\n effect(() => {\n const value = this.resolved();\n const html = this.doc.documentElement;\n if (html) html.setAttribute(this.config.attribute, value);\n this.persist(this.mode());\n });\n }\n\n /** Switch to a specific mode. */\n set(mode: WrThemeMode): void {\n this.mode.set(mode);\n }\n\n /** Cycle: light → dark → light (skips auto). */\n toggle(): void {\n this.mode.set(this.resolved() === 'dark' ? 'light' : 'dark');\n }\n\n // Persistence\n\n private readPersisted(): WrThemeMode | null {\n if (!this.config.storageKey) return null;\n const raw = this.storage.get<WrThemeMode>(this.config.storageKey);\n return isThemeMode(raw) ? raw : null;\n }\n\n private persist(mode: WrThemeMode): void {\n if (!this.config.storageKey) return;\n this.storage.set(this.config.storageKey, mode);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n type EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n} from '@angular/core';\n\nimport { WrTheme } from './services/wr-theme';\nimport { DEFAULT_WR_THEME_CONFIG, WR_THEME_CONFIG, type WrThemeConfig } from './wr-theme-config';\n\n/**\n * Configure and activate {@link WrTheme}. Config is partial — merged with\n * defaults.\n *\n * Calling this is all it takes: the initializer instantiates {@link WrTheme},\n * whose constructor effect mirrors the resolved theme onto `<html>`. Without\n * that, the service (a tree-shakable `@Service()`) would only be created once\n * something injected it, so `[data-theme]` was never written and an app that\n * followed the docs silently ignored `defaultMode: 'auto'`.\n *\n * @example\n * ```ts\n * provideWrTheme({ defaultMode: 'dark', storageKey: 'my-app-theme' })\n * ```\n */\nexport function provideWrTheme(config: Partial<WrThemeConfig> = {}): EnvironmentProviders {\n const merged: WrThemeConfig = { ...DEFAULT_WR_THEME_CONFIG, ...config };\n return makeEnvironmentProviders([\n { provide: WR_THEME_CONFIG, useValue: merged },\n provideEnvironmentInitializer(() => void inject(WrTheme)),\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
/** The overlay `[glare]` appends — `aria-hidden`, and the only node the directive adds. */
|
|
10
|
+
const GLARE = '.wr-tilt-glare';
|
|
11
|
+
/** Pull one `name(value)` out of a transform string. */
|
|
12
|
+
function fromTransform(transform, fn) {
|
|
13
|
+
const match = new RegExp(`${fn}\\(([-\\d.]+)`).exec(transform);
|
|
14
|
+
return match ? Number.parseFloat(match[1]) : null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Test harness for `[wrTilt]` and for `<wr-tilt-card>`, which applies it as a host
|
|
18
|
+
* directive — one class, because they are one behaviour on two hosts.
|
|
19
|
+
*
|
|
20
|
+
* **The tilt itself needs a box, and a unit test has none.** The directive turns a
|
|
21
|
+
* pointer position into two rotations by dividing by `getBoundingClientRect()`, which
|
|
22
|
+
* is 0×0 in jsdom, so a move dispatched at a component that has not been given
|
|
23
|
+
* dimensions writes `rotateX(NaNdeg)`. {@link movePointerTo} is still here because the
|
|
24
|
+
* fix is one line in the SPEC — stub the host's rect, the way
|
|
25
|
+
* `projects/lib/image-cropper/testing` documents — and because the two things worth
|
|
26
|
+
* asserting on this component are only reachable through it: that a move writes a
|
|
27
|
+
* transform at all, and that {@link leave} takes it away again.
|
|
28
|
+
*
|
|
29
|
+
* **{@link isFlat} is the reduced-motion assertion.** `onMove` returns before touching
|
|
30
|
+
* the host when the user asked for less motion, so "no inline transform after a move"
|
|
31
|
+
* is the whole contract, and it is one of the few reduced-motion behaviours in the
|
|
32
|
+
* animation set that a test can actually see — most of the others live in a `@media`
|
|
33
|
+
* block. Provide the `WrPlatform` stub and assert this.
|
|
34
|
+
*
|
|
35
|
+
* There is no `getGlareOpacity()` or `isGlareVisible()`: the overlay's gradient, its
|
|
36
|
+
* blend mode and its fade all live in the stylesheet. And no `getMaxTilt()` /
|
|
37
|
+
* `getPerspectiveInput()` — those would echo an input back rather than report what the
|
|
38
|
+
* directive wrote; {@link getPerspective} and {@link getScale} read the transform the
|
|
39
|
+
* host is actually carrying, which is the value that can be wrong.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const card = await loader.getHarness(WrTiltHarness);
|
|
44
|
+
* expect(await card.isFlat()).toBe(true);
|
|
45
|
+
*
|
|
46
|
+
* await card.movePointerTo(150, 50); // the spec stubbed the rect first
|
|
47
|
+
* expect((await card.getTilt())?.rotateY).toBeGreaterThan(0);
|
|
48
|
+
*
|
|
49
|
+
* await card.leave();
|
|
50
|
+
* expect(await card.isFlat()).toBe(true);
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @see https://ngwr.dev/guides/testing
|
|
54
|
+
*/
|
|
55
|
+
class WrTiltHarness extends ComponentHarness {
|
|
56
|
+
/** The directive on a consumer's element, and the card that hosts it. */
|
|
57
|
+
static hostSelector = '[wrTilt], wr-tilt-card';
|
|
58
|
+
/** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
|
|
59
|
+
static with(options = {}) {
|
|
60
|
+
return new HarnessPredicate(WrTiltHarness, options)
|
|
61
|
+
.addOption('glare', options.glare, async (harness, glare) => (await harness.hasGlare()) === glare)
|
|
62
|
+
.addOption('flat', options.flat, async (harness, flat) => (await harness.isFlat()) === flat);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether the host is at rest — no inline transform at all.
|
|
66
|
+
*
|
|
67
|
+
* True before the first move, after {@link leave}, and for the whole life of a card
|
|
68
|
+
* whose user asked for less motion. It is the difference between "flat" and
|
|
69
|
+
* "rotated by zero": the directive CLEARS the property rather than writing an
|
|
70
|
+
* identity transform, so an empty string is the state and `rotateX(0deg)` would be
|
|
71
|
+
* a component that tilted and came back the long way.
|
|
72
|
+
*/
|
|
73
|
+
async isFlat() {
|
|
74
|
+
return (await this.getTransform()) === '';
|
|
75
|
+
}
|
|
76
|
+
/** The inline `transform` exactly as written, or `''` at rest. */
|
|
77
|
+
async getTransform() {
|
|
78
|
+
return this.inlineStyle('transform');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The two rotations the host is holding, in degrees, or `null` while it is flat.
|
|
82
|
+
*
|
|
83
|
+
* Parsed from the transform rather than recomputed: the harness has no more access
|
|
84
|
+
* to the pointer maths than a consumer does, and the string is what the compositor
|
|
85
|
+
* will read.
|
|
86
|
+
*/
|
|
87
|
+
async getTilt() {
|
|
88
|
+
const transform = await this.getTransform();
|
|
89
|
+
if (transform === '')
|
|
90
|
+
return null;
|
|
91
|
+
const rotateX = fromTransform(transform, 'rotateX');
|
|
92
|
+
const rotateY = fromTransform(transform, 'rotateY');
|
|
93
|
+
return rotateX === null || rotateY === null ? null : { rotateX, rotateY };
|
|
94
|
+
}
|
|
95
|
+
/** The perspective the transform was written with, in pixels, or `null` while flat. */
|
|
96
|
+
async getPerspective() {
|
|
97
|
+
return fromTransform(await this.getTransform(), 'perspective');
|
|
98
|
+
}
|
|
99
|
+
/** The scale the transform was written with, or `null` while flat. */
|
|
100
|
+
async getScale() {
|
|
101
|
+
return fromTransform(await this.getTransform(), 'scale');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether the host was set up to hold children in 3-D.
|
|
105
|
+
*
|
|
106
|
+
* Written once in the constructor, so it is true from first render and stays true —
|
|
107
|
+
* which makes it the cheapest evidence that the directive is applied at all, on a
|
|
108
|
+
* host where every other read is `''` until something moves.
|
|
109
|
+
*/
|
|
110
|
+
async isPreserve3d() {
|
|
111
|
+
return (await this.inlineStyle('transform-style')) === 'preserve-3d';
|
|
112
|
+
}
|
|
113
|
+
/** Whether the glare overlay is installed (`[glare]`). */
|
|
114
|
+
async hasGlare() {
|
|
115
|
+
return (await this.locatorForOptional(GLARE)()) !== null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether the glare is kept out of the accessibility tree.
|
|
119
|
+
*
|
|
120
|
+
* It is a decorative highlight with no text, so a screen reader must not stop on
|
|
121
|
+
* it. Asserted rather than assumed because the overlay is created imperatively —
|
|
122
|
+
* there is no template line for a reader to check.
|
|
123
|
+
*/
|
|
124
|
+
async isGlareDecorative() {
|
|
125
|
+
const glare = await this.locatorForOptional(GLARE)();
|
|
126
|
+
return glare !== null && (await glare.getAttribute('aria-hidden')) === 'true';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Where the glare is pointing, as the two percentages the directive writes, or
|
|
130
|
+
* `null` when there is no glare or nothing has moved yet.
|
|
131
|
+
*
|
|
132
|
+
* Cleared when the overlay is removed: a card that has given `[glare]` back should
|
|
133
|
+
* not look like it is still tracking a pointer.
|
|
134
|
+
*/
|
|
135
|
+
async getGlarePosition() {
|
|
136
|
+
const x = await this.inlineStyle('--wr-tilt-glare-x');
|
|
137
|
+
const y = await this.inlineStyle('--wr-tilt-glare-y');
|
|
138
|
+
return x === '' || y === '' ? null : { x, y };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Move a pointer over the host.
|
|
142
|
+
*
|
|
143
|
+
* **Stub the host's rect in your spec first.** The directive divides the pointer
|
|
144
|
+
* offset by the host's width and height, and jsdom measures every element at 0×0,
|
|
145
|
+
* so without a box the transform comes out as `NaN` — which is not a failure the
|
|
146
|
+
* component would ever produce in a browser. One line does it:
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* host.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
async movePointerTo(clientX, clientY) {
|
|
153
|
+
await (await this.host()).dispatchEvent('pointermove', { clientX, clientY });
|
|
154
|
+
}
|
|
155
|
+
/** Take the pointer off the host — which clears the transform and settles it flat. */
|
|
156
|
+
async leave() {
|
|
157
|
+
await (await this.host()).dispatchEvent('pointerleave');
|
|
158
|
+
}
|
|
159
|
+
/** The projected content's text, trimmed. */
|
|
160
|
+
async getContentText() {
|
|
161
|
+
return (await this.host()).text();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* One inline declaration, read off the `style` ATTRIBUTE.
|
|
165
|
+
*
|
|
166
|
+
* Not `getCssValue`, which is `getComputedStyle`: that resolves the stylesheet as
|
|
167
|
+
* well, so `transform` would come back as the resolved matrix in a browser and
|
|
168
|
+
* `--wr-tilt-glare-x` would answer with the sheet's own default. The attribute holds
|
|
169
|
+
* what the directive wrote and nothing else, which is the only thing worth pinning.
|
|
170
|
+
*/
|
|
171
|
+
async inlineStyle(property) {
|
|
172
|
+
const style = (await (await this.host()).getAttribute('style')) ?? '';
|
|
173
|
+
for (const declaration of style.split(';')) {
|
|
174
|
+
const colon = declaration.indexOf(':');
|
|
175
|
+
if (colon === -1)
|
|
176
|
+
continue;
|
|
177
|
+
if (declaration.slice(0, colon).trim() !== property)
|
|
178
|
+
continue;
|
|
179
|
+
return declaration.slice(colon + 1).trim();
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Generated bundle index. Do not edit.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export { WrTiltHarness };
|
|
190
|
+
//# sourceMappingURL=ngwr-tilt-card-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-tilt-card-testing.mjs","sources":["../../../projects/lib/tilt-card/testing/wr-tilt-harness.ts","../../../projects/lib/tilt-card/testing/ngwr-tilt-card-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 { WrTiltHarnessFilters, WrTiltRotation } from './interfaces';\n\n/** The overlay `[glare]` appends — `aria-hidden`, and the only node the directive adds. */\nconst GLARE = '.wr-tilt-glare';\n\n/** Pull one `name(value)` out of a transform string. */\nfunction fromTransform(transform: string, fn: string): number | null {\n const match = new RegExp(`${fn}\\\\(([-\\\\d.]+)`).exec(transform);\n return match ? Number.parseFloat(match[1]) : null;\n}\n\n/**\n * Test harness for `[wrTilt]` and for `<wr-tilt-card>`, which applies it as a host\n * directive — one class, because they are one behaviour on two hosts.\n *\n * **The tilt itself needs a box, and a unit test has none.** The directive turns a\n * pointer position into two rotations by dividing by `getBoundingClientRect()`, which\n * is 0×0 in jsdom, so a move dispatched at a component that has not been given\n * dimensions writes `rotateX(NaNdeg)`. {@link movePointerTo} is still here because the\n * fix is one line in the SPEC — stub the host's rect, the way\n * `projects/lib/image-cropper/testing` documents — and because the two things worth\n * asserting on this component are only reachable through it: that a move writes a\n * transform at all, and that {@link leave} takes it away again.\n *\n * **{@link isFlat} is the reduced-motion assertion.** `onMove` returns before touching\n * the host when the user asked for less motion, so \"no inline transform after a move\"\n * is the whole contract, and it is one of the few reduced-motion behaviours in the\n * animation set that a test can actually see — most of the others live in a `@media`\n * block. Provide the `WrPlatform` stub and assert this.\n *\n * There is no `getGlareOpacity()` or `isGlareVisible()`: the overlay's gradient, its\n * blend mode and its fade all live in the stylesheet. And no `getMaxTilt()` /\n * `getPerspectiveInput()` — those would echo an input back rather than report what the\n * directive wrote; {@link getPerspective} and {@link getScale} read the transform the\n * host is actually carrying, which is the value that can be wrong.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrTiltHarness);\n * expect(await card.isFlat()).toBe(true);\n *\n * await card.movePointerTo(150, 50); // the spec stubbed the rect first\n * expect((await card.getTilt())?.rotateY).toBeGreaterThan(0);\n *\n * await card.leave();\n * expect(await card.isFlat()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTiltHarness extends ComponentHarness {\n /** The directive on a consumer's element, and the card that hosts it. */\n static hostSelector = '[wrTilt], wr-tilt-card';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTiltHarnessFilters = {}): HarnessPredicate<WrTiltHarness> {\n return new HarnessPredicate(WrTiltHarness, options)\n .addOption('glare', options.glare, async (harness, glare) => (await harness.hasGlare()) === glare)\n .addOption('flat', options.flat, async (harness, flat) => (await harness.isFlat()) === flat);\n }\n\n /**\n * Whether the host is at rest — no inline transform at all.\n *\n * True before the first move, after {@link leave}, and for the whole life of a card\n * whose user asked for less motion. It is the difference between \"flat\" and\n * \"rotated by zero\": the directive CLEARS the property rather than writing an\n * identity transform, so an empty string is the state and `rotateX(0deg)` would be\n * a component that tilted and came back the long way.\n */\n async isFlat(): Promise<boolean> {\n return (await this.getTransform()) === '';\n }\n\n /** The inline `transform` exactly as written, or `''` at rest. */\n async getTransform(): Promise<string> {\n return this.inlineStyle('transform');\n }\n\n /**\n * The two rotations the host is holding, in degrees, or `null` while it is flat.\n *\n * Parsed from the transform rather than recomputed: the harness has no more access\n * to the pointer maths than a consumer does, and the string is what the compositor\n * will read.\n */\n async getTilt(): Promise<WrTiltRotation | null> {\n const transform = await this.getTransform();\n if (transform === '') return null;\n\n const rotateX = fromTransform(transform, 'rotateX');\n const rotateY = fromTransform(transform, 'rotateY');\n return rotateX === null || rotateY === null ? null : { rotateX, rotateY };\n }\n\n /** The perspective the transform was written with, in pixels, or `null` while flat. */\n async getPerspective(): Promise<number | null> {\n return fromTransform(await this.getTransform(), 'perspective');\n }\n\n /** The scale the transform was written with, or `null` while flat. */\n async getScale(): Promise<number | null> {\n return fromTransform(await this.getTransform(), 'scale');\n }\n\n /**\n * Whether the host was set up to hold children in 3-D.\n *\n * Written once in the constructor, so it is true from first render and stays true —\n * which makes it the cheapest evidence that the directive is applied at all, on a\n * host where every other read is `''` until something moves.\n */\n async isPreserve3d(): Promise<boolean> {\n return (await this.inlineStyle('transform-style')) === 'preserve-3d';\n }\n\n /** Whether the glare overlay is installed (`[glare]`). */\n async hasGlare(): Promise<boolean> {\n return (await this.locatorForOptional(GLARE)()) !== null;\n }\n\n /**\n * Whether the glare is kept out of the accessibility tree.\n *\n * It is a decorative highlight with no text, so a screen reader must not stop on\n * it. Asserted rather than assumed because the overlay is created imperatively —\n * there is no template line for a reader to check.\n */\n async isGlareDecorative(): Promise<boolean> {\n const glare = await this.locatorForOptional(GLARE)();\n return glare !== null && (await glare.getAttribute('aria-hidden')) === 'true';\n }\n\n /**\n * Where the glare is pointing, as the two percentages the directive writes, or\n * `null` when there is no glare or nothing has moved yet.\n *\n * Cleared when the overlay is removed: a card that has given `[glare]` back should\n * not look like it is still tracking a pointer.\n */\n async getGlarePosition(): Promise<{ x: string; y: string } | null> {\n const x = await this.inlineStyle('--wr-tilt-glare-x');\n const y = await this.inlineStyle('--wr-tilt-glare-y');\n return x === '' || y === '' ? null : { x, y };\n }\n\n /**\n * Move a pointer over the host.\n *\n * **Stub the host's rect in your spec first.** The directive divides the pointer\n * offset by the host's width and height, and jsdom measures every element at 0×0,\n * so without a box the transform comes out as `NaN` — which is not a failure the\n * component would ever produce in a browser. One line does it:\n *\n * ```ts\n * host.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n await (await this.host()).dispatchEvent('pointermove', { clientX, clientY });\n }\n\n /** Take the pointer off the host — which clears the transform and settles it flat. */\n async leave(): Promise<void> {\n await (await this.host()).dispatchEvent('pointerleave');\n }\n\n /** The projected content's text, trimmed. */\n async getContentText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /**\n * One inline declaration, read off the `style` ATTRIBUTE.\n *\n * Not `getCssValue`, which is `getComputedStyle`: that resolves the stylesheet as\n * well, so `transform` would come back as the resolved matrix in a browser and\n * `--wr-tilt-glare-x` would answer with the sheet's own default. The attribute holds\n * what the directive wrote and nothing else, which is the only thing worth pinning.\n */\n private async inlineStyle(property: string): Promise<string> {\n const style = (await (await this.host()).getAttribute('style')) ?? '';\n\n for (const declaration of style.split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n if (declaration.slice(0, colon).trim() !== property) continue;\n return declaration.slice(colon + 1).trim();\n }\n return '';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|