@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,237 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { render } from '@testing-library/angular';
|
|
3
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
4
|
+
import { KJ_ROVING_TABINDEX } from '../a11y/roving-tabindex';
|
|
5
|
+
import { KjList, KjListRow } from './list';
|
|
6
|
+
|
|
7
|
+
expect.extend(toHaveNoViolations);
|
|
8
|
+
|
|
9
|
+
const imports = [KjList, KjListRow];
|
|
10
|
+
|
|
11
|
+
describe('KjList', () => {
|
|
12
|
+
describe('container role', () => {
|
|
13
|
+
it('emits role="list" on a <ul> host (defeats Safari list-style:none stripping)', async () => {
|
|
14
|
+
const { container } = await render(
|
|
15
|
+
`<ul kjList aria-label="Recent files"><li kjListRow>A</li></ul>`,
|
|
16
|
+
{ imports },
|
|
17
|
+
);
|
|
18
|
+
const root = container.querySelector('[kjList]')!;
|
|
19
|
+
expect(root.tagName).toBe('UL');
|
|
20
|
+
expect(root).toHaveAttribute('role', 'list');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('emits role="list" on a <ol> host as well', async () => {
|
|
24
|
+
const { container } = await render(
|
|
25
|
+
`<ol kjList kjAs="ol" aria-label="Steps"><li kjListRow>A</li></ol>`,
|
|
26
|
+
{ imports },
|
|
27
|
+
);
|
|
28
|
+
const root = container.querySelector('[kjList]')!;
|
|
29
|
+
expect(root.tagName).toBe('OL');
|
|
30
|
+
expect(root).toHaveAttribute('role', 'list');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('emits role="list" on a <div> host', async () => {
|
|
34
|
+
const { container } = await render(
|
|
35
|
+
`<div kjList kjAs="div" aria-label="Stats"><div kjListRow>A</div></div>`,
|
|
36
|
+
{ imports },
|
|
37
|
+
);
|
|
38
|
+
const root = container.querySelector('[kjList]')!;
|
|
39
|
+
expect(root).toHaveAttribute('role', 'list');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('as="nav" landmark', () => {
|
|
44
|
+
it('switches the host to a <nav> landmark and omits role="list"', async () => {
|
|
45
|
+
const { container } = await render(
|
|
46
|
+
`<nav kjList kjAs="nav" aria-label="Primary"><div kjListRow>A</div></nav>`,
|
|
47
|
+
{ imports },
|
|
48
|
+
);
|
|
49
|
+
const root = container.querySelector('[kjList]')!;
|
|
50
|
+
expect(root.tagName).toBe('NAV');
|
|
51
|
+
expect(root).not.toHaveAttribute('role');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('warns in dev mode when kjAs="nav" host has no aria-label / aria-labelledby', async () => {
|
|
55
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
56
|
+
try {
|
|
57
|
+
await render(
|
|
58
|
+
`<nav kjList kjAs="nav"><div kjListRow>A</div></nav>`,
|
|
59
|
+
{ imports },
|
|
60
|
+
);
|
|
61
|
+
expect(warn).toHaveBeenCalled();
|
|
62
|
+
const message = warn.mock.calls.map((args) => args.join(' ')).join('\n');
|
|
63
|
+
expect(message).toMatch(/kjAs="nav"/);
|
|
64
|
+
expect(message).toMatch(/aria-label/);
|
|
65
|
+
} finally {
|
|
66
|
+
warn.mockRestore();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does not warn when kjAs="nav" host has aria-label', async () => {
|
|
71
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
72
|
+
try {
|
|
73
|
+
await render(
|
|
74
|
+
`<nav kjList kjAs="nav" aria-label="Primary"><div kjListRow>A</div></nav>`,
|
|
75
|
+
{ imports },
|
|
76
|
+
);
|
|
77
|
+
const navWarnings = warn.mock.calls
|
|
78
|
+
.map((args) => args.join(' '))
|
|
79
|
+
.filter((line) => line.includes('kjAs="nav"'));
|
|
80
|
+
expect(navWarnings).toHaveLength(0);
|
|
81
|
+
} finally {
|
|
82
|
+
warn.mockRestore();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('orientation propagation', () => {
|
|
88
|
+
it('reflects vertical (default) on data-orientation', async () => {
|
|
89
|
+
const { container } = await render(
|
|
90
|
+
`<ul kjList aria-label="L"><li kjListRow>A</li></ul>`,
|
|
91
|
+
{ imports },
|
|
92
|
+
);
|
|
93
|
+
const root = container.querySelector('[kjList]')!;
|
|
94
|
+
expect(root).toHaveAttribute('data-orientation', 'vertical');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('reflects horizontal when kjOrientation="horizontal"', async () => {
|
|
98
|
+
const { container } = await render(
|
|
99
|
+
`<ul kjList kjOrientation="horizontal" aria-label="L"><li kjListRow>A</li></ul>`,
|
|
100
|
+
{ imports },
|
|
101
|
+
);
|
|
102
|
+
const root = container.querySelector('[kjList]')!;
|
|
103
|
+
expect(root).toHaveAttribute('data-orientation', 'horizontal');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('does not bind aria-orientation (axe aria-allowed-attr forbids it on role="list")', async () => {
|
|
107
|
+
const { container } = await render(
|
|
108
|
+
`<ul kjList kjOrientation="horizontal" aria-label="L"><li kjListRow>A</li></ul>`,
|
|
109
|
+
{ imports },
|
|
110
|
+
);
|
|
111
|
+
const root = container.querySelector('[kjList]')!;
|
|
112
|
+
expect(root).not.toHaveAttribute('aria-orientation');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('reflects data-divided and data-hoverable when toggled', async () => {
|
|
116
|
+
const { container } = await render(
|
|
117
|
+
`<ul kjList [kjDivided]="true" [kjHoverable]="true" aria-label="L"><li kjListRow>A</li></ul>`,
|
|
118
|
+
{ imports },
|
|
119
|
+
);
|
|
120
|
+
const root = container.querySelector('[kjList]')!;
|
|
121
|
+
expect(root).toHaveAttribute('data-divided', '');
|
|
122
|
+
expect(root).toHaveAttribute('data-hoverable', '');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('arrow-nav opt-in composes KjRovingTabindex', () => {
|
|
127
|
+
it('exposes the roving primitive as a host directive on the root', async () => {
|
|
128
|
+
// KjList unconditionally composes KjRovingTabindex (per accordion's
|
|
129
|
+
// "always-applied roving primitive that no-ops when the flag is off"
|
|
130
|
+
// pattern). Verify the primitive is provided in the root's injector by
|
|
131
|
+
// resolving its DI token from a child element.
|
|
132
|
+
@Component({
|
|
133
|
+
standalone: true,
|
|
134
|
+
imports,
|
|
135
|
+
template: `
|
|
136
|
+
<ul kjList kjArrowNavigation aria-label="Primary">
|
|
137
|
+
<li kjListRow>A</li>
|
|
138
|
+
</ul>
|
|
139
|
+
`,
|
|
140
|
+
})
|
|
141
|
+
class Host {}
|
|
142
|
+
|
|
143
|
+
const { fixture } = await render(Host);
|
|
144
|
+
// The KjRovingTabindex provider lives at the root host's element
|
|
145
|
+
// injector — resolve it through a debug element lookup.
|
|
146
|
+
const rootDebugEl = fixture.debugElement.query(
|
|
147
|
+
(de) => de.nativeElement?.matches?.('[kjList]'),
|
|
148
|
+
);
|
|
149
|
+
const rovingFromChild: unknown = rootDebugEl.injector.get(KJ_ROVING_TABINDEX, null);
|
|
150
|
+
expect(rovingFromChild).not.toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('KjListRow', () => {
|
|
155
|
+
it('omits role on <li> and emits role="listitem" on <div>', async () => {
|
|
156
|
+
const { container } = await render(
|
|
157
|
+
`<ul kjList aria-label="L">
|
|
158
|
+
<li kjListRow>A</li>
|
|
159
|
+
<div kjListRow>B</div>
|
|
160
|
+
</ul>`,
|
|
161
|
+
{ imports },
|
|
162
|
+
);
|
|
163
|
+
const items = Array.from(container.querySelectorAll('[kjListRow]'));
|
|
164
|
+
expect(items[0].tagName).toBe('LI');
|
|
165
|
+
expect(items[0]).not.toHaveAttribute('role');
|
|
166
|
+
expect(items[1].tagName).toBe('DIV');
|
|
167
|
+
expect(items[1]).toHaveAttribute('role', 'listitem');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('reflects kjActive to data-active and clears it when toggled off', async () => {
|
|
171
|
+
@Component({
|
|
172
|
+
standalone: true,
|
|
173
|
+
imports,
|
|
174
|
+
template: `
|
|
175
|
+
<ul kjList aria-label="L">
|
|
176
|
+
<li kjListRow [kjActive]="active()">A</li>
|
|
177
|
+
</ul>
|
|
178
|
+
`,
|
|
179
|
+
})
|
|
180
|
+
class Host {
|
|
181
|
+
active = signal(true);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const { container, fixture } = await render(Host);
|
|
185
|
+
const item = container.querySelector('[kjListRow]')!;
|
|
186
|
+
expect(item).toHaveAttribute('data-active', '');
|
|
187
|
+
|
|
188
|
+
fixture.componentInstance.active.set(false);
|
|
189
|
+
fixture.detectChanges();
|
|
190
|
+
expect(item).not.toHaveAttribute('data-active');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('reflects kjDisabled to data-disabled', async () => {
|
|
194
|
+
const { container } = await render(
|
|
195
|
+
`<ul kjList aria-label="L">
|
|
196
|
+
<li kjListRow [kjDisabled]="true">A</li>
|
|
197
|
+
</ul>`,
|
|
198
|
+
{ imports },
|
|
199
|
+
);
|
|
200
|
+
const item = container.querySelector('[kjListRow]')!;
|
|
201
|
+
expect(item).toHaveAttribute('data-disabled', '');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('axe', () => {
|
|
206
|
+
it('passes axe audit on a basic <ul> list', async () => {
|
|
207
|
+
const { container } = await render(
|
|
208
|
+
`<ul kjList aria-label="Recent files">
|
|
209
|
+
<li kjListRow>A</li>
|
|
210
|
+
<li kjListRow>B</li>
|
|
211
|
+
<li kjListRow>C</li>
|
|
212
|
+
</ul>`,
|
|
213
|
+
{ imports },
|
|
214
|
+
);
|
|
215
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('passes axe audit on a <nav>-wrapped sidebar list with active row', async () => {
|
|
219
|
+
// Recommended idiom for a sidebar nav: the <nav> landmark wraps the
|
|
220
|
+
// labelled <ul kjList>, so listitems retain a `role="list"` parent and
|
|
221
|
+
// the landmark gets a clean accessible name. The kjAs="nav" form is
|
|
222
|
+
// available for layouts that prefer to put kjList on <nav> directly.
|
|
223
|
+
const { container } = await render(
|
|
224
|
+
`<nav aria-label="Primary">
|
|
225
|
+
<ul kjList>
|
|
226
|
+
<li kjListRow [kjActive]="true">
|
|
227
|
+
<a href="/home" aria-current="page">Home</a>
|
|
228
|
+
</li>
|
|
229
|
+
<li kjListRow><a href="/about">About</a></li>
|
|
230
|
+
</ul>
|
|
231
|
+
</nav>`,
|
|
232
|
+
{ imports },
|
|
233
|
+
);
|
|
234
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
package/src/list/list.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Directive,
|
|
3
|
+
ElementRef,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
Signal,
|
|
6
|
+
computed,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
isDevMode,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { KjRovingTabindex } from '../a11y/roving-tabindex';
|
|
13
|
+
import { KjSize, KjVariant } from '../presets';
|
|
14
|
+
import type { KjListOrientation } from '../primitives/list/tokens';
|
|
15
|
+
|
|
16
|
+
export type { KjListOrientation };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Host element kind the consumer chose for the list root. The directive does
|
|
20
|
+
* not render this element — the consumer does — but the input lets the
|
|
21
|
+
* directive emit the right `role` and warn on landmark misuse.
|
|
22
|
+
*
|
|
23
|
+
* `'ul'` is the default and the recommended choice in 90% of cases.
|
|
24
|
+
*/
|
|
25
|
+
export type KjListAs = 'ul' | 'ol' | 'div' | 'nav';
|
|
26
|
+
|
|
27
|
+
/** @internal Public shape of the parent list context exposed to descendants. */
|
|
28
|
+
export interface KjListContext {
|
|
29
|
+
readonly kjOrientation: Signal<KjListOrientation>;
|
|
30
|
+
readonly kjDivided: Signal<boolean>;
|
|
31
|
+
readonly kjHoverable: Signal<boolean>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Context token for the parent `KjList`, injected by descendants. */
|
|
35
|
+
export const KJ_LIST = new InjectionToken<KjListContext>('KjList');
|
|
36
|
+
|
|
37
|
+
/** Context token for the parent `KjListRow`, injected by descendants. */
|
|
38
|
+
export const KJ_LIST_ROW = new InjectionToken<KjListRowContext>('KjListRow');
|
|
39
|
+
|
|
40
|
+
/** @internal Public shape of the per-row context exposed to descendants. */
|
|
41
|
+
export interface KjListRowContext {
|
|
42
|
+
readonly active: Signal<boolean>;
|
|
43
|
+
readonly disabled: Signal<boolean>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Root list container. Owns the container ARIA role, orientation,
|
|
48
|
+
* divider/hover data attributes, and an opt-in roving-tabindex composition for
|
|
49
|
+
* sidebar-nav-list use cases. Composes `KjVariant` + `KjSize` via
|
|
50
|
+
* `hostDirectives` so theme CSS can read the standard `data-variant` /
|
|
51
|
+
* `data-size` mirrors.
|
|
52
|
+
*
|
|
53
|
+
* The directive does **not** render the host element — the consumer does. The
|
|
54
|
+
* `kjAs` input declares which element the consumer chose so the directive can
|
|
55
|
+
* emit (or omit) `role="list"` and warn when a `<nav>` host is missing an
|
|
56
|
+
* accessible name.
|
|
57
|
+
*
|
|
58
|
+
* **Why `role="list"` on `<ul>`/`<ol>`?** Safari's WebKit "list voice"
|
|
59
|
+
* heuristic strips the implicit `role="list"` from a `<ul>` whose CSS sets
|
|
60
|
+
* `list-style: none` (the default in our wrapper CSS). Re-emitting the role
|
|
61
|
+
* defeats the heuristic. The redundancy is deliberate.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```html
|
|
65
|
+
* <ul kjList kjOrientation="vertical" aria-label="Recent files">
|
|
66
|
+
* <li kjListRow>Item A</li>
|
|
67
|
+
* <li kjListRow>Item B</li>
|
|
68
|
+
* </ul>
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Sidebar nav with arrow-key navigation
|
|
72
|
+
* ```html
|
|
73
|
+
* <nav kjList kjAs="nav" kjArrowNavigation aria-label="Primary">
|
|
74
|
+
* <div kjListRow [kjActive]="true">
|
|
75
|
+
* <a kjLink kjRovingTabindexItem aria-current="page">Home</a>
|
|
76
|
+
* </div>
|
|
77
|
+
* <div kjListRow>
|
|
78
|
+
* <a kjLink kjRovingTabindexItem>Settings</a>
|
|
79
|
+
* </div>
|
|
80
|
+
* </nav>
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @doc-category Core/Data display
|
|
84
|
+
* @doc
|
|
85
|
+
* @doc-name list
|
|
86
|
+
* @doc-description Unstyled list container with role, orientation, and opt-in arrow-key navigation for sidebars.
|
|
87
|
+
* @doc-is-main
|
|
88
|
+
*/
|
|
89
|
+
@Directive({
|
|
90
|
+
selector: '[kjList]',
|
|
91
|
+
standalone: true,
|
|
92
|
+
hostDirectives: [
|
|
93
|
+
{ directive: KjVariant, inputs: ['kjVariant'] },
|
|
94
|
+
{ directive: KjSize, inputs: ['kjSize'] },
|
|
95
|
+
// Forward the composed roving primitive's orientation under the same
|
|
96
|
+
// external name as `kjOrientation` so consumers writing
|
|
97
|
+
// `<ul kjList kjOrientation="horizontal">` get both axes wired.
|
|
98
|
+
{
|
|
99
|
+
directive: KjRovingTabindex,
|
|
100
|
+
inputs: ['kjRovingOrientation: kjOrientation'],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
providers: [{ provide: KJ_LIST, useExisting: KjList }],
|
|
104
|
+
exportAs: 'kjList',
|
|
105
|
+
host: {
|
|
106
|
+
'[attr.role]': 'roleAttr()',
|
|
107
|
+
// `aria-orientation` is rejected by axe's `aria-allowed-attr` on
|
|
108
|
+
// `role="list"` (the implicit/explicit list role does not list
|
|
109
|
+
// `aria-orientation` as a supported attribute in ARIA 1.2). The keyboard
|
|
110
|
+
// axis is enforced via the composed `KjRovingTabindex.kjRovingOrientation`
|
|
111
|
+
// and themes read `data-orientation` for visual layout. Same precedent
|
|
112
|
+
// as `KjStepper`.
|
|
113
|
+
'[attr.data-orientation]': 'kjOrientation()',
|
|
114
|
+
'[attr.data-divided]': 'kjDivided() ? "" : null',
|
|
115
|
+
'[attr.data-hoverable]': 'kjHoverable() ? "" : null',
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
export class KjList implements KjListContext {
|
|
119
|
+
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Declares which host element the consumer is rendering this list as.
|
|
123
|
+
* Default `'ul'` matches the `<ul kjList>` ergonomic. `'nav'` switches the
|
|
124
|
+
* implicit role to the `navigation` landmark (the directive omits its
|
|
125
|
+
* `role="list"` binding in that case) and triggers a dev-mode warning when
|
|
126
|
+
* neither `aria-label` nor `aria-labelledby` is set on the host.
|
|
127
|
+
*/
|
|
128
|
+
readonly kjAs = input<KjListAs>('ul');
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* List orientation. Drives `aria-orientation` and `data-orientation`.
|
|
132
|
+
* Forwarded to the composed `KjRovingTabindex` (when `kjArrowNavigation` is
|
|
133
|
+
* `true`) so arrow-key navigation moves on the right axis.
|
|
134
|
+
*/
|
|
135
|
+
readonly kjOrientation = input<KjListOrientation>('vertical');
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether the list draws between-row borders. Theme CSS reads
|
|
139
|
+
* `data-divided` on the root and applies a `:not(:last-child)` border on
|
|
140
|
+
* `KjListRow`. Pure CSS effect — no JS.
|
|
141
|
+
*/
|
|
142
|
+
readonly kjDivided = input<boolean>(false);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether rows highlight on hover. Theme CSS reads `data-hoverable` on the
|
|
146
|
+
* root. Off by default because purely informational lists (stat blocks)
|
|
147
|
+
* should not hint at interactivity.
|
|
148
|
+
*/
|
|
149
|
+
readonly kjHoverable = input<boolean>(false);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Opt-in flag that turns the list into a roving-tabindex group. When
|
|
153
|
+
* `false` (default), the composed `KjRovingTabindex` is configured with
|
|
154
|
+
* `kjRovingOrientation="vertical" | "horizontal"` but no item is registered
|
|
155
|
+
* unless the consumer applies `kjRovingTabindexItem` to a focusable child.
|
|
156
|
+
* Setting this to `true` documents the intent at the consumer site; the
|
|
157
|
+
* actual focus-stop wiring is done by the consumer applying
|
|
158
|
+
* `kjRovingTabindexItem` on their projected `<a>` / `<button>`.
|
|
159
|
+
*/
|
|
160
|
+
readonly kjArrowNavigation = input<boolean>(false);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Whether arrow-key navigation wraps at the ends. Forwarded conceptually to
|
|
164
|
+
* theme/keyboard logic; only meaningful when `kjArrowNavigation` is `true`.
|
|
165
|
+
* Defaults to `true` to match the typical sidebar-nav UX.
|
|
166
|
+
*/
|
|
167
|
+
readonly kjListWrap = input<boolean>(true);
|
|
168
|
+
|
|
169
|
+
/** @internal Computes the role the directive should host-bind. */
|
|
170
|
+
protected readonly roleAttr = computed<string | null>(() => {
|
|
171
|
+
const as = this.kjAs();
|
|
172
|
+
// `<nav>` is a landmark (implicit `role="navigation"`); emitting
|
|
173
|
+
// `role="list"` on top of it would conflict with the landmark semantics.
|
|
174
|
+
if (as === 'nav') return null;
|
|
175
|
+
// Always emit on `<ul>` / `<ol>` to defeat Safari's `list-style: none`
|
|
176
|
+
// voice-stripping heuristic, and on `<div>` because there is no implicit
|
|
177
|
+
// role to inherit from.
|
|
178
|
+
return 'list';
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
constructor() {
|
|
182
|
+
if (isDevMode()) {
|
|
183
|
+
// Landmark naming discipline: a `<nav>` MUST have an accessible name.
|
|
184
|
+
// We warn (not throw) so downstream tooling that adds the attribute via
|
|
185
|
+
// a wrapper or a parent `aria-labelledby` on the surrounding heading
|
|
186
|
+
// does not flake under unit tests; the wrapper layer escalates to an
|
|
187
|
+
// error per the analysis doc.
|
|
188
|
+
effect(() => {
|
|
189
|
+
if (this.kjAs() !== 'nav') return;
|
|
190
|
+
// Read host attributes lazily — the host element is the consumer's,
|
|
191
|
+
// so attribute presence is the source of truth.
|
|
192
|
+
const host = this.el.nativeElement;
|
|
193
|
+
const hasLabel =
|
|
194
|
+
host.hasAttribute('aria-label') || host.hasAttribute('aria-labelledby');
|
|
195
|
+
if (!hasLabel) {
|
|
196
|
+
console.warn(
|
|
197
|
+
'[kj] kjList with kjAs="nav" requires aria-label or aria-labelledby on the host. ' +
|
|
198
|
+
'A <nav> is a landmark and must be named.',
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Per-row directive for themed list surfaces (settings panels, sidebar nav).
|
|
208
|
+
* Owns the listitem ARIA role and reflects active / disabled state to data
|
|
209
|
+
* attributes the wrapper CSS reads to paint chrome. Provides a tiny
|
|
210
|
+
* `KJ_LIST_ROW` token so descendants (e.g. an icon button inside the row) can
|
|
211
|
+
* read the parent's active/disabled signals.
|
|
212
|
+
*
|
|
213
|
+
* Distinct from the listbox/menu/tree primitive `KjListItem` in
|
|
214
|
+
* `primitives/list` — that one drives selection, ARIA `aria-selected`, and
|
|
215
|
+
* activation, whereas this directive is purely a presentational row in a
|
|
216
|
+
* navigation/list surface.
|
|
217
|
+
*
|
|
218
|
+
* The row host is **not** a focus stop. Keyboard reachability lives on the
|
|
219
|
+
* projected interactive child (`<a kjLink>`, `<button kjButton>`). The
|
|
220
|
+
* directive's `kjDisabled` only paints the chrome via `data-disabled`; it
|
|
221
|
+
* does **not** wire `aria-disabled` on the projected child — that is the
|
|
222
|
+
* projected child's responsibility (via `KjDisabled`).
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```html
|
|
226
|
+
* <li kjListRow [kjActive]="route === '/home'">
|
|
227
|
+
* <a kjLink href="/home" aria-current="page">Home</a>
|
|
228
|
+
* </li>
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* @doc-category Core/Data display
|
|
232
|
+
* @doc
|
|
233
|
+
* @doc-name list
|
|
234
|
+
*/
|
|
235
|
+
@Directive({
|
|
236
|
+
selector: '[kjListRow]',
|
|
237
|
+
standalone: true,
|
|
238
|
+
providers: [{ provide: KJ_LIST_ROW, useExisting: KjListRow }],
|
|
239
|
+
exportAs: 'kjListRow',
|
|
240
|
+
host: {
|
|
241
|
+
'[attr.role]': 'roleAttr()',
|
|
242
|
+
'[attr.data-active]': 'kjActive() ? "" : null',
|
|
243
|
+
'[attr.data-disabled]': 'kjDisabled() ? "" : null',
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
export class KjListRow implements KjListRowContext {
|
|
247
|
+
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Whether the row is the current/active selection (e.g. the current page in
|
|
251
|
+
* a sidebar nav). Reflects to `data-active=""`. The directive does **not**
|
|
252
|
+
* infer `aria-current` from this — the consumer sets `aria-current="page"`
|
|
253
|
+
* (or `step`, `date`, `true`) on the projected link/button because the
|
|
254
|
+
* right token depends on the consumer's domain.
|
|
255
|
+
*/
|
|
256
|
+
readonly kjActive = input<boolean>(false);
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Whether the row is disabled. Reflects to `data-disabled=""` so theme CSS
|
|
260
|
+
* can dim the projected interactive child. Does not block keyboard
|
|
261
|
+
* reachability — that responsibility is the projected child's (via
|
|
262
|
+
* `KjDisabled`).
|
|
263
|
+
*/
|
|
264
|
+
readonly kjDisabled = input<boolean>(false);
|
|
265
|
+
|
|
266
|
+
/** @internal Mirrors `kjActive` for descendant-side reads via `KJ_LIST_ROW`. */
|
|
267
|
+
readonly active = this.kjActive;
|
|
268
|
+
|
|
269
|
+
/** @internal Mirrors `kjDisabled` for descendant-side reads via `KJ_LIST_ROW`. */
|
|
270
|
+
readonly disabled = this.kjDisabled;
|
|
271
|
+
|
|
272
|
+
/** @internal Computes the role to host-bind, omitting when the host is `<li>`. */
|
|
273
|
+
protected readonly roleAttr = computed<string | null>(() => {
|
|
274
|
+
const tag = this.el.nativeElement.tagName?.toLowerCase();
|
|
275
|
+
return tag === 'li' ? null : 'listitem';
|
|
276
|
+
});
|
|
277
|
+
}
|