@kouji-ui/core 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -4
- package/CHANGELOG.md +169 -0
- package/ng-package.json +5 -1
- package/package.json +18 -6
- package/src/a11y/aria-describedby.ts +7 -3
- package/src/a11y/aria-labelledby.spec.ts +55 -0
- package/src/a11y/aria-labelledby.ts +38 -0
- package/src/a11y/focus-trap.ts +4 -2
- package/src/a11y/index.ts +1 -0
- package/src/a11y/live-region.ts +4 -2
- package/src/a11y/roving-tabindex.spec.ts +139 -0
- package/src/a11y/roving-tabindex.ts +56 -7
- package/src/a11y/visually-hidden.ts +4 -2
- package/src/accordion/accordion.context.ts +22 -0
- package/src/accordion/accordion.spec.ts +233 -23
- package/src/accordion/accordion.ts +311 -39
- package/src/accordion/index.ts +8 -2
- package/src/alert/alert.context.ts +28 -0
- package/src/alert/alert.spec.ts +217 -0
- package/src/alert/alert.ts +417 -0
- package/src/alert/config.ts +46 -0
- package/src/alert/index.ts +19 -0
- package/src/avatar/avatar-group.context.ts +45 -0
- package/src/avatar/avatar-group.spec.ts +358 -0
- package/src/avatar/avatar-group.ts +259 -0
- package/src/avatar/avatar.ts +17 -4
- package/src/avatar/index.ts +11 -1
- package/src/badge/badge.spec.ts +8 -0
- package/src/badge/badge.ts +24 -3
- package/src/breadcrumb/breadcrumb-current.ts +38 -0
- package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
- package/src/breadcrumb/breadcrumb-item.ts +41 -0
- package/src/breadcrumb/breadcrumb-link.ts +44 -0
- package/src/breadcrumb/breadcrumb-list.ts +42 -0
- package/src/breadcrumb/breadcrumb-separator.ts +45 -0
- package/src/breadcrumb/breadcrumb.context.ts +45 -0
- package/src/breadcrumb/breadcrumb.spec.ts +354 -0
- package/src/breadcrumb/breadcrumb.ts +239 -0
- package/src/breadcrumb/config.ts +84 -0
- package/src/breadcrumb/index.ts +18 -0
- package/src/button/_examples/button.example.ts +71 -0
- package/src/button/_examples/button.finance.example.ts +83 -0
- package/src/button/_examples/button.retro.example.ts +91 -0
- package/src/button/_examples/button.sizes.example.ts +60 -0
- package/src/button/_examples/index.ts +6 -0
- package/src/button/button.spec.ts +139 -8
- package/src/button/button.ts +114 -16
- package/src/button/config.spec.ts +33 -0
- package/src/button/config.ts +45 -0
- package/src/button/index.ts +2 -1
- package/src/button-group/button-group.context.ts +28 -0
- package/src/button-group/button-group.spec.ts +99 -0
- package/src/button-group/button-group.ts +99 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar-day.ts +102 -0
- package/src/calendar/calendar-grid.ts +90 -0
- package/src/calendar/calendar-header.ts +65 -0
- package/src/calendar/calendar.context.ts +53 -0
- package/src/calendar/calendar.spec.ts +225 -0
- package/src/calendar/calendar.ts +233 -0
- package/src/calendar/date-utils.ts +225 -0
- package/src/calendar/index.ts +28 -0
- package/src/carousel/carousel.context.ts +81 -0
- package/src/carousel/carousel.spec.ts +337 -0
- package/src/carousel/carousel.ts +919 -0
- package/src/carousel/index.ts +22 -0
- package/src/cascade-select/cascade-select-option.ts +205 -0
- package/src/cascade-select/cascade-select-panel.ts +106 -0
- package/src/cascade-select/cascade-select-root.ts +265 -0
- package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
- package/src/cascade-select/cascade-select-trigger.ts +44 -0
- package/src/cascade-select/cascade-select.context.ts +71 -0
- package/src/cascade-select/cascade-select.spec.ts +241 -0
- package/src/cascade-select/cascade-select.ts +5 -0
- package/src/cascade-select/index.ts +11 -0
- package/src/chart/chart.ts +6 -2
- package/src/chat/chat-avatar.ts +31 -0
- package/src/chat/chat-bubble.ts +49 -0
- package/src/chat/chat-footer.ts +67 -0
- package/src/chat/chat-header.ts +41 -0
- package/src/chat/chat-log.ts +43 -0
- package/src/chat/chat.context.ts +64 -0
- package/src/chat/chat.spec.ts +341 -0
- package/src/chat/chat.ts +191 -0
- package/src/chat/config.ts +53 -0
- package/src/chat/index.ts +21 -0
- package/src/checkbox/checkbox.ts +9 -2
- package/src/color-picker/color-picker.context.ts +74 -0
- package/src/color-picker/color-picker.spec.ts +241 -0
- package/src/color-picker/color-picker.ts +698 -0
- package/src/color-picker/color-picker.utils.ts +140 -0
- package/src/color-picker/index.ts +28 -0
- package/src/combobox/combobox-input.ts +143 -0
- package/src/combobox/combobox-listbox.ts +49 -0
- package/src/combobox/combobox-option.ts +43 -0
- package/src/combobox/combobox-root.ts +281 -0
- package/src/combobox/combobox.spec.ts +75 -0
- package/src/combobox/combobox.ts +4 -0
- package/src/combobox/index.ts +4 -0
- package/src/command-palette/command-empty.ts +36 -0
- package/src/command-palette/command-group.ts +21 -0
- package/src/command-palette/command-input.ts +70 -0
- package/src/command-palette/command-item.ts +60 -0
- package/src/command-palette/command-list.ts +25 -0
- package/src/command-palette/command-palette-dialog.ts +74 -0
- package/src/command-palette/command-palette-trigger.ts +64 -0
- package/src/command-palette/command-palette.filters.ts +53 -0
- package/src/command-palette/command-palette.spec.ts +349 -0
- package/src/command-palette/command-palette.ts +204 -0
- package/src/command-palette/command-separator.ts +18 -0
- package/src/command-palette/index.ts +15 -0
- package/src/confirm-popup/confirm-popup-action.ts +39 -0
- package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
- package/src/confirm-popup/confirm-popup-content.ts +113 -0
- package/src/confirm-popup/confirm-popup-message.ts +36 -0
- package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
- package/src/confirm-popup/confirm-popup.context.ts +41 -0
- package/src/confirm-popup/confirm-popup.spec.ts +337 -0
- package/src/confirm-popup/confirm-popup.ts +109 -0
- package/src/confirm-popup/index.ts +12 -0
- package/src/date-picker/date-picker-calendar.ts +65 -0
- package/src/date-picker/date-picker-trigger.ts +215 -0
- package/src/date-picker/date-picker.context.ts +50 -0
- package/src/date-picker/date-picker.spec.ts +117 -0
- package/src/date-picker/date-picker.ts +127 -0
- package/src/date-picker/index.ts +7 -0
- package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
- package/src/dialog/_examples/dialog.example.ts +106 -0
- package/src/dialog/_examples/dialog.finance.example.ts +121 -0
- package/src/dialog/_examples/dialog.retro.example.ts +126 -0
- package/src/dialog/_examples/index.ts +6 -0
- package/src/dialog/dialog.ref.ts +42 -0
- package/src/dialog/dialog.service.ts +57 -167
- package/src/dialog/dialog.spec.ts +35 -120
- package/src/dialog/dialog.ts +17 -192
- package/src/dialog/index.ts +3 -9
- package/src/divider/divider.spec.ts +223 -0
- package/src/divider/divider.ts +137 -0
- package/src/divider/index.ts +5 -0
- package/src/drawer/drawer.ref.ts +63 -0
- package/src/drawer/drawer.service.ts +106 -0
- package/src/drawer/drawer.spec.ts +117 -0
- package/src/drawer/drawer.ts +122 -0
- package/src/drawer/index.ts +10 -0
- package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
- package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
- package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
- package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
- package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
- package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
- package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
- package/src/dropdown-menu/dropdown-menu.ts +60 -0
- package/src/dropdown-menu/index.ts +15 -0
- package/src/example-components.ts +39 -30
- package/src/field/field-error.ts +49 -0
- package/src/field/field-group.ts +39 -0
- package/src/field/field-help.ts +47 -0
- package/src/field/field-label.ts +30 -0
- package/src/field/field.context.ts +28 -0
- package/src/field/field.spec.ts +179 -0
- package/src/field/field.ts +121 -0
- package/src/field/index.ts +6 -0
- package/src/file-upload/file-upload.context.ts +89 -0
- package/src/file-upload/file-upload.spec.ts +290 -0
- package/src/file-upload/file-upload.ts +639 -0
- package/src/file-upload/file-upload.types.ts +59 -0
- package/src/file-upload/index.ts +23 -0
- package/src/form/form-error-summary.ts +87 -0
- package/src/form/form-field.context.ts +3 -0
- package/src/form/form-field.spec.ts +25 -0
- package/src/form/form-field.ts +52 -0
- package/src/form/form.context.ts +52 -2
- package/src/form/form.spec.ts +155 -18
- package/src/form/form.ts +315 -35
- package/src/form/index.ts +11 -2
- package/src/icon/E2E.md +14 -0
- package/src/icon/icon.css +40 -0
- package/src/icon/icon.directive.spec.ts +198 -0
- package/src/icon/icon.directive.ts +113 -0
- package/src/icon/icon.mode.spec.ts +29 -0
- package/src/icon/icon.mode.ts +23 -0
- package/src/icon/icon.providers.spec.ts +63 -0
- package/src/icon/icon.providers.ts +47 -0
- package/src/icon/icon.resolver.spec.ts +96 -0
- package/src/icon/icon.resolver.ts +49 -0
- package/src/icon/icon.tokens.ts +88 -0
- package/src/icon/icon.types.ts +48 -0
- package/src/icon/index.ts +21 -0
- package/src/input/input.ts +12 -3
- package/src/input-group/index.ts +3 -0
- package/src/input-group/input-group-addon.ts +92 -0
- package/src/input-group/input-group.context.ts +27 -0
- package/src/input-group/input-group.spec.ts +186 -0
- package/src/input-group/input-group.ts +136 -0
- package/src/input-mask/index.ts +11 -0
- package/src/input-mask/input-mask.compile.ts +69 -0
- package/src/input-mask/input-mask.engine.ts +376 -0
- package/src/input-mask/input-mask.presets.ts +24 -0
- package/src/input-mask/input-mask.spec.ts +304 -0
- package/src/input-mask/input-mask.tokens.ts +49 -0
- package/src/input-mask/input-mask.ts +280 -0
- package/src/input-otp/index.ts +2 -0
- package/src/input-otp/input-otp.context.ts +41 -0
- package/src/input-otp/input-otp.spec.ts +308 -0
- package/src/input-otp/input-otp.ts +470 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.spec.ts +127 -0
- package/src/kbd/kbd.ts +132 -0
- package/src/link/config.ts +44 -0
- package/src/link/index.ts +2 -0
- package/src/link/link.spec.ts +246 -0
- package/src/link/link.ts +226 -0
- package/src/list/index.ts +11 -0
- package/src/list/list.spec.ts +237 -0
- package/src/list/list.ts +277 -0
- package/src/menubar/index.ts +7 -0
- package/src/menubar/menubar-item.ts +204 -0
- package/src/menubar/menubar.context.ts +43 -0
- package/src/menubar/menubar.spec.ts +433 -0
- package/src/menubar/menubar.ts +309 -0
- package/src/number-input/index.ts +4 -0
- package/src/number-input/number-input-group.ts +64 -0
- package/src/number-input/number-input.context.ts +31 -0
- package/src/number-input/number-input.format.ts +148 -0
- package/src/number-input/number-input.spec.ts +170 -0
- package/src/number-input/number-input.ts +457 -0
- package/src/number-input/number-stepper.ts +193 -0
- package/src/overlay-badge/index.ts +7 -0
- package/src/overlay-badge/overlay-badge-content.ts +73 -0
- package/src/overlay-badge/overlay-badge.context.ts +37 -0
- package/src/overlay-badge/overlay-badge.spec.ts +220 -0
- package/src/overlay-badge/overlay-badge.ts +128 -0
- package/src/pagination/config.ts +82 -0
- package/src/pagination/index.ts +19 -0
- package/src/pagination/page-tokens.ts +78 -0
- package/src/pagination/pagination-ellipsis.ts +69 -0
- package/src/pagination/pagination-first.ts +84 -0
- package/src/pagination/pagination-info.ts +70 -0
- package/src/pagination/pagination-item.ts +81 -0
- package/src/pagination/pagination-last.ts +85 -0
- package/src/pagination/pagination-next.ts +86 -0
- package/src/pagination/pagination-previous.ts +90 -0
- package/src/pagination/pagination.context.ts +52 -0
- package/src/pagination/pagination.spec.ts +388 -0
- package/src/pagination/pagination.ts +288 -0
- package/src/password-input/index.ts +15 -0
- package/src/password-input/password-input.context.ts +65 -0
- package/src/password-input/password-input.scorer.ts +34 -0
- package/src/password-input/password-input.spec.ts +274 -0
- package/src/password-input/password-input.ts +488 -0
- package/src/popover/_examples/index.ts +5 -0
- package/src/popover/_examples/popover.example.ts +92 -0
- package/src/popover/_examples/popover.finance.example.ts +106 -0
- package/src/popover/_examples/popover.retro.example.ts +103 -0
- package/src/popover/index.ts +5 -12
- package/src/popover/popover-arrow.ts +30 -0
- package/src/popover/popover-close.ts +36 -0
- package/src/popover/popover-content.ts +41 -0
- package/src/popover/popover-title.ts +39 -0
- package/src/popover/popover-trigger.ts +40 -0
- package/src/popover/popover.spec.ts +51 -44
- package/src/popover/popover.ts +5 -124
- package/src/presets/bind-presets.spec.ts +50 -0
- package/src/presets/bind-presets.ts +43 -0
- package/src/presets/index.ts +3 -0
- package/src/presets/size.spec.ts +67 -0
- package/src/presets/size.ts +59 -0
- package/src/presets/variant.spec.ts +72 -0
- package/src/presets/variant.ts +62 -0
- package/src/primitives/directionality/directionality.spec.ts +112 -0
- package/src/primitives/directionality/directionality.ts +89 -0
- package/src/primitives/directionality/index.ts +1 -0
- package/src/primitives/forms/form-control.spec.ts +11 -3
- package/src/primitives/forms/form-control.ts +6 -2
- package/src/primitives/index.ts +3 -0
- package/src/primitives/interaction/disabled.ts +12 -5
- package/src/primitives/interaction/focus-ring.ts +4 -2
- package/src/primitives/list/filterable-list.spec.ts +114 -0
- package/src/primitives/list/filterable-list.ts +126 -0
- package/src/primitives/list/group.spec.ts +99 -0
- package/src/primitives/list/group.ts +139 -0
- package/src/primitives/list/index.ts +8 -0
- package/src/primitives/list/inject-helpers.ts +8 -0
- package/src/primitives/list/item.spec.ts +173 -0
- package/src/primitives/list/item.ts +222 -0
- package/src/primitives/list/navigator.spec.ts +286 -0
- package/src/primitives/list/navigator.ts +238 -0
- package/src/primitives/list/selection.spec.ts +214 -0
- package/src/primitives/list/selection.ts +339 -0
- package/src/primitives/list/tokens.ts +106 -0
- package/src/primitives/list/type-ahead.spec.ts +58 -0
- package/src/primitives/list/type-ahead.ts +46 -0
- package/src/primitives/overlay/backdrop.spec.ts +54 -0
- package/src/primitives/overlay/backdrop.ts +31 -0
- package/src/primitives/overlay/builder.spec.ts +38 -0
- package/src/primitives/overlay/builder.ts +151 -0
- package/src/primitives/overlay/container.ts +42 -0
- package/src/primitives/overlay/context.ts +14 -0
- package/src/primitives/overlay/controller.spec.ts +86 -0
- package/src/primitives/overlay/controller.ts +190 -0
- package/src/primitives/overlay/id.spec.ts +24 -0
- package/src/primitives/overlay/id.ts +17 -0
- package/src/primitives/overlay/index.ts +18 -1
- package/src/primitives/overlay/overlay.css +42 -0
- package/src/primitives/overlay/panel.spec.ts +52 -0
- package/src/primitives/overlay/panel.ts +102 -0
- package/src/primitives/overlay/stack.spec.ts +55 -0
- package/src/primitives/overlay/stack.ts +113 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
- package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
- package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
- package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
- package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
- package/src/primitives/overlay/strategies/index.ts +34 -0
- package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
- package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
- package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
- package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
- package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
- package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
- package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
- package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
- package/src/primitives/overlay/strategies/position/corner.ts +33 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
- package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
- package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
- package/src/primitives/overlay/tokens.ts +54 -0
- package/src/primitives/overlay/trigger.spec.ts +103 -0
- package/src/primitives/overlay/trigger.ts +75 -0
- package/src/primitives/overlay/types.ts +24 -0
- package/src/primitives/overlay/wrapper.ts +50 -0
- package/src/primitives/signals/deep-signal.ts +109 -0
- package/src/primitives/signals/index.ts +1 -0
- package/src/progress-bar/config.ts +48 -0
- package/src/progress-bar/index.ts +12 -0
- package/src/progress-bar/progress-bar-fill.ts +37 -0
- package/src/progress-bar/progress-bar.context.ts +34 -0
- package/src/progress-bar/progress-bar.spec.ts +423 -0
- package/src/progress-bar/progress-bar.ts +217 -0
- package/src/public-api.ts +58 -9
- package/src/radio/radio.ts +14 -3
- package/src/select/index.ts +4 -2
- package/src/select/select-content.ts +69 -0
- package/src/select/select-option.ts +35 -0
- package/src/select/select-root.ts +129 -0
- package/src/select/select-trigger.ts +93 -0
- package/src/select/select.spec.ts +133 -140
- package/src/select/select.ts +4 -250
- package/src/skeleton/index.ts +5 -0
- package/src/skeleton/skeleton.spec.ts +115 -0
- package/src/skeleton/skeleton.ts +56 -0
- package/src/slider/index.ts +10 -0
- package/src/slider/slider-range.ts +49 -0
- package/src/slider/slider-thumb.ts +335 -0
- package/src/slider/slider-track.ts +70 -0
- package/src/slider/slider.context.ts +79 -0
- package/src/slider/slider.geometry.ts +73 -0
- package/src/slider/slider.spec.ts +216 -0
- package/src/slider/slider.ts +443 -0
- package/src/speed-dial/index.ts +10 -0
- package/src/speed-dial/speed-dial-action.ts +40 -0
- package/src/speed-dial/speed-dial-actions.ts +31 -0
- package/src/speed-dial/speed-dial-trigger.ts +55 -0
- package/src/speed-dial/speed-dial.context.ts +36 -0
- package/src/speed-dial/speed-dial.spec.ts +143 -0
- package/src/speed-dial/speed-dial.ts +127 -0
- package/src/spinner/config.ts +48 -0
- package/src/spinner/index.ts +8 -0
- package/src/spinner/spinner.spec.ts +262 -0
- package/src/spinner/spinner.ts +129 -0
- package/src/stepper/index.ts +15 -0
- package/src/stepper/stepper.context.ts +63 -0
- package/src/stepper/stepper.spec.ts +403 -0
- package/src/stepper/stepper.ts +544 -0
- package/src/styles.css +17 -0
- package/src/table/column-helpers.spec.ts +74 -0
- package/src/table/column-helpers.ts +68 -0
- package/src/table/filter-fns.ts +230 -0
- package/src/table/filter-models.spec.ts +240 -0
- package/src/table/filter-models.ts +124 -0
- package/src/table/filter-params.ts +90 -0
- package/src/table/grid-api-impl.ts +321 -0
- package/src/table/grid-api.spec.ts +178 -0
- package/src/table/grid-api.ts +285 -0
- package/src/table/index.ts +73 -1
- package/src/table/table-cell.spec.ts +43 -0
- package/src/table/table-cell.ts +38 -0
- package/src/table/table-filter-outlet.ts +120 -0
- package/src/table/table-header.spec.ts +54 -0
- package/src/table/table-header.ts +63 -0
- package/src/table/table-keyboard.spec.ts +68 -0
- package/src/table/table-keyboard.ts +107 -0
- package/src/table/table-resource.spec.ts +37 -0
- package/src/table/table-resource.ts +36 -0
- package/src/table/table-row.spec.ts +54 -0
- package/src/table/table-row.ts +31 -0
- package/src/table/table-storage.spec.ts +67 -0
- package/src/table/table-storage.ts +76 -0
- package/src/table/table.spec.ts +101 -42
- package/src/table/table.ts +228 -73
- package/src/table/table.types.ts +83 -0
- package/src/tabs/index.ts +7 -2
- package/src/tabs/tabs.context.ts +27 -4
- package/src/tabs/tabs.spec.ts +336 -24
- package/src/tabs/tabs.ts +313 -79
- package/src/tag/config.ts +53 -0
- package/src/tag/index.ts +7 -0
- package/src/tag/tag-list.ts +76 -0
- package/src/tag/tag-remove.ts +61 -0
- package/src/tag/tag.context.ts +36 -0
- package/src/tag/tag.spec.ts +240 -0
- package/src/tag/tag.ts +203 -0
- package/src/textarea/config.ts +42 -0
- package/src/textarea/index.ts +8 -0
- package/src/textarea/textarea.spec.ts +176 -0
- package/src/textarea/textarea.ts +235 -0
- package/src/time-picker/index.ts +15 -0
- package/src/time-picker/time-picker-meridiem.ts +123 -0
- package/src/time-picker/time-picker-segment.ts +318 -0
- package/src/time-picker/time-picker.context.ts +38 -0
- package/src/time-picker/time-picker.format.ts +181 -0
- package/src/time-picker/time-picker.spec.ts +189 -0
- package/src/time-picker/time-picker.ts +274 -0
- package/src/toast/_examples/index.ts +5 -0
- package/src/toast/_examples/toast.example.ts +282 -0
- package/src/toast/_examples/toast.finance.example.ts +180 -0
- package/src/toast/_examples/toast.retro.example.ts +161 -0
- package/src/toast/index.ts +8 -1
- package/src/toast/toast.ref.ts +26 -0
- package/src/toast/toast.service.ts +227 -41
- package/src/toast/toast.spec.ts +166 -14
- package/src/toast/toast.strategy.ts +10 -0
- package/src/toast/toast.ts +177 -21
- package/src/toggle/toggle.ts +10 -2
- package/src/tooltip/_examples/index.ts +6 -0
- package/src/tooltip/_examples/tooltip.example.ts +82 -0
- package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
- package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
- package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
- package/src/tooltip/index.ts +4 -5
- package/src/tooltip/tooltip-arrow.ts +31 -0
- package/src/tooltip/tooltip-content.ts +38 -0
- package/src/tooltip/tooltip-group.ts +32 -0
- package/src/tooltip/tooltip-trigger.ts +47 -0
- package/src/tooltip/tooltip.spec.ts +38 -102
- package/src/tooltip/tooltip.ts +5 -129
- package/src/tree-select/index.ts +12 -0
- package/src/tree-select/tree-select-content.ts +199 -0
- package/src/tree-select/tree-select-node.ts +132 -0
- package/src/tree-select/tree-select-root.ts +272 -0
- package/src/tree-select/tree-select-trigger.ts +90 -0
- package/src/tree-select/tree-select.context.ts +63 -0
- package/src/tree-select/tree-select.spec.ts +433 -0
- package/src/tree-select/tree-select.ts +2 -0
- package/src/typography/blockquote.ts +57 -0
- package/src/typography/code.ts +53 -0
- package/src/typography/index.ts +22 -0
- package/src/typography/lead.ts +49 -0
- package/src/typography/muted.ts +29 -0
- package/src/typography/prose.css +312 -0
- package/src/typography/truncate.ts +99 -0
- package/src/typography/typography.spec.ts +203 -0
- package/tsconfig.lib.json +9 -1
- package/tsconfig.lib.prod.json +7 -1
- package/tsconfig.spec.json +9 -1
- package/vite.config.ts +8 -0
- package/src/button/button.example.ts +0 -30
- package/src/button/button.finance.example.ts +0 -37
- package/src/button/button.retro.example.ts +0 -47
- package/src/button/button.sizes.example.ts +0 -25
- package/src/dialog/dialog.confirm.example.ts +0 -57
- package/src/dialog/dialog.context.ts +0 -11
- package/src/dialog/dialog.example.ts +0 -40
- package/src/dialog/dialog.finance.example.ts +0 -47
- package/src/dialog/dialog.retro.example.ts +0 -46
- package/src/menu/index.ts +0 -2
- package/src/menu/menu.context.ts +0 -10
- package/src/menu/menu.example.ts +0 -39
- package/src/menu/menu.spec.ts +0 -168
- package/src/menu/menu.ts +0 -176
- package/src/popover/popover.context.ts +0 -18
- package/src/popover/popover.example.ts +0 -50
- package/src/popover/popover.finance.example.ts +0 -52
- package/src/popover/popover.retro.example.ts +0 -53
- package/src/primitives/overlay/overlay.spec.ts +0 -90
- package/src/primitives/overlay/overlay.ts +0 -106
- package/src/select/select.context.ts +0 -3
- package/src/toast/toast.example.ts +0 -134
- package/src/toast/toast.finance.example.ts +0 -68
- package/src/toast/toast.retro.example.ts +0 -69
- package/src/tooltip/tooltip.example.ts +0 -46
- package/src/tooltip/tooltip.finance.example.ts +0 -54
- package/src/tooltip/tooltip.placements.example.ts +0 -56
- package/src/tooltip/tooltip.retro.example.ts +0 -65
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
ElementRef,
|
|
5
|
+
Injector,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
signal,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { KjOverlayPanel } from '../primitives/overlay/panel';
|
|
13
|
+
import type { KjOverlayController } from '../primitives/overlay/controller';
|
|
14
|
+
import {
|
|
15
|
+
KJ_OVERLAY_MOUNT_STRATEGY,
|
|
16
|
+
KJ_OVERLAY_POSITION_STRATEGY,
|
|
17
|
+
KJ_OVERLAY_PANEL_ROLE,
|
|
18
|
+
} from '../primitives/overlay/tokens';
|
|
19
|
+
import type { KjSide, KjAlign } from '../primitives/overlay/types';
|
|
20
|
+
import { bodyPortal } from '../primitives/overlay/strategies/mount/body-portal';
|
|
21
|
+
import { anchoredTo } from '../primitives/overlay/strategies/position/anchored-to';
|
|
22
|
+
import {
|
|
23
|
+
KJ_LIST_FOCUS_MODE,
|
|
24
|
+
KjListNavigator,
|
|
25
|
+
type KjListFocusMode,
|
|
26
|
+
} from '../primitives/list';
|
|
27
|
+
import { KJ_TREE_SELECT } from './tree-select.context';
|
|
28
|
+
|
|
29
|
+
// Token override below pins the focus mode to `'roving'` for descendant
|
|
30
|
+
// `KjListItem`s. Defined at module scope so it can be referenced from
|
|
31
|
+
// the `providers` array literal without a class capture.
|
|
32
|
+
const ROVING_FOCUS_MODE = signal<KjListFocusMode>('roving');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tree panel container. Composes `KjOverlayPanel` for mount/position/role
|
|
36
|
+
* wiring (carries `role="tree"` from the panel role provider) and
|
|
37
|
+
* `KjListNavigator` for the generic Up/Down / Home/End / Enter / Space /
|
|
38
|
+
* type-ahead contract. The tree-specific ArrowLeft / ArrowRight keys
|
|
39
|
+
* remain handled here because they carry expand/collapse +
|
|
40
|
+
* parent/first-child semantics the generic navigator doesn't cover.
|
|
41
|
+
*
|
|
42
|
+
* Roving DOM focus is wired here (rather than via the navigator's own
|
|
43
|
+
* `kjFocusMode="roving"` mode) because Angular's `hostDirectives.inputs`
|
|
44
|
+
* surface can only rename inputs — it can't push a static default into a
|
|
45
|
+
* composed input signal. Instead this component (1) overrides the
|
|
46
|
+
* `KJ_LIST_FOCUS_MODE` provider so child `KjListItem`s flip their
|
|
47
|
+
* `tabindex` correctly, and (2) drives the active-item DOM focus via a
|
|
48
|
+
* local effect on `KjListNavigator.activeId()`. The navigator itself
|
|
49
|
+
* continues to run its keyboard / type-ahead state machine unchanged.
|
|
50
|
+
*
|
|
51
|
+
* @doc-category Core/Inputs
|
|
52
|
+
*/
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'kj-tree-select-content',
|
|
55
|
+
standalone: true,
|
|
56
|
+
hostDirectives: [
|
|
57
|
+
{ directive: KjOverlayPanel, inputs: ['kjFor'] },
|
|
58
|
+
KjListNavigator,
|
|
59
|
+
],
|
|
60
|
+
providers: [
|
|
61
|
+
{ provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'tree' as const },
|
|
62
|
+
{ provide: KJ_OVERLAY_MOUNT_STRATEGY, useFactory: () => bodyPortal() },
|
|
63
|
+
{ provide: KJ_OVERLAY_POSITION_STRATEGY, useFactory: () => anchoredTo() },
|
|
64
|
+
// Pin items' focus model to `'roving'` (per WAI-ARIA APG tree). This
|
|
65
|
+
// overrides the provider the navigator registers (which mirrors the
|
|
66
|
+
// navigator's own `kjFocusMode` signal). The navigator's internal
|
|
67
|
+
// seed / focus effects are skipped — both are driven here instead.
|
|
68
|
+
{ provide: KJ_LIST_FOCUS_MODE, useValue: ROVING_FOCUS_MODE },
|
|
69
|
+
],
|
|
70
|
+
host: {
|
|
71
|
+
'[attr.aria-multiselectable]':
|
|
72
|
+
'ctx?.selectionMode() === "multiple" ? "true" : null',
|
|
73
|
+
'(keydown)': 'onKeydown($event)',
|
|
74
|
+
'(document:keydown.escape)': 'controller?.close("esc")',
|
|
75
|
+
'(document:click)': 'onDocClick($event)',
|
|
76
|
+
'(click)': '$event.stopPropagation()',
|
|
77
|
+
},
|
|
78
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
79
|
+
encapsulation: ViewEncapsulation.None,
|
|
80
|
+
template: `<ng-content />`,
|
|
81
|
+
})
|
|
82
|
+
export class KjTreeSelectContent {
|
|
83
|
+
private readonly el = inject(ElementRef<HTMLElement>);
|
|
84
|
+
private readonly _injector = inject(Injector);
|
|
85
|
+
private _panelCache: KjOverlayPanel | null | undefined = undefined;
|
|
86
|
+
private get _panel(): KjOverlayPanel | null {
|
|
87
|
+
if (this._panelCache === undefined) {
|
|
88
|
+
this._panelCache = this._injector.get(KjOverlayPanel, null);
|
|
89
|
+
}
|
|
90
|
+
return this._panelCache;
|
|
91
|
+
}
|
|
92
|
+
/** @internal */
|
|
93
|
+
get controller(): KjOverlayController | null {
|
|
94
|
+
return this._panel?.controller ?? null;
|
|
95
|
+
}
|
|
96
|
+
/** @internal */
|
|
97
|
+
readonly ctx = inject(KJ_TREE_SELECT, { optional: true });
|
|
98
|
+
// The composed `KjListNavigator` owns the generic Up/Down/Home/End/
|
|
99
|
+
// Enter/Space/type-ahead contract. We read its `activeItem()` below to
|
|
100
|
+
// anchor ArrowLeft / ArrowRight off the currently focused tree node
|
|
101
|
+
// and to follow the active id with DOM focus (roving model).
|
|
102
|
+
private readonly nav = inject(KjListNavigator);
|
|
103
|
+
|
|
104
|
+
readonly kjSide = input<KjSide>('bottom');
|
|
105
|
+
readonly kjAlign = input<KjAlign>('start');
|
|
106
|
+
readonly kjOffset = input<number, unknown>(4, {
|
|
107
|
+
transform: v => Number(v) || 4,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
constructor() {
|
|
111
|
+
const pos = inject(KJ_OVERLAY_POSITION_STRATEGY) as ReturnType<typeof anchoredTo>;
|
|
112
|
+
pos.configure({ side: this.kjSide, align: this.kjAlign, offset: this.kjOffset, matchTriggerWidth: 'min' });
|
|
113
|
+
|
|
114
|
+
// Roving focus follow: mirror the navigator's `activeId` to DOM
|
|
115
|
+
// focus. Equivalent to `KjListNavigator`'s internal focus effect,
|
|
116
|
+
// re-implemented here because the navigator's effect is gated on
|
|
117
|
+
// `kjFocusMode() === 'roving'` and we can't statically push that
|
|
118
|
+
// value into a hostDirective input signal.
|
|
119
|
+
effect(() => {
|
|
120
|
+
const item = this.nav.activeItem();
|
|
121
|
+
if (!item) return;
|
|
122
|
+
const host = item._host();
|
|
123
|
+
if (host && document.activeElement !== host) host.focus();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @internal */
|
|
128
|
+
onDocClick(event: MouseEvent): void {
|
|
129
|
+
const ctrl = this.controller;
|
|
130
|
+
if (!ctrl?.isOpen()) return;
|
|
131
|
+
const target = event.target as Node | null;
|
|
132
|
+
if (!target) return;
|
|
133
|
+
if (this.el.nativeElement.contains(target)) return;
|
|
134
|
+
const trigger = ctrl.triggerEl();
|
|
135
|
+
if (trigger && trigger.contains(target)) return;
|
|
136
|
+
ctrl.close('outside');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Tree-specific ArrowLeft / ArrowRight handling. Up/Down/Home/End/
|
|
141
|
+
* Enter/Space/type-ahead all flow through the composed
|
|
142
|
+
* `KjListNavigator` — they aren't reimplemented here.
|
|
143
|
+
*
|
|
144
|
+
* - ArrowRight on a collapsed branch → expand (toggle).
|
|
145
|
+
* - ArrowRight on an expanded branch → move active to first child.
|
|
146
|
+
* - ArrowLeft on an expanded branch → collapse (toggle).
|
|
147
|
+
* - ArrowLeft elsewhere → move active to the parent (prior item with
|
|
148
|
+
* `aria-level` less than current).
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
onKeydown(event: KeyboardEvent): void {
|
|
153
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
|
154
|
+
const active = this.nav.activeItem();
|
|
155
|
+
if (!active) return;
|
|
156
|
+
const node = active._host();
|
|
157
|
+
const items = this._domNodes();
|
|
158
|
+
const idx = items.indexOf(node);
|
|
159
|
+
if (idx === -1) return;
|
|
160
|
+
const hasChildren = node.getAttribute('data-has-children') === 'true';
|
|
161
|
+
const expanded = node.getAttribute('data-expanded') === 'true';
|
|
162
|
+
|
|
163
|
+
if (event.key === 'ArrowRight') {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
if (hasChildren && !expanded) {
|
|
166
|
+
const toggle = node.querySelector('[kjTreeSelectToggle]') as HTMLElement | null;
|
|
167
|
+
toggle?.click();
|
|
168
|
+
} else if (hasChildren && expanded) {
|
|
169
|
+
const next = items[idx + 1];
|
|
170
|
+
if (next) this.nav.setActive(next.id);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ArrowLeft
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
if (hasChildren && expanded) {
|
|
178
|
+
const toggle = node.querySelector('[kjTreeSelectToggle]') as HTMLElement | null;
|
|
179
|
+
toggle?.click();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const currentLevel = parseInt(node.getAttribute('aria-level') ?? '1', 10);
|
|
183
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
184
|
+
const lvl = parseInt(items[i]?.getAttribute('aria-level') ?? '1', 10);
|
|
185
|
+
if (lvl < currentLevel) {
|
|
186
|
+
this.nav.setActive(items[i].id);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** DOM-order list of tree nodes scoped to this panel. */
|
|
193
|
+
private _domNodes(): HTMLElement[] {
|
|
194
|
+
return Array.from(
|
|
195
|
+
this.el.nativeElement.querySelectorAll('[kjTreeSelectNode]'),
|
|
196
|
+
) as HTMLElement[];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
booleanAttribute,
|
|
3
|
+
computed,
|
|
4
|
+
Directive,
|
|
5
|
+
inject,
|
|
6
|
+
input,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { KjListItem, injectListItem } from '../primitives/list';
|
|
9
|
+
import { KJ_TREE_SELECT } from './tree-select.context';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Individual tree node (treeitem). Composes `KjListItem` via
|
|
13
|
+
* `hostDirectives` — the list item owns the stable id, the
|
|
14
|
+
* click/Enter/Space activation, the `aria-selected` / `aria-disabled`
|
|
15
|
+
* bindings, and the shared `KjSelectionModel` toggle. This directive
|
|
16
|
+
* contributes only tree-specific semantics: `role="treeitem"`,
|
|
17
|
+
* `aria-level` / `aria-setsize` / `aria-posinset` from the parent's
|
|
18
|
+
* derived metadata, `aria-expanded` from the tree context's
|
|
19
|
+
* expanded-id set, and the `data-*` styling hooks.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```html
|
|
23
|
+
* <div
|
|
24
|
+
* kjTreeSelectNode
|
|
25
|
+
* [kjValue]="node.value"
|
|
26
|
+
* [kjLabel]="node.label"
|
|
27
|
+
* [kjNodeLevel]="1"
|
|
28
|
+
* [kjHasChildren]="true"
|
|
29
|
+
* >
|
|
30
|
+
* <button kjTreeSelectToggle>▶</button>
|
|
31
|
+
* <span>{{ node.label }}</span>
|
|
32
|
+
* </div>
|
|
33
|
+
* ```
|
|
34
|
+
* @doc-category Core/Inputs
|
|
35
|
+
* @doc
|
|
36
|
+
* @doc-name tree-select
|
|
37
|
+
* @doc-description Renders one tree node with accessible treeitem semantics; selection + activation owned by the composed KjListItem.
|
|
38
|
+
*/
|
|
39
|
+
@Directive({
|
|
40
|
+
selector: '[kjTreeSelectNode]',
|
|
41
|
+
standalone: true,
|
|
42
|
+
exportAs: 'kjTreeSelectNode',
|
|
43
|
+
hostDirectives: [
|
|
44
|
+
{
|
|
45
|
+
directive: KjListItem,
|
|
46
|
+
inputs: [
|
|
47
|
+
'kjItemValue:kjValue',
|
|
48
|
+
'kjItemLabel:kjLabel',
|
|
49
|
+
'kjDisabled:kjDisabled',
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
host: {
|
|
54
|
+
'class': 'kj-tree-select-node',
|
|
55
|
+
'role': 'treeitem',
|
|
56
|
+
'[attr.aria-level]': 'kjNodeLevel()',
|
|
57
|
+
'[attr.aria-setsize]': 'kjNodeSize()',
|
|
58
|
+
'[attr.aria-posinset]': 'kjNodePos()',
|
|
59
|
+
'[attr.aria-expanded]': 'kjHasChildren() ? (isExpanded() ? "true" : "false") : null',
|
|
60
|
+
'[attr.data-expanded]': 'isExpanded() ? "true" : "false"',
|
|
61
|
+
'[attr.data-has-children]': 'kjHasChildren() ? "true" : "false"',
|
|
62
|
+
// `aria-selected` is owned by the composed `KjListItem` (it binds
|
|
63
|
+
// off the shared `KjSelectionModel`). We surface it as a
|
|
64
|
+
// `data-selected` styling hook here so consumer CSS doesn't have to
|
|
65
|
+
// care that the source of truth is one directive layer down.
|
|
66
|
+
'[attr.data-selected]': 'item.ariaSelected() === "true" ? "" : null',
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
export class KjTreeSelectNode {
|
|
70
|
+
/** Nesting depth (1-based). Maps to `aria-level`. */
|
|
71
|
+
readonly kjNodeLevel = input<number>(1);
|
|
72
|
+
/** Number of sibling nodes at the same level. Maps to `aria-setsize`. */
|
|
73
|
+
readonly kjNodeSize = input<number>(1);
|
|
74
|
+
/** 1-based position among siblings. Maps to `aria-posinset`. */
|
|
75
|
+
readonly kjNodePos = input<number>(1);
|
|
76
|
+
/** Whether this node has child nodes (branch vs. leaf). */
|
|
77
|
+
readonly kjHasChildren = input(false, { transform: booleanAttribute });
|
|
78
|
+
|
|
79
|
+
private readonly ctx = inject(KJ_TREE_SELECT);
|
|
80
|
+
/** @internal — composed list-item primitive providing id/value/disabled/activation. */
|
|
81
|
+
readonly item = injectListItem<unknown>();
|
|
82
|
+
|
|
83
|
+
/** Stable id minted by the composed `KjListItem` (used by toggle + aria refs). */
|
|
84
|
+
get id(): string { return this.item.id; }
|
|
85
|
+
/** Current value carried by this node (from the composed `KjListItem`). */
|
|
86
|
+
get value(): unknown { return this.item.value(); }
|
|
87
|
+
/** Whether this node is disabled (delegated to the composed `KjListItem`). */
|
|
88
|
+
readonly disabled = this.item.disabled;
|
|
89
|
+
/** Whether the node is currently expanded (reactive computed from context). */
|
|
90
|
+
readonly isExpanded = computed(() => this.ctx.isExpanded(this.id));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Expand/collapse toggle button inside a `KjTreeSelectNode`. Intercepts
|
|
95
|
+
* clicks before they bubble to the node (preventing accidental
|
|
96
|
+
* activation through the composed `KjListItem`) and toggles the
|
|
97
|
+
* tree-context expansion state by both the node's id and its value.
|
|
98
|
+
* Only meaningful on branch nodes (`[kjHasChildren]="true"`).
|
|
99
|
+
*
|
|
100
|
+
* Carries `aria-label` that reflects the current state.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```html
|
|
104
|
+
* <button kjTreeSelectToggle aria-hidden="true"></button>
|
|
105
|
+
* ```
|
|
106
|
+
* @doc-category Core/Inputs
|
|
107
|
+
*/
|
|
108
|
+
@Directive({
|
|
109
|
+
selector: '[kjTreeSelectToggle]',
|
|
110
|
+
standalone: true,
|
|
111
|
+
host: {
|
|
112
|
+
'[attr.aria-label]': 'isExpanded() ? "Collapse" : "Expand"',
|
|
113
|
+
'[attr.data-expanded]': 'isExpanded() ? "true" : "false"',
|
|
114
|
+
'(click)': 'handleClick($event)',
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
export class KjTreeSelectToggle {
|
|
118
|
+
private readonly ctx = inject(KJ_TREE_SELECT);
|
|
119
|
+
/** @internal — parent node context */
|
|
120
|
+
private readonly node = inject(KjTreeSelectNode);
|
|
121
|
+
|
|
122
|
+
/** Whether the parent node is currently expanded (reactive computed). */
|
|
123
|
+
readonly isExpanded = this.node.isExpanded;
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
handleClick(event: Event): void {
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
if (this.node.disabled()) return;
|
|
129
|
+
this.ctx.toggleNode(this.node.id);
|
|
130
|
+
this.ctx.toggleNodeByValue(this.node.value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
contentChildren,
|
|
4
|
+
Directive,
|
|
5
|
+
effect,
|
|
6
|
+
forwardRef,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
model,
|
|
10
|
+
output,
|
|
11
|
+
signal,
|
|
12
|
+
untracked,
|
|
13
|
+
type Signal,
|
|
14
|
+
type WritableSignal,
|
|
15
|
+
} from '@angular/core';
|
|
16
|
+
import { KjOverlayController } from '../primitives/overlay/controller';
|
|
17
|
+
import {
|
|
18
|
+
KJ_LIST_NAVIGATOR_CONFIG,
|
|
19
|
+
KjListItem,
|
|
20
|
+
KjSelectionModel,
|
|
21
|
+
type KjCompareFn,
|
|
22
|
+
type KjListNavigatorConfig,
|
|
23
|
+
type KjListSelectionMode,
|
|
24
|
+
type KjTreeShape,
|
|
25
|
+
} from '../primitives/list';
|
|
26
|
+
import { KJ_TREE_SELECT, type KjTreeNode, type KjTreeSelectContext } from './tree-select.context';
|
|
27
|
+
|
|
28
|
+
let _treeSelectIdCounter = 0;
|
|
29
|
+
|
|
30
|
+
function setsEqual(a: ReadonlySet<unknown>, b: ReadonlySet<unknown>): boolean {
|
|
31
|
+
if (a.size !== b.size) return false;
|
|
32
|
+
for (const v of a) if (!b.has(v)) return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Root Tree Select container. Owns selection model, expanded node state,
|
|
38
|
+
* and the shared `KjOverlayController` so the trigger, content, and
|
|
39
|
+
* individual nodes all wire to the same overlay state.
|
|
40
|
+
*
|
|
41
|
+
* Mirrors the `[kjSelect]` pattern: umbrella root holds context +
|
|
42
|
+
* controller; leaf directives read from it via DI.
|
|
43
|
+
*
|
|
44
|
+
* Supports two selection modes:
|
|
45
|
+
* - `'single'` (default) — selecting a node writes that value and closes the panel.
|
|
46
|
+
* - `'multiple'` — selecting toggles the value in the selection set; the panel
|
|
47
|
+
* stays open.
|
|
48
|
+
*
|
|
49
|
+
* Implements {@link KjListNavigatorConfig}: descendants (`KjListNavigator`,
|
|
50
|
+
* `KjSelectionModel`) read the canonical `value`, `mode`, `compareBy`, and
|
|
51
|
+
* optional `treeShape` from the same root via `KJ_LIST_NAVIGATOR_CONFIG`.
|
|
52
|
+
* The legacy {@link KjTreeSelectContext} surface is preserved as a thin
|
|
53
|
+
* delegate so existing node/toggle directives keep working.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```html
|
|
57
|
+
* <div kjTreeSelect [(kjValue)]="selected" [kjNodes]="categories">
|
|
58
|
+
* <button kjTreeSelectTrigger #t="kjTreeSelectTrigger">Select category</button>
|
|
59
|
+
* <kj-tree-select-content [kjFor]="t"></kj-tree-select-content>
|
|
60
|
+
* </div>
|
|
61
|
+
* ```
|
|
62
|
+
* @doc-category Core/Inputs
|
|
63
|
+
* @doc
|
|
64
|
+
* @doc-name tree-select
|
|
65
|
+
* @doc-is-main
|
|
66
|
+
* @doc-description Unstyled tree-select root that holds the selection model and shares state with trigger, content, and nodes.
|
|
67
|
+
*/
|
|
68
|
+
@Directive({
|
|
69
|
+
selector: '[kjTreeSelect]',
|
|
70
|
+
standalone: true,
|
|
71
|
+
providers: [
|
|
72
|
+
{ provide: KJ_TREE_SELECT, useExisting: KjTreeSelect },
|
|
73
|
+
{ provide: KJ_LIST_NAVIGATOR_CONFIG, useExisting: forwardRef(() => KjTreeSelect) },
|
|
74
|
+
KjSelectionModel,
|
|
75
|
+
KjOverlayController,
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
export class KjTreeSelect implements KjListNavigatorConfig, KjTreeSelectContext {
|
|
79
|
+
// Note: selection state (current value, isSelected membership) lives
|
|
80
|
+
// on the shared `KjSelectionModel` injected by `KjListItem`. The
|
|
81
|
+
// tree-select context here only owns tree-specific surface:
|
|
82
|
+
// expansion, the node-data signal, and the public `selectionMode`
|
|
83
|
+
// input name. Programmatic selection routes through the same
|
|
84
|
+
// `kjValue` signal that the selection model writes to — see
|
|
85
|
+
// `afterSelect` below.
|
|
86
|
+
/** @internal — shared overlay controller; trigger + content + nodes all see this same instance. */
|
|
87
|
+
private readonly controller = inject(KjOverlayController);
|
|
88
|
+
|
|
89
|
+
/** Tree node data. Each node may carry children, forming a hierarchy. */
|
|
90
|
+
readonly kjNodes = input<readonly KjTreeNode[]>([]);
|
|
91
|
+
|
|
92
|
+
/** Selection mode: `'single'` closes the panel on select; `'multiple'` toggles. */
|
|
93
|
+
readonly kjSelectionMode = input<'single' | 'multiple'>('single');
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Two-way model for the selected value(s). In `'single'` mode this holds
|
|
97
|
+
* one value (or `undefined`); in `'multiple'` mode it holds an array.
|
|
98
|
+
*/
|
|
99
|
+
readonly kjValue = model<unknown>(undefined);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Optional tree topology used by `KjSelectionModel` in `'leaf'` /
|
|
103
|
+
* `'cascade'` selection modes. Exposed as a signal so the model can
|
|
104
|
+
* follow it reactively without `KjTreeSelect` having to inject the
|
|
105
|
+
* model (that would re-introduce the NG0200 cycle).
|
|
106
|
+
*/
|
|
107
|
+
readonly kjTreeShape = input<KjTreeShape<unknown> | null>(null);
|
|
108
|
+
|
|
109
|
+
/** Set of node IDs that are currently expanded (two-way bindable). */
|
|
110
|
+
readonly kjExpandedKeys = model<readonly unknown[]>([]);
|
|
111
|
+
|
|
112
|
+
/** Emitted when a node is selected. */
|
|
113
|
+
readonly kjNodeSelect = output<unknown>();
|
|
114
|
+
|
|
115
|
+
/** Emitted when a node branch is expanded. */
|
|
116
|
+
readonly kjNodeExpand = output<unknown>();
|
|
117
|
+
|
|
118
|
+
/** Emitted when a node branch is collapsed. */
|
|
119
|
+
readonly kjNodeCollapse = output<unknown>();
|
|
120
|
+
|
|
121
|
+
/** Stable panel id used for `aria-controls`. */
|
|
122
|
+
readonly panelId = `kj-tree-select-panel-${++_treeSelectIdCounter}`;
|
|
123
|
+
|
|
124
|
+
private readonly _expandedIds = signal<Set<string>>(new Set());
|
|
125
|
+
private readonly _expandedValues = signal<Set<unknown>>(new Set());
|
|
126
|
+
|
|
127
|
+
// ── KjListNavigatorConfig implementation ────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Items contributed by `KjListItem` instances under this root. Empty
|
|
131
|
+
* until node-level wiring lands (Task 3 of the migration plan); kept
|
|
132
|
+
* here now so the config contract is satisfied today.
|
|
133
|
+
*/
|
|
134
|
+
readonly items = contentChildren(KjListItem, { descendants: true });
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Single canonical value signal. Shared with the legacy `kjValue`
|
|
138
|
+
* model — `KjSelectionModel` reads / writes through this signal.
|
|
139
|
+
*/
|
|
140
|
+
readonly value = this.kjValue as unknown as WritableSignal<
|
|
141
|
+
unknown | readonly unknown[] | null
|
|
142
|
+
>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* `KjListSelectionMode` view of {@link kjSelectionMode}:
|
|
146
|
+
* - `'single'` → `'single'` (one value, closes panel)
|
|
147
|
+
* - `'multiple'` → `'cascade'` (tri-state tree selection: toggling a
|
|
148
|
+
* parent selects/deselects every leaf descendant; parent renders
|
|
149
|
+
* `aria-checked="mixed"` when only some descendants are selected).
|
|
150
|
+
*
|
|
151
|
+
* Cascade mode requires a tree shape; {@link treeShape} falls back to
|
|
152
|
+
* a shape auto-derived from {@link kjNodes} when the consumer does
|
|
153
|
+
* not supply one.
|
|
154
|
+
*/
|
|
155
|
+
readonly mode: Signal<KjListSelectionMode> = computed(() =>
|
|
156
|
+
this.kjSelectionMode() === 'multiple' ? 'cascade' : 'single',
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
/** Equality fn used by `KjSelectionModel`. Defaults to `Object.is`. */
|
|
160
|
+
readonly compareBy = signal<KjCompareFn<unknown>>(Object.is as KjCompareFn<unknown>);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Effective tree topology. Consumer-supplied `kjTreeShape` takes
|
|
164
|
+
* precedence; otherwise an auto-built shape is derived from the
|
|
165
|
+
* `kjNodes` input so cascade mode works out of the box for the
|
|
166
|
+
* canonical data-driven usage.
|
|
167
|
+
*/
|
|
168
|
+
readonly treeShape = computed<KjTreeShape<unknown> | null>(() =>
|
|
169
|
+
this.kjTreeShape() ?? this._nodeShape(),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @internal Tree shape built from the `kjNodes` data structure. Walks
|
|
174
|
+
* each branch once to populate parent / children / leaf maps keyed by
|
|
175
|
+
* `node.value`.
|
|
176
|
+
*/
|
|
177
|
+
private readonly _nodeShape = computed<KjTreeShape<unknown> | null>(() => {
|
|
178
|
+
const roots = this.kjNodes();
|
|
179
|
+
if (roots.length === 0) return null;
|
|
180
|
+
const parentOf = new Map<unknown, unknown | null>();
|
|
181
|
+
const childrenOf = new Map<unknown, unknown[]>();
|
|
182
|
+
const walk = (node: KjTreeNode, parent: unknown | null): void => {
|
|
183
|
+
parentOf.set(node.value, parent);
|
|
184
|
+
const kids = node.children ?? [];
|
|
185
|
+
childrenOf.set(node.value, kids.map(c => c.value));
|
|
186
|
+
for (const c of kids) walk(c, node.value);
|
|
187
|
+
};
|
|
188
|
+
for (const r of roots) walk(r, null);
|
|
189
|
+
return {
|
|
190
|
+
getParent: (n) => parentOf.get(n) ?? null,
|
|
191
|
+
getChildren:(n) => childrenOf.get(n) ?? [],
|
|
192
|
+
isLeaf: (n) => (childrenOf.get(n)?.length ?? 0) === 0,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Hook called by `KjListItem` after it toggles the shared selection
|
|
198
|
+
* model. Re-emits the public `kjNodeSelect` output so consumers see
|
|
199
|
+
* the same notification they saw before the migration (the legacy
|
|
200
|
+
* per-node `handleClick` emitted it; activation now happens inside
|
|
201
|
+
* `KjListItem._activate`, so the root is the only place left to
|
|
202
|
+
* forward it). In single mode (the only mode that returns
|
|
203
|
+
* `closeRequested: true`) we then close the overlay; multi keeps the
|
|
204
|
+
* panel open.
|
|
205
|
+
*/
|
|
206
|
+
afterSelect(value: unknown, closeRequested: boolean): void {
|
|
207
|
+
if (value !== undefined) this.kjNodeSelect.emit(value);
|
|
208
|
+
if (closeRequested) this.controller.close('programmatic');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── KjTreeSelectContext implementation ──────────────────────────────
|
|
212
|
+
|
|
213
|
+
readonly selectionMode = computed(() => this.kjSelectionMode());
|
|
214
|
+
readonly expandedIds = computed(() =>
|
|
215
|
+
new Set(this._expandedIds()) as ReadonlySet<string>,
|
|
216
|
+
);
|
|
217
|
+
readonly expandedValues = computed(() =>
|
|
218
|
+
new Set(this._expandedValues()) as ReadonlySet<unknown>,
|
|
219
|
+
);
|
|
220
|
+
readonly nodes = computed(() => this.kjNodes());
|
|
221
|
+
|
|
222
|
+
/** Directly-injected selection model; wired via `bind()` below. */
|
|
223
|
+
private readonly _selection = inject(KjSelectionModel);
|
|
224
|
+
|
|
225
|
+
constructor() {
|
|
226
|
+
this._selection.bind({
|
|
227
|
+
value: this.value,
|
|
228
|
+
items: this.items,
|
|
229
|
+
mode: this.mode,
|
|
230
|
+
compareBy: this.compareBy,
|
|
231
|
+
treeShape: this.treeShape,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Sync kjExpandedKeys (controlled input) → _expandedValues
|
|
235
|
+
effect(() => {
|
|
236
|
+
const keys = this.kjExpandedKeys();
|
|
237
|
+
const next = new Set(keys);
|
|
238
|
+
const current = untracked(this._expandedValues);
|
|
239
|
+
if (!setsEqual(current, next)) {
|
|
240
|
+
this._expandedValues.set(next);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Expansion ops ────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
toggleNode(nodeId: string): void {
|
|
248
|
+
const set = new Set(this._expandedIds());
|
|
249
|
+
if (set.has(nodeId)) set.delete(nodeId);
|
|
250
|
+
else set.add(nodeId);
|
|
251
|
+
this._expandedIds.set(set);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
toggleNodeByValue(value: unknown): void {
|
|
255
|
+
const set = new Set(this._expandedValues());
|
|
256
|
+
const wasExpanded = set.has(value);
|
|
257
|
+
if (wasExpanded) set.delete(value);
|
|
258
|
+
else set.add(value);
|
|
259
|
+
this._expandedValues.set(set);
|
|
260
|
+
this.kjExpandedKeys.set(Array.from(set));
|
|
261
|
+
if (wasExpanded) this.kjNodeCollapse.emit(value);
|
|
262
|
+
else this.kjNodeExpand.emit(value);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
isExpanded(nodeId: string): boolean {
|
|
266
|
+
return this._expandedIds().has(nodeId);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
isValueExpanded(value: unknown): boolean {
|
|
270
|
+
return this._expandedValues().has(value);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Directive, booleanAttribute, inject, input } from '@angular/core';
|
|
2
|
+
import { KjFocusRing } from '../primitives';
|
|
3
|
+
import { KjOverlayTrigger } from '../primitives/overlay/trigger';
|
|
4
|
+
import type { KjOverlayPanel } from '../primitives/overlay/panel';
|
|
5
|
+
import { KjOverlayController } from '../primitives/overlay/controller';
|
|
6
|
+
import {
|
|
7
|
+
KJ_OVERLAY_TRIGGER_EVENT_STRATEGY,
|
|
8
|
+
KJ_OVERLAY_PANEL_ROLE,
|
|
9
|
+
type KjTriggerEventStrategy,
|
|
10
|
+
} from '../primitives/overlay/tokens';
|
|
11
|
+
import { onClick } from '../primitives/overlay/strategies/trigger-event/on-click';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wraps `onClick()` and forces `ariaHasPopup` to `'tree'` so the trigger
|
|
15
|
+
* advertises its panel role to assistive tech.
|
|
16
|
+
*/
|
|
17
|
+
function treeClickTrigger(): KjTriggerEventStrategy {
|
|
18
|
+
const inner = onClick();
|
|
19
|
+
return {
|
|
20
|
+
ariaHasPopup: 'tree',
|
|
21
|
+
attach: c => inner.attach(c),
|
|
22
|
+
bindToggle: t => inner.bindToggle(t),
|
|
23
|
+
onOpen: () => inner.onOpen?.(),
|
|
24
|
+
onClose: () => inner.onClose?.(),
|
|
25
|
+
detach: () => inner.detach(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Trigger button that opens/closes the Tree Select panel. Composes the
|
|
31
|
+
* overlay `KjOverlayTrigger` host directive (which wires `aria-expanded`,
|
|
32
|
+
* `aria-controls`, `aria-haspopup`, and click handling) and forces
|
|
33
|
+
* `role="combobox"` on the host element.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```html
|
|
37
|
+
* <button kjTreeSelectTrigger>Select category</button>
|
|
38
|
+
* ```
|
|
39
|
+
* @doc-category Core/Inputs
|
|
40
|
+
*/
|
|
41
|
+
@Directive({
|
|
42
|
+
selector: '[kjTreeSelectTrigger]',
|
|
43
|
+
exportAs: 'kjTreeSelectTrigger',
|
|
44
|
+
standalone: true,
|
|
45
|
+
hostDirectives: [
|
|
46
|
+
KjFocusRing,
|
|
47
|
+
{ directive: KjOverlayTrigger, inputs: ['kjOpen'] },
|
|
48
|
+
],
|
|
49
|
+
providers: [
|
|
50
|
+
{
|
|
51
|
+
provide: KJ_OVERLAY_TRIGGER_EVENT_STRATEGY,
|
|
52
|
+
useFactory: () => treeClickTrigger(),
|
|
53
|
+
},
|
|
54
|
+
{ provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'tree' as const },
|
|
55
|
+
],
|
|
56
|
+
host: {
|
|
57
|
+
'role': 'combobox',
|
|
58
|
+
'(keydown)': 'onKeydown($event)',
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
export class KjTreeSelectTrigger {
|
|
62
|
+
/** @internal */
|
|
63
|
+
readonly controller = inject(KjOverlayController);
|
|
64
|
+
|
|
65
|
+
/** Disabled state. */
|
|
66
|
+
readonly kjDisabled = input(false, { transform: booleanAttribute });
|
|
67
|
+
|
|
68
|
+
/** @internal */
|
|
69
|
+
onKeydown(event: KeyboardEvent): void {
|
|
70
|
+
switch (event.key) {
|
|
71
|
+
case 'ArrowDown':
|
|
72
|
+
case ' ':
|
|
73
|
+
case 'Enter':
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
if (!this.controller.isOpen()) this.controller.open();
|
|
76
|
+
break;
|
|
77
|
+
case 'Escape':
|
|
78
|
+
if (this.controller.isOpen()) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
this.controller.close('programmatic');
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private readonly _overlayTrigger = inject(KjOverlayTrigger, { self: true });
|
|
87
|
+
attachPanel(panel: KjOverlayPanel): void {
|
|
88
|
+
this._overlayTrigger.attachPanel(panel);
|
|
89
|
+
}
|
|
90
|
+
}
|