@kouji-ui/core 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -3
- package/CHANGELOG.md +177 -0
- package/ng-package.json +5 -1
- package/package.json +18 -6
- package/src/a11y/aria-describedby.ts +7 -3
- package/src/a11y/aria-labelledby.spec.ts +55 -0
- package/src/a11y/aria-labelledby.ts +38 -0
- package/src/a11y/focus-trap.ts +4 -2
- package/src/a11y/index.ts +1 -0
- package/src/a11y/live-region.ts +4 -2
- package/src/a11y/roving-tabindex.spec.ts +139 -0
- package/src/a11y/roving-tabindex.ts +56 -7
- package/src/a11y/visually-hidden.ts +4 -2
- package/src/accordion/accordion.context.ts +22 -0
- package/src/accordion/accordion.spec.ts +233 -23
- package/src/accordion/accordion.ts +311 -39
- package/src/accordion/index.ts +8 -2
- package/src/alert/alert.context.ts +28 -0
- package/src/alert/alert.spec.ts +217 -0
- package/src/alert/alert.ts +417 -0
- package/src/alert/config.ts +46 -0
- package/src/alert/index.ts +19 -0
- package/src/avatar/avatar-group.context.ts +45 -0
- package/src/avatar/avatar-group.spec.ts +358 -0
- package/src/avatar/avatar-group.ts +259 -0
- package/src/avatar/avatar.ts +17 -4
- package/src/avatar/index.ts +11 -1
- package/src/badge/badge.spec.ts +8 -0
- package/src/badge/badge.ts +24 -3
- package/src/breadcrumb/breadcrumb-current.ts +38 -0
- package/src/breadcrumb/breadcrumb-ellipsis.ts +71 -0
- package/src/breadcrumb/breadcrumb-item.ts +41 -0
- package/src/breadcrumb/breadcrumb-link.ts +44 -0
- package/src/breadcrumb/breadcrumb-list.ts +42 -0
- package/src/breadcrumb/breadcrumb-separator.ts +45 -0
- package/src/breadcrumb/breadcrumb.context.ts +45 -0
- package/src/breadcrumb/breadcrumb.spec.ts +354 -0
- package/src/breadcrumb/breadcrumb.ts +239 -0
- package/src/breadcrumb/config.ts +84 -0
- package/src/breadcrumb/index.ts +18 -0
- package/src/button/_examples/button.example.ts +71 -0
- package/src/button/_examples/button.finance.example.ts +83 -0
- package/src/button/_examples/button.retro.example.ts +91 -0
- package/src/button/_examples/button.sizes.example.ts +60 -0
- package/src/button/_examples/index.ts +6 -0
- package/src/button/button.spec.ts +139 -8
- package/src/button/button.ts +114 -16
- package/src/button/config.spec.ts +33 -0
- package/src/button/config.ts +45 -0
- package/src/button/index.ts +2 -1
- package/src/button-group/button-group.context.ts +28 -0
- package/src/button-group/button-group.spec.ts +99 -0
- package/src/button-group/button-group.ts +99 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar-day.ts +102 -0
- package/src/calendar/calendar-grid.ts +90 -0
- package/src/calendar/calendar-header.ts +65 -0
- package/src/calendar/calendar.context.ts +53 -0
- package/src/calendar/calendar.spec.ts +225 -0
- package/src/calendar/calendar.ts +233 -0
- package/src/calendar/date-utils.ts +225 -0
- package/src/calendar/index.ts +28 -0
- package/src/carousel/carousel.context.ts +81 -0
- package/src/carousel/carousel.spec.ts +337 -0
- package/src/carousel/carousel.ts +919 -0
- package/src/carousel/index.ts +22 -0
- package/src/cascade-select/cascade-select-option.ts +205 -0
- package/src/cascade-select/cascade-select-panel.ts +106 -0
- package/src/cascade-select/cascade-select-root.ts +265 -0
- package/src/cascade-select/cascade-select-sub-panel.ts +168 -0
- package/src/cascade-select/cascade-select-trigger.ts +44 -0
- package/src/cascade-select/cascade-select.context.ts +71 -0
- package/src/cascade-select/cascade-select.spec.ts +241 -0
- package/src/cascade-select/cascade-select.ts +5 -0
- package/src/cascade-select/index.ts +11 -0
- package/src/chart/chart.ts +6 -2
- package/src/chat/chat-avatar.ts +31 -0
- package/src/chat/chat-bubble.ts +49 -0
- package/src/chat/chat-footer.ts +67 -0
- package/src/chat/chat-header.ts +41 -0
- package/src/chat/chat-log.ts +43 -0
- package/src/chat/chat.context.ts +64 -0
- package/src/chat/chat.spec.ts +341 -0
- package/src/chat/chat.ts +191 -0
- package/src/chat/config.ts +53 -0
- package/src/chat/index.ts +21 -0
- package/src/checkbox/checkbox.ts +9 -2
- package/src/color-picker/color-picker.context.ts +74 -0
- package/src/color-picker/color-picker.spec.ts +241 -0
- package/src/color-picker/color-picker.ts +698 -0
- package/src/color-picker/color-picker.utils.ts +140 -0
- package/src/color-picker/index.ts +28 -0
- package/src/combobox/combobox-input.ts +143 -0
- package/src/combobox/combobox-listbox.ts +49 -0
- package/src/combobox/combobox-option.ts +43 -0
- package/src/combobox/combobox-root.ts +281 -0
- package/src/combobox/combobox.spec.ts +75 -0
- package/src/combobox/combobox.ts +4 -0
- package/src/combobox/index.ts +4 -0
- package/src/command-palette/command-empty.ts +36 -0
- package/src/command-palette/command-group.ts +21 -0
- package/src/command-palette/command-input.ts +70 -0
- package/src/command-palette/command-item.ts +60 -0
- package/src/command-palette/command-list.ts +25 -0
- package/src/command-palette/command-palette-dialog.ts +74 -0
- package/src/command-palette/command-palette-trigger.ts +64 -0
- package/src/command-palette/command-palette.filters.ts +53 -0
- package/src/command-palette/command-palette.spec.ts +349 -0
- package/src/command-palette/command-palette.ts +204 -0
- package/src/command-palette/command-separator.ts +18 -0
- package/src/command-palette/index.ts +15 -0
- package/src/confirm-popup/confirm-popup-action.ts +39 -0
- package/src/confirm-popup/confirm-popup-cancel.ts +38 -0
- package/src/confirm-popup/confirm-popup-content.ts +113 -0
- package/src/confirm-popup/confirm-popup-message.ts +36 -0
- package/src/confirm-popup/confirm-popup-trigger.ts +31 -0
- package/src/confirm-popup/confirm-popup.context.ts +41 -0
- package/src/confirm-popup/confirm-popup.spec.ts +337 -0
- package/src/confirm-popup/confirm-popup.ts +109 -0
- package/src/confirm-popup/index.ts +12 -0
- package/src/date-picker/date-picker-calendar.ts +65 -0
- package/src/date-picker/date-picker-trigger.ts +215 -0
- package/src/date-picker/date-picker.context.ts +50 -0
- package/src/date-picker/date-picker.spec.ts +117 -0
- package/src/date-picker/date-picker.ts +127 -0
- package/src/date-picker/index.ts +7 -0
- package/src/dialog/_examples/dialog.confirm.example.ts +145 -0
- package/src/dialog/_examples/dialog.example.ts +106 -0
- package/src/dialog/_examples/dialog.finance.example.ts +121 -0
- package/src/dialog/_examples/dialog.retro.example.ts +126 -0
- package/src/dialog/_examples/index.ts +6 -0
- package/src/dialog/dialog.ref.ts +42 -0
- package/src/dialog/dialog.service.ts +57 -167
- package/src/dialog/dialog.spec.ts +35 -120
- package/src/dialog/dialog.ts +17 -192
- package/src/dialog/index.ts +3 -9
- package/src/divider/divider.spec.ts +223 -0
- package/src/divider/divider.ts +137 -0
- package/src/divider/index.ts +5 -0
- package/src/drawer/drawer.ref.ts +63 -0
- package/src/drawer/drawer.service.ts +106 -0
- package/src/drawer/drawer.spec.ts +117 -0
- package/src/drawer/drawer.ts +122 -0
- package/src/drawer/index.ts +10 -0
- package/src/dropdown-menu/dropdown-menu-content.ts +258 -0
- package/src/dropdown-menu/dropdown-menu-group.ts +31 -0
- package/src/dropdown-menu/dropdown-menu-item.ts +51 -0
- package/src/dropdown-menu/dropdown-menu-label.ts +41 -0
- package/src/dropdown-menu/dropdown-menu-separator.ts +27 -0
- package/src/dropdown-menu/dropdown-menu-trigger.ts +140 -0
- package/src/dropdown-menu/dropdown-menu.spec.ts +265 -0
- package/src/dropdown-menu/dropdown-menu.ts +60 -0
- package/src/dropdown-menu/index.ts +15 -0
- package/src/example-components.ts +39 -30
- package/src/field/field-error.ts +49 -0
- package/src/field/field-group.ts +39 -0
- package/src/field/field-help.ts +47 -0
- package/src/field/field-label.ts +30 -0
- package/src/field/field.context.ts +28 -0
- package/src/field/field.spec.ts +179 -0
- package/src/field/field.ts +121 -0
- package/src/field/index.ts +6 -0
- package/src/file-upload/file-upload.context.ts +89 -0
- package/src/file-upload/file-upload.spec.ts +290 -0
- package/src/file-upload/file-upload.ts +639 -0
- package/src/file-upload/file-upload.types.ts +59 -0
- package/src/file-upload/index.ts +23 -0
- package/src/form/form-error-summary.ts +87 -0
- package/src/form/form-field.context.ts +3 -0
- package/src/form/form-field.spec.ts +25 -0
- package/src/form/form-field.ts +52 -0
- package/src/form/form.context.ts +52 -2
- package/src/form/form.spec.ts +155 -18
- package/src/form/form.ts +315 -35
- package/src/form/index.ts +11 -2
- package/src/icon/E2E.md +14 -0
- package/src/icon/icon.css +40 -0
- package/src/icon/icon.directive.spec.ts +198 -0
- package/src/icon/icon.directive.ts +113 -0
- package/src/icon/icon.mode.spec.ts +29 -0
- package/src/icon/icon.mode.ts +23 -0
- package/src/icon/icon.providers.spec.ts +63 -0
- package/src/icon/icon.providers.ts +47 -0
- package/src/icon/icon.resolver.spec.ts +96 -0
- package/src/icon/icon.resolver.ts +49 -0
- package/src/icon/icon.tokens.ts +88 -0
- package/src/icon/icon.types.ts +48 -0
- package/src/icon/index.ts +21 -0
- package/src/input/input.ts +12 -3
- package/src/input-group/index.ts +3 -0
- package/src/input-group/input-group-addon.ts +92 -0
- package/src/input-group/input-group.context.ts +27 -0
- package/src/input-group/input-group.spec.ts +186 -0
- package/src/input-group/input-group.ts +136 -0
- package/src/input-mask/index.ts +11 -0
- package/src/input-mask/input-mask.compile.ts +69 -0
- package/src/input-mask/input-mask.engine.ts +376 -0
- package/src/input-mask/input-mask.presets.ts +24 -0
- package/src/input-mask/input-mask.spec.ts +304 -0
- package/src/input-mask/input-mask.tokens.ts +49 -0
- package/src/input-mask/input-mask.ts +280 -0
- package/src/input-otp/index.ts +2 -0
- package/src/input-otp/input-otp.context.ts +41 -0
- package/src/input-otp/input-otp.spec.ts +308 -0
- package/src/input-otp/input-otp.ts +470 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.spec.ts +127 -0
- package/src/kbd/kbd.ts +132 -0
- package/src/link/config.ts +44 -0
- package/src/link/index.ts +2 -0
- package/src/link/link.spec.ts +246 -0
- package/src/link/link.ts +226 -0
- package/src/list/index.ts +11 -0
- package/src/list/list.spec.ts +237 -0
- package/src/list/list.ts +277 -0
- package/src/menubar/index.ts +7 -0
- package/src/menubar/menubar-item.ts +204 -0
- package/src/menubar/menubar.context.ts +43 -0
- package/src/menubar/menubar.spec.ts +433 -0
- package/src/menubar/menubar.ts +309 -0
- package/src/number-input/index.ts +4 -0
- package/src/number-input/number-input-group.ts +64 -0
- package/src/number-input/number-input.context.ts +31 -0
- package/src/number-input/number-input.format.ts +148 -0
- package/src/number-input/number-input.spec.ts +170 -0
- package/src/number-input/number-input.ts +457 -0
- package/src/number-input/number-stepper.ts +193 -0
- package/src/overlay-badge/index.ts +7 -0
- package/src/overlay-badge/overlay-badge-content.ts +73 -0
- package/src/overlay-badge/overlay-badge.context.ts +37 -0
- package/src/overlay-badge/overlay-badge.spec.ts +220 -0
- package/src/overlay-badge/overlay-badge.ts +128 -0
- package/src/pagination/config.ts +82 -0
- package/src/pagination/index.ts +19 -0
- package/src/pagination/page-tokens.ts +78 -0
- package/src/pagination/pagination-ellipsis.ts +69 -0
- package/src/pagination/pagination-first.ts +84 -0
- package/src/pagination/pagination-info.ts +70 -0
- package/src/pagination/pagination-item.ts +81 -0
- package/src/pagination/pagination-last.ts +85 -0
- package/src/pagination/pagination-next.ts +86 -0
- package/src/pagination/pagination-previous.ts +90 -0
- package/src/pagination/pagination.context.ts +52 -0
- package/src/pagination/pagination.spec.ts +388 -0
- package/src/pagination/pagination.ts +288 -0
- package/src/password-input/index.ts +15 -0
- package/src/password-input/password-input.context.ts +65 -0
- package/src/password-input/password-input.scorer.ts +34 -0
- package/src/password-input/password-input.spec.ts +274 -0
- package/src/password-input/password-input.ts +488 -0
- package/src/popover/_examples/index.ts +5 -0
- package/src/popover/_examples/popover.example.ts +92 -0
- package/src/popover/_examples/popover.finance.example.ts +106 -0
- package/src/popover/_examples/popover.retro.example.ts +103 -0
- package/src/popover/index.ts +5 -12
- package/src/popover/popover-arrow.ts +30 -0
- package/src/popover/popover-close.ts +36 -0
- package/src/popover/popover-content.ts +41 -0
- package/src/popover/popover-title.ts +39 -0
- package/src/popover/popover-trigger.ts +40 -0
- package/src/popover/popover.spec.ts +51 -44
- package/src/popover/popover.ts +5 -124
- package/src/presets/bind-presets.spec.ts +50 -0
- package/src/presets/bind-presets.ts +43 -0
- package/src/presets/index.ts +3 -0
- package/src/presets/size.spec.ts +67 -0
- package/src/presets/size.ts +59 -0
- package/src/presets/variant.spec.ts +72 -0
- package/src/presets/variant.ts +62 -0
- package/src/primitives/directionality/directionality.spec.ts +112 -0
- package/src/primitives/directionality/directionality.ts +89 -0
- package/src/primitives/directionality/index.ts +1 -0
- package/src/primitives/forms/form-control.spec.ts +14 -6
- package/src/primitives/forms/form-control.ts +6 -2
- package/src/primitives/index.ts +3 -0
- package/src/primitives/interaction/disabled.ts +12 -5
- package/src/primitives/interaction/focus-ring.ts +4 -2
- package/src/primitives/list/filterable-list.spec.ts +114 -0
- package/src/primitives/list/filterable-list.ts +126 -0
- package/src/primitives/list/group.spec.ts +99 -0
- package/src/primitives/list/group.ts +139 -0
- package/src/primitives/list/index.ts +8 -0
- package/src/primitives/list/inject-helpers.ts +8 -0
- package/src/primitives/list/item.spec.ts +173 -0
- package/src/primitives/list/item.ts +222 -0
- package/src/primitives/list/navigator.spec.ts +286 -0
- package/src/primitives/list/navigator.ts +238 -0
- package/src/primitives/list/selection.spec.ts +214 -0
- package/src/primitives/list/selection.ts +339 -0
- package/src/primitives/list/tokens.ts +106 -0
- package/src/primitives/list/type-ahead.spec.ts +58 -0
- package/src/primitives/list/type-ahead.ts +46 -0
- package/src/primitives/overlay/backdrop.spec.ts +54 -0
- package/src/primitives/overlay/backdrop.ts +31 -0
- package/src/primitives/overlay/builder.spec.ts +38 -0
- package/src/primitives/overlay/builder.ts +151 -0
- package/src/primitives/overlay/container.ts +42 -0
- package/src/primitives/overlay/context.ts +14 -0
- package/src/primitives/overlay/controller.spec.ts +86 -0
- package/src/primitives/overlay/controller.ts +190 -0
- package/src/primitives/overlay/id.spec.ts +24 -0
- package/src/primitives/overlay/id.ts +17 -0
- package/src/primitives/overlay/index.ts +18 -1
- package/src/primitives/overlay/overlay.css +42 -0
- package/src/primitives/overlay/panel.spec.ts +52 -0
- package/src/primitives/overlay/panel.ts +102 -0
- package/src/primitives/overlay/stack.spec.ts +55 -0
- package/src/primitives/overlay/stack.ts +113 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.spec.ts +11 -0
- package/src/primitives/overlay/strategies/backdrop/blurred.ts +5 -0
- package/src/primitives/overlay/strategies/backdrop/none.spec.ts +10 -0
- package/src/primitives/overlay/strategies/backdrop/none.ts +9 -0
- package/src/primitives/overlay/strategies/backdrop/solid.spec.ts +18 -0
- package/src/primitives/overlay/strategies/backdrop/solid.ts +23 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.spec.ts +28 -0
- package/src/primitives/overlay/strategies/focus-trap/inert-based.ts +38 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.spec.ts +9 -0
- package/src/primitives/overlay/strategies/focus-trap/no-trap.ts +8 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.spec.ts +49 -0
- package/src/primitives/overlay/strategies/focus-trap/tab-cycle.ts +85 -0
- package/src/primitives/overlay/strategies/index.ts +34 -0
- package/src/primitives/overlay/strategies/live-announcer/_announce.ts +35 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/assertive.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.spec.ts +21 -0
- package/src/primitives/overlay/strategies/live-announcer/polite.ts +11 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.spec.ts +12 -0
- package/src/primitives/overlay/strategies/live-announcer/silent.ts +5 -0
- package/src/primitives/overlay/strategies/mount/body-portal.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/body-portal.ts +91 -0
- package/src/primitives/overlay/strategies/mount/in-container.spec.ts +43 -0
- package/src/primitives/overlay/strategies/mount/in-container.ts +39 -0
- package/src/primitives/overlay/strategies/mount/in-place.spec.ts +40 -0
- package/src/primitives/overlay/strategies/mount/in-place.ts +20 -0
- package/src/primitives/overlay/strategies/position/anchored-to.spec.ts +56 -0
- package/src/primitives/overlay/strategies/position/anchored-to.ts +248 -0
- package/src/primitives/overlay/strategies/position/corner.spec.ts +36 -0
- package/src/primitives/overlay/strategies/position/corner.ts +33 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.spec.ts +32 -0
- package/src/primitives/overlay/strategies/position/edge-sheet.ts +36 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.spec.ts +25 -0
- package/src/primitives/overlay/strategies/position/in-place-sibling.ts +13 -0
- package/src/primitives/overlay/strategies/position/point-at.spec.ts +40 -0
- package/src/primitives/overlay/strategies/position/point-at.ts +34 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.spec.ts +31 -0
- package/src/primitives/overlay/strategies/position/viewport-centered.ts +31 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.spec.ts +15 -0
- package/src/primitives/overlay/strategies/scroll-lock/css-clip.ts +39 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.spec.ts +21 -0
- package/src/primitives/overlay/strategies/scroll-lock/html-overflow.ts +44 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.spec.ts +9 -0
- package/src/primitives/overlay/strategies/scroll-lock/none.ts +5 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.spec.ts +32 -0
- package/src/primitives/overlay/strategies/trigger-event/on-click.ts +28 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.spec.ts +45 -0
- package/src/primitives/overlay/strategies/trigger-event/on-context-menu.ts +70 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.spec.ts +35 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus-or-input.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.spec.ts +64 -0
- package/src/primitives/overlay/strategies/trigger-event/on-focus.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.spec.ts +37 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hotkey.ts +59 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.spec.ts +34 -0
- package/src/primitives/overlay/strategies/trigger-event/on-hover.ts +86 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.spec.ts +16 -0
- package/src/primitives/overlay/strategies/trigger-event/programmatic.ts +12 -0
- package/src/primitives/overlay/tokens.ts +54 -0
- package/src/primitives/overlay/trigger.spec.ts +103 -0
- package/src/primitives/overlay/trigger.ts +75 -0
- package/src/primitives/overlay/types.ts +24 -0
- package/src/primitives/overlay/wrapper.ts +50 -0
- package/src/primitives/signals/deep-signal.ts +109 -0
- package/src/primitives/signals/index.ts +1 -0
- package/src/progress-bar/config.ts +48 -0
- package/src/progress-bar/index.ts +12 -0
- package/src/progress-bar/progress-bar-fill.ts +37 -0
- package/src/progress-bar/progress-bar.context.ts +34 -0
- package/src/progress-bar/progress-bar.spec.ts +423 -0
- package/src/progress-bar/progress-bar.ts +217 -0
- package/src/public-api.ts +58 -9
- package/src/radio/radio.ts +14 -3
- package/src/select/index.ts +4 -2
- package/src/select/select-content.ts +69 -0
- package/src/select/select-option.ts +35 -0
- package/src/select/select-root.ts +129 -0
- package/src/select/select-trigger.ts +93 -0
- package/src/select/select.spec.ts +133 -140
- package/src/select/select.ts +4 -250
- package/src/skeleton/index.ts +5 -0
- package/src/skeleton/skeleton.spec.ts +115 -0
- package/src/skeleton/skeleton.ts +56 -0
- package/src/slider/index.ts +10 -0
- package/src/slider/slider-range.ts +49 -0
- package/src/slider/slider-thumb.ts +335 -0
- package/src/slider/slider-track.ts +70 -0
- package/src/slider/slider.context.ts +79 -0
- package/src/slider/slider.geometry.ts +73 -0
- package/src/slider/slider.spec.ts +216 -0
- package/src/slider/slider.ts +443 -0
- package/src/speed-dial/index.ts +10 -0
- package/src/speed-dial/speed-dial-action.ts +40 -0
- package/src/speed-dial/speed-dial-actions.ts +31 -0
- package/src/speed-dial/speed-dial-trigger.ts +55 -0
- package/src/speed-dial/speed-dial.context.ts +36 -0
- package/src/speed-dial/speed-dial.spec.ts +143 -0
- package/src/speed-dial/speed-dial.ts +127 -0
- package/src/spinner/config.ts +48 -0
- package/src/spinner/index.ts +8 -0
- package/src/spinner/spinner.spec.ts +262 -0
- package/src/spinner/spinner.ts +129 -0
- package/src/stepper/index.ts +15 -0
- package/src/stepper/stepper.context.ts +63 -0
- package/src/stepper/stepper.spec.ts +403 -0
- package/src/stepper/stepper.ts +544 -0
- package/src/styles.css +17 -0
- package/src/table/column-helpers.spec.ts +74 -0
- package/src/table/column-helpers.ts +68 -0
- package/src/table/filter-fns.ts +230 -0
- package/src/table/filter-models.spec.ts +240 -0
- package/src/table/filter-models.ts +124 -0
- package/src/table/filter-params.ts +90 -0
- package/src/table/grid-api-impl.ts +321 -0
- package/src/table/grid-api.spec.ts +178 -0
- package/src/table/grid-api.ts +285 -0
- package/src/table/index.ts +73 -1
- package/src/table/table-cell.spec.ts +43 -0
- package/src/table/table-cell.ts +38 -0
- package/src/table/table-filter-outlet.ts +120 -0
- package/src/table/table-header.spec.ts +54 -0
- package/src/table/table-header.ts +63 -0
- package/src/table/table-keyboard.spec.ts +68 -0
- package/src/table/table-keyboard.ts +107 -0
- package/src/table/table-resource.spec.ts +37 -0
- package/src/table/table-resource.ts +36 -0
- package/src/table/table-row.spec.ts +54 -0
- package/src/table/table-row.ts +31 -0
- package/src/table/table-storage.spec.ts +67 -0
- package/src/table/table-storage.ts +76 -0
- package/src/table/table.spec.ts +102 -43
- package/src/table/table.ts +228 -73
- package/src/table/table.types.ts +83 -0
- package/src/tabs/index.ts +7 -2
- package/src/tabs/tabs.context.ts +27 -4
- package/src/tabs/tabs.spec.ts +336 -24
- package/src/tabs/tabs.ts +313 -79
- package/src/tag/config.ts +53 -0
- package/src/tag/index.ts +7 -0
- package/src/tag/tag-list.ts +76 -0
- package/src/tag/tag-remove.ts +61 -0
- package/src/tag/tag.context.ts +36 -0
- package/src/tag/tag.spec.ts +240 -0
- package/src/tag/tag.ts +203 -0
- package/src/textarea/config.ts +42 -0
- package/src/textarea/index.ts +8 -0
- package/src/textarea/textarea.spec.ts +176 -0
- package/src/textarea/textarea.ts +235 -0
- package/src/time-picker/index.ts +15 -0
- package/src/time-picker/time-picker-meridiem.ts +123 -0
- package/src/time-picker/time-picker-segment.ts +318 -0
- package/src/time-picker/time-picker.context.ts +38 -0
- package/src/time-picker/time-picker.format.ts +181 -0
- package/src/time-picker/time-picker.spec.ts +189 -0
- package/src/time-picker/time-picker.ts +274 -0
- package/src/toast/_examples/index.ts +5 -0
- package/src/toast/_examples/toast.example.ts +282 -0
- package/src/toast/_examples/toast.finance.example.ts +180 -0
- package/src/toast/_examples/toast.retro.example.ts +161 -0
- package/src/toast/index.ts +8 -1
- package/src/toast/toast.ref.ts +26 -0
- package/src/toast/toast.service.ts +227 -41
- package/src/toast/toast.spec.ts +178 -25
- package/src/toast/toast.strategy.ts +10 -0
- package/src/toast/toast.ts +178 -22
- package/src/toggle/toggle.ts +10 -2
- package/src/tooltip/_examples/index.ts +6 -0
- package/src/tooltip/_examples/tooltip.example.ts +82 -0
- package/src/tooltip/_examples/tooltip.finance.example.ts +97 -0
- package/src/tooltip/_examples/tooltip.placements.example.ts +83 -0
- package/src/tooltip/_examples/tooltip.retro.example.ts +107 -0
- package/src/tooltip/index.ts +4 -5
- package/src/tooltip/tooltip-arrow.ts +31 -0
- package/src/tooltip/tooltip-content.ts +38 -0
- package/src/tooltip/tooltip-group.ts +32 -0
- package/src/tooltip/tooltip-trigger.ts +47 -0
- package/src/tooltip/tooltip.spec.ts +38 -102
- package/src/tooltip/tooltip.ts +5 -129
- package/src/tree-select/index.ts +12 -0
- package/src/tree-select/tree-select-content.ts +199 -0
- package/src/tree-select/tree-select-node.ts +132 -0
- package/src/tree-select/tree-select-root.ts +272 -0
- package/src/tree-select/tree-select-trigger.ts +90 -0
- package/src/tree-select/tree-select.context.ts +63 -0
- package/src/tree-select/tree-select.spec.ts +433 -0
- package/src/tree-select/tree-select.ts +2 -0
- package/src/typography/blockquote.ts +57 -0
- package/src/typography/code.ts +53 -0
- package/src/typography/index.ts +22 -0
- package/src/typography/lead.ts +49 -0
- package/src/typography/muted.ts +29 -0
- package/src/typography/prose.css +312 -0
- package/src/typography/truncate.ts +99 -0
- package/src/typography/typography.spec.ts +203 -0
- package/tsconfig.lib.json +9 -1
- package/tsconfig.lib.prod.json +7 -1
- package/tsconfig.spec.json +9 -1
- package/vite.config.ts +8 -0
- package/src/button/button.example.ts +0 -30
- package/src/button/button.finance.example.ts +0 -37
- package/src/button/button.retro.example.ts +0 -47
- package/src/button/button.sizes.example.ts +0 -25
- package/src/dialog/dialog.confirm.example.ts +0 -57
- package/src/dialog/dialog.context.ts +0 -11
- package/src/dialog/dialog.example.ts +0 -40
- package/src/dialog/dialog.finance.example.ts +0 -47
- package/src/dialog/dialog.retro.example.ts +0 -46
- package/src/menu/index.ts +0 -2
- package/src/menu/menu.context.ts +0 -10
- package/src/menu/menu.example.ts +0 -39
- package/src/menu/menu.spec.ts +0 -168
- package/src/menu/menu.ts +0 -176
- package/src/popover/popover.context.ts +0 -18
- package/src/popover/popover.example.ts +0 -50
- package/src/popover/popover.finance.example.ts +0 -52
- package/src/popover/popover.retro.example.ts +0 -53
- package/src/primitives/overlay/overlay.spec.ts +0 -90
- package/src/primitives/overlay/overlay.ts +0 -102
- package/src/select/select.context.ts +0 -3
- package/src/toast/toast.example.ts +0 -134
- package/src/toast/toast.finance.example.ts +0 -68
- package/src/toast/toast.retro.example.ts +0 -69
- package/src/tooltip/tooltip.example.ts +0 -46
- package/src/tooltip/tooltip.finance.example.ts +0 -54
- package/src/tooltip/tooltip.placements.example.ts +0 -56
- package/src/tooltip/tooltip.retro.example.ts +0 -65
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ColumnDef, FilterFn, RowData } from '@tanstack/angular-table';
|
|
2
|
+
import {
|
|
3
|
+
kjDateFilterFn,
|
|
4
|
+
kjNumberFilterFn,
|
|
5
|
+
kjSetFilterFn,
|
|
6
|
+
kjTextFilterFn,
|
|
7
|
+
} from './filter-fns';
|
|
8
|
+
import type { KjColumnDef, KjColumnMeta, KjColumnType } from './table.types';
|
|
9
|
+
|
|
10
|
+
const KJ_KEYS = [
|
|
11
|
+
'kjType', 'kjEditable', 'kjFilterable', 'kjPin', 'kjGroup', 'kjAgg',
|
|
12
|
+
'kjEditor', 'kjFilterUi', 'kjSelectOptions', 'kjExportable', 'kjPersist',
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
/** Auto-wired matcher per `kjType`. Picked up only when the column def
|
|
16
|
+
* doesn't already supply its own `filterFn`. */
|
|
17
|
+
const DEFAULT_FILTER_FNS: Readonly<Record<KjColumnType, FilterFn<unknown>>> = {
|
|
18
|
+
text: kjTextFilterFn as unknown as FilterFn<unknown>,
|
|
19
|
+
number: kjNumberFilterFn as unknown as FilterFn<unknown>,
|
|
20
|
+
date: kjDateFilterFn as unknown as FilterFn<unknown>,
|
|
21
|
+
select: kjSetFilterFn as unknown as FilterFn<unknown>,
|
|
22
|
+
boolean: kjSetFilterFn as unknown as FilterFn<unknown>,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Strip leading 'kj' and lowercase first char. */
|
|
26
|
+
function unprefix(k: string): keyof KjColumnMeta {
|
|
27
|
+
return (k.slice(2, 3).toLowerCase() + k.slice(3)) as keyof KjColumnMeta;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Define a column for `<kj-data-table>`. Pass-through for every TanStack
|
|
32
|
+
* `ColumnDef` field; kj-specific top-level knobs (`kjType`, `kjEditable`,
|
|
33
|
+
* `kjPin`, …) are hoisted into `meta.kj` for the wrapper to read.
|
|
34
|
+
*/
|
|
35
|
+
export function kjColumn<TData extends RowData>(def: KjColumnDef<TData>): ColumnDef<TData> {
|
|
36
|
+
const kjMeta: Partial<KjColumnMeta<TData>> = {};
|
|
37
|
+
const rest: Record<string, unknown> = {};
|
|
38
|
+
for (const [k, v] of Object.entries(def)) {
|
|
39
|
+
if ((KJ_KEYS as readonly string[]).includes(k)) {
|
|
40
|
+
if (v !== undefined) kjMeta[unprefix(k)] = v as never;
|
|
41
|
+
} else {
|
|
42
|
+
rest[k] = v;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const existingMeta = (rest['meta'] ?? {}) as Record<string, unknown>;
|
|
46
|
+
const meta = Object.keys(kjMeta).length === 0
|
|
47
|
+
? existingMeta
|
|
48
|
+
: { ...existingMeta, kj: kjMeta };
|
|
49
|
+
|
|
50
|
+
// Auto-wire `filterFn` from `kjType` so columns built with `kjColumn({ ... })`
|
|
51
|
+
// actually understand the structured `KjFilterModel` the built-in filter UIs
|
|
52
|
+
// write. User-supplied `filterFn` always wins.
|
|
53
|
+
if (rest['filterFn'] === undefined && kjMeta.type) {
|
|
54
|
+
const fn = DEFAULT_FILTER_FNS[kjMeta.type as KjColumnType];
|
|
55
|
+
if (fn) rest['filterFn'] = fn as unknown as FilterFn<TData>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ...rest, meta } as unknown as ColumnDef<TData>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Define a column group (header grouping). Children are `kjColumn()` defs. */
|
|
62
|
+
export function kjColumnGroup<TData extends RowData>(def: {
|
|
63
|
+
id?: string;
|
|
64
|
+
header: ColumnDef<TData>['header'];
|
|
65
|
+
columns: ColumnDef<TData>[];
|
|
66
|
+
}): ColumnDef<TData> {
|
|
67
|
+
return def as unknown as ColumnDef<TData>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { Row } from '@tanstack/angular-table';
|
|
2
|
+
import {
|
|
3
|
+
isKjFilterModel,
|
|
4
|
+
type KjDateFilterModel,
|
|
5
|
+
type KjFilterModel,
|
|
6
|
+
type KjMultiConditionFilterModel,
|
|
7
|
+
type KjNumberFilterModel,
|
|
8
|
+
type KjSetFilterModel,
|
|
9
|
+
type KjTextFilterModel,
|
|
10
|
+
} from './filter-models';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* TanStack `filterFn` for {@link KjTextFilterModel}. Accepts the model as
|
|
14
|
+
* the filter value and dispatches on `model.type`. Tolerates a bare
|
|
15
|
+
* string (legacy "contains" raw value) so users migrating filters one at
|
|
16
|
+
* a time don't have to flip every column simultaneously.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```ts
|
|
20
|
+
* kjColumn<User>({ accessorKey: 'name', filterFn: kjTextFilterFn })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function kjTextFilterFn<TData>(
|
|
24
|
+
row: Row<TData>,
|
|
25
|
+
columnId: string,
|
|
26
|
+
filterValue: unknown,
|
|
27
|
+
): boolean {
|
|
28
|
+
if (filterValue == null || filterValue === '') return true;
|
|
29
|
+
|
|
30
|
+
// Legacy raw-string compatibility — treated as `contains`.
|
|
31
|
+
if (typeof filterValue === 'string') {
|
|
32
|
+
return contains(read(row, columnId), filterValue, /* caseSensitive */ false);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!isKjFilterModel(filterValue) || filterValue.filterType !== 'text') return true;
|
|
36
|
+
const model = filterValue as KjTextFilterModel;
|
|
37
|
+
const cell = read(row, columnId);
|
|
38
|
+
const cs = model.caseSensitive === true;
|
|
39
|
+
|
|
40
|
+
switch (model.type) {
|
|
41
|
+
case 'blank': return cell == null || cell === '';
|
|
42
|
+
case 'notBlank': return cell != null && cell !== '';
|
|
43
|
+
case 'equals': return text(cell, cs) === text(model.filter ?? '', cs);
|
|
44
|
+
case 'notEquals': return text(cell, cs) !== text(model.filter ?? '', cs);
|
|
45
|
+
case 'contains': return contains(cell, model.filter ?? '', cs);
|
|
46
|
+
case 'notContains': return !contains(cell, model.filter ?? '', cs);
|
|
47
|
+
case 'startsWith': return text(cell, cs).startsWith(text(model.filter ?? '', cs));
|
|
48
|
+
case 'endsWith': return text(cell, cs).endsWith(text(model.filter ?? '', cs));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* TanStack `filterFn` for {@link KjNumberFilterModel}. Tolerates a bare
|
|
54
|
+
* `[min, max]` tuple as legacy raw value (treated as `inRange`).
|
|
55
|
+
*/
|
|
56
|
+
export function kjNumberFilterFn<TData>(
|
|
57
|
+
row: Row<TData>,
|
|
58
|
+
columnId: string,
|
|
59
|
+
filterValue: unknown,
|
|
60
|
+
): boolean {
|
|
61
|
+
if (filterValue == null) return true;
|
|
62
|
+
|
|
63
|
+
// Legacy `[min, max]` tuple → inRange.
|
|
64
|
+
if (Array.isArray(filterValue)) {
|
|
65
|
+
const [min, max] = filterValue as [number | undefined, number | undefined];
|
|
66
|
+
return inNumberRange(Number(read(row, columnId)), min, max);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!isKjFilterModel(filterValue) || filterValue.filterType !== 'number') return true;
|
|
70
|
+
const model = filterValue as KjNumberFilterModel;
|
|
71
|
+
const cell = Number(read(row, columnId));
|
|
72
|
+
|
|
73
|
+
if (model.type === 'blank') return Number.isNaN(cell);
|
|
74
|
+
if (model.type === 'notBlank') return !Number.isNaN(cell);
|
|
75
|
+
|
|
76
|
+
// `inRange` is the one operator where a missing `filter` is still
|
|
77
|
+
// meaningful — it just leaves the lower bound open. Every other operator
|
|
78
|
+
// needs an explicit `filter`, so we treat its absence as "no filter".
|
|
79
|
+
if (model.type === 'inRange') {
|
|
80
|
+
if (model.filter == null && model.filterTo == null) return true;
|
|
81
|
+
return inNumberRange(cell, model.filter, model.filterTo);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const value = model.filter;
|
|
85
|
+
if (value == null) return true;
|
|
86
|
+
|
|
87
|
+
switch (model.type) {
|
|
88
|
+
case 'equals': return cell === value;
|
|
89
|
+
case 'notEquals': return cell !== value;
|
|
90
|
+
case 'greaterThan': return cell > value;
|
|
91
|
+
case 'greaterThanOrEqual': return cell >= value;
|
|
92
|
+
case 'lessThan': return cell < value;
|
|
93
|
+
case 'lessThanOrEqual': return cell <= value;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* TanStack `filterFn` for {@link KjDateFilterModel}. Compares ISO date
|
|
100
|
+
* strings as calendar dates (no time component, no TZ math). Tolerates a
|
|
101
|
+
* `[from, to]` tuple of ISO strings as legacy raw value.
|
|
102
|
+
*/
|
|
103
|
+
export function kjDateFilterFn<TData>(
|
|
104
|
+
row: Row<TData>,
|
|
105
|
+
columnId: string,
|
|
106
|
+
filterValue: unknown,
|
|
107
|
+
): boolean {
|
|
108
|
+
if (filterValue == null) return true;
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(filterValue)) {
|
|
111
|
+
const [from, to] = filterValue as [string | undefined, string | undefined];
|
|
112
|
+
return inIsoRange(isoOf(read(row, columnId)), from, to);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!isKjFilterModel(filterValue) || filterValue.filterType !== 'date') return true;
|
|
116
|
+
const model = filterValue as KjDateFilterModel;
|
|
117
|
+
const cell = isoOf(read(row, columnId));
|
|
118
|
+
|
|
119
|
+
if (model.type === 'blank') return !cell;
|
|
120
|
+
if (model.type === 'notBlank') return !!cell;
|
|
121
|
+
if (!cell) return false;
|
|
122
|
+
|
|
123
|
+
switch (model.type) {
|
|
124
|
+
case 'equals': return cell === model.dateFrom;
|
|
125
|
+
case 'notEquals': return cell !== model.dateFrom;
|
|
126
|
+
case 'greaterThan': return !!model.dateFrom && cell > model.dateFrom;
|
|
127
|
+
case 'lessThan': return !!model.dateFrom && cell < model.dateFrom;
|
|
128
|
+
case 'inRange': return inIsoRange(cell, model.dateFrom, model.dateTo);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* TanStack `filterFn` for {@link KjSetFilterModel}. The row passes if the
|
|
134
|
+
* cell's stringified value matches any selected entry. Tolerates a bare
|
|
135
|
+
* (non-array) value as legacy raw equality.
|
|
136
|
+
*/
|
|
137
|
+
export function kjSetFilterFn<TData>(
|
|
138
|
+
row: Row<TData>,
|
|
139
|
+
columnId: string,
|
|
140
|
+
filterValue: unknown,
|
|
141
|
+
): boolean {
|
|
142
|
+
if (filterValue == null) return true;
|
|
143
|
+
|
|
144
|
+
if (!isKjFilterModel(filterValue)) {
|
|
145
|
+
return Object.is(read(row, columnId), filterValue);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (filterValue.filterType !== 'set') return true;
|
|
149
|
+
const model = filterValue as KjSetFilterModel;
|
|
150
|
+
if (model.values.length === 0) return true;
|
|
151
|
+
const cell = read(row, columnId);
|
|
152
|
+
return model.values.some((v) => Object.is(v, cell));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* TanStack `filterFn` for {@link KjMultiConditionFilterModel}.
|
|
157
|
+
* Recursively delegates each condition to the matching helper based on
|
|
158
|
+
* `condition.filterType`.
|
|
159
|
+
*/
|
|
160
|
+
export function kjMultiFilterFn<TData>(
|
|
161
|
+
row: Row<TData>,
|
|
162
|
+
columnId: string,
|
|
163
|
+
filterValue: unknown,
|
|
164
|
+
): boolean {
|
|
165
|
+
if (!isKjFilterModel(filterValue) || filterValue.filterType !== 'multi') return true;
|
|
166
|
+
const model = filterValue as KjMultiConditionFilterModel;
|
|
167
|
+
const reduce = model.operator === 'AND'
|
|
168
|
+
? model.conditions.every.bind(model.conditions)
|
|
169
|
+
: model.conditions.some.bind(model.conditions);
|
|
170
|
+
return reduce((c) => evaluate(row, columnId, c));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Dispatch a single model against the correct typed helper. */
|
|
174
|
+
function evaluate<TData>(
|
|
175
|
+
row: Row<TData>,
|
|
176
|
+
columnId: string,
|
|
177
|
+
model: KjFilterModel,
|
|
178
|
+
): boolean {
|
|
179
|
+
switch (model.filterType) {
|
|
180
|
+
case 'text': return kjTextFilterFn(row, columnId, model);
|
|
181
|
+
case 'number': return kjNumberFilterFn(row, columnId, model);
|
|
182
|
+
case 'date': return kjDateFilterFn(row, columnId, model);
|
|
183
|
+
case 'set': return kjSetFilterFn(row, columnId, model);
|
|
184
|
+
case 'multi': return kjMultiFilterFn(row, columnId, model);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── primitives ─────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
function read<TData>(row: Row<TData>, columnId: string): unknown {
|
|
191
|
+
return row.getValue(columnId);
|
|
192
|
+
}
|
|
193
|
+
function text(value: unknown, caseSensitive: boolean): string {
|
|
194
|
+
const s = value == null ? '' : String(value);
|
|
195
|
+
return caseSensitive ? s : s.toLowerCase();
|
|
196
|
+
}
|
|
197
|
+
function contains(cell: unknown, needle: string, caseSensitive: boolean): boolean {
|
|
198
|
+
return text(cell, caseSensitive).includes(text(needle, caseSensitive));
|
|
199
|
+
}
|
|
200
|
+
function inNumberRange(
|
|
201
|
+
cell: number,
|
|
202
|
+
min: number | undefined,
|
|
203
|
+
max: number | undefined,
|
|
204
|
+
): boolean {
|
|
205
|
+
if (Number.isNaN(cell)) return false;
|
|
206
|
+
if (min !== undefined && cell < min) return false;
|
|
207
|
+
if (max !== undefined && cell > max) return false;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
function inIsoRange(
|
|
211
|
+
cell: string | undefined,
|
|
212
|
+
from: string | undefined,
|
|
213
|
+
to: string | undefined,
|
|
214
|
+
): boolean {
|
|
215
|
+
if (!cell) return false;
|
|
216
|
+
if (from && cell < from) return false;
|
|
217
|
+
if (to && cell > to) return false;
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
function isoOf(value: unknown): string | undefined {
|
|
221
|
+
if (!value) return undefined;
|
|
222
|
+
if (value instanceof Date) {
|
|
223
|
+
const y = value.getFullYear();
|
|
224
|
+
const m = String(value.getMonth() + 1).padStart(2, '0');
|
|
225
|
+
const d = String(value.getDate()).padStart(2, '0');
|
|
226
|
+
return `${y}-${m}-${d}`;
|
|
227
|
+
}
|
|
228
|
+
if (typeof value === 'string') return value.slice(0, 10);
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
+
import { render } from '@testing-library/angular';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { KjTable } from './table';
|
|
5
|
+
import { kjColumn } from './column-helpers';
|
|
6
|
+
import {
|
|
7
|
+
kjTextFilterFn,
|
|
8
|
+
kjNumberFilterFn,
|
|
9
|
+
kjDateFilterFn,
|
|
10
|
+
kjSetFilterFn,
|
|
11
|
+
kjMultiFilterFn,
|
|
12
|
+
} from './filter-fns';
|
|
13
|
+
import type {
|
|
14
|
+
KjDateFilterModel,
|
|
15
|
+
KjMultiConditionFilterModel,
|
|
16
|
+
KjNumberFilterModel,
|
|
17
|
+
KjSetFilterModel,
|
|
18
|
+
KjTextFilterModel,
|
|
19
|
+
} from './filter-models';
|
|
20
|
+
|
|
21
|
+
interface Row {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
age: number;
|
|
25
|
+
joined: string;
|
|
26
|
+
role: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Component({
|
|
30
|
+
standalone: true,
|
|
31
|
+
imports: [KjTable],
|
|
32
|
+
changeDetection: ChangeDetectionStrategy.Eager,
|
|
33
|
+
template: `<table
|
|
34
|
+
[kjTable]="cols"
|
|
35
|
+
[kjTableData]="data()"
|
|
36
|
+
[kjGetRowId]="getId"
|
|
37
|
+
#t="kjTable"
|
|
38
|
+
></table>`,
|
|
39
|
+
})
|
|
40
|
+
class Host {
|
|
41
|
+
protected readonly cols = [
|
|
42
|
+
kjColumn<Row>({ accessorKey: 'name', header: 'Name', filterFn: kjTextFilterFn }),
|
|
43
|
+
kjColumn<Row>({ accessorKey: 'age', header: 'Age', filterFn: kjNumberFilterFn }),
|
|
44
|
+
kjColumn<Row>({ accessorKey: 'joined', header: 'Joined', filterFn: kjDateFilterFn }),
|
|
45
|
+
kjColumn<Row>({ accessorKey: 'role', header: 'Role', filterFn: kjSetFilterFn }),
|
|
46
|
+
];
|
|
47
|
+
protected readonly data = signal<Row[]>([
|
|
48
|
+
{ id: '1', name: 'Alice', age: 30, joined: '2024-01-15', role: 'admin' },
|
|
49
|
+
{ id: '2', name: 'Bob', age: 25, joined: '2024-06-01', role: 'editor' },
|
|
50
|
+
{ id: '3', name: 'Carol', age: 40, joined: '2025-02-10', role: 'viewer' },
|
|
51
|
+
{ id: '4', name: 'Dave', age: 35, joined: '2025-08-22', role: 'editor' },
|
|
52
|
+
]);
|
|
53
|
+
protected readonly getId = (row: Row) => row.id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function getDir() {
|
|
57
|
+
const r = await render(Host);
|
|
58
|
+
return r.fixture.debugElement.children[0].injector.get(KjTable<Row>);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('text filter model', () => {
|
|
62
|
+
it('contains', async () => {
|
|
63
|
+
const { gridApi } = await getDir();
|
|
64
|
+
gridApi.filter.set('name', {
|
|
65
|
+
filterType: 'text',
|
|
66
|
+
type: 'contains',
|
|
67
|
+
filter: 'al',
|
|
68
|
+
} as KjTextFilterModel);
|
|
69
|
+
expect(gridApi.rows.filteredCount()).toBe(1); // Alice
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('startsWith / endsWith', async () => {
|
|
73
|
+
const { gridApi } = await getDir();
|
|
74
|
+
gridApi.filter.set('name', { filterType: 'text', type: 'startsWith', filter: 'B' });
|
|
75
|
+
expect(gridApi.rows.filteredCount()).toBe(1);
|
|
76
|
+
gridApi.filter.set('name', { filterType: 'text', type: 'endsWith', filter: 'l' });
|
|
77
|
+
expect(gridApi.rows.filteredCount()).toBe(1); // Carol
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('blank / notBlank', async () => {
|
|
81
|
+
const { gridApi } = await getDir();
|
|
82
|
+
gridApi.filter.set('name', { filterType: 'text', type: 'notBlank' });
|
|
83
|
+
expect(gridApi.rows.filteredCount()).toBe(4);
|
|
84
|
+
gridApi.filter.set('name', { filterType: 'text', type: 'blank' });
|
|
85
|
+
expect(gridApi.rows.filteredCount()).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('number filter model', () => {
|
|
90
|
+
it('equals / notEquals', async () => {
|
|
91
|
+
const { gridApi } = await getDir();
|
|
92
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'equals', filter: 30 });
|
|
93
|
+
expect(gridApi.rows.filteredCount()).toBe(1);
|
|
94
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'notEquals', filter: 30 });
|
|
95
|
+
expect(gridApi.rows.filteredCount()).toBe(3);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('inRange', async () => {
|
|
99
|
+
const { gridApi } = await getDir();
|
|
100
|
+
const model: KjNumberFilterModel = {
|
|
101
|
+
filterType: 'number',
|
|
102
|
+
type: 'inRange',
|
|
103
|
+
filter: 28,
|
|
104
|
+
filterTo: 36,
|
|
105
|
+
};
|
|
106
|
+
gridApi.filter.set('age', model);
|
|
107
|
+
expect(gridApi.rows.filteredCount()).toBe(2); // 30, 35
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('greaterThan / lessThanOrEqual', async () => {
|
|
111
|
+
const { gridApi } = await getDir();
|
|
112
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'greaterThan', filter: 30 });
|
|
113
|
+
expect(gridApi.rows.filteredCount()).toBe(2);
|
|
114
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'lessThanOrEqual', filter: 30 });
|
|
115
|
+
expect(gridApi.rows.filteredCount()).toBe(2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('date filter model', () => {
|
|
120
|
+
it('inRange', async () => {
|
|
121
|
+
const { gridApi } = await getDir();
|
|
122
|
+
const model: KjDateFilterModel = {
|
|
123
|
+
filterType: 'date',
|
|
124
|
+
type: 'inRange',
|
|
125
|
+
dateFrom: '2024-01-01',
|
|
126
|
+
dateTo: '2024-12-31',
|
|
127
|
+
};
|
|
128
|
+
gridApi.filter.set('joined', model);
|
|
129
|
+
expect(gridApi.rows.filteredCount()).toBe(2);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('greaterThan / lessThan', async () => {
|
|
133
|
+
const { gridApi } = await getDir();
|
|
134
|
+
gridApi.filter.set('joined', {
|
|
135
|
+
filterType: 'date',
|
|
136
|
+
type: 'greaterThan',
|
|
137
|
+
dateFrom: '2025-01-01',
|
|
138
|
+
});
|
|
139
|
+
expect(gridApi.rows.filteredCount()).toBe(2);
|
|
140
|
+
gridApi.filter.set('joined', { filterType: 'date', type: 'lessThan', dateFrom: '2024-06-01' });
|
|
141
|
+
expect(gridApi.rows.filteredCount()).toBe(1);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('set filter model', () => {
|
|
146
|
+
it('matches any of the listed values', async () => {
|
|
147
|
+
const { gridApi } = await getDir();
|
|
148
|
+
const model: KjSetFilterModel = { filterType: 'set', values: ['admin', 'editor'] };
|
|
149
|
+
gridApi.filter.set('role', model);
|
|
150
|
+
expect(gridApi.rows.filteredCount()).toBe(3);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('empty values = no filter', async () => {
|
|
154
|
+
const { gridApi } = await getDir();
|
|
155
|
+
gridApi.filter.set('role', { filterType: 'set', values: [] });
|
|
156
|
+
expect(gridApi.rows.filteredCount()).toBe(4);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('multi-condition filter (AND/OR)', () => {
|
|
161
|
+
it('AND', async () => {
|
|
162
|
+
const { gridApi } = await getDir();
|
|
163
|
+
const model: KjMultiConditionFilterModel = {
|
|
164
|
+
filterType: 'multi',
|
|
165
|
+
operator: 'AND',
|
|
166
|
+
conditions: [
|
|
167
|
+
{ filterType: 'number', type: 'greaterThan', filter: 28 },
|
|
168
|
+
{ filterType: 'number', type: 'lessThan', filter: 36 },
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
// Apply against age column — kjMultiFilterFn dispatches by inner filterType.
|
|
172
|
+
// Use direct fn invocation here because TanStack columns only have one filterFn at a time.
|
|
173
|
+
const passed = gridApi.rows
|
|
174
|
+
.all()
|
|
175
|
+
.filter((r) =>
|
|
176
|
+
kjMultiFilterFn(
|
|
177
|
+
{ getValue: () => r.age } as unknown as Parameters<typeof kjMultiFilterFn>[0],
|
|
178
|
+
'age',
|
|
179
|
+
model,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
expect(passed.length).toBe(2);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('OR', async () => {
|
|
186
|
+
const { gridApi } = await getDir();
|
|
187
|
+
const model: KjMultiConditionFilterModel = {
|
|
188
|
+
filterType: 'multi',
|
|
189
|
+
operator: 'OR',
|
|
190
|
+
conditions: [
|
|
191
|
+
{ filterType: 'number', type: 'lessThan', filter: 26 },
|
|
192
|
+
{ filterType: 'number', type: 'greaterThan', filter: 38 },
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
const passed = gridApi.rows
|
|
196
|
+
.all()
|
|
197
|
+
.filter((r) =>
|
|
198
|
+
kjMultiFilterFn(
|
|
199
|
+
{ getValue: () => r.age } as unknown as Parameters<typeof kjMultiFilterFn>[0],
|
|
200
|
+
'age',
|
|
201
|
+
model,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
expect(passed.length).toBe(2);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('gridApi.filter.getModel / setModel', () => {
|
|
209
|
+
it('getModel returns structured models keyed by column id', async () => {
|
|
210
|
+
const { gridApi } = await getDir();
|
|
211
|
+
gridApi.filter.set('name', { filterType: 'text', type: 'contains', filter: 'al' });
|
|
212
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'equals', filter: 30 });
|
|
213
|
+
const model = gridApi.filter.getModel();
|
|
214
|
+
expect(model['name']).toEqual({ filterType: 'text', type: 'contains', filter: 'al' });
|
|
215
|
+
expect(model['age']).toEqual({ filterType: 'number', type: 'equals', filter: 30 });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('getModel omits raw (non-model) entries', async () => {
|
|
219
|
+
const { gridApi } = await getDir();
|
|
220
|
+
gridApi.filter.set('name', 'alice'); // legacy raw value
|
|
221
|
+
expect(gridApi.filter.getModel()).toEqual({});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('setModel replaces every column filter at once', async () => {
|
|
225
|
+
const { gridApi } = await getDir();
|
|
226
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'equals', filter: 30 });
|
|
227
|
+
gridApi.filter.setModel({
|
|
228
|
+
role: { filterType: 'set', values: ['editor'] },
|
|
229
|
+
});
|
|
230
|
+
expect(gridApi.filter.getModelFor('age')).toBeUndefined();
|
|
231
|
+
expect(gridApi.filter.getModelFor('role')).toEqual({ filterType: 'set', values: ['editor'] });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('setModel(null) clears everything', async () => {
|
|
235
|
+
const { gridApi } = await getDir();
|
|
236
|
+
gridApi.filter.set('age', { filterType: 'number', type: 'equals', filter: 30 });
|
|
237
|
+
gridApi.filter.setModel(null);
|
|
238
|
+
expect(gridApi.filter.all()).toEqual([]);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured filter models — a near-direct adaptation of AG-Grid's
|
|
3
|
+
* `FilterModel` shape. Each built-in filter writes one of these models
|
|
4
|
+
* to `column.setFilterValue(...)` instead of a raw value. The structured
|
|
5
|
+
* shape buys two things:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Server-side rendering.** `gridApi.filter.getModel()` returns a
|
|
8
|
+
* `KjGridFilterModel` (`Record<columnId, KjFilterModel>`) that
|
|
9
|
+
* serialises cleanly into a JSON payload your backend can translate
|
|
10
|
+
* to SQL/Mongo/etc. without re-parsing free-form values. Pair with
|
|
11
|
+
* `kjTableResource({ loader })` to fan changes back to the server.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Cross-filter introspection.** Code that needs to "what does the
|
|
14
|
+
* user currently have selected on column X" can discriminate on
|
|
15
|
+
* `filterType` and `type` instead of guessing from a `string | unknown`
|
|
16
|
+
* value.
|
|
17
|
+
*
|
|
18
|
+
* The companion `filter-fns` helpers (`kjTextFilterFn` etc.) implement
|
|
19
|
+
* client-side filtering against these models so client-mode tables work
|
|
20
|
+
* out of the box — point `kjColumn({ filterFn: kjTextFilterFn })` at one
|
|
21
|
+
* and TanStack will dispatch row matching for you.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Comparison operators for {@link KjTextFilterModel}. */
|
|
25
|
+
export type KjTextFilterType =
|
|
26
|
+
| 'contains'
|
|
27
|
+
| 'notContains'
|
|
28
|
+
| 'equals'
|
|
29
|
+
| 'notEquals'
|
|
30
|
+
| 'startsWith'
|
|
31
|
+
| 'endsWith'
|
|
32
|
+
| 'blank'
|
|
33
|
+
| 'notBlank';
|
|
34
|
+
|
|
35
|
+
export interface KjTextFilterModel {
|
|
36
|
+
readonly filterType: 'text';
|
|
37
|
+
readonly type: KjTextFilterType;
|
|
38
|
+
/** Match string. Omitted for `blank` / `notBlank`. */
|
|
39
|
+
readonly filter?: string;
|
|
40
|
+
/** Case-insensitive matching. Defaults to `true`. */
|
|
41
|
+
readonly caseSensitive?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Comparison operators for {@link KjNumberFilterModel}. */
|
|
45
|
+
export type KjNumberFilterType =
|
|
46
|
+
| 'equals'
|
|
47
|
+
| 'notEquals'
|
|
48
|
+
| 'greaterThan'
|
|
49
|
+
| 'greaterThanOrEqual'
|
|
50
|
+
| 'lessThan'
|
|
51
|
+
| 'lessThanOrEqual'
|
|
52
|
+
| 'inRange'
|
|
53
|
+
| 'blank'
|
|
54
|
+
| 'notBlank';
|
|
55
|
+
|
|
56
|
+
export interface KjNumberFilterModel {
|
|
57
|
+
readonly filterType: 'number';
|
|
58
|
+
readonly type: KjNumberFilterType;
|
|
59
|
+
/** Primary number. Omitted for `blank` / `notBlank`. */
|
|
60
|
+
readonly filter?: number;
|
|
61
|
+
/** Range upper bound — used by `inRange`. */
|
|
62
|
+
readonly filterTo?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Comparison operators for {@link KjDateFilterModel}. */
|
|
66
|
+
export type KjDateFilterType =
|
|
67
|
+
| 'equals'
|
|
68
|
+
| 'notEquals'
|
|
69
|
+
| 'greaterThan' // after
|
|
70
|
+
| 'lessThan' // before
|
|
71
|
+
| 'inRange'
|
|
72
|
+
| 'blank'
|
|
73
|
+
| 'notBlank';
|
|
74
|
+
|
|
75
|
+
export interface KjDateFilterModel {
|
|
76
|
+
readonly filterType: 'date';
|
|
77
|
+
readonly type: KjDateFilterType;
|
|
78
|
+
/** Primary date — ISO `YYYY-MM-DD`. */
|
|
79
|
+
readonly dateFrom?: string;
|
|
80
|
+
/** Range upper bound — used by `inRange`. */
|
|
81
|
+
readonly dateTo?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set / multi-select filter — the row passes if the cell value is in
|
|
86
|
+
* `values`. An empty `values` array means "no filter" (cleared); pair
|
|
87
|
+
* with `setFilterValue(undefined)` for the cleared state by preference.
|
|
88
|
+
*/
|
|
89
|
+
export interface KjSetFilterModel {
|
|
90
|
+
readonly filterType: 'set';
|
|
91
|
+
readonly values: readonly unknown[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Compound filter — combine two conditions with AND / OR. */
|
|
95
|
+
export interface KjMultiConditionFilterModel<TModel extends KjFilterModel = KjFilterModel> {
|
|
96
|
+
readonly filterType: 'multi';
|
|
97
|
+
readonly operator: 'AND' | 'OR';
|
|
98
|
+
readonly conditions: readonly TModel[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Discriminated union of every built-in filter model. */
|
|
102
|
+
export type KjFilterModel =
|
|
103
|
+
| KjTextFilterModel
|
|
104
|
+
| KjNumberFilterModel
|
|
105
|
+
| KjDateFilterModel
|
|
106
|
+
| KjSetFilterModel
|
|
107
|
+
| KjMultiConditionFilterModel;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Bulk filter snapshot — keyed by column id. Shape mirrors AG-Grid's
|
|
111
|
+
* `api.getFilterModel()` return so SSR loaders that already understand
|
|
112
|
+
* AG-Grid payloads can consume it as-is.
|
|
113
|
+
*/
|
|
114
|
+
export type KjGridFilterModel = Record<string, KjFilterModel>;
|
|
115
|
+
|
|
116
|
+
/** Narrowing helper. */
|
|
117
|
+
export function isKjFilterModel(value: unknown): value is KjFilterModel {
|
|
118
|
+
return (
|
|
119
|
+
!!value &&
|
|
120
|
+
typeof value === 'object' &&
|
|
121
|
+
'filterType' in (value as object) &&
|
|
122
|
+
typeof (value as { filterType: unknown }).filterType === 'string'
|
|
123
|
+
);
|
|
124
|
+
}
|