@kouji-ui/core 0.0.4 → 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 +7 -4
- package/CHANGELOG.md +169 -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 +11 -3
- 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 +101 -42
- 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 +166 -14
- package/src/toast/toast.strategy.ts +10 -0
- package/src/toast/toast.ts +177 -21
- 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 -106
- 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
package/src/chat/chat.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DestroyRef,
|
|
3
|
+
Directive,
|
|
4
|
+
ElementRef,
|
|
5
|
+
Injector,
|
|
6
|
+
afterNextRender,
|
|
7
|
+
computed,
|
|
8
|
+
effect,
|
|
9
|
+
inject,
|
|
10
|
+
input,
|
|
11
|
+
signal,
|
|
12
|
+
} from '@angular/core';
|
|
13
|
+
import {
|
|
14
|
+
KJ_CHAT,
|
|
15
|
+
KJ_CHAT_LOG,
|
|
16
|
+
type KjChatContext,
|
|
17
|
+
type KjChatRole,
|
|
18
|
+
type KjChatSide,
|
|
19
|
+
} from './chat.context';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Element → directive map used by `KjChat` to walk previous siblings without
|
|
23
|
+
* relying on Angular dev-mode internals. Each row registers itself on its host
|
|
24
|
+
* element on construction and unregisters on destroy.
|
|
25
|
+
*/
|
|
26
|
+
const CHAT_ROW_REGISTRY = new WeakMap<Element, KjChat>();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A single chat row. Owns the logical side, the per-row role, and the
|
|
30
|
+
* group-by-sender computation; reflects header / footer ids registered by
|
|
31
|
+
* its children for `aria-labelledby` / `aria-describedby` wiring.
|
|
32
|
+
*
|
|
33
|
+
* **Grouping uses DOM traversal, not a registry**, deliberately: the registry
|
|
34
|
+
* pattern races on the bottom-up `ngOnInit` order (children before parent),
|
|
35
|
+
* which means the first row could ask for its previous sibling before the
|
|
36
|
+
* previous row has registered. DOM traversal via `previousElementSibling` is
|
|
37
|
+
* deterministic and works at first render.
|
|
38
|
+
*
|
|
39
|
+
* Standalone rows (no `KjChatLog` parent) skip auto-grouping entirely;
|
|
40
|
+
* consumers set `kjChatGrouped` explicitly when they want grouped chrome.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```html
|
|
44
|
+
* <div kjChat kjSide="start" kjChatAuthor="alice">
|
|
45
|
+
* <span kjChatAvatar>…</span>
|
|
46
|
+
* <header kjChatHeader>Alice 12:46</header>
|
|
47
|
+
* <p kjChatBubble>Hey!</p>
|
|
48
|
+
* <footer kjChatFooter kjState="read">Read 12:46</footer>
|
|
49
|
+
* </div>
|
|
50
|
+
* ```
|
|
51
|
+
* @doc-category Core/Data display
|
|
52
|
+
* @doc
|
|
53
|
+
* @doc-name chat
|
|
54
|
+
* @doc-description Unstyled chat row with side, sender grouping, and ARIA wiring for message log semantics.
|
|
55
|
+
* @doc-is-main
|
|
56
|
+
*/
|
|
57
|
+
@Directive({
|
|
58
|
+
selector: '[kjChat]',
|
|
59
|
+
standalone: true,
|
|
60
|
+
providers: [{ provide: KJ_CHAT, useExisting: KjChat }],
|
|
61
|
+
host: {
|
|
62
|
+
'[attr.role]': 'kjRole()',
|
|
63
|
+
'[attr.data-side]': 'kjSide()',
|
|
64
|
+
'[attr.data-grouped]': 'grouped() ? "" : null',
|
|
65
|
+
'[attr.aria-labelledby]': 'headerId() ?? null',
|
|
66
|
+
'[attr.aria-describedby]': 'footerId() ?? null',
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
export class KjChat implements KjChatContext {
|
|
70
|
+
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
71
|
+
private readonly injector = inject(Injector);
|
|
72
|
+
private readonly destroyRef = inject(DestroyRef);
|
|
73
|
+
private readonly log = inject(KJ_CHAT_LOG, { optional: true });
|
|
74
|
+
|
|
75
|
+
/** Logical side of this row. CSS reads `data-side` and handles RTL flip. */
|
|
76
|
+
readonly kjSide = input<KjChatSide>('start');
|
|
77
|
+
|
|
78
|
+
/** Sender id — drives auto-grouping with the previous sibling row. */
|
|
79
|
+
readonly kjChatAuthor = input<string | undefined>(undefined);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Explicit group override. When defined, wins over the auto-computed value;
|
|
83
|
+
* use this when no `KjChatLog` parent is present.
|
|
84
|
+
*/
|
|
85
|
+
readonly kjChatGrouped = input<boolean | undefined>(undefined);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Per-row role. Default `'article'` gives AT users a per-message landmark
|
|
89
|
+
* navigable via JAWS / NVDA `M`; `'listitem'` opts in to list semantics
|
|
90
|
+
* (parent log must then expose `role="list"`); `null` drops the attribute.
|
|
91
|
+
*/
|
|
92
|
+
readonly kjRole = input<KjChatRole>('article');
|
|
93
|
+
|
|
94
|
+
private readonly headerIds = signal<readonly string[]>([]);
|
|
95
|
+
private readonly footerIds = signal<readonly string[]>([]);
|
|
96
|
+
|
|
97
|
+
/** Composed `aria-labelledby` value. */
|
|
98
|
+
readonly headerId = computed(() => {
|
|
99
|
+
const ids = this.headerIds();
|
|
100
|
+
return ids.length === 0 ? null : ids.join(' ');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** Composed `aria-describedby` value. */
|
|
104
|
+
readonly footerId = computed(() => {
|
|
105
|
+
const ids = this.footerIds();
|
|
106
|
+
return ids.length === 0 ? null : ids.join(' ');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
private readonly autoGrouped = signal(false);
|
|
110
|
+
|
|
111
|
+
/** True when this row groups with the previous one (same author). */
|
|
112
|
+
readonly grouped = computed(
|
|
113
|
+
() => this.kjChatGrouped() ?? this.autoGrouped(),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
constructor() {
|
|
117
|
+
// Registry write happens in the constructor so any sibling rendered
|
|
118
|
+
// *after* us finds us during its own afterNextRender pass. (Angular
|
|
119
|
+
// builds child instances bottom-up, but DOM siblings are ordered
|
|
120
|
+
// top-down — by the time the second row's afterNextRender fires,
|
|
121
|
+
// the first row's host element exists and is keyed in the WeakMap.)
|
|
122
|
+
CHAT_ROW_REGISTRY.set(this.el.nativeElement, this);
|
|
123
|
+
this.destroyRef.onDestroy(() =>
|
|
124
|
+
CHAT_ROW_REGISTRY.delete(this.el.nativeElement),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// DOM-traversal grouping: run after first render (so the previous
|
|
128
|
+
// sibling exists) and re-run whenever this row's author changes.
|
|
129
|
+
afterNextRender(() => {
|
|
130
|
+
this.recomputeAutoGrouped();
|
|
131
|
+
});
|
|
132
|
+
effect(
|
|
133
|
+
() => {
|
|
134
|
+
// Track the input — touching it inside the effect creates the dep.
|
|
135
|
+
this.kjChatAuthor();
|
|
136
|
+
this.recomputeAutoGrouped();
|
|
137
|
+
},
|
|
138
|
+
{ injector: this.injector },
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @internal */
|
|
143
|
+
registerHeaderId(id: string): void {
|
|
144
|
+
this.headerIds.update(ids => [...ids, id]);
|
|
145
|
+
this.destroyRef.onDestroy(() => this.unregisterHeaderId(id));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** @internal */
|
|
149
|
+
unregisterHeaderId(id: string): void {
|
|
150
|
+
this.headerIds.update(ids => ids.filter(x => x !== id));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @internal */
|
|
154
|
+
registerFooterId(id: string): void {
|
|
155
|
+
this.footerIds.update(ids => [...ids, id]);
|
|
156
|
+
this.destroyRef.onDestroy(() => this.unregisterFooterId(id));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @internal */
|
|
160
|
+
unregisterFooterId(id: string): void {
|
|
161
|
+
this.footerIds.update(ids => ids.filter(x => x !== id));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private recomputeAutoGrouped(): void {
|
|
165
|
+
if (!this.log) {
|
|
166
|
+
this.autoGrouped.set(false);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const me = this.kjChatAuthor();
|
|
170
|
+
if (me === undefined) {
|
|
171
|
+
this.autoGrouped.set(false);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const prev = this.previousChatRow();
|
|
175
|
+
if (!prev) {
|
|
176
|
+
this.autoGrouped.set(false);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.autoGrouped.set(prev.kjChatAuthor() === me);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private previousChatRow(): KjChat | null {
|
|
183
|
+
let sib = this.el.nativeElement.previousElementSibling as Element | null;
|
|
184
|
+
while (sib) {
|
|
185
|
+
const row = CHAT_ROW_REGISTRY.get(sib);
|
|
186
|
+
if (row) return row;
|
|
187
|
+
sib = sib.previousElementSibling;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { InjectionToken, Provider } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/** Shape of the bubble's preset configuration. */
|
|
4
|
+
export interface KjChatBubbleConfig {
|
|
5
|
+
variants: string[];
|
|
6
|
+
sizes: string[];
|
|
7
|
+
defaults: { variant: string; size: string };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default Chat-Bubble presets shipped by kouji-ui. Exported so consumers can
|
|
12
|
+
* spread when extending: `[...KJ_CHAT_BUBBLE_DEFAULTS.variants, 'brand']`.
|
|
13
|
+
*/
|
|
14
|
+
export const KJ_CHAT_BUBBLE_DEFAULTS: KjChatBubbleConfig = {
|
|
15
|
+
variants: [
|
|
16
|
+
'default',
|
|
17
|
+
'primary',
|
|
18
|
+
'secondary',
|
|
19
|
+
'accent',
|
|
20
|
+
'info',
|
|
21
|
+
'success',
|
|
22
|
+
'warning',
|
|
23
|
+
'error',
|
|
24
|
+
],
|
|
25
|
+
sizes: ['sm', 'md', 'lg'],
|
|
26
|
+
defaults: { variant: 'default', size: 'md' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* DI token for the active Chat-Bubble presets. Default factory yields
|
|
31
|
+
* `KJ_CHAT_BUBBLE_DEFAULTS`. Override via `provideKjChatBubble(…)` at the
|
|
32
|
+
* application or component scope.
|
|
33
|
+
*/
|
|
34
|
+
export const KJ_CHAT_BUBBLE_CONFIG = new InjectionToken<KjChatBubbleConfig>(
|
|
35
|
+
'kj.chat-bubble.config',
|
|
36
|
+
{ factory: () => KJ_CHAT_BUBBLE_DEFAULTS },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configures the Chat-Bubble presets for the enclosing injector. Replaces
|
|
41
|
+
* (does not merge) `variants` and `sizes`; spread `KJ_CHAT_BUBBLE_DEFAULTS.variants`
|
|
42
|
+
* to extend.
|
|
43
|
+
*/
|
|
44
|
+
export function provideKjChatBubble(
|
|
45
|
+
config: Partial<KjChatBubbleConfig>,
|
|
46
|
+
): Provider[] {
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
provide: KJ_CHAT_BUBBLE_CONFIG,
|
|
50
|
+
useValue: { ...KJ_CHAT_BUBBLE_DEFAULTS, ...config },
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { KjChatLog } from './chat-log';
|
|
2
|
+
export { KjChat } from './chat';
|
|
3
|
+
export { KjChatAvatar } from './chat-avatar';
|
|
4
|
+
export { KjChatHeader } from './chat-header';
|
|
5
|
+
export { KjChatBubble } from './chat-bubble';
|
|
6
|
+
export { KjChatFooter } from './chat-footer';
|
|
7
|
+
export {
|
|
8
|
+
KJ_CHAT,
|
|
9
|
+
KJ_CHAT_LOG,
|
|
10
|
+
type KjChatContext,
|
|
11
|
+
type KjChatLogContext,
|
|
12
|
+
type KjChatRole,
|
|
13
|
+
type KjChatSide,
|
|
14
|
+
type KjChatState,
|
|
15
|
+
} from './chat.context';
|
|
16
|
+
export {
|
|
17
|
+
KJ_CHAT_BUBBLE_CONFIG,
|
|
18
|
+
KJ_CHAT_BUBBLE_DEFAULTS,
|
|
19
|
+
provideKjChatBubble,
|
|
20
|
+
type KjChatBubbleConfig,
|
|
21
|
+
} from './config';
|
package/src/checkbox/checkbox.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { Directive, inject, model } from '@angular/core';
|
|
2
2
|
import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Adds checkbox semantics with Angular forms integration.
|
|
6
6
|
* Supports `formControl`, `formControlName`, and `ngModel` bindings.
|
|
7
7
|
*
|
|
8
|
+
* Apply `[kjCheckbox]` to any focusable element to get `role="checkbox"`,
|
|
9
|
+
* `aria-checked` reflection, Space/click toggle, and the shared focus-ring
|
|
10
|
+
* primitive — all composable onto custom markup with no styling attached.
|
|
11
|
+
*
|
|
8
12
|
* @example
|
|
9
13
|
* ```html
|
|
10
14
|
* <div kjCheckbox tabindex="0" [(kjChecked)]="accepted" aria-label="Accept terms">Accept</div>
|
|
@@ -44,7 +48,10 @@ import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
|
|
|
44
48
|
* newsletter = signal(true);
|
|
45
49
|
* }
|
|
46
50
|
* ```
|
|
47
|
-
* @category Core/Inputs
|
|
51
|
+
* @doc-category Core/Inputs
|
|
52
|
+
* @doc-name checkbox
|
|
53
|
+
* @doc-description Adds accessible checkbox behaviour and Angular forms support to any element you bring.
|
|
54
|
+
* @doc-is-main
|
|
48
55
|
*/
|
|
49
56
|
@Directive({
|
|
50
57
|
selector: '[kjCheckbox]',
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { InjectionToken, Signal } from '@angular/core';
|
|
2
|
+
import type { KjHsl, KjHsv, KjRgb } from './color-picker.utils';
|
|
3
|
+
|
|
4
|
+
/** Output formats supported by `KjColorPicker`. */
|
|
5
|
+
export type KjColorFormat = 'hex' | 'rgb' | 'hsl';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Public emitted value for `KjColorPicker`.
|
|
9
|
+
* Concrete shape depends on the active `kjFormat`:
|
|
10
|
+
* - `'hex'` → `string` (`'#rrggbb'` or `'#rrggbbaa'` when alpha is on).
|
|
11
|
+
* - `'rgb'` → `[r, g, b]` or `[r, g, b, a]` when alpha is on.
|
|
12
|
+
* - `'hsl'` → `{ h, s, l }` or `{ h, s, l, a }` when alpha is on.
|
|
13
|
+
*/
|
|
14
|
+
export type KjColorValue =
|
|
15
|
+
| string
|
|
16
|
+
| readonly [number, number, number]
|
|
17
|
+
| readonly [number, number, number, number]
|
|
18
|
+
| { h: number; s: number; l: number }
|
|
19
|
+
| { h: number; s: number; l: number; a: number };
|
|
20
|
+
|
|
21
|
+
/** A single preset swatch in the color picker's palette grid. */
|
|
22
|
+
export interface KjColorPreset {
|
|
23
|
+
/** Any parseable hex color string. */
|
|
24
|
+
readonly value: string;
|
|
25
|
+
/** Optional accessible name announced to AT in place of the raw hex. */
|
|
26
|
+
readonly label?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Shared state container exposed to every `KjColorPicker` sub-directive
|
|
31
|
+
* via the `KJ_COLOR_PICKER` token. The root directive is the sole writer;
|
|
32
|
+
* sub-directives read derived signals and call mutator methods.
|
|
33
|
+
*/
|
|
34
|
+
export interface KjColorPickerContext {
|
|
35
|
+
// Canonical state
|
|
36
|
+
readonly hue: Signal<number>;
|
|
37
|
+
readonly saturation: Signal<number>;
|
|
38
|
+
readonly value: Signal<number>;
|
|
39
|
+
readonly alpha: Signal<number>;
|
|
40
|
+
|
|
41
|
+
// Derived projections
|
|
42
|
+
readonly hex: Signal<string>;
|
|
43
|
+
readonly rgb: Signal<KjRgb>;
|
|
44
|
+
readonly hsv: Signal<KjHsv>;
|
|
45
|
+
readonly hsl: Signal<KjHsl>;
|
|
46
|
+
|
|
47
|
+
// Mutators
|
|
48
|
+
setHue(h: number): void;
|
|
49
|
+
setSaturationValue(s: number, v: number): void;
|
|
50
|
+
setAlpha(a: number): void;
|
|
51
|
+
setHex(hex: string): boolean;
|
|
52
|
+
setRgb(r: number, g: number, b: number, a?: number): void;
|
|
53
|
+
|
|
54
|
+
// UI state
|
|
55
|
+
readonly open: Signal<boolean>;
|
|
56
|
+
readonly disabled: Signal<boolean>;
|
|
57
|
+
readonly format: Signal<KjColorFormat>;
|
|
58
|
+
readonly showAlpha: Signal<boolean>;
|
|
59
|
+
readonly presets: Signal<readonly KjColorPreset[]>;
|
|
60
|
+
setOpen(open: boolean): void;
|
|
61
|
+
|
|
62
|
+
// Wiring ids
|
|
63
|
+
readonly panelId: Signal<string>;
|
|
64
|
+
readonly triggerId: Signal<string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* DI token consumed by every `KjColorPicker*` sub-directive. Provided by
|
|
69
|
+
* the root `KjColorPicker` directive — sub-directives outside that
|
|
70
|
+
* context throw at injection time in dev.
|
|
71
|
+
*/
|
|
72
|
+
export const KJ_COLOR_PICKER = new InjectionToken<KjColorPickerContext>(
|
|
73
|
+
'KjColorPicker',
|
|
74
|
+
);
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { render, fireEvent } from '@testing-library/angular';
|
|
2
|
+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
4
|
+
import {
|
|
5
|
+
KjColorPicker,
|
|
6
|
+
KjColorPickerAlphaSlider,
|
|
7
|
+
KjColorPickerArea,
|
|
8
|
+
KjColorPickerHueSlider,
|
|
9
|
+
KjColorPickerInput,
|
|
10
|
+
KjColorPickerPanel,
|
|
11
|
+
KjColorPickerTrigger,
|
|
12
|
+
} from './color-picker';
|
|
13
|
+
import {
|
|
14
|
+
kjHsvToRgb,
|
|
15
|
+
kjParseHex,
|
|
16
|
+
kjRgbToHex,
|
|
17
|
+
kjRgbToHsv,
|
|
18
|
+
} from './color-picker.utils';
|
|
19
|
+
|
|
20
|
+
expect.extend(toHaveNoViolations);
|
|
21
|
+
|
|
22
|
+
const directiveImports = [
|
|
23
|
+
KjColorPicker,
|
|
24
|
+
KjColorPickerTrigger,
|
|
25
|
+
KjColorPickerPanel,
|
|
26
|
+
KjColorPickerArea,
|
|
27
|
+
KjColorPickerHueSlider,
|
|
28
|
+
KjColorPickerAlphaSlider,
|
|
29
|
+
KjColorPickerInput,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const fullTemplate = `
|
|
33
|
+
<div kjColorPicker [formControl]="ctrl" [kjShowAlpha]="showAlpha">
|
|
34
|
+
<button kjColorPickerTrigger>open</button>
|
|
35
|
+
<div kjColorPickerPanel>
|
|
36
|
+
<div kjColorPickerArea></div>
|
|
37
|
+
<input kjColorPickerHueSlider />
|
|
38
|
+
<input kjColorPickerAlphaSlider />
|
|
39
|
+
<input kjColorPickerInput />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
/** When `bodyPortal` mounts the panel out to <body>, search there too. */
|
|
45
|
+
function findPanel(container: HTMLElement): HTMLElement {
|
|
46
|
+
return (
|
|
47
|
+
container.querySelector('[kjColorPickerPanel]') ??
|
|
48
|
+
document.body.querySelector('[kjColorPickerPanel]')!
|
|
49
|
+
) as HTMLElement;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('color-picker utils', () => {
|
|
53
|
+
it('parses 6-char hex with leading hash', () => {
|
|
54
|
+
expect(kjParseHex('#ff0000')).toEqual({ r: 255, g: 0, b: 0, a: 1 });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('parses 3-char hex without leading hash', () => {
|
|
58
|
+
expect(kjParseHex('f00')).toEqual({ r: 255, g: 0, b: 0, a: 1 });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('parses 8-char hex with alpha', () => {
|
|
62
|
+
const r = kjParseHex('#ff000080');
|
|
63
|
+
expect(r?.r).toBe(255);
|
|
64
|
+
expect(r?.g).toBe(0);
|
|
65
|
+
expect(r?.b).toBe(0);
|
|
66
|
+
expect(r?.a).toBeCloseTo(0x80 / 255, 5);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns null for invalid hex', () => {
|
|
70
|
+
expect(kjParseHex('not-a-color')).toBeNull();
|
|
71
|
+
expect(kjParseHex('#xyzxyz')).toBeNull();
|
|
72
|
+
expect(kjParseHex('#ff')).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('round-trips RGB → HSV → RGB for primary red', () => {
|
|
76
|
+
const hsv = kjRgbToHsv({ r: 255, g: 0, b: 0, a: 1 });
|
|
77
|
+
const back = kjHsvToRgb(hsv);
|
|
78
|
+
expect(back.r).toBe(255);
|
|
79
|
+
expect(back.g).toBe(0);
|
|
80
|
+
expect(back.b).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('round-trips RGB → HSV → RGB for arbitrary color', () => {
|
|
84
|
+
const original = { r: 184, g: 245, b: 0, a: 1 };
|
|
85
|
+
const hsv = kjRgbToHsv(original);
|
|
86
|
+
const back = kjHsvToRgb(hsv);
|
|
87
|
+
expect(back.r).toBe(original.r);
|
|
88
|
+
expect(back.g).toBe(original.g);
|
|
89
|
+
expect(back.b).toBe(original.b);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('formats RGB to lowercase six-char hex by default', () => {
|
|
93
|
+
expect(kjRgbToHex({ r: 0xab, g: 0xcd, b: 0xef, a: 1 }, false))
|
|
94
|
+
.toBe('#abcdef');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('formats RGB to eight-char hex when alpha is requested', () => {
|
|
98
|
+
expect(kjRgbToHex({ r: 0, g: 0, b: 0, a: 0.5 }, true))
|
|
99
|
+
.toBe('#00000080');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('KjColorPicker', () => {
|
|
104
|
+
function makeCtrl(initial = '#ff0000') {
|
|
105
|
+
return new FormControl(initial);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
it('panel is hidden by default', async () => {
|
|
109
|
+
const { container } = await render(fullTemplate, {
|
|
110
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
111
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
112
|
+
});
|
|
113
|
+
expect(findPanel(container)).toHaveAttribute('hidden', '');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('clicking the trigger opens the panel', async () => {
|
|
117
|
+
const { container } = await render(fullTemplate, {
|
|
118
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
119
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
120
|
+
});
|
|
121
|
+
fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
|
|
122
|
+
expect(findPanel(container)).not.toHaveAttribute('hidden');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('trigger reflects aria-expanded based on open state', async () => {
|
|
126
|
+
const { container } = await render(fullTemplate, {
|
|
127
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
128
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
129
|
+
});
|
|
130
|
+
const trigger = container.querySelector('[kjColorPickerTrigger]')!;
|
|
131
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
132
|
+
fireEvent.click(trigger);
|
|
133
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('trigger has aria-haspopup="dialog"', async () => {
|
|
137
|
+
const { container } = await render(fullTemplate, {
|
|
138
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
139
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
140
|
+
});
|
|
141
|
+
const trigger = container.querySelector('[kjColorPickerTrigger]')!;
|
|
142
|
+
expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('panel has role="dialog"', async () => {
|
|
146
|
+
const { container } = await render(fullTemplate, {
|
|
147
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
148
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
149
|
+
});
|
|
150
|
+
const panel = findPanel(container);
|
|
151
|
+
expect(panel).toHaveAttribute('role', 'dialog');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('sat/value area has role="slider" with aria-valuetext', async () => {
|
|
155
|
+
const { container } = await render(fullTemplate, {
|
|
156
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
157
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
158
|
+
});
|
|
159
|
+
const area = container.querySelector('[kjColorPickerArea]')!;
|
|
160
|
+
expect(area).toHaveAttribute('role', 'slider');
|
|
161
|
+
expect(area.getAttribute('aria-valuetext')).toMatch(/saturation/i);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('arrow keys on the sat/value area update aria-valuenow', async () => {
|
|
165
|
+
const { container } = await render(fullTemplate, {
|
|
166
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
167
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
168
|
+
});
|
|
169
|
+
fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
|
|
170
|
+
const area = container.querySelector('[kjColorPickerArea]')
|
|
171
|
+
?? document.body.querySelector('[kjColorPickerArea]')!;
|
|
172
|
+
// Initial red has saturation=1; ArrowLeft is the unambiguous direction to move.
|
|
173
|
+
const before = area.getAttribute('aria-valuenow');
|
|
174
|
+
fireEvent.keyDown(area, { key: 'ArrowLeft' });
|
|
175
|
+
const after = area.getAttribute('aria-valuenow');
|
|
176
|
+
expect(after).not.toBe(before);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('hex input commits a parseable value on Enter', async () => {
|
|
180
|
+
const ctrl = makeCtrl();
|
|
181
|
+
const { container, fixture } = await render(fullTemplate, {
|
|
182
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
183
|
+
componentProperties: { ctrl, showAlpha: false },
|
|
184
|
+
});
|
|
185
|
+
fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
|
|
186
|
+
const input = (container.querySelector('[kjColorPickerInput]')
|
|
187
|
+
?? document.body.querySelector('[kjColorPickerInput]')) as HTMLInputElement;
|
|
188
|
+
input.value = '#00ff00';
|
|
189
|
+
fireEvent.input(input, { target: input });
|
|
190
|
+
fireEvent.keyDown(input, { key: 'Enter' });
|
|
191
|
+
fixture.detectChanges();
|
|
192
|
+
expect(ctrl.value).toBe('#00ff00');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('writeValue parses external hex and reflects it on the trigger', async () => {
|
|
196
|
+
const ctrl = makeCtrl('#000000');
|
|
197
|
+
const { container, fixture } = await render(fullTemplate, {
|
|
198
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
199
|
+
componentProperties: { ctrl, showAlpha: false },
|
|
200
|
+
});
|
|
201
|
+
ctrl.setValue('#0000ff');
|
|
202
|
+
fixture.detectChanges();
|
|
203
|
+
await fixture.whenStable();
|
|
204
|
+
fixture.detectChanges();
|
|
205
|
+
const trigger = container.querySelector('[kjColorPickerTrigger]') as HTMLElement;
|
|
206
|
+
expect(trigger.style.getPropertyValue('--kj-color-picker-current'))
|
|
207
|
+
.toBe('#0000ff');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('hue slider has aria-label="Hue" and is a native range input', async () => {
|
|
211
|
+
const { container } = await render(fullTemplate, {
|
|
212
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
213
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
214
|
+
});
|
|
215
|
+
const hue = container.querySelector('[kjColorPickerHueSlider]')!;
|
|
216
|
+
expect(hue).toHaveAttribute('aria-label', 'Hue');
|
|
217
|
+
expect(hue).toHaveAttribute('type', 'range');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('alpha slider mounts but is disabled until kjShowAlpha is true', async () => {
|
|
221
|
+
const { container, fixture } = await render(fullTemplate, {
|
|
222
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
223
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
224
|
+
});
|
|
225
|
+
const alpha = container.querySelector('[kjColorPickerAlphaSlider]') as HTMLInputElement;
|
|
226
|
+
expect(alpha.disabled).toBe(true);
|
|
227
|
+
(fixture.componentInstance as { showAlpha: boolean }).showAlpha = true;
|
|
228
|
+
fixture.detectChanges();
|
|
229
|
+
expect(alpha.disabled).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('panel passes axe audit when open', async () => {
|
|
233
|
+
const { container } = await render(fullTemplate, {
|
|
234
|
+
imports: [...directiveImports, ReactiveFormsModule],
|
|
235
|
+
componentProperties: { ctrl: makeCtrl(), showAlpha: false },
|
|
236
|
+
});
|
|
237
|
+
fireEvent.click(container.querySelector('[kjColorPickerTrigger]')!);
|
|
238
|
+
const results = await axe(document.body);
|
|
239
|
+
expect(results).toHaveNoViolations();
|
|
240
|
+
});
|
|
241
|
+
});
|