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
|
@@ -93,7 +93,10 @@ class WrSidebar {
|
|
|
93
93
|
uid = ++sidebarUid;
|
|
94
94
|
router = inject(Router);
|
|
95
95
|
currentUrl = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map(e => e.urlAfterRedirects), startWith(this.router.url)), { initialValue: this.router.url });
|
|
96
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Last URL we resolved to a group — prevents re-opening one the user collapsed.
|
|
98
|
+
* A URL no entry claims is deliberately NOT recorded: see the effect below.
|
|
99
|
+
*/
|
|
97
100
|
lastAutoUrl = null;
|
|
98
101
|
constructor() {
|
|
99
102
|
// Seed `defaultOpen` groups — once per group, not once per `entries` emission.
|
|
@@ -118,9 +121,19 @@ class WrSidebar {
|
|
|
118
121
|
const url = this.currentUrl();
|
|
119
122
|
if (url === this.lastAutoUrl)
|
|
120
123
|
return;
|
|
121
|
-
|
|
124
|
+
// Latch AFTER the lookup, never before it. `entries` often arrives from an
|
|
125
|
+
// HTTP call, so the first run matches nothing — and latching first told the
|
|
126
|
+
// re-run that landed with the real nav that this URL was already handled.
|
|
127
|
+
// A deep link into a `defaultOpen: false` group then stayed collapsed, with
|
|
128
|
+
// the active link out of the tab order, until the user navigated away and
|
|
129
|
+
// back. Only a run that found a group has actually handled the URL; the
|
|
130
|
+
// user-collapse latch is unaffected, since that run is also the one that
|
|
131
|
+
// records it.
|
|
122
132
|
const match = this.findGroupForUrl(url);
|
|
123
|
-
if (!match
|
|
133
|
+
if (!match)
|
|
134
|
+
return;
|
|
135
|
+
this.lastAutoUrl = url;
|
|
136
|
+
if (this.opened().has(match))
|
|
124
137
|
return;
|
|
125
138
|
this.opened.update(prev => {
|
|
126
139
|
const next = new Set(prev);
|
|
@@ -134,16 +147,18 @@ class WrSidebar {
|
|
|
134
147
|
}
|
|
135
148
|
/**
|
|
136
149
|
* Stable id for the group body, referenced by the toggle's `aria-controls`.
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
150
|
+
*
|
|
151
|
+
* Keyed by POSITION, not by the title. Slugging the title kept the id readable
|
|
152
|
+
* but was not injective: it lowercases and collapses every non-`[a-z0-9]` run,
|
|
153
|
+
* so `Data & charts` and `Data / charts` produced the same string, and a title
|
|
154
|
+
* with no ASCII alphanumerics at all — `Компоненты`, `数据` — produced the empty
|
|
155
|
+
* one. A whole Russian or Chinese sidebar therefore emitted N identical ids and
|
|
156
|
+
* every toggle's `aria-controls` resolved to the FIRST group's body. The toggle
|
|
157
|
+
* and its body read `$index` from the same `@for` iteration, so the two always
|
|
158
|
+
* name each other — reordering `entries` renumbers both together.
|
|
140
159
|
*/
|
|
141
|
-
groupBodyId(
|
|
142
|
-
|
|
143
|
-
.toLowerCase()
|
|
144
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
145
|
-
.replace(/^-|-$/g, '');
|
|
146
|
-
return `wr-sidebar-${this.uid}-group-${slug}`;
|
|
160
|
+
groupBodyId(index) {
|
|
161
|
+
return `wr-sidebar-${this.uid}-group-${index}`;
|
|
147
162
|
}
|
|
148
163
|
toggleGroup(title) {
|
|
149
164
|
this.opened.update(prev => {
|
|
@@ -171,12 +186,12 @@ class WrSidebar {
|
|
|
171
186
|
}
|
|
172
187
|
return null;
|
|
173
188
|
}
|
|
174
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
175
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
189
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
190
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSidebar, isStandalone: true, selector: "wr-sidebar", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, defaultGroupIcon: { classPropertyName: "defaultGroupIcon", publicName: "defaultGroupIcon", isSignal: true, isRequired: false, transformFunction: null }, defaultItemIcon: { classPropertyName: "defaultItemIcon", publicName: "defaultItemIcon", isSignal: true, isRequired: false, transformFunction: null }, autoExpand: { classPropertyName: "autoExpand", publicName: "autoExpand", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "resolvedAriaLabel()" }, classAttribute: "wr-sidebar" }, ngImport: i0, template: "<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <!--\n A span, not a greyed anchor, exactly as the nested branch below does it.\n `--disabled` only paints: `pointer-events: none` stops a real pointer but\n not the keyboard, so a disabled entry stayed in the tab order and Enter\n navigated \u2014 `disabled` on a `WrSidebarItem` is documented as \"no\n navigation\". An empty `url` is the same case: `[routerLink]=\"[]\"` resolves\n to the current route, which is a link that goes nowhere.\n -->\n @if (asItem(entry).disabled || !asItem(entry).url.length) {\n <span class=\"wr-sidebar__entry wr-sidebar__entry--disabled\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__entry\" routerLinkActive=\"wr-sidebar__entry--active\" [routerLink]=\"asItem(entry).url\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n }\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId($index)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron \u2014 not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId($index)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
176
191
|
}
|
|
177
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
192
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSidebar, decorators: [{
|
|
178
193
|
type: Component,
|
|
179
|
-
args: [{ selector: 'wr-sidebar', encapsulation: ViewEncapsulation.None, host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' }, imports: [RouterLink, RouterLinkActive, WrIcon], template: "<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n
|
|
194
|
+
args: [{ selector: 'wr-sidebar', encapsulation: ViewEncapsulation.None, host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' }, imports: [RouterLink, RouterLinkActive, WrIcon], template: "<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <!--\n A span, not a greyed anchor, exactly as the nested branch below does it.\n `--disabled` only paints: `pointer-events: none` stops a real pointer but\n not the keyboard, so a disabled entry stayed in the tab order and Enter\n navigated \u2014 `disabled` on a `WrSidebarItem` is documented as \"no\n navigation\". An empty `url` is the same case: `[routerLink]=\"[]\"` resolves\n to the current route, which is a link that goes nowhere.\n -->\n @if (asItem(entry).disabled || !asItem(entry).url.length) {\n <span class=\"wr-sidebar__entry wr-sidebar__entry--disabled\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__entry\" routerLinkActive=\"wr-sidebar__entry--active\" [routerLink]=\"asItem(entry).url\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n }\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId($index)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron \u2014 not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId($index)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n" }]
|
|
180
195
|
}], ctorParameters: () => [], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], defaultGroupIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultGroupIcon", required: false }] }], defaultItemIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultItemIcon", required: false }] }], autoExpand: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoExpand", required: false }] }] } });
|
|
181
196
|
|
|
182
197
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-sidebar.mjs","sources":["../../../projects/lib/sidebar/sidebar.ts","../../../projects/lib/sidebar/sidebar.html","../../../projects/lib/sidebar/ngwr-sidebar.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 } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, effect, inject, input, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSidebarEntry, WrSidebarGroup, WrSidebarItem } from './interfaces';\n\nfunction isGroup(entry: WrSidebarEntry): entry is WrSidebarGroup {\n return 'children' in entry;\n}\n\n/**\n * Instance counter for the group-body ids. Deterministic under prerender, unlike\n * `randomId()`, and unique across sidebars — a title-only id collided between two\n * sidebars on one page, and `aria-controls` then pointed at the wrong body.\n */\nlet sidebarUid = 0;\n\n/**\n * A `routerLink` segment array as an absolute path.\n *\n * The documented form carries the leading slash on the FIRST segment\n * (`['/settings', 'profile']`), and prefixing another one produced\n * `//settings/profile` — a path no URL ever equals, which is why auto-expand\n * never fired for anyone following the example. Both forms normalise here.\n */\nfunction routePath(segments: readonly (string | number)[]): string {\n return `/${segments.join('/')}`.replace(/\\/{2,}/g, '/');\n}\n\n/**\n * Whether `url` is `route` or something below it. A bare `startsWith` matched a\n * SIBLING whose path merely began the same way, so visiting `/table` expanded the\n * group that owns `/tabs`.\n */\nfunction matchesRoute(url: string, route: string): boolean {\n if (!url.startsWith(route)) return false;\n const rest = url.slice(route.length);\n return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');\n}\n\n/**\n * Data-driven sidebar nav. Pass `[entries]` — either flat\n * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal\n * child items). Active route auto-expands its containing group.\n *\n * @example\n * ```html\n * <wr-sidebar [entries]=\"[\n * { title: 'Dashboard', icon: 'home', url: ['/dashboard'] },\n * {\n * title: 'Settings',\n * icon: 'cog',\n * children: [\n * { title: 'Profile', url: ['/settings', 'profile'] },\n * { title: 'Billing', url: ['/settings', 'billing'] }\n * ]\n * }\n * ]\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sidebar\n */\n@Component({\n selector: 'wr-sidebar',\n templateUrl: './sidebar.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n imports: [RouterLink, RouterLinkActive, WrIcon],\n})\nexport class WrSidebar {\n readonly entries = input<readonly WrSidebarEntry[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `sidebar.label`,\n * then `'Sidebar'` — a page with a sidebar AND a table of contents has two\n * navigation landmarks, and an unnamed one is announced as just \"navigation\".\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'sidebar.label', 'Sidebar');\n\n /** Default icon for entries without one. @default 'folder' */\n readonly defaultGroupIcon = input<string>('folder');\n\n /** Default icon for items without one. @default 'caret-forward' */\n readonly defaultItemIcon = input<string>('caret-forward');\n\n /** Auto-expand the group containing the active route. @default true */\n readonly autoExpand = input(true, { transform: coerceBooleanProperty });\n\n protected readonly isGroup = isGroup;\n\n private readonly opened = signal<ReadonlySet<string>>(new Set());\n\n /** Group titles already seeded from `defaultOpen`, so it seeds once and not again. */\n private readonly seeded = new Set<string>();\n\n private readonly uid = ++sidebarUid;\n\n private readonly router = inject(Router);\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n /** Last URL we auto-expanded — prevents re-opening a group the user collapsed. */\n private lastAutoUrl: string | null = null;\n\n constructor() {\n // Seed `defaultOpen` groups — once per group, not once per `entries` emission.\n // Re-seeding meant any later change to the array (a badge count, a filtered\n // list) re-opened a group the user had deliberately collapsed.\n effect(() => {\n const fresh: string[] = [];\n for (const entry of this.entries()) {\n if (isGroup(entry) && entry.defaultOpen && !this.seeded.has(entry.title)) {\n this.seeded.add(entry.title);\n fresh.push(entry.title);\n }\n }\n if (fresh.length > 0) {\n this.opened.update(prev => new Set([...prev, ...fresh]));\n }\n });\n\n // Auto-expand the group containing the current URL.\n effect(() => {\n if (!this.autoExpand()) return;\n const url = this.currentUrl();\n if (url === this.lastAutoUrl) return;\n this.lastAutoUrl = url;\n const match = this.findGroupForUrl(url);\n if (!match || this.opened().has(match)) return;\n this.opened.update(prev => {\n const next = new Set(prev);\n next.add(match);\n return next;\n });\n });\n }\n\n protected isOpen(title: string): boolean {\n return this.opened().has(title);\n }\n\n /**\n * Stable id for the group body, referenced by the toggle's `aria-controls`.\n * Scoped to this sidebar and stripped of anything that is not id-safe: a title\n * like `Data & charts` used to produce `wr-sidebar-group-data-&-charts`, which\n * no selector can address.\n */\n protected groupBodyId(title: string): string {\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n return `wr-sidebar-${this.uid}-group-${slug}`;\n }\n\n protected toggleGroup(title: string): void {\n this.opened.update(prev => {\n const next = new Set(prev);\n if (next.has(title)) next.delete(title);\n else next.add(title);\n return next;\n });\n }\n\n protected asItem(entry: WrSidebarEntry): WrSidebarItem {\n return entry as WrSidebarItem;\n }\n\n protected asGroup(entry: WrSidebarEntry): WrSidebarGroup {\n return entry as WrSidebarGroup;\n }\n\n private findGroupForUrl(url: string): string | null {\n for (const entry of this.entries()) {\n if (!isGroup(entry)) continue;\n const hit = entry.children.some(child => matchesRoute(url, routePath(child.url)));\n if (hit) return entry.title;\n }\n return null;\n }\n}\n","<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <a\n class=\"wr-sidebar__entry\"\n routerLinkActive=\"wr-sidebar__entry--active\"\n [routerLink]=\"asItem(entry).url\"\n [class.wr-sidebar__entry--disabled]=\"asItem(entry).disabled\"\n >\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId(entry.title)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron — not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId(entry.title)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-sidebar.mjs","sources":["../../../projects/lib/sidebar/sidebar.ts","../../../projects/lib/sidebar/sidebar.html","../../../projects/lib/sidebar/ngwr-sidebar.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 } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, effect, inject, input, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSidebarEntry, WrSidebarGroup, WrSidebarItem } from './interfaces';\n\nfunction isGroup(entry: WrSidebarEntry): entry is WrSidebarGroup {\n return 'children' in entry;\n}\n\n/**\n * Instance counter for the group-body ids. Deterministic under prerender, unlike\n * `randomId()`, and unique across sidebars — a title-only id collided between two\n * sidebars on one page, and `aria-controls` then pointed at the wrong body.\n */\nlet sidebarUid = 0;\n\n/**\n * A `routerLink` segment array as an absolute path.\n *\n * The documented form carries the leading slash on the FIRST segment\n * (`['/settings', 'profile']`), and prefixing another one produced\n * `//settings/profile` — a path no URL ever equals, which is why auto-expand\n * never fired for anyone following the example. Both forms normalise here.\n */\nfunction routePath(segments: readonly (string | number)[]): string {\n return `/${segments.join('/')}`.replace(/\\/{2,}/g, '/');\n}\n\n/**\n * Whether `url` is `route` or something below it. A bare `startsWith` matched a\n * SIBLING whose path merely began the same way, so visiting `/table` expanded the\n * group that owns `/tabs`.\n */\nfunction matchesRoute(url: string, route: string): boolean {\n if (!url.startsWith(route)) return false;\n const rest = url.slice(route.length);\n return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');\n}\n\n/**\n * Data-driven sidebar nav. Pass `[entries]` — either flat\n * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal\n * child items). Active route auto-expands its containing group.\n *\n * @example\n * ```html\n * <wr-sidebar [entries]=\"[\n * { title: 'Dashboard', icon: 'home', url: ['/dashboard'] },\n * {\n * title: 'Settings',\n * icon: 'cog',\n * children: [\n * { title: 'Profile', url: ['/settings', 'profile'] },\n * { title: 'Billing', url: ['/settings', 'billing'] }\n * ]\n * }\n * ]\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sidebar\n */\n@Component({\n selector: 'wr-sidebar',\n templateUrl: './sidebar.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n imports: [RouterLink, RouterLinkActive, WrIcon],\n})\nexport class WrSidebar {\n readonly entries = input<readonly WrSidebarEntry[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `sidebar.label`,\n * then `'Sidebar'` — a page with a sidebar AND a table of contents has two\n * navigation landmarks, and an unnamed one is announced as just \"navigation\".\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'sidebar.label', 'Sidebar');\n\n /** Default icon for entries without one. @default 'folder' */\n readonly defaultGroupIcon = input<string>('folder');\n\n /** Default icon for items without one. @default 'caret-forward' */\n readonly defaultItemIcon = input<string>('caret-forward');\n\n /** Auto-expand the group containing the active route. @default true */\n readonly autoExpand = input(true, { transform: coerceBooleanProperty });\n\n protected readonly isGroup = isGroup;\n\n private readonly opened = signal<ReadonlySet<string>>(new Set());\n\n /** Group titles already seeded from `defaultOpen`, so it seeds once and not again. */\n private readonly seeded = new Set<string>();\n\n private readonly uid = ++sidebarUid;\n\n private readonly router = inject(Router);\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n /**\n * Last URL we resolved to a group — prevents re-opening one the user collapsed.\n * A URL no entry claims is deliberately NOT recorded: see the effect below.\n */\n private lastAutoUrl: string | null = null;\n\n constructor() {\n // Seed `defaultOpen` groups — once per group, not once per `entries` emission.\n // Re-seeding meant any later change to the array (a badge count, a filtered\n // list) re-opened a group the user had deliberately collapsed.\n effect(() => {\n const fresh: string[] = [];\n for (const entry of this.entries()) {\n if (isGroup(entry) && entry.defaultOpen && !this.seeded.has(entry.title)) {\n this.seeded.add(entry.title);\n fresh.push(entry.title);\n }\n }\n if (fresh.length > 0) {\n this.opened.update(prev => new Set([...prev, ...fresh]));\n }\n });\n\n // Auto-expand the group containing the current URL.\n effect(() => {\n if (!this.autoExpand()) return;\n const url = this.currentUrl();\n if (url === this.lastAutoUrl) return;\n // Latch AFTER the lookup, never before it. `entries` often arrives from an\n // HTTP call, so the first run matches nothing — and latching first told the\n // re-run that landed with the real nav that this URL was already handled.\n // A deep link into a `defaultOpen: false` group then stayed collapsed, with\n // the active link out of the tab order, until the user navigated away and\n // back. Only a run that found a group has actually handled the URL; the\n // user-collapse latch is unaffected, since that run is also the one that\n // records it.\n const match = this.findGroupForUrl(url);\n if (!match) return;\n this.lastAutoUrl = url;\n if (this.opened().has(match)) return;\n this.opened.update(prev => {\n const next = new Set(prev);\n next.add(match);\n return next;\n });\n });\n }\n\n protected isOpen(title: string): boolean {\n return this.opened().has(title);\n }\n\n /**\n * Stable id for the group body, referenced by the toggle's `aria-controls`.\n *\n * Keyed by POSITION, not by the title. Slugging the title kept the id readable\n * but was not injective: it lowercases and collapses every non-`[a-z0-9]` run,\n * so `Data & charts` and `Data / charts` produced the same string, and a title\n * with no ASCII alphanumerics at all — `Компоненты`, `数据` — produced the empty\n * one. A whole Russian or Chinese sidebar therefore emitted N identical ids and\n * every toggle's `aria-controls` resolved to the FIRST group's body. The toggle\n * and its body read `$index` from the same `@for` iteration, so the two always\n * name each other — reordering `entries` renumbers both together.\n */\n protected groupBodyId(index: number): string {\n return `wr-sidebar-${this.uid}-group-${index}`;\n }\n\n protected toggleGroup(title: string): void {\n this.opened.update(prev => {\n const next = new Set(prev);\n if (next.has(title)) next.delete(title);\n else next.add(title);\n return next;\n });\n }\n\n protected asItem(entry: WrSidebarEntry): WrSidebarItem {\n return entry as WrSidebarItem;\n }\n\n protected asGroup(entry: WrSidebarEntry): WrSidebarGroup {\n return entry as WrSidebarGroup;\n }\n\n private findGroupForUrl(url: string): string | null {\n for (const entry of this.entries()) {\n if (!isGroup(entry)) continue;\n const hit = entry.children.some(child => matchesRoute(url, routePath(child.url)));\n if (hit) return entry.title;\n }\n return null;\n }\n}\n","<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <!--\n A span, not a greyed anchor, exactly as the nested branch below does it.\n `--disabled` only paints: `pointer-events: none` stops a real pointer but\n not the keyboard, so a disabled entry stayed in the tab order and Enter\n navigated — `disabled` on a `WrSidebarItem` is documented as \"no\n navigation\". An empty `url` is the same case: `[routerLink]=\"[]\"` resolves\n to the current route, which is a link that goes nowhere.\n -->\n @if (asItem(entry).disabled || !asItem(entry).url.length) {\n <span class=\"wr-sidebar__entry wr-sidebar__entry--disabled\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__entry\" routerLinkActive=\"wr-sidebar__entry--active\" [routerLink]=\"asItem(entry).url\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n }\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId($index)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron — not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId($index)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -49,10 +49,10 @@ class WrSkeleton {
|
|
|
49
49
|
return parts.join(' ');
|
|
50
50
|
}, /* @ts-ignore */
|
|
51
51
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
52
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
53
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.
|
|
52
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
53
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: WrSkeleton, isStandalone: true, selector: "wr-skeleton", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, animated: { classPropertyName: "animated", publicName: "animated", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-busy": "true", "aria-live": "polite" }, properties: { "class": "classes()" } }, ngImport: i0, template: '', isInline: true, encapsulation: i0.ViewEncapsulation.None });
|
|
54
54
|
}
|
|
55
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
55
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSkeleton, decorators: [{
|
|
56
56
|
type: Component,
|
|
57
57
|
args: [{
|
|
58
58
|
selector: 'wr-skeleton',
|
package/fesm2022/ngwr-slider.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Directionality } from '@angular/cdk/bidi';
|
|
2
2
|
import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { input, computed, model, output, signal, viewChild,
|
|
4
|
+
import { input, computed, inject, model, output, signal, viewChild, effect, untracked, ViewEncapsulation, Component } from '@angular/core';
|
|
5
|
+
import { WR_FORM_FIELD } from 'ngwr/form';
|
|
6
|
+
import { useI18nText } from 'ngwr/i18n';
|
|
5
7
|
import { clamp, round } from 'ngwr/utils';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -73,6 +75,69 @@ class WrSlider {
|
|
|
73
75
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
74
76
|
/** Render the current value below the track. @default true */
|
|
75
77
|
showLabel = input(true, { ...(ngDevMode ? { debugName: "showLabel" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
78
|
+
/**
|
|
79
|
+
* Accessible name of the thumb — the single one, or the LOWER one in range
|
|
80
|
+
* mode. Falls back to `slider.label` / `slider.lower`.
|
|
81
|
+
*/
|
|
82
|
+
ariaLabel = input(null, /* @ts-ignore */
|
|
83
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
84
|
+
/** Accessible name of the upper thumb in range mode. Falls back to `slider.upper`. */
|
|
85
|
+
upperLabel = input(null, /* @ts-ignore */
|
|
86
|
+
...(ngDevMode ? [{ debugName: "upperLabel" }] : /* istanbul ignore next */ []));
|
|
87
|
+
/**
|
|
88
|
+
* A thumb projects no text, so these ARE the accessible names — and they were
|
|
89
|
+
* hard-coded English with no key behind them, so every slider on a page
|
|
90
|
+
* announced the same untranslated "Value" whatever it controlled.
|
|
91
|
+
*
|
|
92
|
+
* Range mode reads `ariaLabel` as the LOWER end rather than as the whole
|
|
93
|
+
* control: a name given once must land on something, and naming both thumbs
|
|
94
|
+
* the same thing would read two different values under one name.
|
|
95
|
+
*/
|
|
96
|
+
singleLabel = useI18nText(this.ariaLabel, 'slider.label', 'Value');
|
|
97
|
+
lowerLabel = useI18nText(this.ariaLabel, 'slider.lower', 'Lower value');
|
|
98
|
+
resolvedUpperLabel = useI18nText(this.upperLabel, 'slider.upper', 'Upper value');
|
|
99
|
+
resolvedLowLabel = computed(() => (this.range() ? this.lowerLabel() : this.singleLabel()), /* @ts-ignore */
|
|
100
|
+
...(ngDevMode ? [{ debugName: "resolvedLowLabel" }] : /* istanbul ignore next */ []));
|
|
101
|
+
field = inject(WR_FORM_FIELD, { optional: true });
|
|
102
|
+
/**
|
|
103
|
+
* Id the surrounding `<wr-form-field>`'s `<label for>` points at, put on the
|
|
104
|
+
* single thumb — the LOWER one in range mode, the same end `ariaLabel` names.
|
|
105
|
+
*
|
|
106
|
+
* The field renders its label before it can see what was projected into it, so
|
|
107
|
+
* the id travels the other way and the control adopts it, exactly as
|
|
108
|
+
* `[wrInput]` does. Without this the `for` named an element that was nowhere
|
|
109
|
+
* in the document: clicking the label did nothing, and the field's own text
|
|
110
|
+
* reached the thumb through no path at all.
|
|
111
|
+
*
|
|
112
|
+
* It lands on the THUMB rather than on the host because only a labelable
|
|
113
|
+
* element can be a label's target, and `<wr-slider>` is not one — a `for`
|
|
114
|
+
* pointing at the host would resolve and still name nothing. The thumb is a
|
|
115
|
+
* real `<button>`, so it is.
|
|
116
|
+
*
|
|
117
|
+
* What this does NOT change is the thumb's name: an `aria-label` outranks a
|
|
118
|
+
* `<label>` in the accname order, and the thumb keeps its own because the
|
|
119
|
+
* field cannot promise it has one to give — `<wr-form-field>` renders a label
|
|
120
|
+
* only when `label` is set, and a slider that traded a generic name for no
|
|
121
|
+
* name would be the worse bug. Set `[ariaLabel]` to the field's label where
|
|
122
|
+
* the two should read alike.
|
|
123
|
+
*/
|
|
124
|
+
thumbId = computed(() => this.field?.controlId() ?? null, /* @ts-ignore */
|
|
125
|
+
...(ngDevMode ? [{ debugName: "thumbId" }] : /* istanbul ignore next */ []));
|
|
126
|
+
/**
|
|
127
|
+
* The field's error message, wired to the thumb the same way `wrInput` wires it
|
|
128
|
+
* to its native element.
|
|
129
|
+
*
|
|
130
|
+
* Without this the messages `<wr-form-field>` renders are visible and nothing
|
|
131
|
+
* else: a screen reader on the thumb never learns the field is invalid, nor
|
|
132
|
+
* what the message says. `aria-invalid` is keyed on the message EXISTING rather
|
|
133
|
+
* than on `errorKeys()`, because the field only publishes an id once it is
|
|
134
|
+
* showing something — announcing "invalid" while pointing at nothing is worse
|
|
135
|
+
* than staying quiet.
|
|
136
|
+
*/
|
|
137
|
+
describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
|
|
138
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
139
|
+
ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null), /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
|
|
76
141
|
/**
|
|
77
142
|
* Current value. Bound by `[formField]`, or two-way via `[(value)]`. Shape
|
|
78
143
|
* follows `range`: a plain `number`, or `[low, high]` in range mode.
|
|
@@ -83,8 +148,23 @@ class WrSlider {
|
|
|
83
148
|
touch = output();
|
|
84
149
|
low = signal(0, /* @ts-ignore */
|
|
85
150
|
...(ngDevMode ? [{ debugName: "low" }] : /* istanbul ignore next */ []));
|
|
151
|
+
/**
|
|
152
|
+
* The high thumb's cell. Its literal default stands in for `maxValue()`,
|
|
153
|
+
* which cannot be read at field-initialisation time — see `highSeeded` and
|
|
154
|
+
* the constructor effect.
|
|
155
|
+
*/
|
|
86
156
|
high = signal(100, /* @ts-ignore */
|
|
87
157
|
...(ngDevMode ? [{ debugName: "high" }] : /* istanbul ignore next */ []));
|
|
158
|
+
/**
|
|
159
|
+
* Whether `high` has ever been told a real value by the model.
|
|
160
|
+
*
|
|
161
|
+
* `<wr-slider range [min]="200" [max]="300" />` binds no value, so the model
|
|
162
|
+
* holds its own scalar default and the tuple branch below never runs: `high`
|
|
163
|
+
* kept the literal 100, which is not inside every `[min, max]`. The thumb
|
|
164
|
+
* rendered off the track and the first interaction committed `[low, 100]` —
|
|
165
|
+
* a value the slider itself calls out of range.
|
|
166
|
+
*/
|
|
167
|
+
highSeeded = false;
|
|
88
168
|
track = viewChild.required('track', /* @ts-ignore */
|
|
89
169
|
...(ngDevMode ? [{ debugName: "track" }] : /* istanbul ignore next */ []));
|
|
90
170
|
/**
|
|
@@ -121,16 +201,39 @@ class WrSlider {
|
|
|
121
201
|
constructor() {
|
|
122
202
|
// Keep the internal thumbs in sync with external writes to `value`
|
|
123
203
|
// (the old `writeValue`): split the tuple / clamp into the low & high cells.
|
|
204
|
+
// The cells are clamped and the MODEL IS NOT TOUCHED, and the asymmetry is
|
|
205
|
+
// deliberate. A thumb cannot render outside its own track, so the display
|
|
206
|
+
// has no choice — but writing that clamp back into `value` would erase the
|
|
207
|
+
// error it represents: under `[formField]` the bounds reach this component
|
|
208
|
+
// only through the schema's `min()` / `max()` rules (binding `[min]` beside
|
|
209
|
+
// `[formField]` is an NG8022 compile error), so the write-back deleted the
|
|
210
|
+
// out-of-range value those rules exist to report, and marked a pristine
|
|
211
|
+
// form dirty on first paint besides. The first real interaction emits an
|
|
212
|
+
// in-range number through `emitChange` and the two agree from then on.
|
|
124
213
|
effect(() => {
|
|
125
214
|
const v = this.value();
|
|
126
215
|
if (Array.isArray(v)) {
|
|
127
216
|
const tuple = v;
|
|
128
|
-
this.
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.
|
|
217
|
+
const lo = this.clampToBounds(tuple[0]);
|
|
218
|
+
const hi = this.clampToBounds(tuple[1]);
|
|
219
|
+
this.low.set(lo);
|
|
220
|
+
this.high.set(hi);
|
|
221
|
+
this.highSeeded = true;
|
|
222
|
+
return;
|
|
133
223
|
}
|
|
224
|
+
if (typeof v !== 'number')
|
|
225
|
+
return;
|
|
226
|
+
const lo = this.clampToBounds(v);
|
|
227
|
+
this.low.set(lo);
|
|
228
|
+
if (!this.range())
|
|
229
|
+
return;
|
|
230
|
+
// Range mode holding a scalar: the high thumb has never been told
|
|
231
|
+
// anything, and its literal default is not inside every `[min, max]`.
|
|
232
|
+
// Seed it at the top of the range — which is what the 100 meant back when
|
|
233
|
+
// the bounds were 0–100. The MODEL is left alone here too; the shape it
|
|
234
|
+
// holds is the consumer's business until they move a thumb.
|
|
235
|
+
this.high.set(this.highSeeded ? this.clampToBounds(untracked(this.high)) : this.maxValue());
|
|
236
|
+
this.highSeeded = true;
|
|
134
237
|
});
|
|
135
238
|
}
|
|
136
239
|
// Interaction
|
|
@@ -140,15 +243,26 @@ class WrSlider {
|
|
|
140
243
|
event.preventDefault();
|
|
141
244
|
const target = event.currentTarget;
|
|
142
245
|
target.setPointerCapture(event.pointerId);
|
|
246
|
+
// `preventDefault` above also suppresses the default focus, so the arrows
|
|
247
|
+
// did nothing after a mouse drag until the thumb was found again with Tab —
|
|
248
|
+
// while a click on the bare TRACK focused it. Synchronously, not through
|
|
249
|
+
// `focusThumb`: `event.currentTarget` is already the thumb, so that
|
|
250
|
+
// helper's `queueMicrotask` would only defer a focus nothing is waiting on.
|
|
251
|
+
target.focus();
|
|
143
252
|
const move = (e) => this.updateFromEvent(e, thumb);
|
|
144
|
-
const
|
|
253
|
+
const cleanup = (e) => {
|
|
145
254
|
target.releasePointerCapture(e.pointerId);
|
|
146
255
|
target.removeEventListener('pointermove', move);
|
|
147
|
-
target.removeEventListener('pointerup',
|
|
256
|
+
target.removeEventListener('pointerup', cleanup);
|
|
257
|
+
target.removeEventListener('pointercancel', cleanup);
|
|
148
258
|
this.touch.emit();
|
|
149
259
|
};
|
|
150
260
|
target.addEventListener('pointermove', move);
|
|
151
|
-
target.addEventListener('pointerup',
|
|
261
|
+
target.addEventListener('pointerup', cleanup);
|
|
262
|
+
// `pointercancel` is never followed by a `pointerup`, so without this the
|
|
263
|
+
// move listener outlived the drag: the thumb kept following a pointer that
|
|
264
|
+
// was merely hovering over it, with no button held.
|
|
265
|
+
target.addEventListener('pointercancel', cleanup);
|
|
152
266
|
this.updateFromEvent(event, thumb);
|
|
153
267
|
}
|
|
154
268
|
onTrackPointerDown(event) {
|
|
@@ -199,6 +313,21 @@ class WrSlider {
|
|
|
199
313
|
this.emitChange();
|
|
200
314
|
this.touch.emit();
|
|
201
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* A bound field learns it may show its validation copy from `touch`, and
|
|
318
|
+
* pointer-up plus a value-changing keypress cannot be the only sources: a
|
|
319
|
+
* slider tabbed into and out of without an arrow press left the field
|
|
320
|
+
* untouched forever — which is exactly the case `touched` exists for, and
|
|
321
|
+
* what this output has always documented itself as doing. `wr-knob` and
|
|
322
|
+
* `wr-rating` bind `(blur)` on the one element they own; this component has
|
|
323
|
+
* two thumbs, so it listens for the bubbling `focusout` on its host instead
|
|
324
|
+
* — and ignores the one that only moves focus from one thumb to the other.
|
|
325
|
+
*/
|
|
326
|
+
onFocusOut(event) {
|
|
327
|
+
if (this.track().nativeElement.contains(event.relatedTarget))
|
|
328
|
+
return;
|
|
329
|
+
this.touch.emit();
|
|
330
|
+
}
|
|
202
331
|
// Internals
|
|
203
332
|
isRtl() {
|
|
204
333
|
return this.dir?.value === 'rtl';
|
|
@@ -268,13 +397,13 @@ class WrSlider {
|
|
|
268
397
|
const value = this.range() ? [this.low(), this.high()] : this.low();
|
|
269
398
|
this.value.set(value);
|
|
270
399
|
}
|
|
271
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
272
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
400
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
401
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSlider, isStandalone: true, selector: "wr-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, upperLabel: { classPropertyName: "upperLabel", publicName: "upperLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [attr.id]=\"thumbId()\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"resolvedLowLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n [attr.aria-label]=\"resolvedUpperLabel()\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
273
402
|
}
|
|
274
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
403
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSlider, decorators: [{
|
|
275
404
|
type: Component,
|
|
276
|
-
args: [{ selector: 'wr-slider', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"
|
|
277
|
-
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
|
|
405
|
+
args: [{ selector: 'wr-slider', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()', '(focusout)': 'onFocusOut($event)' }, template: "<div #track class=\"wr-slider__track\" (pointerdown)=\"onTrackPointerDown($event)\">\n <!--\n Positioned on the INLINE axis, not with `left`: `inset-inline-start` is the\n same edge in LTR and mirrors under `dir=\"rtl\"`, so the track paints itself\n the right way round without the component reading a direction to draw.\n -->\n <span class=\"wr-slider__fill\" [style.inset-inline-start.%]=\"fillStart()\" [style.width.%]=\"fillWidth()\"></span>\n\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--low\"\n role=\"slider\"\n [attr.id]=\"thumbId()\"\n [style.inset-inline-start.%]=\"lowPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"range() ? high() : max()\"\n [attr.aria-valuenow]=\"low()\"\n [attr.aria-label]=\"resolvedLowLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n (pointerdown)=\"onPointerDown($event, 'low')\"\n (keydown)=\"onKey($event, 'low')\"\n ></button>\n\n @if (range()) {\n <button\n type=\"button\"\n class=\"wr-slider__thumb wr-slider__thumb--high\"\n role=\"slider\"\n [style.inset-inline-start.%]=\"highPercent()\"\n [disabled]=\"disabled()\"\n [attr.aria-valuemin]=\"low()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"high()\"\n [attr.aria-label]=\"resolvedUpperLabel()\"\n (pointerdown)=\"onPointerDown($event, 'high')\"\n (keydown)=\"onKey($event, 'high')\"\n ></button>\n }\n</div>\n\n@if (showLabel()) {\n <div class=\"wr-slider__label\">{{ label() }}</div>\n}\n" }]
|
|
406
|
+
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], upperLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "upperLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
|
|
278
407
|
|
|
279
408
|
/**
|
|
280
409
|
* Generated bundle index. Do not edit.
|