@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,223 @@
|
|
|
1
|
+
import { render } from '@testing-library/angular';
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { KjDivider } from './divider';
|
|
5
|
+
|
|
6
|
+
expect.extend(toHaveNoViolations);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Flush microtasks + the rAF that backs afterNextRender so the directive has
|
|
10
|
+
* a chance to inspect host child nodes and update its with-content / isHr
|
|
11
|
+
* signals.
|
|
12
|
+
*/
|
|
13
|
+
async function flushAfterNextRender(): Promise<void> {
|
|
14
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
15
|
+
await Promise.resolve();
|
|
16
|
+
await Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('KjDivider', () => {
|
|
20
|
+
describe('decorative (default)', () => {
|
|
21
|
+
it('on <hr> host: aria-hidden="true" and no role', async () => {
|
|
22
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
23
|
+
await flushAfterNextRender();
|
|
24
|
+
const host = container.querySelector('hr')!;
|
|
25
|
+
expect(host).toHaveAttribute('aria-hidden', 'true');
|
|
26
|
+
expect(host).not.toHaveAttribute('role');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('on <div> host: aria-hidden="true" and no role', async () => {
|
|
30
|
+
const { container } = await render(`<div kjDivider></div>`, { imports: [KjDivider] });
|
|
31
|
+
await flushAfterNextRender();
|
|
32
|
+
const host = container.querySelector('div')!;
|
|
33
|
+
expect(host).toHaveAttribute('aria-hidden', 'true');
|
|
34
|
+
expect(host).not.toHaveAttribute('role');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('omits aria-orientation in decorative mode (even when vertical)', async () => {
|
|
38
|
+
const { container } = await render(
|
|
39
|
+
`<hr kjDivider [kjOrientation]="'vertical'" />`,
|
|
40
|
+
{ imports: [KjDivider] },
|
|
41
|
+
);
|
|
42
|
+
await flushAfterNextRender();
|
|
43
|
+
const host = container.querySelector('hr')!;
|
|
44
|
+
expect(host).not.toHaveAttribute('aria-orientation');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('structural mode', () => {
|
|
49
|
+
it('on <div> host: role="separator" and no aria-hidden', async () => {
|
|
50
|
+
const { container } = await render(
|
|
51
|
+
`<div kjDivider [kjStructural]="true"></div>`,
|
|
52
|
+
{ imports: [KjDivider] },
|
|
53
|
+
);
|
|
54
|
+
await flushAfterNextRender();
|
|
55
|
+
const host = container.querySelector('div')!;
|
|
56
|
+
expect(host).toHaveAttribute('role', 'separator');
|
|
57
|
+
expect(host).not.toHaveAttribute('aria-hidden');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('on <hr> host: relies on implicit role (no explicit role attr) and no aria-hidden', async () => {
|
|
61
|
+
const { container } = await render(
|
|
62
|
+
`<hr kjDivider [kjStructural]="true" />`,
|
|
63
|
+
{ imports: [KjDivider] },
|
|
64
|
+
);
|
|
65
|
+
await flushAfterNextRender();
|
|
66
|
+
const host = container.querySelector('hr')!;
|
|
67
|
+
// <hr> has implicit role="separator" per HTML AAM; the directive must
|
|
68
|
+
// NOT set role explicitly (would be redundant noise).
|
|
69
|
+
expect(host).not.toHaveAttribute('role');
|
|
70
|
+
expect(host).not.toHaveAttribute('aria-hidden');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('vertical + structural: aria-orientation="vertical"', async () => {
|
|
74
|
+
const { container } = await render(
|
|
75
|
+
`<div kjDivider [kjStructural]="true" [kjOrientation]="'vertical'"></div>`,
|
|
76
|
+
{ imports: [KjDivider] },
|
|
77
|
+
);
|
|
78
|
+
await flushAfterNextRender();
|
|
79
|
+
const host = container.querySelector('div')!;
|
|
80
|
+
expect(host).toHaveAttribute('aria-orientation', 'vertical');
|
|
81
|
+
expect(host).toHaveAttribute('role', 'separator');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('horizontal + structural: omits aria-orientation (spec default)', async () => {
|
|
85
|
+
const { container } = await render(
|
|
86
|
+
`<div kjDivider [kjStructural]="true" [kjOrientation]="'horizontal'"></div>`,
|
|
87
|
+
{ imports: [KjDivider] },
|
|
88
|
+
);
|
|
89
|
+
await flushAfterNextRender();
|
|
90
|
+
const host = container.querySelector('div')!;
|
|
91
|
+
expect(host).not.toHaveAttribute('aria-orientation');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('orientation reflection', () => {
|
|
96
|
+
it('reflects data-orientation="horizontal" by default', async () => {
|
|
97
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
98
|
+
await flushAfterNextRender();
|
|
99
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-orientation', 'horizontal');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('reflects data-orientation="vertical" when kjOrientation="vertical"', async () => {
|
|
103
|
+
const { container } = await render(
|
|
104
|
+
`<hr kjDivider [kjOrientation]="'vertical'" />`,
|
|
105
|
+
{ imports: [KjDivider] },
|
|
106
|
+
);
|
|
107
|
+
await flushAfterNextRender();
|
|
108
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-orientation', 'vertical');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('with-content auto-detection', () => {
|
|
113
|
+
it('rule-only on <hr>: data-with-content="false"', async () => {
|
|
114
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
115
|
+
await flushAfterNextRender();
|
|
116
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-with-content', 'false');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('empty <div>: data-with-content="false"', async () => {
|
|
120
|
+
const { container } = await render(`<div kjDivider></div>`, { imports: [KjDivider] });
|
|
121
|
+
await flushAfterNextRender();
|
|
122
|
+
expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'false');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('<div> with text content: data-with-content="true"', async () => {
|
|
126
|
+
const { container } = await render(
|
|
127
|
+
`<div kjDivider>OR</div>`,
|
|
128
|
+
{ imports: [KjDivider] },
|
|
129
|
+
);
|
|
130
|
+
await flushAfterNextRender();
|
|
131
|
+
expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'true');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('<div> with element content: data-with-content="true"', async () => {
|
|
135
|
+
const { container } = await render(
|
|
136
|
+
`<div kjDivider><span>OR</span></div>`,
|
|
137
|
+
{ imports: [KjDivider] },
|
|
138
|
+
);
|
|
139
|
+
await flushAfterNextRender();
|
|
140
|
+
expect(container.querySelector('div[kjDivider]')).toHaveAttribute(
|
|
141
|
+
'data-with-content',
|
|
142
|
+
'true',
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('<div> with whitespace-only content: data-with-content="false"', async () => {
|
|
147
|
+
const { container } = await render(
|
|
148
|
+
`<div kjDivider> </div>`,
|
|
149
|
+
{ imports: [KjDivider] },
|
|
150
|
+
);
|
|
151
|
+
await flushAfterNextRender();
|
|
152
|
+
expect(container.querySelector('div')).toHaveAttribute('data-with-content', 'false');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('align reflection', () => {
|
|
157
|
+
it('defaults to data-align="center"', async () => {
|
|
158
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
159
|
+
await flushAfterNextRender();
|
|
160
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-align', 'center');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('reflects kjAlign to data-align', async () => {
|
|
164
|
+
const { container } = await render(
|
|
165
|
+
`<div kjDivider [kjAlign]="'start'">Today</div>`,
|
|
166
|
+
{ imports: [KjDivider] },
|
|
167
|
+
);
|
|
168
|
+
await flushAfterNextRender();
|
|
169
|
+
expect(container.querySelector('div')).toHaveAttribute('data-align', 'start');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('variant + size composition', () => {
|
|
174
|
+
it('reflects default data-variant from KjVariant preset', async () => {
|
|
175
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
176
|
+
await flushAfterNextRender();
|
|
177
|
+
// KjVariant default factory: { default: 'default' }
|
|
178
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-variant', 'default');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('reflects default data-size from KjSize preset', async () => {
|
|
182
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
183
|
+
await flushAfterNextRender();
|
|
184
|
+
// KjSize default factory: { default: 'md' }
|
|
185
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-size', 'md');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('forwards kjVariant input (composed via hostDirectives)', async () => {
|
|
189
|
+
const { container } = await render(
|
|
190
|
+
`<hr kjDivider [kjVariant]="'dashed'" />`,
|
|
191
|
+
{ imports: [KjDivider] },
|
|
192
|
+
);
|
|
193
|
+
await flushAfterNextRender();
|
|
194
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-variant', 'dashed');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('forwards kjSize input (composed via hostDirectives)', async () => {
|
|
198
|
+
const { container } = await render(
|
|
199
|
+
`<hr kjDivider [kjSize]="'lg'" />`,
|
|
200
|
+
{ imports: [KjDivider] },
|
|
201
|
+
);
|
|
202
|
+
await flushAfterNextRender();
|
|
203
|
+
expect(container.querySelector('hr')).toHaveAttribute('data-size', 'lg');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('a11y audits', () => {
|
|
208
|
+
it('decorative <hr> divider passes axe', async () => {
|
|
209
|
+
const { container } = await render(`<hr kjDivider />`, { imports: [KjDivider] });
|
|
210
|
+
await flushAfterNextRender();
|
|
211
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('structural with-content <div> divider passes axe', async () => {
|
|
215
|
+
const { container } = await render(
|
|
216
|
+
`<div kjDivider [kjStructural]="true">OR</div>`,
|
|
217
|
+
{ imports: [KjDivider] },
|
|
218
|
+
);
|
|
219
|
+
await flushAfterNextRender();
|
|
220
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Directive,
|
|
3
|
+
ElementRef,
|
|
4
|
+
afterNextRender,
|
|
5
|
+
computed,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
signal,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { KjSize, KjVariant } from '../presets';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Orientation of the divider rule.
|
|
14
|
+
*/
|
|
15
|
+
export type KjDividerOrientation = 'horizontal' | 'vertical';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Alignment of the projected content within a with-content divider.
|
|
19
|
+
*/
|
|
20
|
+
export type KjDividerAlign = 'start' | 'center' | 'end';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Marks an element as a kouji divider / separator. Owns the small a11y opinion
|
|
24
|
+
* (decorative-by-default vs. structural) and reflects data attributes that
|
|
25
|
+
* theme CSS reads to render the rule (rule-only vs. with-content layout,
|
|
26
|
+
* orientation, alignment, variant, size).
|
|
27
|
+
*
|
|
28
|
+
* The host element is consumer-supplied — `<hr kjDivider>` for the rule-only
|
|
29
|
+
* case (recommended; native semantic, implicit `role="separator"`) or
|
|
30
|
+
* `<div kjDivider>Title</div>` for the with-content case (label projected in
|
|
31
|
+
* the middle of the rule).
|
|
32
|
+
*
|
|
33
|
+
* **Decorative by default.** Most dividers in real templates are visual
|
|
34
|
+
* punctuation between regions already demarcated by surrounding headings or
|
|
35
|
+
* landmarks, so AT should not announce them. Set `kjStructural=true` when the
|
|
36
|
+
* divider is the only thing demarcating two regions (e.g. an "OR" sign-in
|
|
37
|
+
* divider with no surrounding heading).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```html
|
|
41
|
+
* <hr kjDivider />
|
|
42
|
+
* <hr kjDivider [kjStructural]="true" kjOrientation="vertical" />
|
|
43
|
+
* <div kjDivider [kjStructural]="true">OR</div>
|
|
44
|
+
* ```
|
|
45
|
+
* @doc-category Core/Layout/Divider
|
|
46
|
+
* @doc
|
|
47
|
+
* @doc-name divider
|
|
48
|
+
* @doc-description Unstyled divider for separating content with horizontal, vertical, and label-in-rule layouts.
|
|
49
|
+
* @doc-is-main
|
|
50
|
+
*/
|
|
51
|
+
@Directive({
|
|
52
|
+
selector: '[kjDivider]',
|
|
53
|
+
standalone: true,
|
|
54
|
+
hostDirectives: [
|
|
55
|
+
{ directive: KjVariant, inputs: ['kjVariant'] },
|
|
56
|
+
{ directive: KjSize, inputs: ['kjSize'] },
|
|
57
|
+
],
|
|
58
|
+
host: {
|
|
59
|
+
'[attr.data-orientation]': 'kjOrientation()',
|
|
60
|
+
'[attr.data-align]': 'kjAlign()',
|
|
61
|
+
'[attr.data-with-content]': 'withContent() ? "true" : "false"',
|
|
62
|
+
'[attr.role]': 'roleAttr()',
|
|
63
|
+
'[attr.aria-orientation]': 'ariaOrientationAttr()',
|
|
64
|
+
'[attr.aria-hidden]': 'ariaHiddenAttr()',
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
export class KjDivider {
|
|
68
|
+
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Visual orientation of the rule. Reflects to `data-orientation`. When
|
|
72
|
+
* `kjStructural=true` and orientation is `vertical`, also reflects
|
|
73
|
+
* `aria-orientation="vertical"`. The horizontal case omits `aria-orientation`
|
|
74
|
+
* (the spec default; setting it explicitly is redundant noise).
|
|
75
|
+
*/
|
|
76
|
+
readonly kjOrientation = input<KjDividerOrientation>('horizontal');
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether the divider carries semantic weight (separates two regions that
|
|
80
|
+
* are not already demarcated by surrounding heading or landmark structure).
|
|
81
|
+
*
|
|
82
|
+
* - `false` (default): host gets `aria-hidden="true"`; AT skips the divider.
|
|
83
|
+
* - `true`: host gets `role="separator"` (suppressed if host is `<hr>`,
|
|
84
|
+
* which has implicit role); when `kjOrientation='vertical'`, also gets
|
|
85
|
+
* `aria-orientation="vertical"`.
|
|
86
|
+
*/
|
|
87
|
+
readonly kjStructural = input<boolean>(false);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Alignment of the projected content within a with-content divider.
|
|
91
|
+
* Affects the with-content layout only; ignored when no content is present.
|
|
92
|
+
* Reflects to `data-align`.
|
|
93
|
+
*/
|
|
94
|
+
readonly kjAlign = input<KjDividerAlign>('center');
|
|
95
|
+
|
|
96
|
+
/** Auto-detected from host child nodes (any text or element child). */
|
|
97
|
+
protected readonly withContent = signal<boolean>(false);
|
|
98
|
+
|
|
99
|
+
/** True when the host element is an `<hr>` (case-insensitive). */
|
|
100
|
+
protected readonly isHr = signal<boolean>(false);
|
|
101
|
+
|
|
102
|
+
protected readonly roleAttr = computed(() =>
|
|
103
|
+
this.kjStructural() && !this.isHr() ? 'separator' : null,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
protected readonly ariaOrientationAttr = computed(() =>
|
|
107
|
+
this.kjStructural() && this.kjOrientation() === 'vertical' ? 'vertical' : null,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
protected readonly ariaHiddenAttr = computed(() =>
|
|
111
|
+
this.kjStructural() ? null : 'true',
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
constructor() {
|
|
115
|
+
afterNextRender(() => {
|
|
116
|
+
const host = this.el.nativeElement;
|
|
117
|
+
this.isHr.set(host.tagName?.toLowerCase() === 'hr');
|
|
118
|
+
// Detect with-content from any non-empty child nodes (text or elements).
|
|
119
|
+
// Treat whitespace-only text nodes as no content so a multi-line template
|
|
120
|
+
// with a self-closing-style divider doesn't accidentally light up.
|
|
121
|
+
this.withContent.set(hasMeaningfulChildNodes(host));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hasMeaningfulChildNodes(host: HTMLElement): boolean {
|
|
127
|
+
const nodes = host.childNodes;
|
|
128
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
129
|
+
const node = nodes[i];
|
|
130
|
+
if (node.nodeType === Node.ELEMENT_NODE) return true;
|
|
131
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
132
|
+
const text = node.nodeValue ?? '';
|
|
133
|
+
if (text.trim().length > 0) return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Subject, type Observable } from 'rxjs';
|
|
2
|
+
import type { Signal } from '@angular/core';
|
|
3
|
+
import type { KjOverlayController } from '../primitives/overlay/controller';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reference returned by `KjDrawer.open()`. Mirrors {@link import('../dialog/dialog.ref').KjDialogRef}.
|
|
7
|
+
*
|
|
8
|
+
* Use `close(result?)` to dismiss the drawer programmatically. Subscribe to
|
|
9
|
+
* `afterClosed$` for the close result, or await the `result` promise.
|
|
10
|
+
*
|
|
11
|
+
* @doc-category Core/Overlay
|
|
12
|
+
*/
|
|
13
|
+
export class KjDrawerRef<T, R = unknown> {
|
|
14
|
+
private _instance: T | null = null;
|
|
15
|
+
private _result: R | undefined;
|
|
16
|
+
private resolveResult!: (r: R | undefined) => void;
|
|
17
|
+
/** Promise resolving with the close result. */
|
|
18
|
+
readonly result: Promise<R | undefined>;
|
|
19
|
+
|
|
20
|
+
private readonly _afterOpened = new Subject<void>();
|
|
21
|
+
private readonly _afterClosed = new Subject<R | undefined>();
|
|
22
|
+
/** Emits once after the drawer has finished opening. */
|
|
23
|
+
readonly afterOpened$: Observable<void> = this._afterOpened.asObservable();
|
|
24
|
+
/** Emits the close result once the drawer has finished closing. */
|
|
25
|
+
readonly afterClosed$: Observable<R | undefined> = this._afterClosed.asObservable();
|
|
26
|
+
|
|
27
|
+
/** Reactive lifecycle state mirrored from the underlying controller. */
|
|
28
|
+
readonly state: Signal<'closed' | 'opening' | 'open' | 'closing'>;
|
|
29
|
+
/** Convenience for `state() === 'open' || 'opening'`. */
|
|
30
|
+
readonly isOpen: Signal<boolean>;
|
|
31
|
+
|
|
32
|
+
constructor(public readonly controller: KjOverlayController) {
|
|
33
|
+
this.state = controller.state;
|
|
34
|
+
this.isOpen = controller.isOpen;
|
|
35
|
+
this.result = new Promise<R | undefined>((res) => {
|
|
36
|
+
this.resolveResult = res;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @internal Bind the rendered component instance for `instance`. */
|
|
41
|
+
bindInstance(instance: T): void {
|
|
42
|
+
this._instance = instance;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The rendered drawer component instance. */
|
|
46
|
+
get instance(): T {
|
|
47
|
+
if (!this._instance) {
|
|
48
|
+
throw new Error('KjDrawerRef: instance not bound');
|
|
49
|
+
}
|
|
50
|
+
return this._instance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Close the drawer with an optional result payload. */
|
|
54
|
+
close(result?: R): void {
|
|
55
|
+
this._result = result;
|
|
56
|
+
this.controller.close('programmatic');
|
|
57
|
+
queueMicrotask(() => {
|
|
58
|
+
this._afterClosed.next(this._result);
|
|
59
|
+
this._afterClosed.complete();
|
|
60
|
+
this.resolveResult(this._result);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EnvironmentInjector,
|
|
3
|
+
Injectable,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
Type,
|
|
6
|
+
effect,
|
|
7
|
+
inject,
|
|
8
|
+
runInInjectionContext,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { KjOverlayBuilder } from '../primitives/overlay/builder';
|
|
11
|
+
import { inPlace } from '../primitives/overlay/strategies/mount/in-place';
|
|
12
|
+
import { edgeSheet } from '../primitives/overlay/strategies/position/edge-sheet';
|
|
13
|
+
import { solidBackdrop } from '../primitives/overlay/strategies/backdrop/solid';
|
|
14
|
+
import { tabCycle } from '../primitives/overlay/strategies/focus-trap/tab-cycle';
|
|
15
|
+
import { htmlOverflow } from '../primitives/overlay/strategies/scroll-lock/html-overflow';
|
|
16
|
+
import { silent } from '../primitives/overlay/strategies/live-announcer/silent';
|
|
17
|
+
import { programmatic } from '../primitives/overlay/strategies/trigger-event/programmatic';
|
|
18
|
+
import { KjDrawerRef } from './drawer.ref';
|
|
19
|
+
|
|
20
|
+
/** Side of the viewport the drawer slides in from. */
|
|
21
|
+
export type KjDrawerSide = 'left' | 'right' | 'top' | 'bottom';
|
|
22
|
+
|
|
23
|
+
/** Configuration options for {@link KjDrawer.open}. */
|
|
24
|
+
export interface KjDrawerOpenOptions<D = unknown> {
|
|
25
|
+
/** Side of the viewport the drawer slides in from. Defaults to `'right'`. */
|
|
26
|
+
side?: KjDrawerSide;
|
|
27
|
+
/** Enables drag-to-dismiss when the body component implements it. */
|
|
28
|
+
drag?: boolean;
|
|
29
|
+
/** Data injected into the rendered component via `DRAWER_DATA` (when wired). */
|
|
30
|
+
data?: D;
|
|
31
|
+
/** Whether clicking the backdrop closes the drawer. Defaults to `true`. */
|
|
32
|
+
closeOnOutside?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Programmatic service for opening drawers. Mirrors `KjDialog`. Builds an
|
|
37
|
+
* overlay handle wired to the overlay primitives and renders a component
|
|
38
|
+
* inside the singleton overlay container, positioned via `edgeSheet({side})`.
|
|
39
|
+
*
|
|
40
|
+
* @doc-category Core/Overlay
|
|
41
|
+
*/
|
|
42
|
+
@Injectable({ providedIn: 'root' })
|
|
43
|
+
export class KjDrawerService {
|
|
44
|
+
private readonly builder = inject(KjOverlayBuilder);
|
|
45
|
+
private readonly env = inject(EnvironmentInjector);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Open a component as a modal drawer.
|
|
49
|
+
*
|
|
50
|
+
* @param component - The component class to render inside the drawer.
|
|
51
|
+
* @param opts - Side, drag, data, and close-on-outside controls.
|
|
52
|
+
* @returns A {@link KjDrawerRef} for closing and observing the drawer.
|
|
53
|
+
*/
|
|
54
|
+
open<T, R = unknown, D = unknown>(
|
|
55
|
+
component: Type<T>,
|
|
56
|
+
opts: KjDrawerOpenOptions<D> = {},
|
|
57
|
+
): KjDrawerRef<T, R> {
|
|
58
|
+
const side: KjDrawerSide = opts.side ?? 'right';
|
|
59
|
+
|
|
60
|
+
const handle = this.builder.create({
|
|
61
|
+
mount: inPlace(),
|
|
62
|
+
position: edgeSheet({ side }),
|
|
63
|
+
backdrop: solidBackdrop({
|
|
64
|
+
inert: true,
|
|
65
|
+
closeOnClick: opts.closeOnOutside ?? true,
|
|
66
|
+
}),
|
|
67
|
+
focusTrap: tabCycle({ returnFocus: true }),
|
|
68
|
+
scrollLock: htmlOverflow(),
|
|
69
|
+
liveAnnouncer: silent(),
|
|
70
|
+
trigger: programmatic(),
|
|
71
|
+
panelRole: 'dialog',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const ref = new KjDrawerRef<T, R>(handle.controller);
|
|
75
|
+
const cmpRef = this.builder.attachComponent(handle, component, {
|
|
76
|
+
providers: [
|
|
77
|
+
{ provide: KjDrawerRef, useValue: ref },
|
|
78
|
+
{ provide: DRAWER_DATA, useValue: opts.data },
|
|
79
|
+
{ provide: DRAWER_SIDE, useValue: side },
|
|
80
|
+
{ provide: DRAWER_DRAG, useValue: !!opts.drag },
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
ref.bindInstance(cmpRef.instance);
|
|
84
|
+
|
|
85
|
+
runInInjectionContext(this.env, () => {
|
|
86
|
+
let wasOpen = false;
|
|
87
|
+
const eff = effect(() => {
|
|
88
|
+
const s = handle.controller.state();
|
|
89
|
+
if (s === 'open' || s === 'opening') wasOpen = true;
|
|
90
|
+
if (s === 'closed' && wasOpen) {
|
|
91
|
+
eff.destroy();
|
|
92
|
+
queueMicrotask(() => handle.destroy());
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
handle.controller.open();
|
|
97
|
+
return ref;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Token for passing data to a programmatically opened drawer component. */
|
|
102
|
+
export const DRAWER_DATA = new InjectionToken<unknown>('KjDrawerData');
|
|
103
|
+
/** Token exposing the resolved drawer side to the rendered component. */
|
|
104
|
+
export const DRAWER_SIDE = new InjectionToken<KjDrawerSide>('KjDrawerSide');
|
|
105
|
+
/** Token exposing whether drag-to-dismiss is enabled to the rendered component. */
|
|
106
|
+
export const DRAWER_DRAG = new InjectionToken<boolean>('KjDrawerDrag');
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { KjDrawer } from './drawer';
|
|
6
|
+
import { KjDrawerService, DRAWER_DATA } from './drawer.service';
|
|
7
|
+
import { KjDrawerRef } from './drawer.ref';
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'kj-simple-drawer',
|
|
11
|
+
standalone: true,
|
|
12
|
+
imports: [KjDrawer],
|
|
13
|
+
changeDetection: ChangeDetectionStrategy.Eager,
|
|
14
|
+
template: `<kj-drawer><button id="ok" (click)="ref.close('ok')">OK</button></kj-drawer>`,
|
|
15
|
+
})
|
|
16
|
+
class SimpleDrawer {
|
|
17
|
+
readonly ref = inject<KjDrawerRef<SimpleDrawer, string>>(KjDrawerRef);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'kj-data-drawer',
|
|
22
|
+
standalone: true,
|
|
23
|
+
imports: [KjDrawer],
|
|
24
|
+
changeDetection: ChangeDetectionStrategy.Eager,
|
|
25
|
+
template: `<kj-drawer>{{ data }}</kj-drawer>`,
|
|
26
|
+
})
|
|
27
|
+
class DataDrawer {
|
|
28
|
+
readonly data = inject<string>(DRAWER_DATA);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findPanel(): HTMLElement | null {
|
|
32
|
+
return document.body.querySelector<HTMLElement>('kj-drawer');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cleanupOverlays(): void {
|
|
36
|
+
document.body.querySelectorAll('kj-drawer').forEach((el) => el.remove());
|
|
37
|
+
document.documentElement.style.overflow = '';
|
|
38
|
+
document.documentElement.style.paddingRight = '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('KjDrawerService', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
TestBed.resetTestingModule();
|
|
44
|
+
TestBed.configureTestingModule({});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
cleanupOverlays();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('open returns a ref with a controller', () => {
|
|
52
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
53
|
+
const ref = svc.open(SimpleDrawer);
|
|
54
|
+
expect(ref).toBeTruthy();
|
|
55
|
+
expect(typeof ref.close).toBe('function');
|
|
56
|
+
expect(ref.controller).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rendered panel uses role="dialog"', () => {
|
|
60
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
61
|
+
svc.open(SimpleDrawer);
|
|
62
|
+
const panel = findPanel();
|
|
63
|
+
expect(panel).toBeTruthy();
|
|
64
|
+
expect(panel!.getAttribute('role')).toBe('dialog');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('side option is reflected on the host as data-kj-side', () => {
|
|
68
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
69
|
+
svc.open(SimpleDrawer, { side: 'left' });
|
|
70
|
+
expect(findPanel()!.getAttribute('data-kj-side')).toBe('left');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('defaults side to "right"', () => {
|
|
74
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
75
|
+
svc.open(SimpleDrawer);
|
|
76
|
+
expect(findPanel()!.getAttribute('data-kj-side')).toBe('right');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('close resolves the result promise', async () => {
|
|
80
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
81
|
+
const ref = svc.open<SimpleDrawer, string>(SimpleDrawer);
|
|
82
|
+
ref.close('hello');
|
|
83
|
+
await expect(ref.result).resolves.toBe('hello');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('passes data through DRAWER_DATA', () => {
|
|
87
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
88
|
+
svc.open(DataDrawer, { data: 'greetings' });
|
|
89
|
+
expect(findPanel()!.textContent).toContain('greetings');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('drag option is exposed to the body component', () => {
|
|
93
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
94
|
+
const ref = svc.open(SimpleDrawer, { side: 'bottom', drag: true });
|
|
95
|
+
expect(ref.instance).toBeInstanceOf(SimpleDrawer);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('Escape closes the drawer via the overlay-stack coordinator', async () => {
|
|
99
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
100
|
+
const ref = svc.open<SimpleDrawer, unknown>(SimpleDrawer);
|
|
101
|
+
expect(findPanel()).toBeTruthy();
|
|
102
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
|
103
|
+
// The stack coordinator runs synchronously on Esc.
|
|
104
|
+
await ref.result;
|
|
105
|
+
// After close, the controller state should have transitioned away from open.
|
|
106
|
+
expect(ref.state()).not.toBe('open');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('KjDrawer (body component)', () => {
|
|
111
|
+
it('exposes the resolved side through `side`', () => {
|
|
112
|
+
const svc = TestBed.inject(KjDrawerService);
|
|
113
|
+
const ref = svc.open(SimpleDrawer, { side: 'top' });
|
|
114
|
+
expect(ref.instance as unknown as { ref: KjDrawerRef<unknown> }).toBeTruthy();
|
|
115
|
+
cleanupOverlays();
|
|
116
|
+
});
|
|
117
|
+
});
|