@kouji-ui/core 0.0.3 → 0.1.1
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/.turbo/turbo-build.log +6 -3
- package/CHANGELOG.md +177 -0
- package/ng-package.json +5 -1
- package/package.json +18 -6
- package/src/a11y/aria-describedby.ts +7 -3
- package/src/a11y/aria-labelledby.spec.ts +55 -0
- package/src/a11y/aria-labelledby.ts +38 -0
- package/src/a11y/focus-trap.ts +4 -2
- package/src/a11y/index.ts +1 -0
- package/src/a11y/live-region.ts +4 -2
- package/src/a11y/roving-tabindex.spec.ts +139 -0
- package/src/a11y/roving-tabindex.ts +56 -7
- package/src/a11y/visually-hidden.ts +4 -2
- package/src/accordion/accordion.context.ts +22 -0
- package/src/accordion/accordion.spec.ts +233 -23
- package/src/accordion/accordion.ts +311 -39
- package/src/accordion/index.ts +8 -2
- package/src/alert/alert.context.ts +28 -0
- package/src/alert/alert.spec.ts +217 -0
- package/src/alert/alert.ts +417 -0
- package/src/alert/config.ts +46 -0
- package/src/alert/index.ts +19 -0
- package/src/avatar/avatar-group.context.ts +45 -0
- package/src/avatar/avatar-group.spec.ts +358 -0
- package/src/avatar/avatar-group.ts +259 -0
- package/src/avatar/avatar.ts +17 -4
- package/src/avatar/index.ts +11 -1
- package/src/badge/badge.spec.ts +8 -0
- package/src/badge/badge.ts +24 -3
- package/src/breadcrumb/breadcrumb-current.ts +38 -0
- package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
- package/src/breadcrumb/breadcrumb-item.ts +41 -0
- package/src/breadcrumb/breadcrumb-link.ts +44 -0
- package/src/breadcrumb/breadcrumb-list.ts +42 -0
- package/src/breadcrumb/breadcrumb-separator.ts +45 -0
- package/src/breadcrumb/breadcrumb.context.ts +45 -0
- package/src/breadcrumb/breadcrumb.spec.ts +354 -0
- package/src/breadcrumb/breadcrumb.ts +239 -0
- package/src/breadcrumb/config.ts +84 -0
- package/src/breadcrumb/index.ts +18 -0
- package/src/button/_examples/button.example.ts +71 -0
- package/src/button/_examples/button.finance.example.ts +83 -0
- package/src/button/_examples/button.retro.example.ts +91 -0
- package/src/button/_examples/button.sizes.example.ts +60 -0
- package/src/button/_examples/index.ts +6 -0
- package/src/button/button.spec.ts +139 -8
- package/src/button/button.ts +114 -16
- package/src/button/config.spec.ts +33 -0
- package/src/button/config.ts +45 -0
- package/src/button/index.ts +2 -1
- package/src/button-group/button-group.context.ts +28 -0
- package/src/button-group/button-group.spec.ts +99 -0
- package/src/button-group/button-group.ts +99 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar-day.ts +102 -0
- package/src/calendar/calendar-grid.ts +90 -0
- package/src/calendar/calendar-header.ts +65 -0
- package/src/calendar/calendar.context.ts +53 -0
- package/src/calendar/calendar.spec.ts +225 -0
- package/src/calendar/calendar.ts +233 -0
- package/src/calendar/date-utils.ts +225 -0
- package/src/calendar/index.ts +28 -0
- package/src/carousel/carousel.context.ts +81 -0
- package/src/carousel/carousel.spec.ts +337 -0
- package/src/carousel/carousel.ts +919 -0
- package/src/carousel/index.ts +22 -0
- package/src/cascade-select/cascade-select-option.ts +205 -0
- package/src/cascade-select/cascade-select-panel.ts +106 -0
- package/src/cascade-select/cascade-select-root.ts +265 -0
- package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
- package/src/cascade-select/cascade-select-trigger.ts +44 -0
- package/src/cascade-select/cascade-select.context.ts +71 -0
- package/src/cascade-select/cascade-select.spec.ts +241 -0
- package/src/cascade-select/cascade-select.ts +5 -0
- package/src/cascade-select/index.ts +11 -0
- package/src/chart/chart.ts +6 -2
- package/src/chat/chat-avatar.ts +31 -0
- package/src/chat/chat-bubble.ts +49 -0
- package/src/chat/chat-footer.ts +67 -0
- package/src/chat/chat-header.ts +41 -0
- package/src/chat/chat-log.ts +43 -0
- package/src/chat/chat.context.ts +64 -0
- package/src/chat/chat.spec.ts +341 -0
- package/src/chat/chat.ts +191 -0
- package/src/chat/config.ts +53 -0
- package/src/chat/index.ts +21 -0
- package/src/checkbox/checkbox.ts +9 -2
- package/src/color-picker/color-picker.context.ts +74 -0
- package/src/color-picker/color-picker.spec.ts +241 -0
- package/src/color-picker/color-picker.ts +698 -0
- package/src/color-picker/color-picker.utils.ts +140 -0
- package/src/color-picker/index.ts +28 -0
- package/src/combobox/combobox-input.ts +143 -0
- package/src/combobox/combobox-listbox.ts +49 -0
- package/src/combobox/combobox-option.ts +43 -0
- package/src/combobox/combobox-root.ts +281 -0
- package/src/combobox/combobox.spec.ts +75 -0
- package/src/combobox/combobox.ts +4 -0
- package/src/combobox/index.ts +4 -0
- package/src/command-palette/command-empty.ts +36 -0
- package/src/command-palette/command-group.ts +21 -0
- package/src/command-palette/command-input.ts +70 -0
- package/src/command-palette/command-item.ts +60 -0
- package/src/command-palette/command-list.ts +25 -0
- package/src/command-palette/command-palette-dialog.ts +74 -0
- package/src/command-palette/command-palette-trigger.ts +64 -0
- package/src/command-palette/command-palette.filters.ts +53 -0
- package/src/command-palette/command-palette.spec.ts +349 -0
- package/src/command-palette/command-palette.ts +204 -0
- package/src/command-palette/command-separator.ts +18 -0
- package/src/command-palette/index.ts +15 -0
- package/src/confirm-popup/confirm-popup-action.ts +39 -0
- package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
- package/src/confirm-popup/confirm-popup-content.ts +113 -0
- package/src/confirm-popup/confirm-popup-message.ts +36 -0
- package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
- package/src/confirm-popup/confirm-popup.context.ts +41 -0
- package/src/confirm-popup/confirm-popup.spec.ts +337 -0
- package/src/confirm-popup/confirm-popup.ts +109 -0
- package/src/confirm-popup/index.ts +12 -0
- package/src/date-picker/date-picker-calendar.ts +65 -0
- package/src/date-picker/date-picker-trigger.ts +215 -0
- package/src/date-picker/date-picker.context.ts +50 -0
- package/src/date-picker/date-picker.spec.ts +117 -0
- package/src/date-picker/date-picker.ts +127 -0
- package/src/date-picker/index.ts +7 -0
- package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
- package/src/dialog/_examples/dialog.example.ts +106 -0
- package/src/dialog/_examples/dialog.finance.example.ts +121 -0
- package/src/dialog/_examples/dialog.retro.example.ts +126 -0
- package/src/dialog/_examples/index.ts +6 -0
- package/src/dialog/dialog.ref.ts +42 -0
- package/src/dialog/dialog.service.ts +57 -167
- package/src/dialog/dialog.spec.ts +35 -120
- package/src/dialog/dialog.ts +17 -192
- package/src/dialog/index.ts +3 -9
- package/src/divider/divider.spec.ts +223 -0
- package/src/divider/divider.ts +137 -0
- package/src/divider/index.ts +5 -0
- package/src/drawer/drawer.ref.ts +63 -0
- package/src/drawer/drawer.service.ts +106 -0
- package/src/drawer/drawer.spec.ts +117 -0
- package/src/drawer/drawer.ts +122 -0
- package/src/drawer/index.ts +10 -0
- package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
- package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
- package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
- package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
- package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
- package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
- package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
- package/src/dropdown-menu/dropdown-menu.ts +60 -0
- package/src/dropdown-menu/index.ts +15 -0
- package/src/example-components.ts +39 -30
- package/src/field/field-error.ts +49 -0
- package/src/field/field-group.ts +39 -0
- package/src/field/field-help.ts +47 -0
- package/src/field/field-label.ts +30 -0
- package/src/field/field.context.ts +28 -0
- package/src/field/field.spec.ts +179 -0
- package/src/field/field.ts +121 -0
- package/src/field/index.ts +6 -0
- package/src/file-upload/file-upload.context.ts +89 -0
- package/src/file-upload/file-upload.spec.ts +290 -0
- package/src/file-upload/file-upload.ts +639 -0
- package/src/file-upload/file-upload.types.ts +59 -0
- package/src/file-upload/index.ts +23 -0
- package/src/form/form-error-summary.ts +87 -0
- package/src/form/form-field.context.ts +3 -0
- package/src/form/form-field.spec.ts +25 -0
- package/src/form/form-field.ts +52 -0
- package/src/form/form.context.ts +52 -2
- package/src/form/form.spec.ts +155 -18
- package/src/form/form.ts +315 -35
- package/src/form/index.ts +11 -2
- package/src/icon/E2E.md +14 -0
- package/src/icon/icon.css +40 -0
- package/src/icon/icon.directive.spec.ts +198 -0
- package/src/icon/icon.directive.ts +113 -0
- package/src/icon/icon.mode.spec.ts +29 -0
- package/src/icon/icon.mode.ts +23 -0
- package/src/icon/icon.providers.spec.ts +63 -0
- package/src/icon/icon.providers.ts +47 -0
- package/src/icon/icon.resolver.spec.ts +96 -0
- package/src/icon/icon.resolver.ts +49 -0
- package/src/icon/icon.tokens.ts +88 -0
- package/src/icon/icon.types.ts +48 -0
- package/src/icon/index.ts +21 -0
- package/src/input/input.ts +12 -3
- package/src/input-group/index.ts +3 -0
- package/src/input-group/input-group-addon.ts +92 -0
- package/src/input-group/input-group.context.ts +27 -0
- package/src/input-group/input-group.spec.ts +186 -0
- package/src/input-group/input-group.ts +136 -0
- package/src/input-mask/index.ts +11 -0
- package/src/input-mask/input-mask.compile.ts +69 -0
- package/src/input-mask/input-mask.engine.ts +376 -0
- package/src/input-mask/input-mask.presets.ts +24 -0
- package/src/input-mask/input-mask.spec.ts +304 -0
- package/src/input-mask/input-mask.tokens.ts +49 -0
- package/src/input-mask/input-mask.ts +280 -0
- package/src/input-otp/index.ts +2 -0
- package/src/input-otp/input-otp.context.ts +41 -0
- package/src/input-otp/input-otp.spec.ts +308 -0
- package/src/input-otp/input-otp.ts +470 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.spec.ts +127 -0
- package/src/kbd/kbd.ts +132 -0
- package/src/link/config.ts +44 -0
- package/src/link/index.ts +2 -0
- package/src/link/link.spec.ts +246 -0
- package/src/link/link.ts +226 -0
- package/src/list/index.ts +11 -0
- package/src/list/list.spec.ts +237 -0
- package/src/list/list.ts +277 -0
- package/src/menubar/index.ts +7 -0
- package/src/menubar/menubar-item.ts +204 -0
- package/src/menubar/menubar.context.ts +43 -0
- package/src/menubar/menubar.spec.ts +433 -0
- package/src/menubar/menubar.ts +309 -0
- package/src/number-input/index.ts +4 -0
- package/src/number-input/number-input-group.ts +64 -0
- package/src/number-input/number-input.context.ts +31 -0
- package/src/number-input/number-input.format.ts +148 -0
- package/src/number-input/number-input.spec.ts +170 -0
- package/src/number-input/number-input.ts +457 -0
- package/src/number-input/number-stepper.ts +193 -0
- package/src/overlay-badge/index.ts +7 -0
- package/src/overlay-badge/overlay-badge-content.ts +73 -0
- package/src/overlay-badge/overlay-badge.context.ts +37 -0
- package/src/overlay-badge/overlay-badge.spec.ts +220 -0
- package/src/overlay-badge/overlay-badge.ts +128 -0
- package/src/pagination/config.ts +82 -0
- package/src/pagination/index.ts +19 -0
- package/src/pagination/page-tokens.ts +78 -0
- package/src/pagination/pagination-ellipsis.ts +69 -0
- package/src/pagination/pagination-first.ts +84 -0
- package/src/pagination/pagination-info.ts +70 -0
- package/src/pagination/pagination-item.ts +81 -0
- package/src/pagination/pagination-last.ts +85 -0
- package/src/pagination/pagination-next.ts +86 -0
- package/src/pagination/pagination-previous.ts +90 -0
- package/src/pagination/pagination.context.ts +52 -0
- package/src/pagination/pagination.spec.ts +388 -0
- package/src/pagination/pagination.ts +288 -0
- package/src/password-input/index.ts +15 -0
- package/src/password-input/password-input.context.ts +65 -0
- package/src/password-input/password-input.scorer.ts +34 -0
- package/src/password-input/password-input.spec.ts +274 -0
- package/src/password-input/password-input.ts +488 -0
- package/src/popover/_examples/index.ts +5 -0
- package/src/popover/_examples/popover.example.ts +92 -0
- package/src/popover/_examples/popover.finance.example.ts +106 -0
- package/src/popover/_examples/popover.retro.example.ts +103 -0
- package/src/popover/index.ts +5 -12
- package/src/popover/popover-arrow.ts +30 -0
- package/src/popover/popover-close.ts +36 -0
- package/src/popover/popover-content.ts +41 -0
- package/src/popover/popover-title.ts +39 -0
- package/src/popover/popover-trigger.ts +40 -0
- package/src/popover/popover.spec.ts +51 -44
- package/src/popover/popover.ts +5 -124
- package/src/presets/bind-presets.spec.ts +50 -0
- package/src/presets/bind-presets.ts +43 -0
- package/src/presets/index.ts +3 -0
- package/src/presets/size.spec.ts +67 -0
- package/src/presets/size.ts +59 -0
- package/src/presets/variant.spec.ts +72 -0
- package/src/presets/variant.ts +62 -0
- package/src/primitives/directionality/directionality.spec.ts +112 -0
- package/src/primitives/directionality/directionality.ts +89 -0
- package/src/primitives/directionality/index.ts +1 -0
- package/src/primitives/forms/form-control.spec.ts +14 -6
- package/src/primitives/forms/form-control.ts +6 -2
- package/src/primitives/index.ts +3 -0
- package/src/primitives/interaction/disabled.ts +12 -5
- package/src/primitives/interaction/focus-ring.ts +4 -2
- package/src/primitives/list/filterable-list.spec.ts +114 -0
- package/src/primitives/list/filterable-list.ts +126 -0
- package/src/primitives/list/group.spec.ts +99 -0
- package/src/primitives/list/group.ts +139 -0
- package/src/primitives/list/index.ts +8 -0
- package/src/primitives/list/inject-helpers.ts +8 -0
- package/src/primitives/list/item.spec.ts +173 -0
- package/src/primitives/list/item.ts +222 -0
- package/src/primitives/list/navigator.spec.ts +286 -0
- package/src/primitives/list/navigator.ts +238 -0
- package/src/primitives/list/selection.spec.ts +214 -0
- package/src/primitives/list/selection.ts +339 -0
- package/src/primitives/list/tokens.ts +106 -0
- package/src/primitives/list/type-ahead.spec.ts +58 -0
- package/src/primitives/list/type-ahead.ts +46 -0
- package/src/primitives/overlay/backdrop.spec.ts +54 -0
- package/src/primitives/overlay/backdrop.ts +31 -0
- package/src/primitives/overlay/builder.spec.ts +38 -0
- package/src/primitives/overlay/builder.ts +151 -0
- package/src/primitives/overlay/container.ts +42 -0
- package/src/primitives/overlay/context.ts +14 -0
- package/src/primitives/overlay/controller.spec.ts +86 -0
- package/src/primitives/overlay/controller.ts +190 -0
- package/src/primitives/overlay/id.spec.ts +24 -0
- package/src/primitives/overlay/id.ts +17 -0
- package/src/primitives/overlay/index.ts +18 -1
- package/src/primitives/overlay/overlay.css +42 -0
- package/src/primitives/overlay/panel.spec.ts +52 -0
- package/src/primitives/overlay/panel.ts +102 -0
- package/src/primitives/overlay/stack.spec.ts +55 -0
- package/src/primitives/overlay/stack.ts +113 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
- package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
- package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
- package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
- package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
- package/src/primitives/overlay/strategies/index.ts +34 -0
- package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
- package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
- package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
- package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
- package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
- package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
- package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
- package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
- package/src/primitives/overlay/strategies/position/corner.ts +33 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
- package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
- package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
- package/src/primitives/overlay/tokens.ts +54 -0
- package/src/primitives/overlay/trigger.spec.ts +103 -0
- package/src/primitives/overlay/trigger.ts +75 -0
- package/src/primitives/overlay/types.ts +24 -0
- package/src/primitives/overlay/wrapper.ts +50 -0
- package/src/primitives/signals/deep-signal.ts +109 -0
- package/src/primitives/signals/index.ts +1 -0
- package/src/progress-bar/config.ts +48 -0
- package/src/progress-bar/index.ts +12 -0
- package/src/progress-bar/progress-bar-fill.ts +37 -0
- package/src/progress-bar/progress-bar.context.ts +34 -0
- package/src/progress-bar/progress-bar.spec.ts +423 -0
- package/src/progress-bar/progress-bar.ts +217 -0
- package/src/public-api.ts +58 -9
- package/src/radio/radio.ts +14 -3
- package/src/select/index.ts +4 -2
- package/src/select/select-content.ts +69 -0
- package/src/select/select-option.ts +35 -0
- package/src/select/select-root.ts +129 -0
- package/src/select/select-trigger.ts +93 -0
- package/src/select/select.spec.ts +133 -140
- package/src/select/select.ts +4 -250
- package/src/skeleton/index.ts +5 -0
- package/src/skeleton/skeleton.spec.ts +115 -0
- package/src/skeleton/skeleton.ts +56 -0
- package/src/slider/index.ts +10 -0
- package/src/slider/slider-range.ts +49 -0
- package/src/slider/slider-thumb.ts +335 -0
- package/src/slider/slider-track.ts +70 -0
- package/src/slider/slider.context.ts +79 -0
- package/src/slider/slider.geometry.ts +73 -0
- package/src/slider/slider.spec.ts +216 -0
- package/src/slider/slider.ts +443 -0
- package/src/speed-dial/index.ts +10 -0
- package/src/speed-dial/speed-dial-action.ts +40 -0
- package/src/speed-dial/speed-dial-actions.ts +31 -0
- package/src/speed-dial/speed-dial-trigger.ts +55 -0
- package/src/speed-dial/speed-dial.context.ts +36 -0
- package/src/speed-dial/speed-dial.spec.ts +143 -0
- package/src/speed-dial/speed-dial.ts +127 -0
- package/src/spinner/config.ts +48 -0
- package/src/spinner/index.ts +8 -0
- package/src/spinner/spinner.spec.ts +262 -0
- package/src/spinner/spinner.ts +129 -0
- package/src/stepper/index.ts +15 -0
- package/src/stepper/stepper.context.ts +63 -0
- package/src/stepper/stepper.spec.ts +403 -0
- package/src/stepper/stepper.ts +544 -0
- package/src/styles.css +17 -0
- package/src/table/column-helpers.spec.ts +74 -0
- package/src/table/column-helpers.ts +68 -0
- package/src/table/filter-fns.ts +230 -0
- package/src/table/filter-models.spec.ts +240 -0
- package/src/table/filter-models.ts +124 -0
- package/src/table/filter-params.ts +90 -0
- package/src/table/grid-api-impl.ts +321 -0
- package/src/table/grid-api.spec.ts +178 -0
- package/src/table/grid-api.ts +285 -0
- package/src/table/index.ts +73 -1
- package/src/table/table-cell.spec.ts +43 -0
- package/src/table/table-cell.ts +38 -0
- package/src/table/table-filter-outlet.ts +120 -0
- package/src/table/table-header.spec.ts +54 -0
- package/src/table/table-header.ts +63 -0
- package/src/table/table-keyboard.spec.ts +68 -0
- package/src/table/table-keyboard.ts +107 -0
- package/src/table/table-resource.spec.ts +37 -0
- package/src/table/table-resource.ts +36 -0
- package/src/table/table-row.spec.ts +54 -0
- package/src/table/table-row.ts +31 -0
- package/src/table/table-storage.spec.ts +67 -0
- package/src/table/table-storage.ts +76 -0
- package/src/table/table.spec.ts +102 -43
- package/src/table/table.ts +228 -73
- package/src/table/table.types.ts +83 -0
- package/src/tabs/index.ts +7 -2
- package/src/tabs/tabs.context.ts +27 -4
- package/src/tabs/tabs.spec.ts +336 -24
- package/src/tabs/tabs.ts +313 -79
- package/src/tag/config.ts +53 -0
- package/src/tag/index.ts +7 -0
- package/src/tag/tag-list.ts +76 -0
- package/src/tag/tag-remove.ts +61 -0
- package/src/tag/tag.context.ts +36 -0
- package/src/tag/tag.spec.ts +240 -0
- package/src/tag/tag.ts +203 -0
- package/src/textarea/config.ts +42 -0
- package/src/textarea/index.ts +8 -0
- package/src/textarea/textarea.spec.ts +176 -0
- package/src/textarea/textarea.ts +235 -0
- package/src/time-picker/index.ts +15 -0
- package/src/time-picker/time-picker-meridiem.ts +123 -0
- package/src/time-picker/time-picker-segment.ts +318 -0
- package/src/time-picker/time-picker.context.ts +38 -0
- package/src/time-picker/time-picker.format.ts +181 -0
- package/src/time-picker/time-picker.spec.ts +189 -0
- package/src/time-picker/time-picker.ts +274 -0
- package/src/toast/_examples/index.ts +5 -0
- package/src/toast/_examples/toast.example.ts +282 -0
- package/src/toast/_examples/toast.finance.example.ts +180 -0
- package/src/toast/_examples/toast.retro.example.ts +161 -0
- package/src/toast/index.ts +8 -1
- package/src/toast/toast.ref.ts +26 -0
- package/src/toast/toast.service.ts +227 -41
- package/src/toast/toast.spec.ts +178 -25
- package/src/toast/toast.strategy.ts +10 -0
- package/src/toast/toast.ts +178 -22
- package/src/toggle/toggle.ts +10 -2
- package/src/tooltip/_examples/index.ts +6 -0
- package/src/tooltip/_examples/tooltip.example.ts +82 -0
- package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
- package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
- package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
- package/src/tooltip/index.ts +4 -5
- package/src/tooltip/tooltip-arrow.ts +31 -0
- package/src/tooltip/tooltip-content.ts +38 -0
- package/src/tooltip/tooltip-group.ts +32 -0
- package/src/tooltip/tooltip-trigger.ts +47 -0
- package/src/tooltip/tooltip.spec.ts +38 -102
- package/src/tooltip/tooltip.ts +5 -129
- package/src/tree-select/index.ts +12 -0
- package/src/tree-select/tree-select-content.ts +199 -0
- package/src/tree-select/tree-select-node.ts +132 -0
- package/src/tree-select/tree-select-root.ts +272 -0
- package/src/tree-select/tree-select-trigger.ts +90 -0
- package/src/tree-select/tree-select.context.ts +63 -0
- package/src/tree-select/tree-select.spec.ts +433 -0
- package/src/tree-select/tree-select.ts +2 -0
- package/src/typography/blockquote.ts +57 -0
- package/src/typography/code.ts +53 -0
- package/src/typography/index.ts +22 -0
- package/src/typography/lead.ts +49 -0
- package/src/typography/muted.ts +29 -0
- package/src/typography/prose.css +312 -0
- package/src/typography/truncate.ts +99 -0
- package/src/typography/typography.spec.ts +203 -0
- package/tsconfig.lib.json +9 -1
- package/tsconfig.lib.prod.json +7 -1
- package/tsconfig.spec.json +9 -1
- package/vite.config.ts +8 -0
- package/src/button/button.example.ts +0 -30
- package/src/button/button.finance.example.ts +0 -37
- package/src/button/button.retro.example.ts +0 -47
- package/src/button/button.sizes.example.ts +0 -25
- package/src/dialog/dialog.confirm.example.ts +0 -57
- package/src/dialog/dialog.context.ts +0 -11
- package/src/dialog/dialog.example.ts +0 -40
- package/src/dialog/dialog.finance.example.ts +0 -47
- package/src/dialog/dialog.retro.example.ts +0 -46
- package/src/menu/index.ts +0 -2
- package/src/menu/menu.context.ts +0 -10
- package/src/menu/menu.example.ts +0 -39
- package/src/menu/menu.spec.ts +0 -168
- package/src/menu/menu.ts +0 -176
- package/src/popover/popover.context.ts +0 -18
- package/src/popover/popover.example.ts +0 -50
- package/src/popover/popover.finance.example.ts +0 -52
- package/src/popover/popover.retro.example.ts +0 -53
- package/src/primitives/overlay/overlay.spec.ts +0 -90
- package/src/primitives/overlay/overlay.ts +0 -102
- package/src/select/select.context.ts +0 -3
- package/src/toast/toast.example.ts +0 -134
- package/src/toast/toast.finance.example.ts +0 -68
- package/src/toast/toast.retro.example.ts +0 -69
- package/src/tooltip/tooltip.example.ts +0 -46
- package/src/tooltip/tooltip.finance.example.ts +0 -54
- package/src/tooltip/tooltip.placements.example.ts +0 -56
- package/src/tooltip/tooltip.retro.example.ts +0 -65
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { render } from '@testing-library/angular';
|
|
4
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
KjAlert,
|
|
8
|
+
KjAlertActions,
|
|
9
|
+
KjAlertDescription,
|
|
10
|
+
KjAlertDismiss,
|
|
11
|
+
KjAlertIcon,
|
|
12
|
+
KjAlertTitle,
|
|
13
|
+
} from './alert';
|
|
14
|
+
import { provideKjAlert } from './config';
|
|
15
|
+
|
|
16
|
+
expect.extend(toHaveNoViolations);
|
|
17
|
+
|
|
18
|
+
const ALERT_IMPORTS = [
|
|
19
|
+
KjAlert,
|
|
20
|
+
KjAlertTitle,
|
|
21
|
+
KjAlertDescription,
|
|
22
|
+
KjAlertIcon,
|
|
23
|
+
KjAlertActions,
|
|
24
|
+
KjAlertDismiss,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
@Component({
|
|
28
|
+
standalone: true,
|
|
29
|
+
imports: [KjAlert, KjAlertTitle, KjAlertDismiss],
|
|
30
|
+
changeDetection: ChangeDetectionStrategy.Eager,
|
|
31
|
+
template: `
|
|
32
|
+
@if (visible()) {
|
|
33
|
+
<div kjAlert (kjAlertDismissed)="onDismissed()">
|
|
34
|
+
<h3 kjAlertTitle>Saved</h3>
|
|
35
|
+
<button kjAlertDismiss>×</button>
|
|
36
|
+
</div>
|
|
37
|
+
}
|
|
38
|
+
`,
|
|
39
|
+
})
|
|
40
|
+
class DismissHost {
|
|
41
|
+
readonly visible = signal(true);
|
|
42
|
+
fired = 0;
|
|
43
|
+
onDismissed(): void {
|
|
44
|
+
this.fired++;
|
|
45
|
+
this.visible.set(false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('KjAlert', () => {
|
|
50
|
+
it('renders with role="status" and aria-live="polite" by default', async () => {
|
|
51
|
+
const { container } = await render(`<div kjAlert><h3 kjAlertTitle>Heads up</h3></div>`, {
|
|
52
|
+
imports: ALERT_IMPORTS,
|
|
53
|
+
});
|
|
54
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
55
|
+
expect(root.getAttribute('role')).toBe('status');
|
|
56
|
+
expect(root.getAttribute('aria-live')).toBe('polite');
|
|
57
|
+
expect(root.getAttribute('aria-atomic')).toBe('true');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('error variant resolves to assertive (role="alert" + aria-live="assertive")', async () => {
|
|
61
|
+
const { container } = await render(
|
|
62
|
+
`<div kjAlert kjVariant="error"><h3 kjAlertTitle>Failed</h3></div>`,
|
|
63
|
+
{ imports: ALERT_IMPORTS },
|
|
64
|
+
);
|
|
65
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
66
|
+
expect(root.getAttribute('role')).toBe('alert');
|
|
67
|
+
expect(root.getAttribute('aria-live')).toBe('assertive');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('static flag resolves to role="region" with no aria-live', async () => {
|
|
71
|
+
const { container } = await render(
|
|
72
|
+
`<div kjAlert [kjAlertStatic]="true" aria-label="Banner"><span kjAlertDescription>x</span></div>`,
|
|
73
|
+
{ imports: ALERT_IMPORTS },
|
|
74
|
+
);
|
|
75
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
76
|
+
expect(root.getAttribute('role')).toBe('region');
|
|
77
|
+
expect(root.getAttribute('aria-live')).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('explicit kjAlertMode wins over the matrix', async () => {
|
|
81
|
+
const { container } = await render(
|
|
82
|
+
`<div kjAlert kjVariant="error" kjAlertMode="polite"><span kjAlertDescription>x</span></div>`,
|
|
83
|
+
{ imports: ALERT_IMPORTS },
|
|
84
|
+
);
|
|
85
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
86
|
+
expect(root.getAttribute('role')).toBe('status');
|
|
87
|
+
expect(root.getAttribute('aria-live')).toBe('polite');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('mode="off" omits role and aria-live', async () => {
|
|
91
|
+
const { container } = await render(
|
|
92
|
+
`<div kjAlert kjAlertMode="off"><span kjAlertDescription>x</span></div>`,
|
|
93
|
+
{ imports: ALERT_IMPORTS },
|
|
94
|
+
);
|
|
95
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
96
|
+
expect(root.getAttribute('role')).toBeNull();
|
|
97
|
+
expect(root.getAttribute('aria-live')).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('reflects data-variant on the host', async () => {
|
|
101
|
+
const { container } = await render(
|
|
102
|
+
`<div kjAlert kjVariant="warning"><span kjAlertDescription>x</span></div>`,
|
|
103
|
+
{ imports: ALERT_IMPORTS },
|
|
104
|
+
);
|
|
105
|
+
expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('warning');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('wires aria-labelledby to the title id', async () => {
|
|
109
|
+
const { container } = await render(`<div kjAlert><h3 kjAlertTitle>Title</h3></div>`, {
|
|
110
|
+
imports: ALERT_IMPORTS,
|
|
111
|
+
});
|
|
112
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
113
|
+
const title = container.querySelector('[kjAlertTitle]')!;
|
|
114
|
+
expect(title.id).toMatch(/-title$/);
|
|
115
|
+
expect(root.getAttribute('aria-labelledby')).toBe(title.id);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('wires aria-describedby to the description id', async () => {
|
|
119
|
+
const { container } = await render(`<div kjAlert><p kjAlertDescription>Body</p></div>`, {
|
|
120
|
+
imports: ALERT_IMPORTS,
|
|
121
|
+
});
|
|
122
|
+
const root = container.querySelector('[kjAlert]')!;
|
|
123
|
+
const desc = container.querySelector('[kjAlertDescription]')!;
|
|
124
|
+
expect(desc.id).toMatch(/-description$/);
|
|
125
|
+
expect(root.getAttribute('aria-describedby')).toBe(desc.id);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('icon sets aria-hidden="true" and mirrors data-variant', async () => {
|
|
129
|
+
const { container } = await render(
|
|
130
|
+
`<div kjAlert kjVariant="success"><span kjAlertIcon></span><span kjAlertDescription>x</span></div>`,
|
|
131
|
+
{ imports: ALERT_IMPORTS },
|
|
132
|
+
);
|
|
133
|
+
const icon = container.querySelector('[kjAlertIcon]')!;
|
|
134
|
+
expect(icon.getAttribute('aria-hidden')).toBe('true');
|
|
135
|
+
expect(icon.getAttribute('data-variant')).toBe('success');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('actions container sets role="group" with default aria-label', async () => {
|
|
139
|
+
const { container } = await render(
|
|
140
|
+
`<div kjAlert>
|
|
141
|
+
<span kjAlertDescription>x</span>
|
|
142
|
+
<div kjAlertActions></div>
|
|
143
|
+
</div>`,
|
|
144
|
+
{ imports: ALERT_IMPORTS },
|
|
145
|
+
);
|
|
146
|
+
const actions = container.querySelector('[kjAlertActions]')!;
|
|
147
|
+
expect(actions.getAttribute('role')).toBe('group');
|
|
148
|
+
expect(actions.getAttribute('aria-label')).toBe('Alert actions');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('dismiss button has aria-label="Dismiss" and inherits KjButton chrome', async () => {
|
|
152
|
+
const { container } = await render(
|
|
153
|
+
`<div kjAlert><span kjAlertDescription>x</span><button kjAlertDismiss>×</button></div>`,
|
|
154
|
+
{ imports: ALERT_IMPORTS },
|
|
155
|
+
);
|
|
156
|
+
const dismiss = container.querySelector('[kjAlertDismiss]')!;
|
|
157
|
+
expect(dismiss.getAttribute('aria-label')).toBe('Dismiss');
|
|
158
|
+
// Inherits the KjButton variant default forwarded by KjAlertDismiss
|
|
159
|
+
expect(dismiss.getAttribute('data-variant')).toBe('ghost');
|
|
160
|
+
expect(dismiss.getAttribute('data-size')).toBe('icon');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('clicking dismiss fires kjAlertDismissed (consumer unmounts)', () => {
|
|
164
|
+
TestBed.configureTestingModule({ imports: [DismissHost] });
|
|
165
|
+
const fixture = TestBed.createComponent(DismissHost);
|
|
166
|
+
fixture.detectChanges();
|
|
167
|
+
const btn = fixture.nativeElement.querySelector('[kjAlertDismiss]');
|
|
168
|
+
btn.click();
|
|
169
|
+
fixture.detectChanges();
|
|
170
|
+
expect(fixture.componentInstance.fired).toBe(1);
|
|
171
|
+
expect(fixture.nativeElement.querySelector('[kjAlert]')).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('default variant is "info" from KJ_ALERT_DEFAULTS', async () => {
|
|
175
|
+
const { container } = await render(`<div kjAlert><span kjAlertDescription>x</span></div>`, {
|
|
176
|
+
imports: ALERT_IMPORTS,
|
|
177
|
+
});
|
|
178
|
+
expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('info');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('provideKjAlert at TestBed scope flows into directive defaults', async () => {
|
|
182
|
+
TestBed.configureTestingModule({
|
|
183
|
+
providers: [
|
|
184
|
+
provideKjAlert({
|
|
185
|
+
variants: ['info', 'success', 'warning', 'error', 'neutral', 'brand'],
|
|
186
|
+
defaults: { variant: 'brand', size: 'md' },
|
|
187
|
+
}),
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
const { container } = await render(`<div kjAlert><span kjAlertDescription>x</span></div>`, {
|
|
191
|
+
imports: ALERT_IMPORTS,
|
|
192
|
+
});
|
|
193
|
+
expect(container.querySelector('[kjAlert]')!.getAttribute('data-variant')).toBe('brand');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('passes axe audit (polite alert)', async () => {
|
|
197
|
+
const { container } = await render(
|
|
198
|
+
`<div kjAlert>
|
|
199
|
+
<h3 kjAlertTitle>Saved</h3>
|
|
200
|
+
<p kjAlertDescription>Your changes were saved.</p>
|
|
201
|
+
</div>`,
|
|
202
|
+
{ imports: ALERT_IMPORTS },
|
|
203
|
+
);
|
|
204
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('passes axe audit (static banner with aria-labelledby)', async () => {
|
|
208
|
+
const { container } = await render(
|
|
209
|
+
`<div kjAlert [kjAlertStatic]="true" aria-labelledby="banner-title">
|
|
210
|
+
<h3 kjAlertTitle id="banner-title">Maintenance</h3>
|
|
211
|
+
<p kjAlertDescription>Down Sat 02:00–04:00 UTC.</p>
|
|
212
|
+
</div>`,
|
|
213
|
+
{ imports: ALERT_IMPORTS },
|
|
214
|
+
);
|
|
215
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Directive,
|
|
3
|
+
ElementRef,
|
|
4
|
+
InputSignalWithTransform,
|
|
5
|
+
type OnDestroy,
|
|
6
|
+
booleanAttribute,
|
|
7
|
+
computed,
|
|
8
|
+
effect,
|
|
9
|
+
inject,
|
|
10
|
+
input,
|
|
11
|
+
isDevMode,
|
|
12
|
+
output,
|
|
13
|
+
signal,
|
|
14
|
+
} from '@angular/core';
|
|
15
|
+
import { KjButton } from '../button/button';
|
|
16
|
+
import { KJ_ALERT, KjAlertContext, KjAlertMode } from './alert.context';
|
|
17
|
+
import { KJ_ALERT_CONFIG } from './config';
|
|
18
|
+
|
|
19
|
+
let _alertIdCounter = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Persistent, in-flow notification (also known as a banner). Owns the
|
|
23
|
+
* `role` / `aria-live` matrix described in `docs/component-analyses/feedback/alert.md`:
|
|
24
|
+
* `kjAlertMode='assertive'` → `role="alert"` + `aria-live="assertive"`;
|
|
25
|
+
* `'polite'` → `role="status"` + `aria-live="polite"`;
|
|
26
|
+
* `'static'` → `role="region"` (no live, requires `aria-label` /
|
|
27
|
+
* `aria-labelledby`); `'off'` → no role, no live region.
|
|
28
|
+
*
|
|
29
|
+
* When `kjAlertMode` is unset, the directive resolves the matrix from
|
|
30
|
+
* `kjAlertStatic` and `kjVariant`:
|
|
31
|
+
*
|
|
32
|
+
* 1. `kjAlertStatic === true` → `'static'`
|
|
33
|
+
* 2. `kjVariant === 'error'` → `'assertive'`
|
|
34
|
+
* 3. otherwise → `'polite'`
|
|
35
|
+
*
|
|
36
|
+
* Visibility is consumer-managed: `KjAlertDismiss` (or programmatic
|
|
37
|
+
* `dismiss()`) fires `kjAlertDismissed` and the parent `*ngIf` / `@if`
|
|
38
|
+
* unmounts. The directive itself is stateless beyond exposing
|
|
39
|
+
* `data-dismissed="true"` for the brief render window before unmount,
|
|
40
|
+
* so themes can run an exit animation.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```html
|
|
44
|
+
* <div kjAlert kjVariant="error" #a="kjAlert">
|
|
45
|
+
* <h3 kjAlertTitle>Could not save draft</h3>
|
|
46
|
+
* <p kjAlertDescription>Network request timed out — retry?</p>
|
|
47
|
+
* <div kjAlertActions>
|
|
48
|
+
* <button kjButton (click)="retry()">Retry</button>
|
|
49
|
+
* </div>
|
|
50
|
+
* <button kjAlertDismiss>×</button>
|
|
51
|
+
* </div>
|
|
52
|
+
* ```
|
|
53
|
+
* @doc-category Core/Feedback
|
|
54
|
+
* @doc
|
|
55
|
+
* @doc-name alert
|
|
56
|
+
* @doc-description Unstyled in-flow alert that picks the right ARIA live-region role for its severity.
|
|
57
|
+
* @doc-is-main
|
|
58
|
+
*/
|
|
59
|
+
@Directive({
|
|
60
|
+
selector: '[kjAlert]',
|
|
61
|
+
standalone: true,
|
|
62
|
+
exportAs: 'kjAlert',
|
|
63
|
+
providers: [
|
|
64
|
+
{ provide: KJ_ALERT, useExisting: KjAlert },
|
|
65
|
+
],
|
|
66
|
+
host: {
|
|
67
|
+
'[attr.role]': 'role()',
|
|
68
|
+
'[attr.aria-live]': 'live()',
|
|
69
|
+
'[attr.aria-atomic]': 'live() ? "true" : null',
|
|
70
|
+
'[attr.aria-labelledby]': 'titleId()',
|
|
71
|
+
'[attr.aria-describedby]': 'descriptionId()',
|
|
72
|
+
'[attr.data-variant]': 'variant()',
|
|
73
|
+
'[attr.data-size]': 'size()',
|
|
74
|
+
'[attr.data-dismissed]': 'dismissed() ? "true" : null',
|
|
75
|
+
'[attr.data-mode]': 'mode()',
|
|
76
|
+
'[id]': 'alertId()',
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
export class KjAlert implements KjAlertContext {
|
|
80
|
+
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
81
|
+
private readonly preset = inject(KJ_ALERT_CONFIG);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Stable, deterministic id for the alert root. Used to derive
|
|
85
|
+
* `${alertId}-title` / `${alertId}-description` ids from descendant
|
|
86
|
+
* directives. The host's `[id]` is set so descendants can reference it
|
|
87
|
+
* via `aria-labelledby` / `aria-describedby` cross-references.
|
|
88
|
+
*/
|
|
89
|
+
readonly alertId = signal(`kj-alert-${++_alertIdCounter}`);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Severity variant (validated against `KJ_ALERT_CONFIG.variants` in dev).
|
|
93
|
+
* Reflects to `data-variant`. Drives the default `kjAlertMode` resolution
|
|
94
|
+
* (error → assertive, others → polite).
|
|
95
|
+
*
|
|
96
|
+
* Field-annotated explicitly so ng-packagr does not narrow the emitted
|
|
97
|
+
* `.d.ts` write-side to `string` (dropping `string | undefined` flow-through
|
|
98
|
+
* — same trick as `KjButton.kjPressed` and `KjVariant.kjVariant`).
|
|
99
|
+
*/
|
|
100
|
+
readonly kjVariant: InputSignalWithTransform<string, string | undefined> = input(
|
|
101
|
+
this.preset.defaults.variant,
|
|
102
|
+
{ transform: (v?: string) => v || this.preset.defaults.variant },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/** Size token validated against `KJ_ALERT_CONFIG.sizes`. Reflects to `data-size`. */
|
|
106
|
+
readonly kjSize: InputSignalWithTransform<string, string | undefined> = input(
|
|
107
|
+
this.preset.defaults.size,
|
|
108
|
+
{ transform: (v?: string) => v || this.preset.defaults.size },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Explicit override for the role/live matrix. Leave unset to let the
|
|
113
|
+
* directive resolve from `kjAlertStatic` / `kjVariant`.
|
|
114
|
+
*/
|
|
115
|
+
readonly kjAlertMode = input<KjAlertMode | undefined>(undefined);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Marks the alert as a static page-level banner. When `true` and
|
|
119
|
+
* `kjAlertMode` is unset, resolves to `'static'` (`role="region"`,
|
|
120
|
+
* no `aria-live`). The host MUST also carry an accessible name —
|
|
121
|
+
* either `aria-label`, `aria-labelledby`, or a projected
|
|
122
|
+
* `[kjAlertTitle]` (validated in dev mode).
|
|
123
|
+
*/
|
|
124
|
+
readonly kjAlertStatic = input(false, { transform: booleanAttribute });
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Last-resort override for the host `role` attribute. Discouraged —
|
|
128
|
+
* prefer `kjAlertMode`. Only honoured when the resolved mode is
|
|
129
|
+
* `'off'`. Kept for consumers who need a non-standard role
|
|
130
|
+
* (e.g., `role="alertdialog"` is wrong here, but `role="status"`
|
|
131
|
+
* forced on a non-error is valid).
|
|
132
|
+
*/
|
|
133
|
+
readonly kjAlertRole = input<string | undefined>(undefined);
|
|
134
|
+
|
|
135
|
+
/** Fired when the dismiss button (or programmatic `dismiss()`) runs. */
|
|
136
|
+
readonly kjAlertDismissed = output<void>();
|
|
137
|
+
|
|
138
|
+
private readonly _titleIds = signal<readonly string[]>([]);
|
|
139
|
+
private readonly _descriptionIds = signal<readonly string[]>([]);
|
|
140
|
+
private readonly _dismissed = signal(false);
|
|
141
|
+
|
|
142
|
+
/** Resolved variant for context consumers (`KjAlertIcon`, etc.). */
|
|
143
|
+
readonly variant = this.kjVariant;
|
|
144
|
+
|
|
145
|
+
/** Resolved size signal exposed for symmetry — currently used only as `data-size`. */
|
|
146
|
+
readonly size = this.kjSize;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolved mode following the matrix:
|
|
150
|
+
* 1. explicit `kjAlertMode`,
|
|
151
|
+
* 2. `kjAlertStatic` → `'static'`,
|
|
152
|
+
* 3. `error` variant → `'assertive'`,
|
|
153
|
+
* 4. else `'polite'`.
|
|
154
|
+
*/
|
|
155
|
+
readonly mode = computed<KjAlertMode>(() => {
|
|
156
|
+
const explicit = this.kjAlertMode();
|
|
157
|
+
if (explicit) return explicit;
|
|
158
|
+
if (this.kjAlertStatic()) return 'static';
|
|
159
|
+
if (this.variant() === 'error') return 'assertive';
|
|
160
|
+
return 'polite';
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/** Resolved `role` attribute value (or `null` when in `'off'` mode without a `kjAlertRole`). */
|
|
164
|
+
readonly role = computed<string | null>(() => {
|
|
165
|
+
switch (this.mode()) {
|
|
166
|
+
case 'assertive': return 'alert';
|
|
167
|
+
case 'polite': return 'status';
|
|
168
|
+
case 'static': return 'region';
|
|
169
|
+
case 'off': return this.kjAlertRole() ?? null;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/** Resolved `aria-live` value, or `null` for static / off. */
|
|
174
|
+
readonly live = computed<'assertive' | 'polite' | null>(() => {
|
|
175
|
+
const m = this.mode();
|
|
176
|
+
return m === 'assertive' ? 'assertive'
|
|
177
|
+
: m === 'polite' ? 'polite'
|
|
178
|
+
: null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/** First registered title id (most recent registration wins, but only one is expected). */
|
|
182
|
+
readonly titleId = computed<string | null>(() => this._titleIds()[0] ?? null);
|
|
183
|
+
|
|
184
|
+
/** First registered description id. */
|
|
185
|
+
readonly descriptionId = computed<string | null>(() => this._descriptionIds()[0] ?? null);
|
|
186
|
+
|
|
187
|
+
/** Brief flag set on `dismiss()` so themes can animate exit. */
|
|
188
|
+
readonly dismissed = this._dismissed.asReadonly();
|
|
189
|
+
|
|
190
|
+
registerTitle(id: string): void {
|
|
191
|
+
this._titleIds.update(ids => ids.includes(id) ? ids : [...ids, id]);
|
|
192
|
+
}
|
|
193
|
+
unregisterTitle(id: string): void {
|
|
194
|
+
this._titleIds.update(ids => ids.filter(x => x !== id));
|
|
195
|
+
}
|
|
196
|
+
registerDescription(id: string): void {
|
|
197
|
+
this._descriptionIds.update(ids => ids.includes(id) ? ids : [...ids, id]);
|
|
198
|
+
}
|
|
199
|
+
unregisterDescription(id: string): void {
|
|
200
|
+
this._descriptionIds.update(ids => ids.filter(x => x !== id));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Marks the alert as dismissed and emits `kjAlertDismissed`. The consumer
|
|
205
|
+
* is responsible for unmounting (this directive does not remove itself).
|
|
206
|
+
*/
|
|
207
|
+
dismiss(): void {
|
|
208
|
+
if (this._dismissed()) return;
|
|
209
|
+
this._dismissed.set(true);
|
|
210
|
+
this.kjAlertDismissed.emit();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
constructor() {
|
|
214
|
+
if (isDevMode()) {
|
|
215
|
+
effect(() => {
|
|
216
|
+
const v = this.kjVariant();
|
|
217
|
+
if (!this.preset.variants.includes(v)) {
|
|
218
|
+
console.warn(
|
|
219
|
+
`[kj-alert] unknown variant "${v}". Allowed values: ${this.preset.variants.join(', ')}.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
effect(() => {
|
|
224
|
+
const s = this.kjSize();
|
|
225
|
+
if (!this.preset.sizes.includes(s)) {
|
|
226
|
+
console.warn(
|
|
227
|
+
`[kj-alert] unknown size "${s}". Allowed values: ${this.preset.sizes.join(', ')}.`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
effect(() => {
|
|
233
|
+
const m = this.mode();
|
|
234
|
+
const node = this.el.nativeElement;
|
|
235
|
+
|
|
236
|
+
// 1. Static mode without an accessible name → unlabelled region.
|
|
237
|
+
if (m === 'static') {
|
|
238
|
+
const hasAriaLabel = node.hasAttribute('aria-label');
|
|
239
|
+
const hasAriaLabelledby = node.hasAttribute('aria-labelledby') || this.titleId() != null;
|
|
240
|
+
if (!hasAriaLabel && !hasAriaLabelledby) {
|
|
241
|
+
console.warn(
|
|
242
|
+
'[kj-alert] static mode requires an accessible name — add aria-label, ' +
|
|
243
|
+
'aria-labelledby, or a [kjAlertTitle] child.',
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 2. No accessible content at all.
|
|
249
|
+
if (this.titleId() == null && this.descriptionId() == null && !node.textContent?.trim()) {
|
|
250
|
+
console.warn('[kj-alert] alert has no accessible content (no title, description, or text).');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 3. Assertive + success — almost certainly a misuse.
|
|
254
|
+
if (m === 'assertive' && this.variant() === 'success') {
|
|
255
|
+
console.warn(
|
|
256
|
+
'[kj-alert] assertive mode with success variant is unusual — success messages ' +
|
|
257
|
+
'should not interrupt screen reader speech.',
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Marks an element as the alert title. Generates `${alertId}-title`,
|
|
267
|
+
* registers with the `KJ_ALERT` context so the root reflects
|
|
268
|
+
* `aria-labelledby`. Apply to a heading element (`<h3 kjAlertTitle>`)
|
|
269
|
+
* when the alert is in a content region.
|
|
270
|
+
*
|
|
271
|
+
* @doc-category Core/Feedback
|
|
272
|
+
* @doc
|
|
273
|
+
* @doc-name alert
|
|
274
|
+
*/
|
|
275
|
+
@Directive({
|
|
276
|
+
selector: '[kjAlertTitle]',
|
|
277
|
+
standalone: true,
|
|
278
|
+
host: {
|
|
279
|
+
'[attr.id]': 'titleId',
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
export class KjAlertTitle implements OnDestroy {
|
|
283
|
+
private readonly ctx = inject(KJ_ALERT);
|
|
284
|
+
readonly titleId = `${this.ctx.alertId()}-title`;
|
|
285
|
+
|
|
286
|
+
constructor() {
|
|
287
|
+
this.ctx.registerTitle(this.titleId);
|
|
288
|
+
inject(ElementRef); // ensure host element is bound before destroy
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
ngOnDestroy(): void {
|
|
292
|
+
this.ctx.unregisterTitle(this.titleId);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Marks an element as the alert description. Generates
|
|
298
|
+
* `${alertId}-description`, registers with `KJ_ALERT` for
|
|
299
|
+
* `aria-describedby`. Same id-registration pattern as `KjAlertTitle`.
|
|
300
|
+
*
|
|
301
|
+
* @doc-category Core/Feedback
|
|
302
|
+
* @doc
|
|
303
|
+
* @doc-name alert
|
|
304
|
+
*/
|
|
305
|
+
@Directive({
|
|
306
|
+
selector: '[kjAlertDescription]',
|
|
307
|
+
standalone: true,
|
|
308
|
+
host: {
|
|
309
|
+
'[attr.id]': 'descriptionId',
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
export class KjAlertDescription implements OnDestroy {
|
|
313
|
+
private readonly ctx = inject(KJ_ALERT);
|
|
314
|
+
readonly descriptionId = `${this.ctx.alertId()}-description`;
|
|
315
|
+
|
|
316
|
+
constructor() {
|
|
317
|
+
this.ctx.registerDescription(this.descriptionId);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
ngOnDestroy(): void {
|
|
321
|
+
this.ctx.unregisterDescription(this.descriptionId);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Decorative icon slot for an alert. Sets `aria-hidden="true"` (the
|
|
327
|
+
* meaning is carried by the title/description) and mirrors the
|
|
328
|
+
* resolved variant as `data-variant` so themes can swap glyphs without
|
|
329
|
+
* the consumer re-typing it.
|
|
330
|
+
*
|
|
331
|
+
* @doc-category Core/Feedback
|
|
332
|
+
* @doc
|
|
333
|
+
* @doc-name alert
|
|
334
|
+
*/
|
|
335
|
+
@Directive({
|
|
336
|
+
selector: '[kjAlertIcon]',
|
|
337
|
+
standalone: true,
|
|
338
|
+
host: {
|
|
339
|
+
'[attr.aria-hidden]': '"true"',
|
|
340
|
+
'[attr.data-variant]': 'variant()',
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
export class KjAlertIcon {
|
|
344
|
+
private readonly ctx = inject(KJ_ALERT);
|
|
345
|
+
readonly variant = this.ctx.variant;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Semantic group for action buttons inside an alert. Sets
|
|
350
|
+
* `role="group"` on the host and an overridable
|
|
351
|
+
* `aria-label="Alert actions"` so AT users can skip past the
|
|
352
|
+
* actions container as a unit.
|
|
353
|
+
*
|
|
354
|
+
* @doc-category Core/Feedback
|
|
355
|
+
* @doc
|
|
356
|
+
* @doc-name alert
|
|
357
|
+
*/
|
|
358
|
+
@Directive({
|
|
359
|
+
selector: '[kjAlertActions]',
|
|
360
|
+
standalone: true,
|
|
361
|
+
host: {
|
|
362
|
+
'role': 'group',
|
|
363
|
+
'[attr.aria-label]': 'kjAlertActionsLabel()',
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
export class KjAlertActions {
|
|
367
|
+
/** AT label for the group. Localisable. */
|
|
368
|
+
readonly kjAlertActionsLabel = input<string>('Alert actions');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Dismiss button for an alert. Composes `KjButton` via host directives
|
|
373
|
+
* so the button gets the full focus-ring / aria-disabled / capture-phase
|
|
374
|
+
* click contract. Calls `KJ_ALERT.dismiss()` on click — the consumer's
|
|
375
|
+
* own `(click)` listener still fires (e.g. for analytics).
|
|
376
|
+
*
|
|
377
|
+
* Aliased inputs: `kjAlertDismissLabel` → `KjButton.kjAriaLabel`,
|
|
378
|
+
* `kjAlertDismissVariant` → `KjButton.kjVariant`,
|
|
379
|
+
* `kjAlertDismissSize` → `KjButton.kjSize`.
|
|
380
|
+
*
|
|
381
|
+
* @doc-category Core/Feedback
|
|
382
|
+
* @doc
|
|
383
|
+
* @doc-name alert
|
|
384
|
+
*/
|
|
385
|
+
@Directive({
|
|
386
|
+
selector: '[kjAlertDismiss]',
|
|
387
|
+
standalone: true,
|
|
388
|
+
hostDirectives: [KjButton],
|
|
389
|
+
host: {
|
|
390
|
+
'[attr.aria-label]': 'kjAlertDismissLabel()',
|
|
391
|
+
'[attr.data-variant]': 'kjAlertDismissVariant()',
|
|
392
|
+
'[attr.data-size]': 'kjAlertDismissSize()',
|
|
393
|
+
'(click)': 'onClick()',
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
export class KjAlertDismiss {
|
|
397
|
+
private readonly ctx = inject(KJ_ALERT);
|
|
398
|
+
|
|
399
|
+
/** AT label for the dismiss button. */
|
|
400
|
+
readonly kjAlertDismissLabel = input<string>('Dismiss');
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Variant token mirrored as `data-variant`. Defaults to `'ghost'` so themes
|
|
404
|
+
* pick up the unobtrusive close-button surface. Mirrored directly rather
|
|
405
|
+
* than aliased through `KjButton`'s host-directive `kjVariant` input
|
|
406
|
+
* because Angular does not let `hostDirectives.inputs` alias an input that
|
|
407
|
+
* is itself only exposed via a nested host directive.
|
|
408
|
+
*/
|
|
409
|
+
readonly kjAlertDismissVariant = input<string>('ghost');
|
|
410
|
+
|
|
411
|
+
/** Size token mirrored as `data-size`. Defaults to `'icon'` for the 44×44 touch target. */
|
|
412
|
+
readonly kjAlertDismissSize = input<string>('icon');
|
|
413
|
+
|
|
414
|
+
onClick(): void {
|
|
415
|
+
this.ctx.dismiss();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { InjectionToken, Provider } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shape of the Alert preset configuration. Mirrors `KjButtonConfig` —
|
|
5
|
+
* `bindPresets(KJ_ALERT_CONFIG)` translates this into the shared
|
|
6
|
+
* `KJ_VARIANT_PRESET` / `KJ_SIZE_PRESET` tokens consumed by the
|
|
7
|
+
* `KjVariant` / `KjSize` host directives composed on `KjAlert`.
|
|
8
|
+
*/
|
|
9
|
+
export interface KjAlertConfig {
|
|
10
|
+
variants: string[];
|
|
11
|
+
sizes: string[];
|
|
12
|
+
defaults: { variant: string; size: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default Alert presets shipped by kouji-ui. Severities follow the APG /
|
|
17
|
+
* shadcn / daisyUI taxonomy. Spread to extend:
|
|
18
|
+
* `[...KJ_ALERT_DEFAULTS.variants, 'brand']`.
|
|
19
|
+
*/
|
|
20
|
+
export const KJ_ALERT_DEFAULTS: KjAlertConfig = {
|
|
21
|
+
variants: ['info', 'success', 'warning', 'error', 'neutral'],
|
|
22
|
+
sizes: ['sm', 'md', 'lg'],
|
|
23
|
+
defaults: { variant: 'info', size: 'md' },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* DI token for the active Alert presets. Default factory yields
|
|
28
|
+
* `KJ_ALERT_DEFAULTS`. Override via `provideKjAlert(…)`.
|
|
29
|
+
*/
|
|
30
|
+
export const KJ_ALERT_CONFIG = new InjectionToken<KjAlertConfig>('kj.alert.config', {
|
|
31
|
+
factory: () => KJ_ALERT_DEFAULTS,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configures the Alert presets for the enclosing injector. Replaces
|
|
36
|
+
* (does not merge) `variants` / `sizes`; spread `KJ_ALERT_DEFAULTS.variants`
|
|
37
|
+
* to extend.
|
|
38
|
+
*/
|
|
39
|
+
export function provideKjAlert(config: Partial<KjAlertConfig>): Provider[] {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
provide: KJ_ALERT_CONFIG,
|
|
43
|
+
useValue: { ...KJ_ALERT_DEFAULTS, ...config },
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
KjAlert,
|
|
3
|
+
KjAlertTitle,
|
|
4
|
+
KjAlertDescription,
|
|
5
|
+
KjAlertIcon,
|
|
6
|
+
KjAlertActions,
|
|
7
|
+
KjAlertDismiss,
|
|
8
|
+
} from './alert';
|
|
9
|
+
export {
|
|
10
|
+
KJ_ALERT,
|
|
11
|
+
type KjAlertContext,
|
|
12
|
+
type KjAlertMode,
|
|
13
|
+
} from './alert.context';
|
|
14
|
+
export {
|
|
15
|
+
KJ_ALERT_CONFIG,
|
|
16
|
+
KJ_ALERT_DEFAULTS,
|
|
17
|
+
type KjAlertConfig,
|
|
18
|
+
provideKjAlert,
|
|
19
|
+
} from './config';
|