@kouji-ui/core 0.1.1 → 0.1.2
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/fesm2022/kouji-ui-core.mjs +26789 -0
- package/fesm2022/kouji-ui-core.mjs.map +1 -0
- package/package.json +5 -6
- package/types/kouji-ui-core.d.ts +13545 -0
- package/.turbo/turbo-build.log +0 -30
- package/CHANGELOG.md +0 -190
- package/ng-package.json +0 -11
- package/src/a11y/.gitkeep +0 -0
- package/src/a11y/aria-describedby.spec.ts +0 -55
- package/src/a11y/aria-describedby.ts +0 -37
- package/src/a11y/aria-labelledby.spec.ts +0 -55
- package/src/a11y/aria-labelledby.ts +0 -38
- package/src/a11y/focus-trap.spec.ts +0 -86
- package/src/a11y/focus-trap.ts +0 -72
- package/src/a11y/index.ts +0 -10
- package/src/a11y/live-region.spec.ts +0 -58
- package/src/a11y/live-region.ts +0 -50
- package/src/a11y/roving-tabindex.spec.ts +0 -201
- package/src/a11y/roving-tabindex.ts +0 -153
- package/src/a11y/visually-hidden.spec.ts +0 -34
- package/src/a11y/visually-hidden.ts +0 -26
- package/src/accordion/accordion.context.ts +0 -44
- package/src/accordion/accordion.spec.ts +0 -242
- package/src/accordion/accordion.ts +0 -401
- package/src/accordion/index.ts +0 -8
- package/src/alert/alert.context.ts +0 -28
- package/src/alert/alert.spec.ts +0 -217
- package/src/alert/alert.ts +0 -417
- package/src/alert/config.ts +0 -46
- package/src/alert/index.ts +0 -19
- package/src/avatar/avatar-group.context.ts +0 -45
- package/src/avatar/avatar-group.spec.ts +0 -358
- package/src/avatar/avatar-group.ts +0 -259
- package/src/avatar/avatar.spec.ts +0 -18
- package/src/avatar/avatar.ts +0 -39
- package/src/avatar/index.ts +0 -11
- package/src/badge/badge.spec.ts +0 -28
- package/src/badge/badge.ts +0 -35
- package/src/badge/index.ts +0 -1
- package/src/breadcrumb/breadcrumb-current.ts +0 -38
- package/src/breadcrumb/breadcrumb-ellipsis.ts +0 -71
- package/src/breadcrumb/breadcrumb-item.ts +0 -41
- package/src/breadcrumb/breadcrumb-link.ts +0 -44
- package/src/breadcrumb/breadcrumb-list.ts +0 -42
- package/src/breadcrumb/breadcrumb-separator.ts +0 -45
- package/src/breadcrumb/breadcrumb.context.ts +0 -45
- package/src/breadcrumb/breadcrumb.spec.ts +0 -354
- package/src/breadcrumb/breadcrumb.ts +0 -239
- package/src/breadcrumb/config.ts +0 -84
- package/src/breadcrumb/index.ts +0 -18
- package/src/button/_examples/button.example.ts +0 -71
- package/src/button/_examples/button.finance.example.ts +0 -83
- package/src/button/_examples/button.retro.example.ts +0 -91
- package/src/button/_examples/button.sizes.example.ts +0 -60
- package/src/button/_examples/index.ts +0 -6
- package/src/button/button.spec.ts +0 -163
- package/src/button/button.ts +0 -142
- package/src/button/config.spec.ts +0 -33
- package/src/button/config.ts +0 -45
- package/src/button/index.ts +0 -2
- package/src/button-group/button-group.context.ts +0 -28
- package/src/button-group/button-group.spec.ts +0 -99
- package/src/button-group/button-group.ts +0 -99
- package/src/button-group/index.ts +0 -2
- package/src/calendar/calendar-day.ts +0 -102
- package/src/calendar/calendar-grid.ts +0 -90
- package/src/calendar/calendar-header.ts +0 -65
- package/src/calendar/calendar.context.ts +0 -53
- package/src/calendar/calendar.spec.ts +0 -225
- package/src/calendar/calendar.ts +0 -233
- package/src/calendar/date-utils.ts +0 -225
- package/src/calendar/index.ts +0 -28
- package/src/carousel/carousel.context.ts +0 -81
- package/src/carousel/carousel.spec.ts +0 -337
- package/src/carousel/carousel.ts +0 -919
- package/src/carousel/index.ts +0 -22
- package/src/cascade-select/cascade-select-option.ts +0 -205
- package/src/cascade-select/cascade-select-panel.ts +0 -106
- package/src/cascade-select/cascade-select-root.ts +0 -265
- package/src/cascade-select/cascade-select-sub-panel.ts +0 -168
- package/src/cascade-select/cascade-select-trigger.ts +0 -44
- package/src/cascade-select/cascade-select.context.ts +0 -71
- package/src/cascade-select/cascade-select.spec.ts +0 -241
- package/src/cascade-select/cascade-select.ts +0 -5
- package/src/cascade-select/index.ts +0 -11
- package/src/chart/chart.spec.ts +0 -27
- package/src/chart/chart.ts +0 -52
- package/src/chart/index.ts +0 -1
- package/src/chat/chat-avatar.ts +0 -31
- package/src/chat/chat-bubble.ts +0 -49
- package/src/chat/chat-footer.ts +0 -67
- package/src/chat/chat-header.ts +0 -41
- package/src/chat/chat-log.ts +0 -43
- package/src/chat/chat.context.ts +0 -64
- package/src/chat/chat.spec.ts +0 -341
- package/src/chat/chat.ts +0 -191
- package/src/chat/config.ts +0 -53
- package/src/chat/index.ts +0 -21
- package/src/checkbox/checkbox.spec.ts +0 -35
- package/src/checkbox/checkbox.ts +0 -89
- package/src/checkbox/index.ts +0 -1
- package/src/color-picker/color-picker.context.ts +0 -74
- package/src/color-picker/color-picker.spec.ts +0 -241
- package/src/color-picker/color-picker.ts +0 -698
- package/src/color-picker/color-picker.utils.ts +0 -140
- package/src/color-picker/index.ts +0 -28
- package/src/combobox/combobox-input.ts +0 -143
- package/src/combobox/combobox-listbox.ts +0 -49
- package/src/combobox/combobox-option.ts +0 -43
- package/src/combobox/combobox-root.ts +0 -281
- package/src/combobox/combobox.spec.ts +0 -75
- package/src/combobox/combobox.ts +0 -4
- package/src/combobox/index.ts +0 -4
- package/src/command-palette/command-empty.ts +0 -36
- package/src/command-palette/command-group.ts +0 -21
- package/src/command-palette/command-input.ts +0 -70
- package/src/command-palette/command-item.ts +0 -60
- package/src/command-palette/command-list.ts +0 -25
- package/src/command-palette/command-palette-dialog.ts +0 -74
- package/src/command-palette/command-palette-trigger.ts +0 -64
- package/src/command-palette/command-palette.filters.ts +0 -53
- package/src/command-palette/command-palette.spec.ts +0 -349
- package/src/command-palette/command-palette.ts +0 -204
- package/src/command-palette/command-separator.ts +0 -18
- package/src/command-palette/index.ts +0 -15
- package/src/confirm-popup/confirm-popup-action.ts +0 -39
- package/src/confirm-popup/confirm-popup-cancel.ts +0 -38
- package/src/confirm-popup/confirm-popup-content.ts +0 -113
- package/src/confirm-popup/confirm-popup-message.ts +0 -36
- package/src/confirm-popup/confirm-popup-trigger.ts +0 -31
- package/src/confirm-popup/confirm-popup.context.ts +0 -41
- package/src/confirm-popup/confirm-popup.spec.ts +0 -337
- package/src/confirm-popup/confirm-popup.ts +0 -109
- package/src/confirm-popup/index.ts +0 -12
- package/src/date-picker/date-picker-calendar.ts +0 -65
- package/src/date-picker/date-picker-trigger.ts +0 -215
- package/src/date-picker/date-picker.context.ts +0 -50
- package/src/date-picker/date-picker.spec.ts +0 -117
- package/src/date-picker/date-picker.ts +0 -127
- package/src/date-picker/index.ts +0 -7
- package/src/dialog/_examples/dialog.confirm.example.ts +0 -145
- package/src/dialog/_examples/dialog.example.ts +0 -106
- package/src/dialog/_examples/dialog.finance.example.ts +0 -121
- package/src/dialog/_examples/dialog.retro.example.ts +0 -126
- package/src/dialog/_examples/index.ts +0 -6
- package/src/dialog/dialog.ref.ts +0 -42
- package/src/dialog/dialog.service.ts +0 -66
- package/src/dialog/dialog.spec.ts +0 -37
- package/src/dialog/dialog.ts +0 -26
- package/src/dialog/index.ts +0 -3
- package/src/divider/divider.spec.ts +0 -223
- package/src/divider/divider.ts +0 -137
- package/src/divider/index.ts +0 -5
- package/src/drawer/drawer.ref.ts +0 -63
- package/src/drawer/drawer.service.ts +0 -106
- package/src/drawer/drawer.spec.ts +0 -117
- package/src/drawer/drawer.ts +0 -122
- package/src/drawer/index.ts +0 -10
- package/src/dropdown-menu/dropdown-menu-content.ts +0 -258
- package/src/dropdown-menu/dropdown-menu-group.ts +0 -31
- package/src/dropdown-menu/dropdown-menu-item.ts +0 -51
- package/src/dropdown-menu/dropdown-menu-label.ts +0 -41
- package/src/dropdown-menu/dropdown-menu-separator.ts +0 -27
- package/src/dropdown-menu/dropdown-menu-trigger.ts +0 -140
- package/src/dropdown-menu/dropdown-menu.spec.ts +0 -265
- package/src/dropdown-menu/dropdown-menu.ts +0 -60
- package/src/dropdown-menu/index.ts +0 -15
- package/src/example-components.ts +0 -42
- package/src/field/field-error.ts +0 -49
- package/src/field/field-group.ts +0 -39
- package/src/field/field-help.ts +0 -47
- package/src/field/field-label.ts +0 -30
- package/src/field/field.context.ts +0 -28
- package/src/field/field.spec.ts +0 -179
- package/src/field/field.ts +0 -121
- package/src/field/index.ts +0 -6
- package/src/file-upload/file-upload.context.ts +0 -89
- package/src/file-upload/file-upload.spec.ts +0 -290
- package/src/file-upload/file-upload.ts +0 -639
- package/src/file-upload/file-upload.types.ts +0 -59
- package/src/file-upload/index.ts +0 -23
- package/src/form/form-error-summary.ts +0 -87
- package/src/form/form-field.context.ts +0 -3
- package/src/form/form-field.spec.ts +0 -25
- package/src/form/form-field.ts +0 -52
- package/src/form/form.context.ts +0 -53
- package/src/form/form.spec.ts +0 -162
- package/src/form/form.ts +0 -326
- package/src/form/index.ts +0 -11
- package/src/icon/E2E.md +0 -14
- package/src/icon/icon.directive.spec.ts +0 -198
- package/src/icon/icon.directive.ts +0 -113
- package/src/icon/icon.mode.spec.ts +0 -29
- package/src/icon/icon.mode.ts +0 -23
- package/src/icon/icon.providers.spec.ts +0 -63
- package/src/icon/icon.providers.ts +0 -47
- package/src/icon/icon.resolver.spec.ts +0 -96
- package/src/icon/icon.resolver.ts +0 -49
- package/src/icon/icon.tokens.ts +0 -88
- package/src/icon/icon.types.ts +0 -48
- package/src/icon/index.ts +0 -21
- package/src/input/index.ts +0 -1
- package/src/input/input.spec.ts +0 -43
- package/src/input/input.ts +0 -89
- package/src/input-group/index.ts +0 -3
- package/src/input-group/input-group-addon.ts +0 -92
- package/src/input-group/input-group.context.ts +0 -27
- package/src/input-group/input-group.spec.ts +0 -186
- package/src/input-group/input-group.ts +0 -136
- package/src/input-mask/index.ts +0 -11
- package/src/input-mask/input-mask.compile.ts +0 -69
- package/src/input-mask/input-mask.engine.ts +0 -376
- package/src/input-mask/input-mask.presets.ts +0 -24
- package/src/input-mask/input-mask.spec.ts +0 -304
- package/src/input-mask/input-mask.tokens.ts +0 -49
- package/src/input-mask/input-mask.ts +0 -280
- package/src/input-otp/index.ts +0 -2
- package/src/input-otp/input-otp.context.ts +0 -41
- package/src/input-otp/input-otp.spec.ts +0 -308
- package/src/input-otp/input-otp.ts +0 -470
- package/src/kbd/index.ts +0 -1
- package/src/kbd/kbd.spec.ts +0 -127
- package/src/kbd/kbd.ts +0 -132
- package/src/link/config.ts +0 -44
- package/src/link/index.ts +0 -2
- package/src/link/link.spec.ts +0 -246
- package/src/link/link.ts +0 -226
- package/src/list/index.ts +0 -11
- package/src/list/list.spec.ts +0 -237
- package/src/list/list.ts +0 -277
- package/src/menubar/index.ts +0 -7
- package/src/menubar/menubar-item.ts +0 -204
- package/src/menubar/menubar.context.ts +0 -43
- package/src/menubar/menubar.spec.ts +0 -433
- package/src/menubar/menubar.ts +0 -309
- package/src/number-input/index.ts +0 -4
- package/src/number-input/number-input-group.ts +0 -64
- package/src/number-input/number-input.context.ts +0 -31
- package/src/number-input/number-input.format.ts +0 -148
- package/src/number-input/number-input.spec.ts +0 -170
- package/src/number-input/number-input.ts +0 -457
- package/src/number-input/number-stepper.ts +0 -193
- package/src/overlay-badge/index.ts +0 -7
- package/src/overlay-badge/overlay-badge-content.ts +0 -73
- package/src/overlay-badge/overlay-badge.context.ts +0 -37
- package/src/overlay-badge/overlay-badge.spec.ts +0 -220
- package/src/overlay-badge/overlay-badge.ts +0 -128
- package/src/pagination/config.ts +0 -82
- package/src/pagination/index.ts +0 -19
- package/src/pagination/page-tokens.ts +0 -78
- package/src/pagination/pagination-ellipsis.ts +0 -69
- package/src/pagination/pagination-first.ts +0 -84
- package/src/pagination/pagination-info.ts +0 -70
- package/src/pagination/pagination-item.ts +0 -81
- package/src/pagination/pagination-last.ts +0 -85
- package/src/pagination/pagination-next.ts +0 -86
- package/src/pagination/pagination-previous.ts +0 -90
- package/src/pagination/pagination.context.ts +0 -52
- package/src/pagination/pagination.spec.ts +0 -388
- package/src/pagination/pagination.ts +0 -288
- package/src/password-input/index.ts +0 -15
- package/src/password-input/password-input.context.ts +0 -65
- package/src/password-input/password-input.scorer.ts +0 -34
- package/src/password-input/password-input.spec.ts +0 -274
- package/src/password-input/password-input.ts +0 -488
- package/src/popover/_examples/index.ts +0 -5
- package/src/popover/_examples/popover.example.ts +0 -92
- package/src/popover/_examples/popover.finance.example.ts +0 -106
- package/src/popover/_examples/popover.retro.example.ts +0 -103
- package/src/popover/index.ts +0 -5
- package/src/popover/popover-arrow.ts +0 -30
- package/src/popover/popover-close.ts +0 -36
- package/src/popover/popover-content.ts +0 -41
- package/src/popover/popover-title.ts +0 -39
- package/src/popover/popover-trigger.ts +0 -40
- package/src/popover/popover.spec.ts +0 -59
- package/src/popover/popover.ts +0 -5
- package/src/presets/bind-presets.spec.ts +0 -50
- package/src/presets/bind-presets.ts +0 -43
- package/src/presets/index.ts +0 -3
- package/src/presets/size.spec.ts +0 -67
- package/src/presets/size.ts +0 -59
- package/src/presets/variant.spec.ts +0 -72
- package/src/presets/variant.ts +0 -62
- package/src/primitives/.gitkeep +0 -0
- package/src/primitives/directionality/directionality.spec.ts +0 -112
- package/src/primitives/directionality/directionality.ts +0 -89
- package/src/primitives/directionality/index.ts +0 -1
- package/src/primitives/forms/form-control.spec.ts +0 -67
- package/src/primitives/forms/form-control.ts +0 -94
- package/src/primitives/forms/index.ts +0 -1
- package/src/primitives/index.ts +0 -6
- package/src/primitives/interaction/disabled.spec.ts +0 -47
- package/src/primitives/interaction/disabled.ts +0 -31
- package/src/primitives/interaction/focus-ring.spec.ts +0 -62
- package/src/primitives/interaction/focus-ring.ts +0 -59
- package/src/primitives/interaction/index.ts +0 -2
- package/src/primitives/list/filterable-list.spec.ts +0 -114
- package/src/primitives/list/filterable-list.ts +0 -126
- package/src/primitives/list/group.spec.ts +0 -99
- package/src/primitives/list/group.ts +0 -139
- package/src/primitives/list/index.ts +0 -8
- package/src/primitives/list/inject-helpers.ts +0 -8
- package/src/primitives/list/item.spec.ts +0 -173
- package/src/primitives/list/item.ts +0 -222
- package/src/primitives/list/navigator.spec.ts +0 -286
- package/src/primitives/list/navigator.ts +0 -238
- package/src/primitives/list/selection.spec.ts +0 -214
- package/src/primitives/list/selection.ts +0 -339
- package/src/primitives/list/tokens.ts +0 -106
- package/src/primitives/list/type-ahead.spec.ts +0 -58
- package/src/primitives/list/type-ahead.ts +0 -46
- package/src/primitives/overlay/backdrop.spec.ts +0 -54
- package/src/primitives/overlay/backdrop.ts +0 -31
- package/src/primitives/overlay/builder.spec.ts +0 -38
- package/src/primitives/overlay/builder.ts +0 -151
- package/src/primitives/overlay/container.ts +0 -42
- package/src/primitives/overlay/context.ts +0 -14
- package/src/primitives/overlay/controller.spec.ts +0 -86
- package/src/primitives/overlay/controller.ts +0 -190
- package/src/primitives/overlay/id.spec.ts +0 -24
- package/src/primitives/overlay/id.ts +0 -17
- package/src/primitives/overlay/index.ts +0 -18
- package/src/primitives/overlay/overlay.css +0 -42
- package/src/primitives/overlay/panel.spec.ts +0 -52
- package/src/primitives/overlay/panel.ts +0 -102
- package/src/primitives/overlay/stack.spec.ts +0 -55
- package/src/primitives/overlay/stack.ts +0 -113
- package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +0 -11
- package/src/primitives/overlay/strategies/backdrop/blurred.ts +0 -5
- package/src/primitives/overlay/strategies/backdrop/none.spec.ts +0 -10
- package/src/primitives/overlay/strategies/backdrop/none.ts +0 -9
- package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +0 -18
- package/src/primitives/overlay/strategies/backdrop/solid.ts +0 -23
- package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +0 -28
- package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +0 -38
- package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +0 -9
- package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +0 -8
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +0 -49
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +0 -85
- package/src/primitives/overlay/strategies/index.ts +0 -34
- package/src/primitives/overlay/strategies/live-announcer/_announce.ts +0 -35
- package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +0 -21
- package/src/primitives/overlay/strategies/live-announcer/assertive.ts +0 -11
- package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +0 -21
- package/src/primitives/overlay/strategies/live-announcer/polite.ts +0 -11
- package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +0 -12
- package/src/primitives/overlay/strategies/live-announcer/silent.ts +0 -5
- package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +0 -40
- package/src/primitives/overlay/strategies/mount/body-portal.ts +0 -91
- package/src/primitives/overlay/strategies/mount/in-container.spec.ts +0 -43
- package/src/primitives/overlay/strategies/mount/in-container.ts +0 -39
- package/src/primitives/overlay/strategies/mount/in-place.spec.ts +0 -40
- package/src/primitives/overlay/strategies/mount/in-place.ts +0 -20
- package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +0 -56
- package/src/primitives/overlay/strategies/position/anchored-to.ts +0 -248
- package/src/primitives/overlay/strategies/position/corner.spec.ts +0 -36
- package/src/primitives/overlay/strategies/position/corner.ts +0 -33
- package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +0 -32
- package/src/primitives/overlay/strategies/position/edge-sheet.ts +0 -36
- package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +0 -25
- package/src/primitives/overlay/strategies/position/in-place-sibling.ts +0 -13
- package/src/primitives/overlay/strategies/position/point-at.spec.ts +0 -40
- package/src/primitives/overlay/strategies/position/point-at.ts +0 -34
- package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +0 -31
- package/src/primitives/overlay/strategies/position/viewport-centered.ts +0 -31
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +0 -15
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +0 -39
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +0 -21
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +0 -44
- package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +0 -9
- package/src/primitives/overlay/strategies/scroll-lock/none.ts +0 -5
- package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +0 -32
- package/src/primitives/overlay/strategies/trigger-event/on-click.ts +0 -28
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +0 -45
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +0 -70
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +0 -35
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +0 -34
- package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +0 -64
- package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +0 -37
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +0 -37
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +0 -59
- package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +0 -34
- package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +0 -86
- package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +0 -16
- package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +0 -12
- package/src/primitives/overlay/tokens.ts +0 -54
- package/src/primitives/overlay/trigger.spec.ts +0 -103
- package/src/primitives/overlay/trigger.ts +0 -75
- package/src/primitives/overlay/types.ts +0 -24
- package/src/primitives/overlay/wrapper.ts +0 -50
- package/src/primitives/signals/deep-signal.ts +0 -109
- package/src/primitives/signals/index.ts +0 -1
- package/src/progress-bar/config.ts +0 -48
- package/src/progress-bar/index.ts +0 -12
- package/src/progress-bar/progress-bar-fill.ts +0 -37
- package/src/progress-bar/progress-bar.context.ts +0 -34
- package/src/progress-bar/progress-bar.spec.ts +0 -423
- package/src/progress-bar/progress-bar.ts +0 -217
- package/src/public-api.ts +0 -84
- package/src/radio/index.ts +0 -2
- package/src/radio/radio.context.ts +0 -3
- package/src/radio/radio.spec.ts +0 -36
- package/src/radio/radio.ts +0 -66
- package/src/select/index.ts +0 -4
- package/src/select/select-content.ts +0 -69
- package/src/select/select-option.ts +0 -35
- package/src/select/select-root.ts +0 -129
- package/src/select/select-trigger.ts +0 -93
- package/src/select/select.spec.ts +0 -138
- package/src/select/select.ts +0 -4
- package/src/skeleton/index.ts +0 -5
- package/src/skeleton/skeleton.spec.ts +0 -115
- package/src/skeleton/skeleton.ts +0 -56
- package/src/slider/index.ts +0 -10
- package/src/slider/slider-range.ts +0 -49
- package/src/slider/slider-thumb.ts +0 -335
- package/src/slider/slider-track.ts +0 -70
- package/src/slider/slider.context.ts +0 -79
- package/src/slider/slider.geometry.ts +0 -73
- package/src/slider/slider.spec.ts +0 -216
- package/src/slider/slider.ts +0 -443
- package/src/speed-dial/index.ts +0 -10
- package/src/speed-dial/speed-dial-action.ts +0 -40
- package/src/speed-dial/speed-dial-actions.ts +0 -31
- package/src/speed-dial/speed-dial-trigger.ts +0 -55
- package/src/speed-dial/speed-dial.context.ts +0 -36
- package/src/speed-dial/speed-dial.spec.ts +0 -143
- package/src/speed-dial/speed-dial.ts +0 -127
- package/src/spinner/config.ts +0 -48
- package/src/spinner/index.ts +0 -8
- package/src/spinner/spinner.spec.ts +0 -262
- package/src/spinner/spinner.ts +0 -129
- package/src/stepper/index.ts +0 -15
- package/src/stepper/stepper.context.ts +0 -63
- package/src/stepper/stepper.spec.ts +0 -403
- package/src/stepper/stepper.ts +0 -544
- package/src/styles/docs-themes.css +0 -82
- package/src/styles.css +0 -17
- package/src/table/column-helpers.spec.ts +0 -74
- package/src/table/column-helpers.ts +0 -68
- package/src/table/filter-fns.ts +0 -230
- package/src/table/filter-models.spec.ts +0 -240
- package/src/table/filter-models.ts +0 -124
- package/src/table/filter-params.ts +0 -90
- package/src/table/grid-api-impl.ts +0 -321
- package/src/table/grid-api.spec.ts +0 -178
- package/src/table/grid-api.ts +0 -285
- package/src/table/index.ts +0 -73
- package/src/table/table-cell.spec.ts +0 -43
- package/src/table/table-cell.ts +0 -38
- package/src/table/table-filter-outlet.ts +0 -120
- package/src/table/table-header.spec.ts +0 -54
- package/src/table/table-header.ts +0 -63
- package/src/table/table-keyboard.spec.ts +0 -68
- package/src/table/table-keyboard.ts +0 -107
- package/src/table/table-resource.spec.ts +0 -37
- package/src/table/table-resource.ts +0 -36
- package/src/table/table-row.spec.ts +0 -54
- package/src/table/table-row.ts +0 -31
- package/src/table/table-storage.spec.ts +0 -67
- package/src/table/table-storage.ts +0 -76
- package/src/table/table.spec.ts +0 -172
- package/src/table/table.ts +0 -281
- package/src/table/table.types.ts +0 -83
- package/src/tabs/index.ts +0 -7
- package/src/tabs/tabs.context.ts +0 -32
- package/src/tabs/tabs.spec.ts +0 -352
- package/src/tabs/tabs.ts +0 -364
- package/src/tag/config.ts +0 -53
- package/src/tag/index.ts +0 -7
- package/src/tag/tag-list.ts +0 -76
- package/src/tag/tag-remove.ts +0 -61
- package/src/tag/tag.context.ts +0 -36
- package/src/tag/tag.spec.ts +0 -240
- package/src/tag/tag.ts +0 -203
- package/src/test-setup.ts +0 -5
- package/src/textarea/config.ts +0 -42
- package/src/textarea/index.ts +0 -8
- package/src/textarea/textarea.spec.ts +0 -176
- package/src/textarea/textarea.ts +0 -235
- package/src/time-picker/index.ts +0 -15
- package/src/time-picker/time-picker-meridiem.ts +0 -123
- package/src/time-picker/time-picker-segment.ts +0 -318
- package/src/time-picker/time-picker.context.ts +0 -38
- package/src/time-picker/time-picker.format.ts +0 -181
- package/src/time-picker/time-picker.spec.ts +0 -189
- package/src/time-picker/time-picker.ts +0 -274
- package/src/toast/_examples/index.ts +0 -5
- package/src/toast/_examples/toast.example.ts +0 -282
- package/src/toast/_examples/toast.finance.example.ts +0 -180
- package/src/toast/_examples/toast.retro.example.ts +0 -161
- package/src/toast/index.ts +0 -28
- package/src/toast/toast.ref.ts +0 -26
- package/src/toast/toast.service.ts +0 -339
- package/src/toast/toast.spec.ts +0 -247
- package/src/toast/toast.strategy.ts +0 -112
- package/src/toast/toast.ts +0 -463
- package/src/toast/toast.types.ts +0 -5
- package/src/toggle/index.ts +0 -1
- package/src/toggle/toggle.spec.ts +0 -24
- package/src/toggle/toggle.ts +0 -48
- package/src/tooltip/_examples/index.ts +0 -6
- package/src/tooltip/_examples/tooltip.example.ts +0 -82
- package/src/tooltip/_examples/tooltip.finance.example.ts +0 -97
- package/src/tooltip/_examples/tooltip.placements.example.ts +0 -83
- package/src/tooltip/_examples/tooltip.retro.example.ts +0 -107
- package/src/tooltip/index.ts +0 -4
- package/src/tooltip/tooltip-arrow.ts +0 -31
- package/src/tooltip/tooltip-content.ts +0 -38
- package/src/tooltip/tooltip-group.ts +0 -32
- package/src/tooltip/tooltip-trigger.ts +0 -47
- package/src/tooltip/tooltip.spec.ts +0 -40
- package/src/tooltip/tooltip.ts +0 -5
- package/src/tree-select/index.ts +0 -12
- package/src/tree-select/tree-select-content.ts +0 -199
- package/src/tree-select/tree-select-node.ts +0 -132
- package/src/tree-select/tree-select-root.ts +0 -272
- package/src/tree-select/tree-select-trigger.ts +0 -90
- package/src/tree-select/tree-select.context.ts +0 -63
- package/src/tree-select/tree-select.spec.ts +0 -433
- package/src/tree-select/tree-select.ts +0 -2
- package/src/typography/blockquote.ts +0 -57
- package/src/typography/code.ts +0 -53
- package/src/typography/index.ts +0 -22
- package/src/typography/lead.ts +0 -49
- package/src/typography/muted.ts +0 -29
- package/src/typography/truncate.ts +0 -99
- package/src/typography/typography.spec.ts +0 -203
- package/tsconfig.lib.json +0 -21
- package/tsconfig.lib.prod.json +0 -17
- package/tsconfig.spec.json +0 -18
- package/vite.config.ts +0 -27
- /package/{src/icon → icon}/icon.css +0 -0
- /package/{src/typography → typography}/prose.css +0 -0
package/src/carousel/carousel.ts
DELETED
|
@@ -1,919 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Directive,
|
|
3
|
-
ElementRef,
|
|
4
|
-
OnDestroy,
|
|
5
|
-
OnInit,
|
|
6
|
-
PLATFORM_ID,
|
|
7
|
-
Signal,
|
|
8
|
-
afterNextRender,
|
|
9
|
-
booleanAttribute,
|
|
10
|
-
computed,
|
|
11
|
-
effect,
|
|
12
|
-
inject,
|
|
13
|
-
input,
|
|
14
|
-
model,
|
|
15
|
-
signal,
|
|
16
|
-
untracked,
|
|
17
|
-
} from '@angular/core';
|
|
18
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
19
|
-
import { KjFocusRing } from '../primitives/interaction/focus-ring';
|
|
20
|
-
import { KjDisabled } from '../primitives/interaction/disabled';
|
|
21
|
-
import { KjRovingTabindex, KjRovingTabindexItemDirective } from '../a11y/roving-tabindex';
|
|
22
|
-
import {
|
|
23
|
-
KJ_CAROUSEL,
|
|
24
|
-
KJ_CAROUSEL_INDICATORS,
|
|
25
|
-
KJ_CAROUSEL_SLIDE,
|
|
26
|
-
KjCarouselAlign,
|
|
27
|
-
KjCarouselContext,
|
|
28
|
-
KjCarouselControlPattern,
|
|
29
|
-
KjCarouselIndicatorsContext,
|
|
30
|
-
KjCarouselOrientation,
|
|
31
|
-
KjCarouselSlideContext,
|
|
32
|
-
} from './carousel.context';
|
|
33
|
-
|
|
34
|
-
let kjCarouselSeedCounter = 0;
|
|
35
|
-
function nextSeed(): string {
|
|
36
|
-
const cryptoLike = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
|
|
37
|
-
if (cryptoLike?.randomUUID) return cryptoLike.randomUUID().slice(0, 8);
|
|
38
|
-
return `kj${(++kjCarouselSeedCounter).toString(36)}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function prefersReducedMotion(): boolean {
|
|
42
|
-
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
43
|
-
try {
|
|
44
|
-
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Root carousel directive — region landmark, source of truth for the active
|
|
52
|
-
* slide, and orchestrator for orientation, loop semantics, autoplay gating,
|
|
53
|
-
* and the live-region announcement cadence.
|
|
54
|
-
*
|
|
55
|
-
* The directive itself owns no DOM beyond the host element; it sits at the
|
|
56
|
-
* outermost element of the carousel composition (the one wearing
|
|
57
|
-
* `role="region"` + `aria-roledescription="carousel"`) and exposes
|
|
58
|
-
* `KJ_CAROUSEL` to descendants for the per-slide ARIA wiring, the
|
|
59
|
-
* previous / next / indicator controls, and the autoplay / pause directives.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```html
|
|
63
|
-
* <div kjCarousel kjLabel="Featured products" [(kjValue)]="active">
|
|
64
|
-
* <button kjCarouselPrevious aria-label="Previous slide">‹</button>
|
|
65
|
-
* <div kjCarouselViewport>
|
|
66
|
-
* <div kjCarouselSlide kjSlideValue="a">…</div>
|
|
67
|
-
* <div kjCarouselSlide kjSlideValue="b">…</div>
|
|
68
|
-
* </div>
|
|
69
|
-
* <button kjCarouselNext aria-label="Next slide">›</button>
|
|
70
|
-
* <div kjCarouselIndicators></div>
|
|
71
|
-
* </div>
|
|
72
|
-
* ```
|
|
73
|
-
* @doc-category Core/Data display
|
|
74
|
-
* @doc
|
|
75
|
-
* @doc-name carousel
|
|
76
|
-
* @doc-description Unstyled carousel root that owns slide state, autoplay, loop semantics, and ARIA announcements.
|
|
77
|
-
* @doc-is-main
|
|
78
|
-
*/
|
|
79
|
-
@Directive({
|
|
80
|
-
selector: '[kjCarousel]',
|
|
81
|
-
standalone: true,
|
|
82
|
-
exportAs: 'kjCarousel',
|
|
83
|
-
providers: [{ provide: KJ_CAROUSEL, useExisting: KjCarousel }],
|
|
84
|
-
host: {
|
|
85
|
-
'[attr.role]': '"region"',
|
|
86
|
-
'[attr.aria-roledescription]': '"carousel"',
|
|
87
|
-
'[attr.aria-label]': 'kjLabel() ?? null',
|
|
88
|
-
'[attr.aria-labelledby]': 'kjLabelledby() ?? null',
|
|
89
|
-
'[attr.aria-orientation]': 'kjOrientation()',
|
|
90
|
-
'[attr.data-paused]': 'paused() ? "" : null',
|
|
91
|
-
'[attr.data-autoplay]': 'autoplayActive() ? "" : null',
|
|
92
|
-
'[attr.data-orientation]': 'kjOrientation()',
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
export class KjCarousel implements KjCarouselContext {
|
|
96
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
97
|
-
private readonly idSeed = nextSeed();
|
|
98
|
-
|
|
99
|
-
/** Two-way bound active slide value. `null` defers to the first registered slide on mount. */
|
|
100
|
-
readonly kjValue = model<string | null>(null);
|
|
101
|
-
|
|
102
|
-
/** When true, prev / next wrap around. Boundary buttons go disabled when false. */
|
|
103
|
-
readonly kjLoop = input(false, { transform: booleanAttribute });
|
|
104
|
-
|
|
105
|
-
/** Carousel orientation. Drives the scroll-snap axis and the keyboard mapping. */
|
|
106
|
-
readonly kjOrientation = input<KjCarouselOrientation>('horizontal');
|
|
107
|
-
|
|
108
|
-
/** Number of slides visible in the viewport at once. `'auto'` defers to slide content widths. */
|
|
109
|
-
readonly kjSlidesPerView = input<number | 'auto'>(1);
|
|
110
|
-
|
|
111
|
-
/** Number of slides moved per `next()` / `prev()` call. Independent of slides-per-view. */
|
|
112
|
-
readonly kjSlidesPerAdvance = input<number>(1);
|
|
113
|
-
|
|
114
|
-
/** Default slide alignment within the viewport. Per-slide override via `KjCarouselSlide.kjSlideAlign`. */
|
|
115
|
-
readonly kjAlign = input<KjCarouselAlign>('start');
|
|
116
|
-
|
|
117
|
-
/** Accessible name for the region. Either this or `kjLabelledby` is required for WCAG 4.1.2. */
|
|
118
|
-
readonly kjLabel = input<string | undefined>(undefined);
|
|
119
|
-
|
|
120
|
-
/** Id of an external label element. Either this or `kjLabel` is required. */
|
|
121
|
-
readonly kjLabelledby = input<string | undefined>(undefined);
|
|
122
|
-
|
|
123
|
-
/** When false, native scroll drag is blocked on the viewport (programmatic advance still works). */
|
|
124
|
-
readonly kjDraggable = input(true, { transform: booleanAttribute });
|
|
125
|
-
|
|
126
|
-
/** Read-only mirror of the orientation for context consumers. */
|
|
127
|
-
readonly orientation: Signal<KjCarouselOrientation> = this.kjOrientation;
|
|
128
|
-
|
|
129
|
-
/** Read-only mirror of the loop flag for context consumers. */
|
|
130
|
-
readonly loop: Signal<boolean> = this.kjLoop;
|
|
131
|
-
|
|
132
|
-
// ── Slide registration ────────────────────────────────────────────────
|
|
133
|
-
private readonly _slides = signal<readonly KjCarouselSlide[]>([]);
|
|
134
|
-
/** Public read-only registration list, in DOM order. */
|
|
135
|
-
readonly slides = this._slides.asReadonly();
|
|
136
|
-
|
|
137
|
-
/** Total count of registered slides (excluding loop clones, which are DOM-only). */
|
|
138
|
-
readonly slideCount = computed(() => this._slides().length);
|
|
139
|
-
|
|
140
|
-
/** Current value, normalised to `null` when the registered set does not contain it. */
|
|
141
|
-
readonly currentValue = computed<string | null>(() => {
|
|
142
|
-
const v = this.kjValue();
|
|
143
|
-
if (v == null) return null;
|
|
144
|
-
return this._slides().some((s) => s.kjSlideValue() === v) ? v : null;
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
/** Index of the active slide in registration order; -1 when nothing is active. */
|
|
148
|
-
readonly currentIndex = computed(() => {
|
|
149
|
-
const v = this.currentValue();
|
|
150
|
-
if (v == null) return -1;
|
|
151
|
-
return this._slides().findIndex((s) => s.kjSlideValue() === v);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// ── Autoplay gating ───────────────────────────────────────────────────
|
|
155
|
-
private readonly _userPaused = signal(false);
|
|
156
|
-
private readonly _hovering = signal(false);
|
|
157
|
-
private readonly _focusWithin = signal(false);
|
|
158
|
-
private readonly _autoplay = signal<KjCarouselAutoplay | null>(null);
|
|
159
|
-
private readonly _liveRegion = signal<{ announce: (msg: string) => void } | null>(null);
|
|
160
|
-
|
|
161
|
-
/** True only when the user has explicitly paused (gate 3). */
|
|
162
|
-
readonly paused: Signal<boolean> = this._userPaused.asReadonly();
|
|
163
|
-
|
|
164
|
-
/** Whether an autoplay directive is registered. */
|
|
165
|
-
readonly autoplayRegistered = computed(() => this._autoplay() != null);
|
|
166
|
-
|
|
167
|
-
/** Whether autoplay should currently be ticking (all five gates open). */
|
|
168
|
-
readonly autoplayActive = computed(() => {
|
|
169
|
-
if (!this._autoplay()) return false;
|
|
170
|
-
if (this._reducedMotion()) return false;
|
|
171
|
-
if (this._userPaused()) return false;
|
|
172
|
-
const ap = this._autoplay()!;
|
|
173
|
-
if (ap.kjPauseOnHover() && this._hovering()) return false;
|
|
174
|
-
if (ap.kjPauseOnFocus() && this._focusWithin()) return false;
|
|
175
|
-
return true;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
/** Whether `prefers-reduced-motion: reduce` matches in the current environment. */
|
|
179
|
-
private readonly _reducedMotion = signal(false);
|
|
180
|
-
|
|
181
|
-
// ── Viewport reference (set by KjCarouselViewport on mount) ───────────
|
|
182
|
-
private _viewport: KjCarouselViewport | null = null;
|
|
183
|
-
|
|
184
|
-
// ── Indicator-group registration (used by slides to flip role to tabpanel) ─
|
|
185
|
-
private readonly _indicators = signal<KjCarouselIndicators | null>(null);
|
|
186
|
-
/** Read-only mirror of the active control pattern (or `'buttons'` when no indicators are mounted). */
|
|
187
|
-
readonly controlPattern = computed<KjCarouselControlPattern>(
|
|
188
|
-
() => this._indicators()?.controlPattern() ?? 'buttons',
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
constructor() {
|
|
192
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
193
|
-
afterNextRender(() => {
|
|
194
|
-
// Refresh reduced-motion any time after first render; effects gate on it.
|
|
195
|
-
this._reducedMotion.set(prefersReducedMotion());
|
|
196
|
-
const mql = typeof window !== 'undefined' && window.matchMedia
|
|
197
|
-
? window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
198
|
-
: null;
|
|
199
|
-
if (mql) {
|
|
200
|
-
const onChange = () => this._reducedMotion.set(mql.matches);
|
|
201
|
-
if (typeof mql.addEventListener === 'function') {
|
|
202
|
-
mql.addEventListener('change', onChange);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Default-value reconciliation: when kjValue is null and slides have registered,
|
|
209
|
-
// adopt the first slide.
|
|
210
|
-
effect(() => {
|
|
211
|
-
const list = this._slides();
|
|
212
|
-
const current = this.kjValue();
|
|
213
|
-
if (current == null && list.length > 0) {
|
|
214
|
-
const first = untracked(() => list[0]);
|
|
215
|
-
if (first) {
|
|
216
|
-
queueMicrotask(() => {
|
|
217
|
-
if (this.kjValue() == null) this.kjValue.set(first.kjSlideValue());
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Programmatic-advance bridge: when kjValue changes (via next/prev/goTo or
|
|
224
|
-
// an external two-way bind), scroll the viewport to the new slide.
|
|
225
|
-
effect(() => {
|
|
226
|
-
const value = this.currentValue();
|
|
227
|
-
if (value == null) return;
|
|
228
|
-
// Run outside the effect's tracking by deferring to a microtask.
|
|
229
|
-
queueMicrotask(() => this.scrollToValue(value));
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** @internal Slide registration. */
|
|
234
|
-
registerSlide(slide: KjCarouselSlide): void {
|
|
235
|
-
this._slides.update((list) => (list.includes(slide) ? list : [...list, slide]));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/** @internal Slide un-registration. */
|
|
239
|
-
unregisterSlide(slide: KjCarouselSlide): void {
|
|
240
|
-
this._slides.update((list) => list.filter((s) => s !== slide));
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** @internal Viewport association. */
|
|
244
|
-
registerViewport(vp: KjCarouselViewport): void {
|
|
245
|
-
this._viewport = vp;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** @internal Viewport teardown. */
|
|
249
|
-
unregisterViewport(vp: KjCarouselViewport): void {
|
|
250
|
-
if (this._viewport === vp) this._viewport = null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/** @internal Autoplay registration. */
|
|
254
|
-
registerAutoplay(ap: KjCarouselAutoplay): void {
|
|
255
|
-
this._autoplay.set(ap);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/** @internal Autoplay teardown. */
|
|
259
|
-
unregisterAutoplay(ap: KjCarouselAutoplay): void {
|
|
260
|
-
if (this._autoplay() === ap) this._autoplay.set(null);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** @internal Indicator-group registration (used by slide role flip). */
|
|
264
|
-
registerIndicators(ind: KjCarouselIndicators): void {
|
|
265
|
-
this._indicators.set(ind);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** @internal Indicator-group teardown. */
|
|
269
|
-
unregisterIndicators(ind: KjCarouselIndicators): void {
|
|
270
|
-
if (this._indicators() === ind) this._indicators.set(null);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** @internal Live-region announcer registration (set by the wrapper). */
|
|
274
|
-
registerLiveRegion(lr: { announce: (msg: string) => void } | null): void {
|
|
275
|
-
this._liveRegion.set(lr);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** @internal Hover/focus state setters used by the host listeners. */
|
|
279
|
-
setHovering(v: boolean): void { this._hovering.set(v); }
|
|
280
|
-
setFocusWithin(v: boolean): void { this._focusWithin.set(v); }
|
|
281
|
-
|
|
282
|
-
/** Whether the slide for `value` is the active one. */
|
|
283
|
-
isActive(value: string): boolean {
|
|
284
|
-
return this.currentValue() === value;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** Stable id for the viewport element (consumed by Previous/Next as `aria-controls`). */
|
|
288
|
-
viewportId(): string {
|
|
289
|
-
return `kj-carousel-viewport-${this.idSeed}`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/** Stable id for the slide identified by `value`. */
|
|
293
|
-
slideId(value: string): string {
|
|
294
|
-
return `kj-carousel-slide-${value}-${this.idSeed}`;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** Imperative: advance to the next slide. No-op at the last slide when `kjLoop` is false. */
|
|
298
|
-
next(): void {
|
|
299
|
-
const list = this._slides();
|
|
300
|
-
if (!list.length) return;
|
|
301
|
-
const advance = Math.max(1, this.kjSlidesPerAdvance());
|
|
302
|
-
const idx = this.currentIndex();
|
|
303
|
-
let nextIdx = idx + advance;
|
|
304
|
-
if (nextIdx >= list.length) {
|
|
305
|
-
if (this.kjLoop()) nextIdx = nextIdx % list.length;
|
|
306
|
-
else nextIdx = list.length - 1;
|
|
307
|
-
}
|
|
308
|
-
if (nextIdx === idx) return;
|
|
309
|
-
this.kjValue.set(list[nextIdx].kjSlideValue());
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/** Imperative: advance to the previous slide. No-op at the first slide when `kjLoop` is false. */
|
|
313
|
-
prev(): void {
|
|
314
|
-
const list = this._slides();
|
|
315
|
-
if (!list.length) return;
|
|
316
|
-
const advance = Math.max(1, this.kjSlidesPerAdvance());
|
|
317
|
-
const idx = this.currentIndex();
|
|
318
|
-
let nextIdx = idx - advance;
|
|
319
|
-
if (nextIdx < 0) {
|
|
320
|
-
if (this.kjLoop()) nextIdx = ((nextIdx % list.length) + list.length) % list.length;
|
|
321
|
-
else nextIdx = 0;
|
|
322
|
-
}
|
|
323
|
-
if (nextIdx === idx) return;
|
|
324
|
-
this.kjValue.set(list[nextIdx].kjSlideValue());
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/** Imperative: jump to a slide by value. Silently ignores unknown values. */
|
|
328
|
-
goTo(value: string): void {
|
|
329
|
-
const list = this._slides();
|
|
330
|
-
if (!list.some((s) => s.kjSlideValue() === value)) return;
|
|
331
|
-
if (this.kjValue() !== value) this.kjValue.set(value);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** Imperative: explicitly pause autoplay. Persists across slide changes (gate 3). */
|
|
335
|
-
pause(): void {
|
|
336
|
-
if (!this._userPaused()) {
|
|
337
|
-
this._userPaused.set(true);
|
|
338
|
-
this.announce('Autoplay paused');
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** Imperative: explicitly resume autoplay (clears gate 3). */
|
|
343
|
-
play(): void {
|
|
344
|
-
if (this._userPaused()) {
|
|
345
|
-
this._userPaused.set(false);
|
|
346
|
-
this.announce('Autoplay resumed');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** @internal Whether the boundary is reached (used by Previous/Next disabled binding). */
|
|
351
|
-
atStart(): boolean {
|
|
352
|
-
return !this.kjLoop() && this.currentIndex() <= 0;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
atEnd(): boolean {
|
|
356
|
-
return !this.kjLoop() && this.currentIndex() >= this._slides().length - 1;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** Push a polite announcement (user-initiated changes only — autoplay path skips this). */
|
|
360
|
-
announce(message: string): void {
|
|
361
|
-
const lr = this._liveRegion();
|
|
362
|
-
if (lr) lr.announce(message);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Build the announcement string for a settled user-initiated advance:
|
|
367
|
-
* `"Slide 3 of 7: <label>"`. Returns `null` if the value is not registered.
|
|
368
|
-
*/
|
|
369
|
-
buildSlideAnnouncement(value: string): string | null {
|
|
370
|
-
const list = this._slides();
|
|
371
|
-
const idx = list.findIndex((s) => s.kjSlideValue() === value);
|
|
372
|
-
if (idx === -1) return null;
|
|
373
|
-
const slide = list[idx];
|
|
374
|
-
const label = slide.kjSlideLabel();
|
|
375
|
-
const base = `Slide ${idx + 1} of ${list.length}`;
|
|
376
|
-
return label ? `${base}: ${label}` : base;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/** @internal Whether reduced motion is currently in force. */
|
|
380
|
-
prefersReducedMotion(): boolean {
|
|
381
|
-
return this._reducedMotion();
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/** @internal Programmatic scrolling — invoked from the kjValue effect and from goTo/next/prev callers. */
|
|
385
|
-
private scrollToValue(value: string): void {
|
|
386
|
-
if (!isPlatformBrowser(this.platformId)) return;
|
|
387
|
-
const vp = this._viewport;
|
|
388
|
-
if (!vp) return;
|
|
389
|
-
const slide = this._slides().find((s) => s.kjSlideValue() === value);
|
|
390
|
-
if (!slide) return;
|
|
391
|
-
const slideEl = slide.el.nativeElement;
|
|
392
|
-
const vpEl = vp.el.nativeElement;
|
|
393
|
-
const orientation = this.kjOrientation();
|
|
394
|
-
const reduced = this._reducedMotion();
|
|
395
|
-
const opts: ScrollToOptions = {
|
|
396
|
-
behavior: reduced ? 'auto' : 'smooth',
|
|
397
|
-
};
|
|
398
|
-
if (orientation === 'horizontal') {
|
|
399
|
-
opts.left = slideEl.offsetLeft - vpEl.offsetLeft;
|
|
400
|
-
} else {
|
|
401
|
-
opts.top = slideEl.offsetTop - vpEl.offsetTop;
|
|
402
|
-
}
|
|
403
|
-
if (typeof vpEl.scrollTo === 'function') {
|
|
404
|
-
vpEl.scrollTo(opts);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Carousel viewport — the scroll container that hosts the slides. Owns
|
|
411
|
-
* `overflow: hidden` (or `auto` when draggable), the scroll-snap axis, and
|
|
412
|
-
* the `IntersectionObserver` that detects which slide has settled into view.
|
|
413
|
-
*
|
|
414
|
-
* Place inside `KjCarousel`. The viewport is *not* a tab stop — the previous
|
|
415
|
-
* / next buttons and indicator dots cover the keyboard advance paths, and a
|
|
416
|
-
* focusable scroll container conflicts with screen-reader virtual-cursor
|
|
417
|
-
* navigation through the projected slide content.
|
|
418
|
-
*
|
|
419
|
-
* @doc-category Core/Data display
|
|
420
|
-
* @doc
|
|
421
|
-
* @doc-name carousel
|
|
422
|
-
*/
|
|
423
|
-
@Directive({
|
|
424
|
-
selector: '[kjCarouselViewport]',
|
|
425
|
-
standalone: true,
|
|
426
|
-
exportAs: 'kjCarouselViewport',
|
|
427
|
-
host: {
|
|
428
|
-
'[attr.id]': 'carousel.viewportId()',
|
|
429
|
-
'[attr.data-orientation]': 'carousel.orientation()',
|
|
430
|
-
'[style.--kj-carousel-slides-per-view]': 'slidesPerViewVar()',
|
|
431
|
-
'(mouseenter)': 'carousel.setHovering(true)',
|
|
432
|
-
'(mouseleave)': 'carousel.setHovering(false)',
|
|
433
|
-
'(focusin)': 'carousel.setFocusWithin(true)',
|
|
434
|
-
'(focusout)': 'onFocusOut($event)',
|
|
435
|
-
},
|
|
436
|
-
})
|
|
437
|
-
export class KjCarouselViewport implements OnInit, OnDestroy {
|
|
438
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
439
|
-
/** @internal */
|
|
440
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
441
|
-
/** @internal Native host element. */
|
|
442
|
-
readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
443
|
-
|
|
444
|
-
private observer: IntersectionObserver | null = null;
|
|
445
|
-
|
|
446
|
-
/** @internal Computed style variable feed for the slide flex-basis. */
|
|
447
|
-
protected slidesPerViewVar(): string {
|
|
448
|
-
const v = this.carousel.kjSlidesPerView();
|
|
449
|
-
return v === 'auto' ? 'auto' : String(v);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
ngOnInit(): void {
|
|
453
|
-
this.carousel.registerViewport(this);
|
|
454
|
-
if (isPlatformBrowser(this.platformId) && typeof IntersectionObserver !== 'undefined') {
|
|
455
|
-
this.observer = new IntersectionObserver(
|
|
456
|
-
(entries) => this.onIntersect(entries),
|
|
457
|
-
{ root: this.el.nativeElement, threshold: [0.6] },
|
|
458
|
-
);
|
|
459
|
-
// Defer slide observation to the next microtask so registered slides have host elements.
|
|
460
|
-
queueMicrotask(() => this.observeSlides());
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
ngOnDestroy(): void {
|
|
465
|
-
this.observer?.disconnect();
|
|
466
|
-
this.observer = null;
|
|
467
|
-
this.carousel.unregisterViewport(this);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/** @internal */
|
|
471
|
-
observeSlides(): void {
|
|
472
|
-
if (!this.observer) return;
|
|
473
|
-
for (const slide of this.carousel.slides()) {
|
|
474
|
-
this.observer.observe(slide.el.nativeElement);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/** @internal */
|
|
479
|
-
onFocusOut(event: FocusEvent): void {
|
|
480
|
-
const next = event.relatedTarget as Node | null;
|
|
481
|
-
if (!next || !this.el.nativeElement.contains(next)) {
|
|
482
|
-
// Inspect the carousel root via the viewport's parentage — the host is
|
|
483
|
-
// somewhere up the tree. If focus left the viewport but stayed within
|
|
484
|
-
// the broader carousel root, leave focusWithin true; otherwise clear.
|
|
485
|
-
// Conservative behaviour: clear on viewport blur — gate 5 only matters
|
|
486
|
-
// for autoplay, and any subsequent focusin will re-set it.
|
|
487
|
-
this.carousel.setFocusWithin(false);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
private lastBest: { ratio: number; value: string } | null = null;
|
|
492
|
-
private settleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
493
|
-
|
|
494
|
-
private onIntersect(entries: IntersectionObserverEntry[]): void {
|
|
495
|
-
// Pick the entry with the highest ratio that crossed the threshold.
|
|
496
|
-
let best: { ratio: number; el: Element } | null = null;
|
|
497
|
-
for (const entry of entries) {
|
|
498
|
-
if (!entry.isIntersecting) continue;
|
|
499
|
-
if (!best || entry.intersectionRatio > best.ratio) {
|
|
500
|
-
best = { ratio: entry.intersectionRatio, el: entry.target };
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (!best) return;
|
|
504
|
-
const slide = this.carousel.slides().find((s) => s.el.nativeElement === best!.el);
|
|
505
|
-
if (!slide) return;
|
|
506
|
-
const value = slide.kjSlideValue();
|
|
507
|
-
if (this.carousel.currentValue() === value) return;
|
|
508
|
-
|
|
509
|
-
// Debounce settle to avoid flicker when two slides cross 0.6 in the same frame.
|
|
510
|
-
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
511
|
-
this.settleTimer = setTimeout(() => {
|
|
512
|
-
// Only commit if this is still the best; in vertical-scroll cases the
|
|
513
|
-
// browser may emit several entries before settling.
|
|
514
|
-
const list = this.carousel.slides();
|
|
515
|
-
if (!list.some((s) => s.kjSlideValue() === value)) return;
|
|
516
|
-
const previous = this.carousel.currentValue();
|
|
517
|
-
this.carousel.kjValue.set(value);
|
|
518
|
-
// Announce only when the carousel is not currently auto-playing;
|
|
519
|
-
// autoplay-driven advances are silent per APG to avoid AT flooding.
|
|
520
|
-
if (previous !== null && previous !== value && !this.carousel.autoplayActive()) {
|
|
521
|
-
const msg = this.carousel.buildSlideAnnouncement(value);
|
|
522
|
-
if (msg) this.carousel.announce(msg);
|
|
523
|
-
}
|
|
524
|
-
}, 50);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Single slide. Hosts `role="group"` + `aria-roledescription="slide"` and
|
|
530
|
-
* `aria-label="N of M[: <label>]"` (or `aria-labelledby` when a projected
|
|
531
|
-
* heading id is supplied via the slide context). Switches to
|
|
532
|
-
* `role="tabpanel"` when the parent indicator group is in
|
|
533
|
-
* `controlPattern="tabs"` mode.
|
|
534
|
-
*
|
|
535
|
-
* @doc-category Core/Data display
|
|
536
|
-
* @doc
|
|
537
|
-
* @doc-name carousel
|
|
538
|
-
*/
|
|
539
|
-
@Directive({
|
|
540
|
-
selector: '[kjCarouselSlide]',
|
|
541
|
-
standalone: true,
|
|
542
|
-
exportAs: 'kjCarouselSlide',
|
|
543
|
-
providers: [{ provide: KJ_CAROUSEL_SLIDE, useExisting: KjCarouselSlide }],
|
|
544
|
-
host: {
|
|
545
|
-
'[attr.id]': 'slideLabelId()',
|
|
546
|
-
'[attr.role]': 'role()',
|
|
547
|
-
'[attr.aria-roledescription]': 'role() === "group" ? "slide" : null',
|
|
548
|
-
'[attr.aria-label]': 'ariaLabel()',
|
|
549
|
-
'[attr.data-active]': 'isActive() ? "" : null',
|
|
550
|
-
'[attr.data-index]': 'index()',
|
|
551
|
-
},
|
|
552
|
-
})
|
|
553
|
-
export class KjCarouselSlide implements KjCarouselSlideContext, OnInit, OnDestroy {
|
|
554
|
-
/** @internal */
|
|
555
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
556
|
-
/** @internal Native host element. */
|
|
557
|
-
readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
558
|
-
|
|
559
|
-
/** Required string identifier for this slide. */
|
|
560
|
-
readonly kjSlideValue = input.required<string>();
|
|
561
|
-
|
|
562
|
-
/** Optional accessible name appended to `"N of M"` to form the slide's `aria-label`. */
|
|
563
|
-
readonly kjSlideLabel = input<string | undefined>(undefined);
|
|
564
|
-
|
|
565
|
-
/** Per-slide alignment override; defaults to the carousel's `kjAlign`. */
|
|
566
|
-
readonly kjSlideAlign = input<KjCarouselAlign | undefined>(undefined);
|
|
567
|
-
|
|
568
|
-
/** Read-only mirror of the slide value. */
|
|
569
|
-
readonly value: Signal<string> = this.kjSlideValue;
|
|
570
|
-
|
|
571
|
-
/** Index of this slide in registration order. */
|
|
572
|
-
readonly index = computed(() =>
|
|
573
|
-
this.carousel.slides().findIndex((s) => s === this),
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
/** Whether this slide is the currently active one. */
|
|
577
|
-
readonly isActive = computed(() => this.carousel.isActive(this.kjSlideValue()));
|
|
578
|
-
|
|
579
|
-
/** Stable id for the slide host element — sourced from the parent carousel so indicator `aria-controls` stays in sync. */
|
|
580
|
-
readonly slideLabelId = computed(() => this.carousel.slideId(this.kjSlideValue()));
|
|
581
|
-
|
|
582
|
-
/** Effective alignment — per-slide override beats carousel-level default. */
|
|
583
|
-
readonly effectiveAlign = computed<KjCarouselAlign>(
|
|
584
|
-
() => this.kjSlideAlign() ?? this.carousel.kjAlign(),
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
/** @internal Computed role — `'tabpanel'` when an indicators group in `controlPattern="tabs"` is registered, `'group'` otherwise. */
|
|
588
|
-
protected role(): string {
|
|
589
|
-
return this.carousel.controlPattern() === 'tabs' ? 'tabpanel' : 'group';
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** @internal Computed aria-label — `"N of M[: <label>]"` when in group mode. */
|
|
593
|
-
protected ariaLabel(): string | null {
|
|
594
|
-
if (this.role() !== 'group') return null;
|
|
595
|
-
const total = this.carousel.slideCount();
|
|
596
|
-
const idx = this.index();
|
|
597
|
-
if (idx < 0) return null;
|
|
598
|
-
const base = `${idx + 1} of ${total}`;
|
|
599
|
-
const label = this.kjSlideLabel();
|
|
600
|
-
return label ? `${base}: ${label}` : base;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
ngOnInit(): void {
|
|
604
|
-
this.carousel.registerSlide(this);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
ngOnDestroy(): void {
|
|
608
|
-
this.carousel.unregisterSlide(this);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Previous-slide control. Composes `KjFocusRing` and `KjDisabled`; when the
|
|
614
|
-
* carousel is not looping and the current slide is the first, the host
|
|
615
|
-
* receives `aria-disabled="true"` and click is a no-op.
|
|
616
|
-
*
|
|
617
|
-
* Apply to a real `<button>` so Enter / Space, focus, and disabled semantics
|
|
618
|
-
* work without extra wiring.
|
|
619
|
-
*
|
|
620
|
-
* @doc-category Core/Data display
|
|
621
|
-
* @doc
|
|
622
|
-
* @doc-name carousel
|
|
623
|
-
*/
|
|
624
|
-
@Directive({
|
|
625
|
-
selector: '[kjCarouselPrevious]',
|
|
626
|
-
standalone: true,
|
|
627
|
-
hostDirectives: [KjFocusRing],
|
|
628
|
-
host: {
|
|
629
|
-
'[attr.aria-controls]': 'carousel.viewportId()',
|
|
630
|
-
'[attr.aria-disabled]': 'disabled() ? "true" : null',
|
|
631
|
-
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
632
|
-
'(click)': 'onClick()',
|
|
633
|
-
},
|
|
634
|
-
})
|
|
635
|
-
export class KjCarouselPrevious {
|
|
636
|
-
/** @internal */
|
|
637
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
638
|
-
|
|
639
|
-
/** Whether the button is currently at the boundary (and not looping). */
|
|
640
|
-
readonly disabled = computed(() => this.carousel.atStart());
|
|
641
|
-
|
|
642
|
-
/** @internal */
|
|
643
|
-
onClick(): void {
|
|
644
|
-
if (this.disabled()) return;
|
|
645
|
-
const previousValue = this.carousel.currentValue();
|
|
646
|
-
this.carousel.prev();
|
|
647
|
-
const next = this.carousel.currentValue();
|
|
648
|
-
if (next != null && next !== previousValue) {
|
|
649
|
-
const msg = this.carousel.buildSlideAnnouncement(next);
|
|
650
|
-
if (msg) this.carousel.announce(msg);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Next-slide control. Symmetric counterpart of `KjCarouselPrevious`.
|
|
657
|
-
*
|
|
658
|
-
* @doc-category Core/Data display
|
|
659
|
-
* @doc
|
|
660
|
-
* @doc-name carousel
|
|
661
|
-
*/
|
|
662
|
-
@Directive({
|
|
663
|
-
selector: '[kjCarouselNext]',
|
|
664
|
-
standalone: true,
|
|
665
|
-
hostDirectives: [KjFocusRing],
|
|
666
|
-
host: {
|
|
667
|
-
'[attr.aria-controls]': 'carousel.viewportId()',
|
|
668
|
-
'[attr.aria-disabled]': 'disabled() ? "true" : null',
|
|
669
|
-
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
670
|
-
'(click)': 'onClick()',
|
|
671
|
-
},
|
|
672
|
-
})
|
|
673
|
-
export class KjCarouselNext {
|
|
674
|
-
/** @internal */
|
|
675
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
676
|
-
|
|
677
|
-
/** Whether the button is currently at the boundary (and not looping). */
|
|
678
|
-
readonly disabled = computed(() => this.carousel.atEnd());
|
|
679
|
-
|
|
680
|
-
/** @internal */
|
|
681
|
-
onClick(): void {
|
|
682
|
-
if (this.disabled()) return;
|
|
683
|
-
const previousValue = this.carousel.currentValue();
|
|
684
|
-
this.carousel.next();
|
|
685
|
-
const next = this.carousel.currentValue();
|
|
686
|
-
if (next != null && next !== previousValue) {
|
|
687
|
-
const msg = this.carousel.buildSlideAnnouncement(next);
|
|
688
|
-
if (msg) this.carousel.announce(msg);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Indicator-group container. Hosts `role="group"` (default) or
|
|
695
|
-
* `role="tablist"` (`controlPattern="tabs"`), composes `KjRovingTabindex`
|
|
696
|
-
* for arrow-key navigation across the dots, and exposes
|
|
697
|
-
* `KJ_CAROUSEL_INDICATORS` to descendants so individual indicator items can
|
|
698
|
-
* read the active control pattern.
|
|
699
|
-
*
|
|
700
|
-
* @doc-category Core/Data display
|
|
701
|
-
* @doc
|
|
702
|
-
* @doc-name carousel
|
|
703
|
-
*/
|
|
704
|
-
@Directive({
|
|
705
|
-
selector: '[kjCarouselIndicators]',
|
|
706
|
-
standalone: true,
|
|
707
|
-
hostDirectives: [KjRovingTabindex],
|
|
708
|
-
providers: [{ provide: KJ_CAROUSEL_INDICATORS, useExisting: KjCarouselIndicators }],
|
|
709
|
-
host: {
|
|
710
|
-
'[attr.role]': 'kjControlPattern() === "tabs" ? "tablist" : "group"',
|
|
711
|
-
'[attr.aria-label]': 'kjAriaLabel()',
|
|
712
|
-
'[attr.aria-orientation]': 'carousel.orientation()',
|
|
713
|
-
'[attr.data-control-pattern]': 'kjControlPattern()',
|
|
714
|
-
},
|
|
715
|
-
})
|
|
716
|
-
export class KjCarouselIndicators implements KjCarouselIndicatorsContext, OnInit, OnDestroy {
|
|
717
|
-
/** @internal */
|
|
718
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
719
|
-
|
|
720
|
-
/** Active control pattern. `'buttons'` is the APG-recommended default; `'tabs'` opts into the tablist variant. */
|
|
721
|
-
readonly kjControlPattern = input<KjCarouselControlPattern>('buttons');
|
|
722
|
-
|
|
723
|
-
/** Accessible name for the indicator group. */
|
|
724
|
-
readonly kjAriaLabel = input<string>('Slide controls');
|
|
725
|
-
|
|
726
|
-
/** Read-only mirror of the control pattern for the per-indicator host bindings. */
|
|
727
|
-
readonly controlPattern: Signal<KjCarouselControlPattern> = this.kjControlPattern;
|
|
728
|
-
|
|
729
|
-
ngOnInit(): void {
|
|
730
|
-
this.carousel.registerIndicators(this);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
ngOnDestroy(): void {
|
|
734
|
-
this.carousel.unregisterIndicators(this);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/** Activate the slide for `value`. */
|
|
738
|
-
activate(value: string): void {
|
|
739
|
-
this.carousel.goTo(value);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Single indicator dot. Composes `KjRovingTabindexItem` and `KjFocusRing`,
|
|
745
|
-
* targets the slide whose value matches `kjForValue` on click, and reflects
|
|
746
|
-
* the active state via `aria-current` (buttons mode) or `aria-selected`
|
|
747
|
-
* (tabs mode).
|
|
748
|
-
*
|
|
749
|
-
* @doc-category Core/Data display
|
|
750
|
-
* @doc
|
|
751
|
-
* @doc-name carousel
|
|
752
|
-
*/
|
|
753
|
-
@Directive({
|
|
754
|
-
selector: '[kjCarouselIndicator]',
|
|
755
|
-
standalone: true,
|
|
756
|
-
hostDirectives: [KjRovingTabindexItemDirective, KjFocusRing],
|
|
757
|
-
host: {
|
|
758
|
-
'[attr.role]': 'indicators.controlPattern() === "tabs" ? "tab" : null',
|
|
759
|
-
'[attr.aria-current]': 'indicators.controlPattern() === "buttons" && isActive() ? "true" : null',
|
|
760
|
-
'[attr.aria-selected]': 'indicators.controlPattern() === "tabs" ? (isActive() ? "true" : "false") : null',
|
|
761
|
-
'[attr.aria-controls]': 'indicators.controlPattern() === "tabs" ? carousel.slideId(kjForValue()) : null',
|
|
762
|
-
'[attr.data-active]': 'isActive() ? "" : null',
|
|
763
|
-
'(click)': 'onClick()',
|
|
764
|
-
'(keydown)': 'onKeydown($event)',
|
|
765
|
-
},
|
|
766
|
-
})
|
|
767
|
-
export class KjCarouselIndicator {
|
|
768
|
-
/** @internal */
|
|
769
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
770
|
-
/** @internal */
|
|
771
|
-
readonly indicators = inject(KJ_CAROUSEL_INDICATORS) as KjCarouselIndicators;
|
|
772
|
-
|
|
773
|
-
/** Slide value this indicator targets. */
|
|
774
|
-
readonly kjForValue = input.required<string>();
|
|
775
|
-
|
|
776
|
-
/** Whether this indicator's slide is the active one. */
|
|
777
|
-
readonly isActive = computed(() => this.carousel.isActive(this.kjForValue()));
|
|
778
|
-
|
|
779
|
-
/** @internal */
|
|
780
|
-
onClick(): void {
|
|
781
|
-
const previousValue = this.carousel.currentValue();
|
|
782
|
-
this.indicators.activate(this.kjForValue());
|
|
783
|
-
const next = this.carousel.currentValue();
|
|
784
|
-
if (next != null && next !== previousValue) {
|
|
785
|
-
const msg = this.carousel.buildSlideAnnouncement(next);
|
|
786
|
-
if (msg) this.carousel.announce(msg);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* @internal Tabs-mode keyboard contract: ArrowLeft/Right (or Up/Down in
|
|
792
|
-
* vertical orientation) advance focus *and* activate, per APG tabs. In
|
|
793
|
-
* buttons mode, the composed `KjRovingTabindex` already handles arrow
|
|
794
|
-
* focus; activation requires Enter / Space, which the native `<button>`
|
|
795
|
-
* resolves on its own.
|
|
796
|
-
*/
|
|
797
|
-
onKeydown(event: KeyboardEvent): void {
|
|
798
|
-
if (this.indicators.controlPattern() !== 'tabs') return;
|
|
799
|
-
// Roving tabindex moves focus on next tick; piggy-back on the
|
|
800
|
-
// focus event by activating the matching slide via the new active item.
|
|
801
|
-
// We don't preventDefault — KjRovingTabindex needs to consume the arrow.
|
|
802
|
-
if (
|
|
803
|
-
event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
|
|
804
|
-
event.key === 'ArrowUp' || event.key === 'ArrowDown'
|
|
805
|
-
) {
|
|
806
|
-
// Defer so the new active item has been focused.
|
|
807
|
-
queueMicrotask(() => {
|
|
808
|
-
const focused = (typeof document !== 'undefined' ? document.activeElement : null) as
|
|
809
|
-
| HTMLElement
|
|
810
|
-
| null;
|
|
811
|
-
if (!focused) return;
|
|
812
|
-
const value = focused.getAttribute('data-kj-indicator-value');
|
|
813
|
-
if (value) this.carousel.goTo(value);
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Autoplay directive — lifecycle-only, no DOM. Manages a `setInterval` that
|
|
821
|
-
* advances the carousel when all five gates are open:
|
|
822
|
-
*
|
|
823
|
-
* 1. The directive is mounted (composition is the on switch).
|
|
824
|
-
* 2. `prefers-reduced-motion: reduce` does not match.
|
|
825
|
-
* 3. The user has not pressed pause (`carousel.paused()` is false).
|
|
826
|
-
* 4. Pointer is not hovering the carousel root, or `kjPauseOnHover` is false.
|
|
827
|
-
* 5. Focus is not within the carousel root, or `kjPauseOnFocus` is false.
|
|
828
|
-
*
|
|
829
|
-
* Per WCAG 2.2.2 a delay greater than 5000 ms requires a pause control to be
|
|
830
|
-
* present in the same composition.
|
|
831
|
-
*
|
|
832
|
-
* @doc-category Core/Data display
|
|
833
|
-
* @doc
|
|
834
|
-
* @doc-name carousel
|
|
835
|
-
*/
|
|
836
|
-
@Directive({
|
|
837
|
-
selector: '[kjCarouselAutoplay]',
|
|
838
|
-
standalone: true,
|
|
839
|
-
exportAs: 'kjCarouselAutoplay',
|
|
840
|
-
})
|
|
841
|
-
export class KjCarouselAutoplay implements OnInit, OnDestroy {
|
|
842
|
-
private readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
843
|
-
|
|
844
|
-
/** Interval between automatic advances. WCAG 2.2.2 requires a pause control when > 5000 ms. */
|
|
845
|
-
readonly kjAutoplayDelay = input<number>(5000);
|
|
846
|
-
|
|
847
|
-
/** Auto-pause while pointer is over the carousel root. */
|
|
848
|
-
readonly kjPauseOnHover = input(true, { transform: booleanAttribute });
|
|
849
|
-
|
|
850
|
-
/** Auto-pause while focus is within the carousel root. APG-required affordance for keyboard users. */
|
|
851
|
-
readonly kjPauseOnFocus = input(true, { transform: booleanAttribute });
|
|
852
|
-
|
|
853
|
-
private timer: ReturnType<typeof setInterval> | null = null;
|
|
854
|
-
|
|
855
|
-
constructor() {
|
|
856
|
-
effect(() => {
|
|
857
|
-
const active = this.carousel.autoplayActive();
|
|
858
|
-
const delay = this.kjAutoplayDelay();
|
|
859
|
-
this.clearTimer();
|
|
860
|
-
if (active && delay > 0) {
|
|
861
|
-
this.timer = setInterval(() => {
|
|
862
|
-
// Re-check gates inside the timer (may have flipped between fires).
|
|
863
|
-
if (!this.carousel.autoplayActive()) return;
|
|
864
|
-
// Auto-advance is silent — buildSlideAnnouncement is *not* called.
|
|
865
|
-
this.carousel.next();
|
|
866
|
-
}, delay);
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
ngOnInit(): void {
|
|
872
|
-
this.carousel.registerAutoplay(this);
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
ngOnDestroy(): void {
|
|
876
|
-
this.clearTimer();
|
|
877
|
-
this.carousel.unregisterAutoplay(this);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
private clearTimer(): void {
|
|
881
|
-
if (this.timer != null) {
|
|
882
|
-
clearInterval(this.timer);
|
|
883
|
-
this.timer = null;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
/**
|
|
889
|
-
* Pause / play toggle. Wraps a real `<button>` and reflects the carousel's
|
|
890
|
-
* `paused` signal as `aria-pressed`. When `prefers-reduced-motion: reduce`
|
|
891
|
-
* matches the host is auto-disabled (autoplay never starts, so the toggle is
|
|
892
|
-
* meaningless).
|
|
893
|
-
*
|
|
894
|
-
* @doc-category Core/Data display
|
|
895
|
-
* @doc
|
|
896
|
-
* @doc-name carousel
|
|
897
|
-
*/
|
|
898
|
-
@Directive({
|
|
899
|
-
selector: '[kjCarouselPauseToggle]',
|
|
900
|
-
standalone: true,
|
|
901
|
-
hostDirectives: [KjFocusRing, KjDisabled],
|
|
902
|
-
host: {
|
|
903
|
-
'[attr.aria-pressed]': 'carousel.paused() ? "true" : "false"',
|
|
904
|
-
'[attr.data-paused]': 'carousel.paused() ? "" : null',
|
|
905
|
-
'[attr.disabled]': 'carousel.prefersReducedMotion() ? "" : null',
|
|
906
|
-
'(click)': 'onClick()',
|
|
907
|
-
},
|
|
908
|
-
})
|
|
909
|
-
export class KjCarouselPauseToggle {
|
|
910
|
-
/** @internal */
|
|
911
|
-
readonly carousel = inject(KJ_CAROUSEL) as KjCarousel;
|
|
912
|
-
|
|
913
|
-
/** @internal */
|
|
914
|
-
onClick(): void {
|
|
915
|
-
if (this.carousel.prefersReducedMotion()) return;
|
|
916
|
-
if (this.carousel.paused()) this.carousel.play();
|
|
917
|
-
else this.carousel.pause();
|
|
918
|
-
}
|
|
919
|
-
}
|