@kouji-ui/components 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 +29 -0
- package/CHANGELOG.md +192 -0
- package/ng-package.json +7 -0
- package/package.json +43 -0
- package/src/accordion/_examples/accordion.default.example.ts +31 -0
- package/src/accordion/_examples/accordion.disabled.example.ts +38 -0
- package/src/accordion/_examples/accordion.multiple.example.ts +31 -0
- package/src/accordion/_examples/accordion.rich-content.example.ts +49 -0
- package/src/accordion/_examples/index.ts +6 -0
- package/src/accordion/accordion.arrow-nav.example.ts +39 -0
- package/src/accordion/accordion.css +68 -0
- package/src/accordion/accordion.playground.ts +119 -0
- package/src/accordion/accordion.spec.ts +117 -0
- package/src/accordion/accordion.ts +153 -0
- package/src/accordion/accordion.usage.example.ts +38 -0
- package/src/accordion/accordion.value.example.ts +60 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/_examples/alert.banner.example.ts +56 -0
- package/src/alert/_examples/alert.dismissible.example.ts +53 -0
- package/src/alert/_examples/alert.example.ts +40 -0
- package/src/alert/_examples/alert.variants.example.ts +66 -0
- package/src/alert/_examples/alert.with-actions.example.ts +57 -0
- package/src/alert/_examples/index.ts +7 -0
- package/src/alert/alert.css +133 -0
- package/src/alert/alert.playground.ts +111 -0
- package/src/alert/alert.spec.ts +167 -0
- package/src/alert/alert.ts +250 -0
- package/src/alert/alert.usage.example.ts +58 -0
- package/src/alert/index.ts +8 -0
- package/src/avatar/_examples/avatar-group.example.ts +31 -0
- package/src/avatar/_examples/avatar-group.list.example.ts +45 -0
- package/src/avatar/_examples/avatar-group.overflow.example.ts +33 -0
- package/src/avatar/_examples/avatar-group.shapes.example.ts +39 -0
- package/src/avatar/_examples/avatar-group.sizes.example.ts +44 -0
- package/src/avatar/_examples/avatar.default.example.ts +18 -0
- package/src/avatar/_examples/avatar.initials.example.ts +20 -0
- package/src/avatar/_examples/avatar.shapes.example.ts +22 -0
- package/src/avatar/_examples/avatar.sizes.example.ts +26 -0
- package/src/avatar/_examples/avatar.with-image.example.ts +20 -0
- package/src/avatar/_examples/index.ts +12 -0
- package/src/avatar/avatar-group.spec.ts +138 -0
- package/src/avatar/avatar-group.ts +127 -0
- package/src/avatar/avatar-group.usage.example.ts +30 -0
- package/src/avatar/avatar.css +58 -0
- package/src/avatar/avatar.playground.ts +95 -0
- package/src/avatar/avatar.ts +123 -0
- package/src/avatar/index.ts +2 -0
- package/src/badge/_examples/badge.default.example.ts +18 -0
- package/src/badge/_examples/badge.sizes.example.ts +25 -0
- package/src/badge/_examples/badge.variants.example.ts +24 -0
- package/src/badge/_examples/badge.with-dot.example.ts +31 -0
- package/src/badge/_examples/badge.with-icon.example.ts +23 -0
- package/src/badge/_examples/index.ts +7 -0
- package/src/badge/badge.css +59 -0
- package/src/badge/badge.playground.ts +49 -0
- package/src/badge/badge.ts +86 -0
- package/src/badge/badge.usage.example.ts +22 -0
- package/src/badge/index.ts +1 -0
- package/src/breadcrumb/_examples/breadcrumb.custom-separator.example.ts +53 -0
- package/src/breadcrumb/_examples/breadcrumb.example.ts +48 -0
- package/src/breadcrumb/_examples/breadcrumb.long-path.example.ts +58 -0
- package/src/breadcrumb/_examples/breadcrumb.truncated.example.ts +59 -0
- package/src/breadcrumb/_examples/breadcrumb.with-icons.example.ts +66 -0
- package/src/breadcrumb/_examples/index.ts +7 -0
- package/src/breadcrumb/breadcrumb.css +121 -0
- package/src/breadcrumb/breadcrumb.playground.ts +93 -0
- package/src/breadcrumb/breadcrumb.spec.ts +230 -0
- package/src/breadcrumb/breadcrumb.ts +272 -0
- package/src/breadcrumb/breadcrumb.usage.example.ts +45 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/_examples/button.anchor.example.ts +18 -0
- package/src/button/_examples/button.configured.example.ts +43 -0
- package/src/button/_examples/button.disabled.example.ts +25 -0
- package/src/button/_examples/button.example.ts +22 -0
- package/src/button/_examples/button.icon.example.ts +27 -0
- package/src/button/_examples/button.loading.example.ts +31 -0
- package/src/button/_examples/button.pressed.example.ts +24 -0
- package/src/button/_examples/button.sizes.example.ts +26 -0
- package/src/button/_examples/button.usage.example.ts +66 -0
- package/src/button/_examples/button.variants.example.ts +26 -0
- package/src/button/_examples/index.ts +12 -0
- package/src/button/button.css +263 -0
- package/src/button/button.playground.ts +79 -0
- package/src/button/button.spec.ts +100 -0
- package/src/button/button.ts +150 -0
- package/src/button/index.ts +1 -0
- package/src/button-group/_examples/button-group.example.ts +30 -0
- package/src/button-group/_examples/button-group.icon-only.example.ts +30 -0
- package/src/button-group/_examples/button-group.toggle.example.ts +36 -0
- package/src/button-group/_examples/button-group.variants.example.ts +40 -0
- package/src/button-group/_examples/button-group.vertical.example.ts +29 -0
- package/src/button-group/_examples/index.ts +7 -0
- package/src/button-group/button-group.css +80 -0
- package/src/button-group/button-group.playground.ts +94 -0
- package/src/button-group/button-group.spec.ts +119 -0
- package/src/button-group/button-group.ts +130 -0
- package/src/button-group/button-group.usage.example.ts +35 -0
- package/src/button-group/index.ts +1 -0
- package/src/calendar/_examples/calendar.example.ts +33 -0
- package/src/calendar/_examples/index.ts +3 -0
- package/src/calendar/calendar.css +129 -0
- package/src/calendar/calendar.playground.ts +84 -0
- package/src/calendar/calendar.spec.ts +50 -0
- package/src/calendar/calendar.ts +171 -0
- package/src/calendar/calendar.usage.example.ts +33 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/_examples/card.cover.example.ts +53 -0
- package/src/card/_examples/card.example.ts +27 -0
- package/src/card/_examples/card.full.example.ts +51 -0
- package/src/card/_examples/index.ts +5 -0
- package/src/card/card.css +120 -0
- package/src/card/card.playground.ts +116 -0
- package/src/card/card.spec.ts +50 -0
- package/src/card/card.ts +194 -0
- package/src/card/card.usage.example.ts +46 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/_examples/carousel.autoplay.example.ts +69 -0
- package/src/carousel/_examples/carousel.example.ts +60 -0
- package/src/carousel/_examples/carousel.indicators.example.ts +64 -0
- package/src/carousel/_examples/carousel.lazy-load.example.ts +85 -0
- package/src/carousel/_examples/carousel.tabs-pattern.example.ts +61 -0
- package/src/carousel/_examples/index.ts +7 -0
- package/src/carousel/carousel.css +181 -0
- package/src/carousel/carousel.playground.ts +135 -0
- package/src/carousel/carousel.spec.ts +211 -0
- package/src/carousel/carousel.ts +399 -0
- package/src/carousel/carousel.usage.example.ts +46 -0
- package/src/carousel/index.ts +1 -0
- package/src/cascade-select/_examples/cascade-select.data-driven.example.ts +119 -0
- package/src/cascade-select/_examples/cascade-select.disabled.example.ts +74 -0
- package/src/cascade-select/_examples/cascade-select.example.ts +80 -0
- package/src/cascade-select/_examples/cascade-select.field.example.ts +73 -0
- package/src/cascade-select/_examples/cascade-select.path.example.ts +85 -0
- package/src/cascade-select/_examples/index.ts +7 -0
- package/src/cascade-select/cascade-select.css +120 -0
- package/src/cascade-select/cascade-select.playground.ts +133 -0
- package/src/cascade-select/cascade-select.ts +201 -0
- package/src/cascade-select/cascade-select.usage.example.ts +44 -0
- package/src/cascade-select/index.ts +5 -0
- package/src/chat/_examples/chat-bubble.example.ts +71 -0
- package/src/chat/_examples/chat-bubble.grouped.example.ts +69 -0
- package/src/chat/_examples/chat-bubble.sides.example.ts +41 -0
- package/src/chat/_examples/chat-bubble.variants.example.ts +58 -0
- package/src/chat/_examples/chat-bubble.with-state.example.ts +57 -0
- package/src/chat/_examples/index.ts +7 -0
- package/src/chat/chat.css +231 -0
- package/src/chat/chat.playground.ts +171 -0
- package/src/chat/chat.spec.ts +343 -0
- package/src/chat/chat.ts +269 -0
- package/src/chat/chat.usage.example.ts +49 -0
- package/src/chat/index.ts +13 -0
- package/src/checkbox/_examples/checkbox.checked.example.ts +20 -0
- package/src/checkbox/_examples/checkbox.default.example.ts +20 -0
- package/src/checkbox/_examples/checkbox.disabled.example.ts +26 -0
- package/src/checkbox/_examples/checkbox.indeterminate.example.ts +20 -0
- package/src/checkbox/_examples/checkbox.usage.example.ts +62 -0
- package/src/checkbox/_examples/index.ts +7 -0
- package/src/checkbox/checkbox.css +74 -0
- package/src/checkbox/checkbox.playground.ts +68 -0
- package/src/checkbox/checkbox.ts +122 -0
- package/src/checkbox/index.ts +1 -0
- package/src/color-picker/_examples/color-picker.alpha.example.ts +36 -0
- package/src/color-picker/_examples/color-picker.example.ts +34 -0
- package/src/color-picker/_examples/color-picker.hex-input.example.ts +53 -0
- package/src/color-picker/_examples/color-picker.in-form.example.ts +60 -0
- package/src/color-picker/_examples/color-picker.usage.example.ts +58 -0
- package/src/color-picker/_examples/color-picker.with-presets.example.ts +45 -0
- package/src/color-picker/_examples/index.ts +8 -0
- package/src/color-picker/color-picker.css +183 -0
- package/src/color-picker/color-picker.playground.ts +83 -0
- package/src/color-picker/color-picker.spec.ts +94 -0
- package/src/color-picker/color-picker.ts +234 -0
- package/src/color-picker/index.ts +1 -0
- package/src/combobox/_examples/combobox.async.example.ts +85 -0
- package/src/combobox/_examples/combobox.empty-state.example.ts +36 -0
- package/src/combobox/_examples/combobox.example.ts +36 -0
- package/src/combobox/_examples/combobox.free-text.example.ts +42 -0
- package/src/combobox/_examples/combobox.usage.example.ts +51 -0
- package/src/combobox/_examples/combobox.with-icons.example.ts +49 -0
- package/src/combobox/_examples/index.ts +8 -0
- package/src/combobox/combobox.css +101 -0
- package/src/combobox/combobox.playground.ts +90 -0
- package/src/combobox/combobox.spec.ts +111 -0
- package/src/combobox/combobox.ts +219 -0
- package/src/combobox/index.ts +6 -0
- package/src/command-palette/_examples/command-palette.async.example.ts +99 -0
- package/src/command-palette/_examples/command-palette.dialog.example.ts +90 -0
- package/src/command-palette/_examples/command-palette.example.ts +40 -0
- package/src/command-palette/_examples/command-palette.fuzzy.example.ts +54 -0
- package/src/command-palette/_examples/command-palette.groups.example.ts +55 -0
- package/src/command-palette/_examples/command-palette.usage.example.ts +49 -0
- package/src/command-palette/_examples/index.ts +8 -0
- package/src/command-palette/command-palette.css +234 -0
- package/src/command-palette/command-palette.playground.ts +119 -0
- package/src/command-palette/command-palette.ts +448 -0
- package/src/command-palette/index.ts +10 -0
- package/src/confirm-popup/_examples/confirm-popup.destructive.example.ts +72 -0
- package/src/confirm-popup/_examples/confirm-popup.example.ts +82 -0
- package/src/confirm-popup/_examples/confirm-popup.placement.example.ts +97 -0
- package/src/confirm-popup/_examples/confirm-popup.usage.example.ts +94 -0
- package/src/confirm-popup/_examples/confirm-popup.with-message.example.ts +76 -0
- package/src/confirm-popup/_examples/index.ts +7 -0
- package/src/confirm-popup/confirm-popup.css +47 -0
- package/src/confirm-popup/confirm-popup.playground.ts +109 -0
- package/src/confirm-popup/confirm-popup.spec.ts +209 -0
- package/src/confirm-popup/confirm-popup.ts +279 -0
- package/src/confirm-popup/index.ts +1 -0
- package/src/date-picker/_examples/date-picker.disabled-dates.example.ts +44 -0
- package/src/date-picker/_examples/date-picker.example.ts +38 -0
- package/src/date-picker/_examples/date-picker.locale.example.ts +50 -0
- package/src/date-picker/_examples/date-picker.readonly.example.ts +32 -0
- package/src/date-picker/_examples/date-picker.usage.example.ts +60 -0
- package/src/date-picker/_examples/date-picker.with-min-max.example.ts +47 -0
- package/src/date-picker/_examples/index.ts +8 -0
- package/src/date-picker/date-picker.css +61 -0
- package/src/date-picker/date-picker.playground.ts +79 -0
- package/src/date-picker/date-picker.spec.ts +71 -0
- package/src/date-picker/date-picker.ts +185 -0
- package/src/date-picker/index.ts +1 -0
- package/src/dialog/_examples/dialog.confirmation.example.ts +30 -0
- package/src/dialog/_examples/dialog.default.example.ts +30 -0
- package/src/dialog/_examples/dialog.nested.example.ts +86 -0
- package/src/dialog/_examples/dialog.scrollable.example.ts +30 -0
- package/src/dialog/_examples/dialog.usage.example.ts +57 -0
- package/src/dialog/_examples/dialog.with-form.example.ts +48 -0
- package/src/dialog/_examples/index.ts +8 -0
- package/src/dialog/dialog.css +56 -0
- package/src/dialog/dialog.playground.ts +136 -0
- package/src/dialog/dialog.ts +82 -0
- package/src/dialog/index.ts +1 -0
- package/src/divider/_examples/divider.example.ts +27 -0
- package/src/divider/_examples/divider.sizes.example.ts +33 -0
- package/src/divider/_examples/divider.structural.example.ts +55 -0
- package/src/divider/_examples/divider.usage.example.ts +48 -0
- package/src/divider/_examples/divider.variants.example.ts +33 -0
- package/src/divider/_examples/divider.vertical.example.ts +38 -0
- package/src/divider/_examples/divider.with-content.example.ts +38 -0
- package/src/divider/_examples/index.ts +9 -0
- package/src/divider/divider.css +93 -0
- package/src/divider/divider.playground.ts +97 -0
- package/src/divider/divider.spec.ts +207 -0
- package/src/divider/divider.ts +220 -0
- package/src/divider/index.ts +1 -0
- package/src/drawer/_examples/drawer.example.ts +30 -0
- package/src/drawer/_examples/drawer.modal-vs-non-modal.example.ts +27 -0
- package/src/drawer/_examples/drawer.scrollable.example.ts +30 -0
- package/src/drawer/_examples/drawer.sides.example.ts +32 -0
- package/src/drawer/_examples/drawer.usage.example.ts +56 -0
- package/src/drawer/_examples/drawer.with-form.example.ts +48 -0
- package/src/drawer/_examples/index.ts +8 -0
- package/src/drawer/drawer.css +130 -0
- package/src/drawer/drawer.playground.ts +107 -0
- package/src/drawer/drawer.spec.ts +216 -0
- package/src/drawer/drawer.ts +85 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/_examples/dropdown-menu.disabled.example.ts +19 -0
- package/src/dropdown-menu/_examples/dropdown-menu.example.ts +20 -0
- package/src/dropdown-menu/_examples/dropdown-menu.shortcuts.example.ts +31 -0
- package/src/dropdown-menu/_examples/dropdown-menu.sides.example.ts +18 -0
- package/src/dropdown-menu/_examples/dropdown-menu.usage.example.ts +56 -0
- package/src/dropdown-menu/_examples/dropdown-menu.with-icons.example.ts +31 -0
- package/src/dropdown-menu/_examples/dropdown-menu.with-separator.example.ts +41 -0
- package/src/dropdown-menu/_examples/index.ts +9 -0
- package/src/dropdown-menu/dropdown-menu.css +108 -0
- package/src/dropdown-menu/dropdown-menu.playground.ts +109 -0
- package/src/dropdown-menu/dropdown-menu.spec.ts +5 -0
- package/src/dropdown-menu/dropdown-menu.ts +128 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty-state/_examples/empty-state.error.example.ts +63 -0
- package/src/empty-state/_examples/empty-state.example.ts +59 -0
- package/src/empty-state/_examples/empty-state.search.example.ts +82 -0
- package/src/empty-state/_examples/empty-state.sizes.example.ts +76 -0
- package/src/empty-state/_examples/empty-state.usage.example.ts +66 -0
- package/src/empty-state/_examples/empty-state.with-actions.example.ts +68 -0
- package/src/empty-state/_examples/index.ts +8 -0
- package/src/empty-state/empty-state.css +114 -0
- package/src/empty-state/empty-state.playground.ts +87 -0
- package/src/empty-state/empty-state.spec.ts +211 -0
- package/src/empty-state/empty-state.ts +276 -0
- package/src/empty-state/index.ts +1 -0
- package/src/example-components.ts +396 -0
- package/src/field/_examples/field.disabled.example.ts +49 -0
- package/src/field/_examples/field.example.ts +51 -0
- package/src/field/_examples/field.required.example.ts +46 -0
- package/src/field/_examples/field.with-error.example.ts +73 -0
- package/src/field/_examples/field.with-prefix-suffix.example.ts +63 -0
- package/src/field/_examples/index.ts +7 -0
- package/src/field/field.css +138 -0
- package/src/field/field.playground.ts +108 -0
- package/src/field/field.spec.ts +132 -0
- package/src/field/field.ts +232 -0
- package/src/field/field.usage.example.ts +63 -0
- package/src/field/index.ts +7 -0
- package/src/file-upload/_examples/file-upload.dropzone.example.ts +32 -0
- package/src/file-upload/_examples/file-upload.example.ts +29 -0
- package/src/file-upload/_examples/file-upload.image-preview.example.ts +31 -0
- package/src/file-upload/_examples/file-upload.multiple.example.ts +23 -0
- package/src/file-upload/_examples/file-upload.with-progress.example.ts +59 -0
- package/src/file-upload/_examples/index.ts +7 -0
- package/src/file-upload/file-upload.css +207 -0
- package/src/file-upload/file-upload.playground.ts +84 -0
- package/src/file-upload/file-upload.spec.ts +94 -0
- package/src/file-upload/file-upload.ts +301 -0
- package/src/file-upload/file-upload.usage.example.ts +45 -0
- package/src/file-upload/index.ts +1 -0
- package/src/form/_examples/form.async-submit.example.ts +72 -0
- package/src/form/_examples/form.compound.example.ts +93 -0
- package/src/form/_examples/form.example.ts +70 -0
- package/src/form/_examples/form.validation.example.ts +67 -0
- package/src/form/_examples/index.ts +6 -0
- package/src/form/form.css +63 -0
- package/src/form/form.playground.ts +145 -0
- package/src/form/form.spec.ts +96 -0
- package/src/form/form.ts +216 -0
- package/src/form/form.usage.example.ts +63 -0
- package/src/form/index.ts +5 -0
- package/src/icon/_examples/icon.gallery.example.ts +199 -0
- package/src/icon/_examples/icon.usage.example.ts +46 -0
- package/src/icon/_examples/index.ts +4 -0
- package/src/icon/icon.playground.ts +63 -0
- package/src/icon/index.ts +5 -0
- package/src/icon/lucide/icon-names.generated.ts +1708 -0
- package/src/icon/lucide/icon-names.ts +18 -0
- package/src/icon/lucide/index.ts +2 -0
- package/src/icon/lucide/provide-lucide-icons.ts +96 -0
- package/src/input/_examples/index.ts +4 -0
- package/src/input/_examples/input.color.example.ts +33 -0
- package/src/input/_examples/input.example.ts +22 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.css +70 -0
- package/src/input/input.playground.ts +79 -0
- package/src/input/input.spec.ts +98 -0
- package/src/input/input.ts +158 -0
- package/src/input/input.usage.example.ts +40 -0
- package/src/input-group/_examples/index.ts +7 -0
- package/src/input-group/_examples/input-group.button.example.ts +43 -0
- package/src/input-group/_examples/input-group.example.ts +39 -0
- package/src/input-group/_examples/input-group.icon.example.ts +49 -0
- package/src/input-group/_examples/input-group.url.example.ts +36 -0
- package/src/input-group/_examples/input-group.variants.example.ts +39 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.css +125 -0
- package/src/input-group/input-group.playground.ts +90 -0
- package/src/input-group/input-group.ts +142 -0
- package/src/input-group/input-group.usage.example.ts +47 -0
- package/src/input-mask/_examples/index.ts +8 -0
- package/src/input-mask/_examples/input-mask.credit-card.example.ts +90 -0
- package/src/input-mask/_examples/input-mask.custom-tokens.example.ts +87 -0
- package/src/input-mask/_examples/input-mask.date.example.ts +61 -0
- package/src/input-mask/_examples/input-mask.example.ts +61 -0
- package/src/input-mask/_examples/input-mask.usage.example.ts +44 -0
- package/src/input-mask/_examples/input-mask.validation.example.ts +80 -0
- package/src/input-mask/index.ts +9 -0
- package/src/input-mask/input-mask.css +39 -0
- package/src/input-mask/input-mask.playground.ts +79 -0
- package/src/input-mask/input-mask.ts +130 -0
- package/src/input-otp/_examples/index.ts +8 -0
- package/src/input-otp/_examples/input-otp.autosubmit.example.ts +71 -0
- package/src/input-otp/_examples/input-otp.example.ts +47 -0
- package/src/input-otp/_examples/input-otp.lengths.example.ts +67 -0
- package/src/input-otp/_examples/input-otp.masked.example.ts +46 -0
- package/src/input-otp/_examples/input-otp.reactive.example.ts +58 -0
- package/src/input-otp/_examples/input-otp.separator.example.ts +49 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.css +87 -0
- package/src/input-otp/input-otp.playground.ts +107 -0
- package/src/input-otp/input-otp.ts +244 -0
- package/src/input-otp/input-otp.usage.example.ts +45 -0
- package/src/kbd/_examples/index.ts +6 -0
- package/src/kbd/_examples/kbd.combo.example.ts +24 -0
- package/src/kbd/_examples/kbd.example.ts +22 -0
- package/src/kbd/_examples/kbd.in-tooltip.example.ts +43 -0
- package/src/kbd/_examples/kbd.sizes.example.ts +31 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.css +67 -0
- package/src/kbd/kbd.playground.ts +51 -0
- package/src/kbd/kbd.spec.ts +123 -0
- package/src/kbd/kbd.ts +123 -0
- package/src/kbd/kbd.usage.example.ts +38 -0
- package/src/link/_examples/index.ts +8 -0
- package/src/link/_examples/link.disabled.example.ts +23 -0
- package/src/link/_examples/link.example.ts +26 -0
- package/src/link/_examples/link.external.example.ts +33 -0
- package/src/link/_examples/link.in-prose.example.ts +36 -0
- package/src/link/_examples/link.sizes.example.ts +28 -0
- package/src/link/_examples/link.variants.example.ts +25 -0
- package/src/link/index.ts +1 -0
- package/src/link/link.css +104 -0
- package/src/link/link.playground.ts +98 -0
- package/src/link/link.spec.ts +146 -0
- package/src/link/link.ts +133 -0
- package/src/link/link.usage.example.ts +37 -0
- package/src/list/_examples/index.ts +7 -0
- package/src/list/_examples/list.divided.example.ts +32 -0
- package/src/list/_examples/list.example.ts +31 -0
- package/src/list/_examples/list.interactive.example.ts +51 -0
- package/src/list/_examples/list.nav.example.ts +44 -0
- package/src/list/_examples/list.with-icons.example.ts +97 -0
- package/src/list/index.ts +1 -0
- package/src/list/list.css +166 -0
- package/src/list/list.playground.ts +90 -0
- package/src/list/list.spec.ts +121 -0
- package/src/list/list.ts +227 -0
- package/src/list/list.usage.example.ts +45 -0
- package/src/menubar/_examples/index.ts +6 -0
- package/src/menubar/_examples/menubar.disabled-item.example.ts +23 -0
- package/src/menubar/_examples/menubar.example.ts +22 -0
- package/src/menubar/_examples/menubar.with-shortcuts.example.ts +33 -0
- package/src/menubar/_examples/menubar.with-submenu.example.ts +76 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.css +62 -0
- package/src/menubar/menubar.playground.ts +96 -0
- package/src/menubar/menubar.ts +160 -0
- package/src/menubar/menubar.usage.example.ts +33 -0
- package/src/number-input/_examples/index.ts +7 -0
- package/src/number-input/_examples/number-input.currency.example.ts +35 -0
- package/src/number-input/_examples/number-input.decimal.example.ts +34 -0
- package/src/number-input/_examples/number-input.example.ts +24 -0
- package/src/number-input/_examples/number-input.min-max.example.ts +33 -0
- package/src/number-input/_examples/number-input.with-stepper.example.ts +45 -0
- package/src/number-input/index.ts +1 -0
- package/src/number-input/number-input.css +115 -0
- package/src/number-input/number-input.playground.ts +92 -0
- package/src/number-input/number-input.spec.ts +102 -0
- package/src/number-input/number-input.ts +212 -0
- package/src/number-input/number-input.usage.example.ts +73 -0
- package/src/overlay/overlay.css +32 -0
- package/src/overlay-badge/_examples/index.ts +7 -0
- package/src/overlay-badge/_examples/overlay-badge.described.example.ts +36 -0
- package/src/overlay-badge/_examples/overlay-badge.dot.example.ts +28 -0
- package/src/overlay-badge/_examples/overlay-badge.example.ts +34 -0
- package/src/overlay-badge/_examples/overlay-badge.on-avatar.example.ts +38 -0
- package/src/overlay-badge/_examples/overlay-badge.positions.example.ts +39 -0
- package/src/overlay-badge/index.ts +1 -0
- package/src/overlay-badge/overlay-badge.css +118 -0
- package/src/overlay-badge/overlay-badge.playground.ts +92 -0
- package/src/overlay-badge/overlay-badge.spec.ts +174 -0
- package/src/overlay-badge/overlay-badge.ts +232 -0
- package/src/overlay-badge/overlay-badge.usage.example.ts +44 -0
- package/src/pagination/_examples/index.ts +7 -0
- package/src/pagination/_examples/pagination.boundaries.example.ts +56 -0
- package/src/pagination/_examples/pagination.compact.example.ts +42 -0
- package/src/pagination/_examples/pagination.example.ts +55 -0
- package/src/pagination/_examples/pagination.large.example.ts +54 -0
- package/src/pagination/_examples/pagination.with-info.example.ts +49 -0
- package/src/pagination/index.ts +1 -0
- package/src/pagination/pagination.css +146 -0
- package/src/pagination/pagination.playground.ts +107 -0
- package/src/pagination/pagination.spec.ts +202 -0
- package/src/pagination/pagination.ts +432 -0
- package/src/pagination/pagination.usage.example.ts +50 -0
- package/src/password-input/_examples/index.ts +6 -0
- package/src/password-input/_examples/password-input.example.ts +24 -0
- package/src/password-input/_examples/password-input.strength.example.ts +45 -0
- package/src/password-input/_examples/password-input.toggle.example.ts +43 -0
- package/src/password-input/_examples/password-input.with-rules.example.ts +78 -0
- package/src/password-input/index.ts +1 -0
- package/src/password-input/password-input.css +155 -0
- package/src/password-input/password-input.playground.ts +81 -0
- package/src/password-input/password-input.spec.ts +129 -0
- package/src/password-input/password-input.ts +254 -0
- package/src/password-input/password-input.usage.example.ts +55 -0
- package/src/playground-types.ts +47 -0
- package/src/popover/_examples/index.ts +7 -0
- package/src/popover/_examples/popover.cancellable.example.ts +20 -0
- package/src/popover/_examples/popover.example.ts +20 -0
- package/src/popover/_examples/popover.modal.example.ts +22 -0
- package/src/popover/_examples/popover.sides.example.ts +37 -0
- package/src/popover/_examples/popover.with-form.example.ts +38 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.css +71 -0
- package/src/popover/popover.playground.ts +86 -0
- package/src/popover/popover.spec.ts +5 -0
- package/src/popover/popover.ts +94 -0
- package/src/popover/popover.usage.example.ts +48 -0
- package/src/progress-bar/_examples/index.ts +7 -0
- package/src/progress-bar/_examples/progress-bar.example.ts +22 -0
- package/src/progress-bar/_examples/progress-bar.indeterminate.example.ts +23 -0
- package/src/progress-bar/_examples/progress-bar.sizes.example.ts +29 -0
- package/src/progress-bar/_examples/progress-bar.variants.example.ts +30 -0
- package/src/progress-bar/_examples/progress-bar.with-label.example.ts +30 -0
- package/src/progress-bar/index.ts +1 -0
- package/src/progress-bar/progress-bar.css +113 -0
- package/src/progress-bar/progress-bar.playground.ts +87 -0
- package/src/progress-bar/progress-bar.spec.ts +233 -0
- package/src/progress-bar/progress-bar.ts +155 -0
- package/src/progress-bar/progress-bar.usage.example.ts +52 -0
- package/src/public-api.ts +61 -0
- package/src/radio/_examples/index.ts +6 -0
- package/src/radio/_examples/radio.default.example.ts +26 -0
- package/src/radio/_examples/radio.disabled.example.ts +26 -0
- package/src/radio/_examples/radio.group.example.ts +27 -0
- package/src/radio/_examples/radio.inline.example.ts +26 -0
- package/src/radio/index.ts +1 -0
- package/src/radio/radio.css +58 -0
- package/src/radio/radio.playground.ts +90 -0
- package/src/radio/radio.ts +143 -0
- package/src/radio/radio.usage.example.ts +54 -0
- package/src/select/_examples/index.ts +6 -0
- package/src/select/_examples/select.default.example.ts +26 -0
- package/src/select/_examples/select.disabled.example.ts +24 -0
- package/src/select/_examples/select.grouped.example.ts +37 -0
- package/src/select/_examples/select.placeholder.example.ts +26 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.css +65 -0
- package/src/select/select.playground.ts +81 -0
- package/src/select/select.ts +176 -0
- package/src/select/select.usage.example.ts +64 -0
- package/src/skeleton/_examples/index.ts +6 -0
- package/src/skeleton/_examples/skeleton.animations.example.ts +52 -0
- package/src/skeleton/_examples/skeleton.card.example.ts +68 -0
- package/src/skeleton/_examples/skeleton.example.ts +24 -0
- package/src/skeleton/_examples/skeleton.shapes.example.ts +55 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.css +111 -0
- package/src/skeleton/skeleton.playground.ts +80 -0
- package/src/skeleton/skeleton.spec.ts +126 -0
- package/src/skeleton/skeleton.ts +155 -0
- package/src/skeleton/skeleton.usage.example.ts +49 -0
- package/src/slider/_examples/index.ts +7 -0
- package/src/slider/_examples/slider.example.ts +41 -0
- package/src/slider/_examples/slider.formatted.example.ts +53 -0
- package/src/slider/_examples/slider.range.example.ts +48 -0
- package/src/slider/_examples/slider.vertical.example.ts +48 -0
- package/src/slider/_examples/slider.with-marks.example.ts +46 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.css +172 -0
- package/src/slider/slider.playground.ts +88 -0
- package/src/slider/slider.spec.ts +139 -0
- package/src/slider/slider.ts +246 -0
- package/src/slider/slider.usage.example.ts +68 -0
- package/src/speed-dial/_examples/index.ts +7 -0
- package/src/speed-dial/_examples/speed-dial.directions.example.ts +85 -0
- package/src/speed-dial/_examples/speed-dial.disabled.example.ts +71 -0
- package/src/speed-dial/_examples/speed-dial.example.ts +48 -0
- package/src/speed-dial/_examples/speed-dial.usage.example.ts +59 -0
- package/src/speed-dial/_examples/speed-dial.with-tooltips.example.ts +65 -0
- package/src/speed-dial/index.ts +1 -0
- package/src/speed-dial/speed-dial.css +137 -0
- package/src/speed-dial/speed-dial.playground.ts +137 -0
- package/src/speed-dial/speed-dial.spec.ts +191 -0
- package/src/speed-dial/speed-dial.ts +257 -0
- package/src/spinner/_examples/index.ts +8 -0
- package/src/spinner/_examples/spinner.animations.example.ts +54 -0
- package/src/spinner/_examples/spinner.example.ts +26 -0
- package/src/spinner/_examples/spinner.in-button.example.ts +51 -0
- package/src/spinner/_examples/spinner.sizes.example.ts +31 -0
- package/src/spinner/_examples/spinner.usage.example.ts +45 -0
- package/src/spinner/_examples/spinner.variants.example.ts +34 -0
- package/src/spinner/index.ts +1 -0
- package/src/spinner/spinner.css +161 -0
- package/src/spinner/spinner.playground.ts +68 -0
- package/src/spinner/spinner.spec.ts +184 -0
- package/src/spinner/spinner.ts +185 -0
- package/src/stepper/_examples/index.ts +8 -0
- package/src/stepper/_examples/stepper.example.ts +62 -0
- package/src/stepper/_examples/stepper.non-linear.example.ts +63 -0
- package/src/stepper/_examples/stepper.optional-step.example.ts +62 -0
- package/src/stepper/_examples/stepper.usage.example.ts +73 -0
- package/src/stepper/_examples/stepper.vertical.example.ts +66 -0
- package/src/stepper/_examples/stepper.with-error.example.ts +63 -0
- package/src/stepper/index.ts +1 -0
- package/src/stepper/stepper.css +147 -0
- package/src/stepper/stepper.playground.ts +88 -0
- package/src/stepper/stepper.spec.ts +200 -0
- package/src/stepper/stepper.ts +242 -0
- package/src/table/_examples/index.ts +23 -0
- package/src/table/_examples/table.column-pinning.example.ts +55 -0
- package/src/table/_examples/table.column-resize.example.ts +49 -0
- package/src/table/_examples/table.column-visibility.example.ts +52 -0
- package/src/table/_examples/table.density.example.ts +71 -0
- package/src/table/_examples/table.editable.example.ts +85 -0
- package/src/table/_examples/table.empty-and-error.example.ts +112 -0
- package/src/table/_examples/table.example.ts +45 -0
- package/src/table/_examples/table.export.example.ts +82 -0
- package/src/table/_examples/table.filterable.example.ts +67 -0
- package/src/table/_examples/table.grouping.example.ts +63 -0
- package/src/table/_examples/table.keyboard.example.ts +115 -0
- package/src/table/_examples/table.master-detail.example.ts +102 -0
- package/src/table/_examples/table.persistence.example.ts +85 -0
- package/src/table/_examples/table.playground.ts +154 -0
- package/src/table/_examples/table.row-pinning.example.ts +71 -0
- package/src/table/_examples/table.selection.example.ts +67 -0
- package/src/table/_examples/table.server-mode.example.ts +120 -0
- package/src/table/_examples/table.sortable.example.ts +49 -0
- package/src/table/_examples/table.theming.example.ts +64 -0
- package/src/table/_examples/table.usage.example.ts +90 -0
- package/src/table/_examples/table.virtualized.example.ts +122 -0
- package/src/table/index.ts +11 -0
- package/src/table/table-cell-editor-outlet.ts +111 -0
- package/src/table/table-editors/boolean-editor.ts +71 -0
- package/src/table/table-editors/date-editor.ts +117 -0
- package/src/table/table-editors/editors.spec.ts +147 -0
- package/src/table/table-editors/index.ts +41 -0
- package/src/table/table-editors/number-editor.ts +89 -0
- package/src/table/table-editors/select-editor.ts +107 -0
- package/src/table/table-editors/text-editor.ts +82 -0
- package/src/table/table-export.spec.ts +33 -0
- package/src/table/table-export.ts +53 -0
- package/src/table/table-filter-outlet.ts +137 -0
- package/src/table/table-filters/date-filter.ts +122 -0
- package/src/table/table-filters/filters.css +53 -0
- package/src/table/table-filters/filters.spec.ts +277 -0
- package/src/table/table-filters/index.ts +11 -0
- package/src/table/table-filters/number-filter.ts +232 -0
- package/src/table/table-filters/select-filter.ts +129 -0
- package/src/table/table-filters/text-filter.ts +110 -0
- package/src/table/table-pagination.spec.ts +118 -0
- package/src/table/table-pagination.ts +191 -0
- package/src/table/table-side-panel.spec.ts +170 -0
- package/src/table/table-side-panel.ts +263 -0
- package/src/table/table-status-bar.spec.ts +78 -0
- package/src/table/table-status-bar.ts +70 -0
- package/src/table/table-toolbar.spec.ts +180 -0
- package/src/table/table-toolbar.ts +244 -0
- package/src/table/table-virtual.spec.ts +103 -0
- package/src/table/table-virtual.ts +180 -0
- package/src/table/table.css +468 -0
- package/src/table/table.spec.ts +175 -0
- package/src/table/table.ts +1009 -0
- package/src/tabs/_examples/index.ts +8 -0
- package/src/tabs/_examples/tabs.controlled.example.ts +59 -0
- package/src/tabs/_examples/tabs.disabled.example.ts +34 -0
- package/src/tabs/_examples/tabs.example.ts +34 -0
- package/src/tabs/_examples/tabs.manual-activation.example.ts +34 -0
- package/src/tabs/_examples/tabs.usage.example.ts +43 -0
- package/src/tabs/_examples/tabs.vertical.example.ts +33 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.css +80 -0
- package/src/tabs/tabs.playground.ts +88 -0
- package/src/tabs/tabs.spec.ts +135 -0
- package/src/tabs/tabs.ts +204 -0
- package/src/tag/_examples/index.ts +9 -0
- package/src/tag/_examples/tag.example.ts +22 -0
- package/src/tag/_examples/tag.list.example.ts +38 -0
- package/src/tag/_examples/tag.removable.example.ts +39 -0
- package/src/tag/_examples/tag.selectable.example.ts +33 -0
- package/src/tag/_examples/tag.sizes.example.ts +25 -0
- package/src/tag/_examples/tag.usage.example.ts +62 -0
- package/src/tag/_examples/tag.variants.example.ts +29 -0
- package/src/tag/index.ts +1 -0
- package/src/tag/tag.css +167 -0
- package/src/tag/tag.playground.ts +63 -0
- package/src/tag/tag.spec.ts +153 -0
- package/src/tag/tag.ts +186 -0
- package/src/test-setup.ts +5 -0
- package/src/textarea/_examples/index.ts +8 -0
- package/src/textarea/_examples/textarea.autoresize.example.ts +37 -0
- package/src/textarea/_examples/textarea.character-count.example.ts +34 -0
- package/src/textarea/_examples/textarea.example.ts +22 -0
- package/src/textarea/_examples/textarea.no-resize.example.ts +28 -0
- package/src/textarea/_examples/textarea.usage.example.ts +50 -0
- package/src/textarea/_examples/textarea.with-field.example.ts +66 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.css +117 -0
- package/src/textarea/textarea.playground.ts +70 -0
- package/src/textarea/textarea.spec.ts +161 -0
- package/src/textarea/textarea.ts +339 -0
- package/src/time-picker/_examples/index.ts +8 -0
- package/src/time-picker/_examples/time-picker.12-hour.example.ts +24 -0
- package/src/time-picker/_examples/time-picker.example.ts +24 -0
- package/src/time-picker/_examples/time-picker.formatted.example.ts +32 -0
- package/src/time-picker/_examples/time-picker.range.example.ts +54 -0
- package/src/time-picker/_examples/time-picker.usage.example.ts +53 -0
- package/src/time-picker/_examples/time-picker.with-seconds.example.ts +24 -0
- package/src/time-picker/index.ts +1 -0
- package/src/time-picker/time-picker.css +126 -0
- package/src/time-picker/time-picker.playground.ts +72 -0
- package/src/time-picker/time-picker.spec.ts +77 -0
- package/src/time-picker/time-picker.ts +207 -0
- package/src/toast/_examples/index.ts +7 -0
- package/src/toast/_examples/toast.default.example.ts +37 -0
- package/src/toast/_examples/toast.dismissible.example.ts +38 -0
- package/src/toast/_examples/toast.usage.example.ts +57 -0
- package/src/toast/_examples/toast.variants.example.ts +43 -0
- package/src/toast/_examples/toast.with-action.example.ts +46 -0
- package/src/toast/index.ts +1 -0
- package/src/toast/toast.css +115 -0
- package/src/toast/toast.playground.ts +61 -0
- package/src/toast/toast.ts +168 -0
- package/src/toggle/_examples/index.ts +7 -0
- package/src/toggle/_examples/toggle.checked.example.ts +20 -0
- package/src/toggle/_examples/toggle.default.example.ts +20 -0
- package/src/toggle/_examples/toggle.disabled.example.ts +26 -0
- package/src/toggle/_examples/toggle.usage.example.ts +51 -0
- package/src/toggle/_examples/toggle.with-label.example.ts +30 -0
- package/src/toggle/index.ts +1 -0
- package/src/toggle/toggle.css +123 -0
- package/src/toggle/toggle.playground.ts +65 -0
- package/src/toggle/toggle.ts +113 -0
- package/src/tooltip/_examples/index.ts +9 -0
- package/src/tooltip/_examples/tooltip.delays.example.ts +16 -0
- package/src/tooltip/_examples/tooltip.disabled.example.ts +16 -0
- package/src/tooltip/_examples/tooltip.example.ts +16 -0
- package/src/tooltip/_examples/tooltip.group.example.ts +20 -0
- package/src/tooltip/_examples/tooltip.rich.example.ts +19 -0
- package/src/tooltip/_examples/tooltip.sides.example.ts +18 -0
- package/src/tooltip/_examples/tooltip.usage.example.ts +50 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.css +61 -0
- package/src/tooltip/tooltip.playground.ts +79 -0
- package/src/tooltip/tooltip.spec.ts +5 -0
- package/src/tooltip/tooltip.ts +102 -0
- package/src/tree-select/_examples/index.ts +8 -0
- package/src/tree-select/_examples/tree-select.disabled.example.ts +106 -0
- package/src/tree-select/_examples/tree-select.example.ts +65 -0
- package/src/tree-select/_examples/tree-select.expanded.example.ts +72 -0
- package/src/tree-select/_examples/tree-select.field.example.ts +77 -0
- package/src/tree-select/_examples/tree-select.multi.example.ts +73 -0
- package/src/tree-select/_examples/tree-select.usage.example.ts +85 -0
- package/src/tree-select/index.ts +1 -0
- package/src/tree-select/tree-select.css +220 -0
- package/src/tree-select/tree-select.playground.ts +83 -0
- package/src/tree-select/tree-select.ts +348 -0
- package/src/typography/_examples/index.ts +9 -0
- package/src/typography/_examples/typography.blockquote.example.ts +34 -0
- package/src/typography/_examples/typography.code.example.ts +32 -0
- package/src/typography/_examples/typography.example.ts +42 -0
- package/src/typography/_examples/typography.lead.example.ts +37 -0
- package/src/typography/_examples/typography.muted.example.ts +27 -0
- package/src/typography/_examples/typography.truncate.example.ts +41 -0
- package/src/typography/_examples/typography.usage.example.ts +44 -0
- package/src/typography/index.ts +8 -0
- package/src/typography/typography.playground.ts +101 -0
- package/src/typography/typography.spec.ts +70 -0
- package/src/typography/typography.ts +105 -0
- package/tsconfig.lib.json +25 -0
- package/tsconfig.lib.prod.json +17 -0
- package/tsconfig.spec.json +16 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
TemplateRef,
|
|
5
|
+
Type,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
computed,
|
|
8
|
+
effect,
|
|
9
|
+
inject,
|
|
10
|
+
input,
|
|
11
|
+
model,
|
|
12
|
+
output,
|
|
13
|
+
signal,
|
|
14
|
+
untracked,
|
|
15
|
+
contentChild,
|
|
16
|
+
} from '@angular/core';
|
|
17
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
18
|
+
import type { ResourceRef } from '@angular/core';
|
|
19
|
+
import {
|
|
20
|
+
KjTable,
|
|
21
|
+
KjTableHeader,
|
|
22
|
+
KjTableRow,
|
|
23
|
+
KjTableCell,
|
|
24
|
+
KjTableKeyboardNav,
|
|
25
|
+
KjIconDirective,
|
|
26
|
+
KJ_TABLE_STORAGE,
|
|
27
|
+
type KjColumnMeta,
|
|
28
|
+
type KjResourceResult,
|
|
29
|
+
type KjStorageAdapter,
|
|
30
|
+
type KjTableState,
|
|
31
|
+
} from '@kouji-ui/core';
|
|
32
|
+
import type { Cell, Column, Header, Row, RowData } from '@tanstack/angular-table';
|
|
33
|
+
import { KjTableVirtual } from './table-virtual';
|
|
34
|
+
import { KjCheckboxComponent } from '../checkbox/checkbox';
|
|
35
|
+
import {
|
|
36
|
+
KjBooleanEditor,
|
|
37
|
+
KjDateEditor,
|
|
38
|
+
KjNumberEditor,
|
|
39
|
+
KjSelectEditor,
|
|
40
|
+
KjTextEditor,
|
|
41
|
+
} from './table-editors';
|
|
42
|
+
import {
|
|
43
|
+
KjDateFilter,
|
|
44
|
+
KjNumberFilter,
|
|
45
|
+
KjSelectFilter,
|
|
46
|
+
KjTextFilter,
|
|
47
|
+
} from './table-filters';
|
|
48
|
+
import { KjCellEditorOutlet } from './table-cell-editor-outlet';
|
|
49
|
+
import { KjFilterCellOutlet } from './table-filter-outlet';
|
|
50
|
+
|
|
51
|
+
/** Cell-edit payload emitted by `(cellEdit)`. */
|
|
52
|
+
export interface KjCellEditEvent<TData> {
|
|
53
|
+
readonly row: TData;
|
|
54
|
+
readonly columnId: string;
|
|
55
|
+
readonly oldValue: unknown;
|
|
56
|
+
readonly newValue: unknown;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Row click payload. */
|
|
60
|
+
export interface KjRowClickEvent<TData> {
|
|
61
|
+
readonly row: TData;
|
|
62
|
+
readonly event: MouseEvent;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const VIRTUAL_AUTO_THRESHOLD = 200;
|
|
66
|
+
|
|
67
|
+
/** Lookup of column type → built-in editor component. */
|
|
68
|
+
const BUILTIN_EDITORS: Readonly<Record<string, Type<unknown>>> = {
|
|
69
|
+
text: KjTextEditor,
|
|
70
|
+
number: KjNumberEditor,
|
|
71
|
+
date: KjDateEditor,
|
|
72
|
+
boolean: KjBooleanEditor,
|
|
73
|
+
select: KjSelectEditor,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Lookup of column type → built-in filter UI. */
|
|
77
|
+
const BUILTIN_FILTERS: Readonly<Record<string, Type<unknown>>> = {
|
|
78
|
+
text: KjTextFilter,
|
|
79
|
+
number: KjNumberFilter,
|
|
80
|
+
date: KjDateFilter,
|
|
81
|
+
select: KjSelectFilter,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Themed root data table built on top of the headless `@kouji-ui/core` table
|
|
86
|
+
* directives. Wires TanStack-backed `<table>` rendering, ARIA grid semantics,
|
|
87
|
+
* density / variant tokens, content projection slots (toolbar, side-panel,
|
|
88
|
+
* empty, error, loading, row-expansion), persistence, virtualization,
|
|
89
|
+
* resource-backed data, inline editing, and column resizing.
|
|
90
|
+
*
|
|
91
|
+
* Three data modes:
|
|
92
|
+
* - **Static**: pass `kjData` as a plain array.
|
|
93
|
+
* - **Signal**: pass a signal of rows via `kjData`.
|
|
94
|
+
* - **Resource**: pass a `ResourceRef<KjResourceResult<T>>` via `kjResource`;
|
|
95
|
+
* `kjData` is ignored and loading / error are derived from the resource.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```html
|
|
99
|
+
* <kj-table [kjData]="rows()" [kjColumns]="cols" kjDensity="compact" />
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @doc-example Default [full]
|
|
103
|
+
* The smallest possible `<kj-table>` — three columns, five rows.
|
|
104
|
+
* @doc-file table.example.ts
|
|
105
|
+
* @doc-example Usage [full]
|
|
106
|
+
* Full host API: density toggle, bordered variant, toolbar / pagination /
|
|
107
|
+
* status-bar slots projected.
|
|
108
|
+
* @doc-file table.usage.example.ts
|
|
109
|
+
* @doc-example Sortable [full]
|
|
110
|
+
* Click a header to sort; click again to flip direction; third click clears.
|
|
111
|
+
* Shift-click a second header to add a secondary sort.
|
|
112
|
+
* @doc-file table.sortable.example.ts
|
|
113
|
+
* @doc-example Filterable [full]
|
|
114
|
+
* `[kjEnableFilters]="true"` renders a filter row under each header. Built-in
|
|
115
|
+
* text / number / date / select filters pick by `kjType`.
|
|
116
|
+
* @doc-file table.filterable.example.ts
|
|
117
|
+
* @doc-example Selection [full]
|
|
118
|
+
* Multi-row selection with status-bar count + bulk-action slot.
|
|
119
|
+
* @doc-file table.selection.example.ts
|
|
120
|
+
* @doc-example Inline editing [full]
|
|
121
|
+
* Double-click a cell to mount the matching editor; Enter commits, Escape
|
|
122
|
+
* cancels. Listen to `(cellEdit)` and persist yourself.
|
|
123
|
+
* @doc-file table.editable.example.ts
|
|
124
|
+
* @doc-example Column visibility [full]
|
|
125
|
+
* Show / hide columns via the toolbar's visibility menu.
|
|
126
|
+
* @doc-file table.column-visibility.example.ts
|
|
127
|
+
* @doc-example Column resize [full]
|
|
128
|
+
* `[kjEnableResize]="true"` puts a grab handle on each `<th>` border.
|
|
129
|
+
* @doc-file table.column-resize.example.ts
|
|
130
|
+
* @doc-example Column pinning [full]
|
|
131
|
+
* `kjPin: 'left' | 'right'` on a column meta pins it. Unpinned columns
|
|
132
|
+
* scroll horizontally between the pinned edges.
|
|
133
|
+
* @doc-file table.column-pinning.example.ts
|
|
134
|
+
* @doc-example Row pinning [full]
|
|
135
|
+
* Pin rows top / bottom via TanStack's row-pinning state.
|
|
136
|
+
* @doc-file table.row-pinning.example.ts
|
|
137
|
+
* @doc-example Grouping [full]
|
|
138
|
+
* Group by a column; toggle group rows to collapse / expand.
|
|
139
|
+
* @doc-file table.grouping.example.ts
|
|
140
|
+
* @doc-example Master / detail [full]
|
|
141
|
+
* `<ng-template #kjRowExpansion let-row>` renders below the expanded row.
|
|
142
|
+
* @doc-file table.master-detail.example.ts
|
|
143
|
+
* @doc-example Virtualized [full]
|
|
144
|
+
* `[kjVirtual]="true"` (or `'auto'` past 200 rows) windows the body via
|
|
145
|
+
* `@tanstack/virtual-core`.
|
|
146
|
+
* @doc-file table.virtualized.example.ts
|
|
147
|
+
* @doc-example Density [full]
|
|
148
|
+
* `compact` / `standard` / `comfortable` adjust row padding.
|
|
149
|
+
* @doc-file table.density.example.ts
|
|
150
|
+
* @doc-example Theming [full]
|
|
151
|
+
* `bordered` / `striped` / `clean` variants for visual weight.
|
|
152
|
+
* @doc-file table.theming.example.ts
|
|
153
|
+
* @doc-example Server mode [full]
|
|
154
|
+
* `kjTableResource()` wires the table state into an async loader; pagination
|
|
155
|
+
* and filtering round-trip through your endpoint.
|
|
156
|
+
* @doc-file table.server-mode.example.ts
|
|
157
|
+
* @doc-example Persistence [full]
|
|
158
|
+
* `[kjStorageKey]` + a `KjStorageAdapter` round-trip sort / filter / pinning
|
|
159
|
+
* / visibility / density through reload.
|
|
160
|
+
* @doc-file table.persistence.example.ts
|
|
161
|
+
* @doc-example Export [full]
|
|
162
|
+
* CSV / JSON / clipboard export via `data-table-export` utilities, wired to
|
|
163
|
+
* the toolbar's export menu.
|
|
164
|
+
* @doc-file table.export.example.ts
|
|
165
|
+
* @doc-example Empty and error [full]
|
|
166
|
+
* Project `[kjEmpty]` / `[kjError]` slots to override the defaults.
|
|
167
|
+
* @doc-file table.empty-and-error.example.ts
|
|
168
|
+
* @doc-example Keyboard [full]
|
|
169
|
+
* Arrow / Home / End / Ctrl+Home/End / PageUp/Down / F2 / Enter / Esc /
|
|
170
|
+
* Space / Cmd+A — full WAI-ARIA Grid pattern.
|
|
171
|
+
* @doc-file table.keyboard.example.ts
|
|
172
|
+
*
|
|
173
|
+
* @doc-category Library/Data display
|
|
174
|
+
* @doc
|
|
175
|
+
* @doc-name table
|
|
176
|
+
* @doc-is-main
|
|
177
|
+
* @doc-description Themed data table built on TanStack — slots for toolbar, side panel, empty/error/loading; inline editing, filtering, resize, persistence, virtualization, resource-backed data.
|
|
178
|
+
*/
|
|
179
|
+
@Component({
|
|
180
|
+
selector: 'kj-table',
|
|
181
|
+
standalone: true,
|
|
182
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
183
|
+
// `hostDirectives: [KjTable]` hoists the core directive (and its
|
|
184
|
+
// `KJ_TABLE` provider) onto the `<kj-table>` host element so projected
|
|
185
|
+
// content — `<kj-table-toolbar>`, `<kj-table-status-bar>`,
|
|
186
|
+
// `<kj-table-pagination>` — can `inject(KJ_TABLE)` from its element
|
|
187
|
+
// injector. The aliased inputs expose the directive surface under
|
|
188
|
+
// wrapper-friendly names (`kjColumns`, `kjData`, `kjGetRowId`).
|
|
189
|
+
hostDirectives: [
|
|
190
|
+
{
|
|
191
|
+
directive: KjTable,
|
|
192
|
+
inputs: ['kjTable: kjColumns', 'kjTableData: kjData', 'kjGetRowId'],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
imports: [
|
|
196
|
+
KjTableHeader,
|
|
197
|
+
KjTableRow,
|
|
198
|
+
KjTableCell,
|
|
199
|
+
KjTableKeyboardNav,
|
|
200
|
+
KjTableVirtual,
|
|
201
|
+
KjIconDirective,
|
|
202
|
+
KjCheckboxComponent,
|
|
203
|
+
KjCellEditorOutlet,
|
|
204
|
+
KjFilterCellOutlet,
|
|
205
|
+
NgTemplateOutlet,
|
|
206
|
+
],
|
|
207
|
+
host: {
|
|
208
|
+
// Read density from the hosted directive's signal — both the wrapper's
|
|
209
|
+
// `kjDensity` input AND the toolbar's density toggle write through to
|
|
210
|
+
// `KjTable.density` via `setState({ density })`, so a single source of
|
|
211
|
+
// truth drives the `data-density` host attribute.
|
|
212
|
+
'[attr.data-density]': 't.state.density()',
|
|
213
|
+
'[attr.data-variant]': 'kjVariant()',
|
|
214
|
+
'[attr.data-loading]': 'isLoading() ? "" : null',
|
|
215
|
+
'[attr.data-error]': 'hasError() ? "" : null',
|
|
216
|
+
},
|
|
217
|
+
template: `
|
|
218
|
+
<div class="kj-table-wrapper">
|
|
219
|
+
<!-- Toolbar projection: matches either an explicit [kjToolbar] marker
|
|
220
|
+
OR the styled <kj-table-toolbar> element directly, so users don't
|
|
221
|
+
have to remember the marker attribute. -->
|
|
222
|
+
<ng-content select="kj-table-toolbar, [kjToolbar]" />
|
|
223
|
+
|
|
224
|
+
<div class="kj-table-body">
|
|
225
|
+
<table kjTableKeyboardNav
|
|
226
|
+
role="grid"
|
|
227
|
+
[attr.aria-rowcount]="aria.rowCount()"
|
|
228
|
+
[attr.aria-colcount]="aria.colCount()"
|
|
229
|
+
>
|
|
230
|
+
<thead>
|
|
231
|
+
@for (g of t.table().getHeaderGroups(); track g.id) {
|
|
232
|
+
<tr role="row">
|
|
233
|
+
@if (showSelectionColumn()) {
|
|
234
|
+
<th scope="col" class="kj-table-select-cell">
|
|
235
|
+
@if (kjSelectionMode() === 'multi') {
|
|
236
|
+
<kj-checkbox
|
|
237
|
+
[checked]="t.table().getIsAllPageRowsSelected()"
|
|
238
|
+
[indeterminate]="t.table().getIsSomePageRowsSelected()"
|
|
239
|
+
(checkedChange)="onSelectAll($event)"
|
|
240
|
+
kjAriaLabel="Select all rows"
|
|
241
|
+
/>
|
|
242
|
+
}
|
|
243
|
+
</th>
|
|
244
|
+
}
|
|
245
|
+
@for (h of g.headers; track h.id) {
|
|
246
|
+
<th kjTableHeader
|
|
247
|
+
[kjHeader]="h"
|
|
248
|
+
scope="col"
|
|
249
|
+
[style.width.px]="kjEnableResize() ? h.getSize() : null"
|
|
250
|
+
[attr.data-pin]="headerPin(h)"
|
|
251
|
+
[attr.data-resizing]="h.column.getIsResizing?.() ? '' : null"
|
|
252
|
+
[style.--kj-resize-delta.px]="t.columnSizingInfo().deltaOffset ?? 0"
|
|
253
|
+
>
|
|
254
|
+
{{ h.column.columnDef.header }}
|
|
255
|
+
@if (h.column.getCanSort?.()) {
|
|
256
|
+
<i
|
|
257
|
+
class="kj-table-sort-indicator"
|
|
258
|
+
kjIconSize="sm"
|
|
259
|
+
aria-hidden="true"
|
|
260
|
+
[kjIcon]="sortIcon(h)"
|
|
261
|
+
></i>
|
|
262
|
+
}
|
|
263
|
+
@if (kjEnableResize() && h.column.getCanResize?.()) {
|
|
264
|
+
<span
|
|
265
|
+
class="kj-table-resize-handle"
|
|
266
|
+
role="separator"
|
|
267
|
+
aria-orientation="vertical"
|
|
268
|
+
aria-label="Resize column"
|
|
269
|
+
(mousedown)="h.getResizeHandler()($event)"
|
|
270
|
+
(touchstart)="h.getResizeHandler()($event)"
|
|
271
|
+
></span>
|
|
272
|
+
}
|
|
273
|
+
</th>
|
|
274
|
+
}
|
|
275
|
+
</tr>
|
|
276
|
+
}
|
|
277
|
+
@if (kjEnableFilters()) {
|
|
278
|
+
<tr role="row" class="kj-table-filter-row">
|
|
279
|
+
@if (showSelectionColumn()) {
|
|
280
|
+
<th scope="col" class="kj-table-select-cell"></th>
|
|
281
|
+
}
|
|
282
|
+
@for (g of t.table().getHeaderGroups(); track g.id) {
|
|
283
|
+
@for (h of g.headers; track h.id) {
|
|
284
|
+
<th scope="col" class="kj-table-filter-cell">
|
|
285
|
+
@if (filterTypeFor(h.column); as ft) {
|
|
286
|
+
<span
|
|
287
|
+
kjFilterCell
|
|
288
|
+
class="kj-table-filter-anchor"
|
|
289
|
+
[kjColumn]="h.column"
|
|
290
|
+
[kjFilter]="filterRendererFor(h.column, ft)"
|
|
291
|
+
></span>
|
|
292
|
+
}
|
|
293
|
+
</th>
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
</tr>
|
|
297
|
+
}
|
|
298
|
+
</thead>
|
|
299
|
+
|
|
300
|
+
@if (kjEnableRowPinning() && t.table().getTopRows().length > 0) {
|
|
301
|
+
<tbody class="kj-table-tbody-pinned" data-pin="top">
|
|
302
|
+
@for (r of t.table().getTopRows(); track r.id) {
|
|
303
|
+
<tr kjTableRow [kjRow]="r"
|
|
304
|
+
(click)="onRowClick(r, $event)"
|
|
305
|
+
(dblclick)="onRowDblClick(r, $event)">
|
|
306
|
+
@if (showSelectionColumn()) {
|
|
307
|
+
<td class="kj-table-select-cell"
|
|
308
|
+
(mousedown)="rememberClickModifiers($event)"
|
|
309
|
+
(click)="$event.stopPropagation()">
|
|
310
|
+
<kj-checkbox
|
|
311
|
+
[checked]="r.getIsSelected()"
|
|
312
|
+
(checkedChange)="onRowCheckboxToggle(r)"
|
|
313
|
+
kjAriaLabel="Select row"
|
|
314
|
+
/>
|
|
315
|
+
</td>
|
|
316
|
+
}
|
|
317
|
+
@for (c of r.getVisibleCells(); track c.id) {
|
|
318
|
+
<td kjTableCell [kjCell]="c" tabindex="-1"
|
|
319
|
+
[class.kj-table-cell--editing]="isEditing(c)"
|
|
320
|
+
(click)="onCellClick(c, $event)"
|
|
321
|
+
(dblclick)="onCellDblClick(c, $event)"
|
|
322
|
+
(keydown)="onCellKeydown(c, $event)">
|
|
323
|
+
@if (isEditing(c)) {
|
|
324
|
+
<span class="kj-table-cell-ghost" aria-hidden="true">{{ c.getValue() }}</span>
|
|
325
|
+
<span
|
|
326
|
+
kjCellEditorOutlet
|
|
327
|
+
class="kj-table-editor-anchor"
|
|
328
|
+
[kjEditor]="editorTypeFor(c)"
|
|
329
|
+
[kjValue]="c.getValue()"
|
|
330
|
+
[kjMeta]="editorMetaFor(c)"
|
|
331
|
+
(commit)="onEditorCommit(c, $event)"
|
|
332
|
+
(editCancel)="onEditorCancel()"
|
|
333
|
+
></span>
|
|
334
|
+
} @else {
|
|
335
|
+
{{ c.getValue() }}
|
|
336
|
+
}
|
|
337
|
+
</td>
|
|
338
|
+
}
|
|
339
|
+
</tr>
|
|
340
|
+
}
|
|
341
|
+
</tbody>
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@if (shouldVirtualize()) {
|
|
345
|
+
<tbody
|
|
346
|
+
kjTableVirtual
|
|
347
|
+
[kjCount]="centerRows().length"
|
|
348
|
+
[kjEstimateSize]="kjEstimatedRowSize()"
|
|
349
|
+
#v="kjTableVirtual"
|
|
350
|
+
>
|
|
351
|
+
@if (v.paddingTop() > 0) {
|
|
352
|
+
<tr aria-hidden="true">
|
|
353
|
+
<td [attr.colspan]="aria.colCount()" [style.height.px]="v.paddingTop()"></td>
|
|
354
|
+
</tr>
|
|
355
|
+
}
|
|
356
|
+
@for (vr of v.virtualRows(); track vr.key) {
|
|
357
|
+
@if (centerRows()[vr.index]; as r) {
|
|
358
|
+
<tr kjTableRow [kjRow]="r"
|
|
359
|
+
(click)="onRowClick(r, $event)"
|
|
360
|
+
(dblclick)="onRowDblClick(r, $event)">
|
|
361
|
+
@if (showSelectionColumn()) {
|
|
362
|
+
<td class="kj-table-select-cell"
|
|
363
|
+
(mousedown)="rememberClickModifiers($event)"
|
|
364
|
+
(click)="$event.stopPropagation()">
|
|
365
|
+
<kj-checkbox
|
|
366
|
+
[checked]="r.getIsSelected()"
|
|
367
|
+
(checkedChange)="onRowCheckboxToggle(r)"
|
|
368
|
+
kjAriaLabel="Select row"
|
|
369
|
+
/>
|
|
370
|
+
</td>
|
|
371
|
+
}
|
|
372
|
+
@for (c of r.getVisibleCells(); track c.id) {
|
|
373
|
+
<td kjTableCell [kjCell]="c" tabindex="-1"
|
|
374
|
+
[class.kj-table-cell--editing]="isEditing(c)"
|
|
375
|
+
[style.width.px]="kjEnableResize() ? c.column.getSize() : null"
|
|
376
|
+
(click)="onCellClick(c, $event)"
|
|
377
|
+
(dblclick)="onCellDblClick(c, $event)"
|
|
378
|
+
(keydown)="onCellKeydown(c, $event)">
|
|
379
|
+
@if (isEditing(c)) {
|
|
380
|
+
<span class="kj-table-cell-ghost" aria-hidden="true">{{ c.getValue() }}</span>
|
|
381
|
+
<span
|
|
382
|
+
kjCellEditorOutlet
|
|
383
|
+
class="kj-table-editor-anchor"
|
|
384
|
+
[kjEditor]="editorTypeFor(c)"
|
|
385
|
+
[kjValue]="c.getValue()"
|
|
386
|
+
[kjMeta]="editorMetaFor(c)"
|
|
387
|
+
(commit)="onEditorCommit(c, $event)"
|
|
388
|
+
(editCancel)="onEditorCancel()"
|
|
389
|
+
></span>
|
|
390
|
+
} @else {
|
|
391
|
+
{{ c.getValue() }}
|
|
392
|
+
}
|
|
393
|
+
</td>
|
|
394
|
+
}
|
|
395
|
+
</tr>
|
|
396
|
+
@if (kjRowExpansionTpl() && r.getIsExpanded?.()) {
|
|
397
|
+
<tr class="kj-table-expansion-row">
|
|
398
|
+
<td [attr.colspan]="aria.colCount()">
|
|
399
|
+
<ng-container
|
|
400
|
+
[ngTemplateOutlet]="kjRowExpansionTpl()!"
|
|
401
|
+
[ngTemplateOutletContext]="{ $implicit: r.original, row: r.original }"
|
|
402
|
+
/>
|
|
403
|
+
</td>
|
|
404
|
+
</tr>
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
@if (v.paddingBottom() > 0) {
|
|
409
|
+
<tr aria-hidden="true">
|
|
410
|
+
<td [attr.colspan]="aria.colCount()" [style.height.px]="v.paddingBottom()"></td>
|
|
411
|
+
</tr>
|
|
412
|
+
}
|
|
413
|
+
</tbody>
|
|
414
|
+
} @else {
|
|
415
|
+
<tbody>
|
|
416
|
+
@for (r of centerRows(); track r.id) {
|
|
417
|
+
<tr kjTableRow [kjRow]="r"
|
|
418
|
+
[attr.data-row-grouped]="r.getIsGrouped?.() ? '' : null"
|
|
419
|
+
(click)="onRowClick(r, $event)"
|
|
420
|
+
(dblclick)="onRowDblClick(r, $event)">
|
|
421
|
+
@if (showSelectionColumn()) {
|
|
422
|
+
<td class="kj-table-select-cell"
|
|
423
|
+
(mousedown)="rememberClickModifiers($event)"
|
|
424
|
+
(click)="$event.stopPropagation()">
|
|
425
|
+
<kj-checkbox
|
|
426
|
+
[checked]="r.getIsSelected()"
|
|
427
|
+
(checkedChange)="onRowCheckboxToggle(r)"
|
|
428
|
+
kjAriaLabel="Select row"
|
|
429
|
+
/>
|
|
430
|
+
</td>
|
|
431
|
+
}
|
|
432
|
+
@for (c of r.getVisibleCells(); track c.id) {
|
|
433
|
+
<td kjTableCell [kjCell]="c" tabindex="-1"
|
|
434
|
+
[class.kj-table-cell--editing]="isEditing(c)"
|
|
435
|
+
[style.width.px]="kjEnableResize() ? c.column.getSize() : null"
|
|
436
|
+
(click)="onCellClick(c, $event)"
|
|
437
|
+
(dblclick)="onCellDblClick(c, $event)"
|
|
438
|
+
(keydown)="onCellKeydown(c, $event)">
|
|
439
|
+
@if (isEditing(c)) {
|
|
440
|
+
<span class="kj-table-cell-ghost" aria-hidden="true">{{ c.getValue() }}</span>
|
|
441
|
+
<span
|
|
442
|
+
kjCellEditorOutlet
|
|
443
|
+
class="kj-table-editor-anchor"
|
|
444
|
+
[kjEditor]="editorTypeFor(c)"
|
|
445
|
+
[kjValue]="c.getValue()"
|
|
446
|
+
[kjMeta]="editorMetaFor(c)"
|
|
447
|
+
(commit)="onEditorCommit(c, $event)"
|
|
448
|
+
(editCancel)="onEditorCancel()"
|
|
449
|
+
></span>
|
|
450
|
+
} @else if (c.getIsGrouped?.()) {
|
|
451
|
+
<button type="button"
|
|
452
|
+
class="kj-table-group-toggle"
|
|
453
|
+
[attr.aria-expanded]="r.getIsExpanded?.() ? 'true' : 'false'"
|
|
454
|
+
(click)="onGroupToggle(r, $event)">
|
|
455
|
+
<span class="kj-table-group-toggle__chevron" aria-hidden="true">›</span>
|
|
456
|
+
{{ c.getValue() }}
|
|
457
|
+
<span class="kj-table-group-toggle__count">({{ r.subRows?.length ?? 0 }})</span>
|
|
458
|
+
</button>
|
|
459
|
+
} @else if (c.getIsAggregated?.()) {
|
|
460
|
+
{{ c.renderValue() }}
|
|
461
|
+
} @else if (c.getIsPlaceholder?.()) {
|
|
462
|
+
} @else {
|
|
463
|
+
{{ c.getValue() }}
|
|
464
|
+
}
|
|
465
|
+
</td>
|
|
466
|
+
}
|
|
467
|
+
</tr>
|
|
468
|
+
@if (kjRowExpansionTpl() && r.getIsExpanded?.() && !r.getIsGrouped?.()) {
|
|
469
|
+
<tr class="kj-table-expansion-row">
|
|
470
|
+
<td [attr.colspan]="aria.colCount()">
|
|
471
|
+
<ng-container
|
|
472
|
+
[ngTemplateOutlet]="kjRowExpansionTpl()!"
|
|
473
|
+
[ngTemplateOutletContext]="{ $implicit: r.original, row: r.original }"
|
|
474
|
+
/>
|
|
475
|
+
</td>
|
|
476
|
+
</tr>
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
</tbody>
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
@if (kjEnableRowPinning() && t.table().getBottomRows().length > 0) {
|
|
483
|
+
<tbody class="kj-table-tbody-pinned" data-pin="bottom">
|
|
484
|
+
@for (r of t.table().getBottomRows(); track r.id) {
|
|
485
|
+
<tr kjTableRow [kjRow]="r"
|
|
486
|
+
(click)="onRowClick(r, $event)"
|
|
487
|
+
(dblclick)="onRowDblClick(r, $event)">
|
|
488
|
+
@if (showSelectionColumn()) {
|
|
489
|
+
<td class="kj-table-select-cell"
|
|
490
|
+
(mousedown)="rememberClickModifiers($event)"
|
|
491
|
+
(click)="$event.stopPropagation()">
|
|
492
|
+
<kj-checkbox
|
|
493
|
+
[checked]="r.getIsSelected()"
|
|
494
|
+
(checkedChange)="onRowCheckboxToggle(r)"
|
|
495
|
+
kjAriaLabel="Select row"
|
|
496
|
+
/>
|
|
497
|
+
</td>
|
|
498
|
+
}
|
|
499
|
+
@for (c of r.getVisibleCells(); track c.id) {
|
|
500
|
+
<td kjTableCell [kjCell]="c" tabindex="-1">{{ c.getValue() }}</td>
|
|
501
|
+
}
|
|
502
|
+
</tr>
|
|
503
|
+
}
|
|
504
|
+
</tbody>
|
|
505
|
+
}
|
|
506
|
+
</table>
|
|
507
|
+
|
|
508
|
+
<ng-content select="[kjSidePanel]" />
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
@if (showEmpty()) {
|
|
512
|
+
<div class="kj-table-empty" role="status">
|
|
513
|
+
<ng-content select="[kjEmpty]" />
|
|
514
|
+
</div>
|
|
515
|
+
}
|
|
516
|
+
@if (hasError()) {
|
|
517
|
+
<div class="kj-table-error" role="alert">
|
|
518
|
+
<ng-content select="[kjError]" />
|
|
519
|
+
</div>
|
|
520
|
+
}
|
|
521
|
+
@if (isLoading()) {
|
|
522
|
+
<div class="kj-table-loading"
|
|
523
|
+
aria-live="polite"
|
|
524
|
+
aria-busy="true"
|
|
525
|
+
[attr.data-has-rows]="effectiveData().length > 0 ? '' : null">
|
|
526
|
+
<ng-content select="[kjLoading]">
|
|
527
|
+
<span class="kj-table-loading-fallback">Loading…</span>
|
|
528
|
+
</ng-content>
|
|
529
|
+
</div>
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
<ng-content />
|
|
533
|
+
</div>
|
|
534
|
+
`,
|
|
535
|
+
styleUrl: './table.css',
|
|
536
|
+
encapsulation: ViewEncapsulation.Emulated,
|
|
537
|
+
})
|
|
538
|
+
export class KjTableComponent<TData extends RowData = unknown> {
|
|
539
|
+
// ── Data ────────────────────────────────────────────────────────────────
|
|
540
|
+
// `kjColumns`, `kjData`, `kjGetRowId` are exposed via `hostDirectives` input
|
|
541
|
+
// forwarding (see the `@Component` metadata) — bindings on `<kj-table>`
|
|
542
|
+
// flow straight into the hosted `KjTable` directive. We read them back via
|
|
543
|
+
// the injected directive instance (`this.t`) when the wrapper needs them.
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Resource-backed data source. When set, rows come from
|
|
547
|
+
* `kjResource.value().rows`, `kjLoading` is derived from `isLoading()`, and
|
|
548
|
+
* the error slot renders when `error()` is non-null. The wrapper writes
|
|
549
|
+
* the resolved rows into the hosted `KjTable.kjTableData` model — the
|
|
550
|
+
* underlying TanStack instance always sees a single rows source.
|
|
551
|
+
*/
|
|
552
|
+
readonly kjResource = input<ResourceRef<KjResourceResult<TData> | undefined> | null>(null);
|
|
553
|
+
|
|
554
|
+
// ── Theming ─────────────────────────────────────────────────────────────
|
|
555
|
+
readonly kjDensity = input<'compact' | 'standard' | 'comfortable'>('standard');
|
|
556
|
+
readonly kjVariant = input<'bordered' | 'striped' | 'clean'>('bordered');
|
|
557
|
+
/** External "I'm loading" flag, ORed with the resource's `isLoading()`. */
|
|
558
|
+
readonly kjLoading = input(false);
|
|
559
|
+
|
|
560
|
+
// ── Selection ───────────────────────────────────────────────────────────
|
|
561
|
+
readonly kjSelection = model<Set<string> | string | null>(null);
|
|
562
|
+
readonly kjSelectionMode = input<'single' | 'multi' | 'none'>('none');
|
|
563
|
+
/**
|
|
564
|
+
* Whether the wrapper auto-renders a leading checkbox column. Defaults to
|
|
565
|
+
* `true` whenever selection is enabled; set `false` to drive selection
|
|
566
|
+
* purely from row clicks / external state without the column.
|
|
567
|
+
*/
|
|
568
|
+
readonly kjShowSelectionColumn = input<boolean>(true);
|
|
569
|
+
|
|
570
|
+
// ── Persistence ─────────────────────────────────────────────────────────
|
|
571
|
+
readonly kjStorageKey = input<string | null>(null);
|
|
572
|
+
readonly kjStorageAdapter = input<KjStorageAdapter | null>(null);
|
|
573
|
+
|
|
574
|
+
// ── Virtualization ──────────────────────────────────────────────────────
|
|
575
|
+
readonly kjVirtual = input<boolean | 'auto'>('auto');
|
|
576
|
+
readonly kjEstimatedRowSize = input<number>(36);
|
|
577
|
+
|
|
578
|
+
// ── Feature toggles ─────────────────────────────────────────────────────
|
|
579
|
+
readonly kjEnableFilters = input<boolean>(false);
|
|
580
|
+
readonly kjEnableResize = input<boolean>(false);
|
|
581
|
+
readonly kjEnableRowPinning = input<boolean>(false);
|
|
582
|
+
readonly kjEditOnDoubleClick = input<boolean>(true);
|
|
583
|
+
|
|
584
|
+
// ── Row expansion template ─────────────────────────────────────────────
|
|
585
|
+
/** Template for master-detail row expansion. Receives `$implicit = row`. */
|
|
586
|
+
readonly kjRowExpansionTpl = contentChild<TemplateRef<unknown>>('kjRowExpansion');
|
|
587
|
+
|
|
588
|
+
// ── Outputs ─────────────────────────────────────────────────────────────
|
|
589
|
+
readonly cellEdit = output<KjCellEditEvent<TData>>();
|
|
590
|
+
readonly stateChange = output<KjTableState>();
|
|
591
|
+
readonly rowClick = output<KjRowClickEvent<TData>>();
|
|
592
|
+
readonly rowDoubleClick = output<KjRowClickEvent<TData>>();
|
|
593
|
+
|
|
594
|
+
// ── Refs ────────────────────────────────────────────────────────────────
|
|
595
|
+
/**
|
|
596
|
+
* The hosted `KjTable` directive instance. Exposed under the legacy
|
|
597
|
+
* `tableRef()` API for examples that drive the table imperatively
|
|
598
|
+
* (`tableRef().setState(...)`, `tableRef().setRowPinning(...)`).
|
|
599
|
+
*
|
|
600
|
+
* `t` is the template-side alias for the same instance, used everywhere
|
|
601
|
+
* the template needs the TanStack table proxy (`t.table().getRowModel()`).
|
|
602
|
+
*/
|
|
603
|
+
protected readonly t = inject(KjTable) as unknown as KjTable<TData>;
|
|
604
|
+
readonly tableRef = (): KjTable<TData> => this.t;
|
|
605
|
+
|
|
606
|
+
// ── Internal state ──────────────────────────────────────────────────────
|
|
607
|
+
private readonly tokenStorage = inject(KJ_TABLE_STORAGE, { optional: true });
|
|
608
|
+
|
|
609
|
+
/** Tracks the cell currently in edit mode by row+column id. */
|
|
610
|
+
protected readonly editingCell = signal<{ rowId: string; columnId: string } | null>(null);
|
|
611
|
+
|
|
612
|
+
// ── Computed ────────────────────────────────────────────────────────────
|
|
613
|
+
/**
|
|
614
|
+
* Effective rows driving TanStack:
|
|
615
|
+
* - `kjResource` set → unwrap `resource.value().rows`
|
|
616
|
+
* - otherwise → use the hosted directive's `kjTableData` model
|
|
617
|
+
* (fed by `[kjData]` on the host).
|
|
618
|
+
*/
|
|
619
|
+
protected readonly effectiveData = computed<TData[]>(() => {
|
|
620
|
+
const res = this.kjResource();
|
|
621
|
+
if (res) {
|
|
622
|
+
// Skip `.value()` when the resource is in error / loading — both throw
|
|
623
|
+
// or return stale data. The error / loading panes render instead, and
|
|
624
|
+
// the table model holds whatever the write-through effect last set.
|
|
625
|
+
if (res.error() != null || res.isLoading()) return [];
|
|
626
|
+
const v = res.value();
|
|
627
|
+
const rows = v?.rows;
|
|
628
|
+
return rows ? Array.from(rows) : [];
|
|
629
|
+
}
|
|
630
|
+
return this.t.kjTableData();
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* All center (non-pinned) rows from TanStack.
|
|
635
|
+
*
|
|
636
|
+
* When virtualization is active we skip pagination — the virtualizer is the
|
|
637
|
+
* windowing mechanism, so feeding it a 25-row page (TanStack's default) makes
|
|
638
|
+
* a 10k dataset look like 25 rows that can't scroll. `getPrePaginationRowModel`
|
|
639
|
+
* keeps sort + filter applied but returns the full slice.
|
|
640
|
+
*/
|
|
641
|
+
protected readonly centerRows = computed<Row<TData>[]>(() => {
|
|
642
|
+
const tbl = this.t.table();
|
|
643
|
+
if (this.kjEnableRowPinning()) return tbl.getCenterRows();
|
|
644
|
+
return this.shouldVirtualize()
|
|
645
|
+
? tbl.getPrePaginationRowModel().rows
|
|
646
|
+
: tbl.getRowModel().rows;
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Whether the leading checkbox column should render. Auto-enables when
|
|
651
|
+
* selection mode is non-`none` AND the user hasn't opted out.
|
|
652
|
+
*/
|
|
653
|
+
protected readonly showSelectionColumn = computed<boolean>(
|
|
654
|
+
() => this.kjSelectionMode() !== 'none' && this.kjShowSelectionColumn(),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Captured at mousedown on a select-cell so the subsequent checkbox
|
|
659
|
+
* `checkedChange` can branch on shift / ctrl/cmd without losing the modifier
|
|
660
|
+
* state (the keyboard modifiers are only on the originating MouseEvent;
|
|
661
|
+
* the change event from kj-checkbox doesn't carry them).
|
|
662
|
+
*/
|
|
663
|
+
private lastClickShift = false;
|
|
664
|
+
private lastAnchorRowId: string | null = null;
|
|
665
|
+
|
|
666
|
+
/** True when the active resource is loading OR `kjLoading` is set. */
|
|
667
|
+
protected readonly isLoading = computed<boolean>(() => {
|
|
668
|
+
if (this.kjLoading()) return true;
|
|
669
|
+
const res = this.kjResource();
|
|
670
|
+
return res ? res.isLoading() : false;
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
/** True when the active resource is in error state. */
|
|
674
|
+
protected readonly hasError = computed<boolean>(() => {
|
|
675
|
+
const res = this.kjResource();
|
|
676
|
+
return res ? res.error() != null : false;
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
/** True when there are no rows AND we are not loading or errored. */
|
|
680
|
+
protected readonly showEmpty = computed<boolean>(
|
|
681
|
+
() => this.effectiveData().length === 0 && !this.isLoading() && !this.hasError(),
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Whether row virtualization should be enabled. Counts against the
|
|
686
|
+
* pre-pagination row model so `'auto'` flips on for large datasets that
|
|
687
|
+
* would otherwise be split across paginated pages.
|
|
688
|
+
*/
|
|
689
|
+
protected readonly shouldVirtualize = computed<boolean>(() => {
|
|
690
|
+
const mode = this.kjVirtual();
|
|
691
|
+
if (mode === true) return true;
|
|
692
|
+
if (mode === false) return false;
|
|
693
|
+
return this.t.table().getPrePaginationRowModel().rows.length > VIRTUAL_AUTO_THRESHOLD;
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
protected readonly aria = {
|
|
697
|
+
rowCount: computed(() => this.effectiveData().length + 1),
|
|
698
|
+
colCount: computed(() => {
|
|
699
|
+
const cols = this.t.kjTable();
|
|
700
|
+
// Approximate: flatten one level of column groups for ARIA.
|
|
701
|
+
let n = 0;
|
|
702
|
+
for (const c of cols) {
|
|
703
|
+
const sub = (c as { columns?: unknown[] }).columns;
|
|
704
|
+
n += Array.isArray(sub) ? sub.length : 1;
|
|
705
|
+
}
|
|
706
|
+
return n;
|
|
707
|
+
}),
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
constructor() {
|
|
711
|
+
// Wrapper's `kjDensity` input → hosted directive's density state. Both
|
|
712
|
+
// the user's external binding AND the toolbar's density toggle write
|
|
713
|
+
// into `KjTable.density` via `setState`, so a single source of truth
|
|
714
|
+
// backs the host's `data-density` attribute and the row-padding CSS.
|
|
715
|
+
//
|
|
716
|
+
// The internal `t.density()` read is wrapped in `untracked` so the
|
|
717
|
+
// effect only fires on `kjDensity` input changes — without that, a
|
|
718
|
+
// toolbar click would update `t.density`, re-trigger this effect, and
|
|
719
|
+
// get clobbered back to the input value (feedback loop).
|
|
720
|
+
effect(() => {
|
|
721
|
+
const next = this.kjDensity();
|
|
722
|
+
untracked(() => {
|
|
723
|
+
if (this.t.state.density() !== next) this.t.setState({ density: next });
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// Pipe resource rows into the hosted KjTable.kjTableData model so the
|
|
728
|
+
// single TanStack instance always sees one rows source. When kjResource
|
|
729
|
+
// is null the user's `[kjData]` binding flows through unchanged.
|
|
730
|
+
//
|
|
731
|
+
// `resource.value()` throws when the resource is in an error state
|
|
732
|
+
// (Angular's contract — read errors live on `.error()`). We guard on
|
|
733
|
+
// both `error()` and `isLoading()` so the error / loading panes own the
|
|
734
|
+
// UX while the table model keeps the last-known rows empty.
|
|
735
|
+
effect(() => {
|
|
736
|
+
const res = this.kjResource();
|
|
737
|
+
if (!res) {
|
|
738
|
+
// No resource → clear the manual-pagination total so the table
|
|
739
|
+
// falls back to client-side pagination on the user's `[kjData]`.
|
|
740
|
+
this.t.setRowCount(null);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (res.error() != null) {
|
|
744
|
+
this.t.kjTableData.set([]);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (res.isLoading()) return;
|
|
748
|
+
const value = res.value();
|
|
749
|
+
const rows = value?.rows;
|
|
750
|
+
this.t.kjTableData.set(rows ? Array.from(rows) as TData[] : []);
|
|
751
|
+
// Flip into manual-pagination mode and report the remote total
|
|
752
|
+
// so the pagination footer / status bar see the full dataset
|
|
753
|
+
// instead of just the current page slice.
|
|
754
|
+
this.t.setRowCount(value?.rowCount ?? null);
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// Persistence: on init, read from adapter and seed table state.
|
|
758
|
+
effect(() => {
|
|
759
|
+
const key = this.kjStorageKey();
|
|
760
|
+
if (!key) return;
|
|
761
|
+
const adapter = this.kjStorageAdapter() ?? this.tokenStorage;
|
|
762
|
+
if (!adapter) return;
|
|
763
|
+
const tbl = untracked(() => this.t);
|
|
764
|
+
if (!tbl) return;
|
|
765
|
+
const persisted = adapter.read<Partial<KjTableState>>(key);
|
|
766
|
+
if (persisted) {
|
|
767
|
+
untracked(() => tbl.setState(persisted));
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// Persistence: watch state and write through. Reading `state()` once
|
|
772
|
+
// is enough — the single signal invalidates on any slice change.
|
|
773
|
+
effect(() => {
|
|
774
|
+
const key = this.kjStorageKey();
|
|
775
|
+
if (!key) return;
|
|
776
|
+
const adapter = this.kjStorageAdapter() ?? this.tokenStorage;
|
|
777
|
+
if (!adapter) return;
|
|
778
|
+
adapter.write(key, this.t.state());
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// State-change emission.
|
|
782
|
+
effect(() => {
|
|
783
|
+
this.stateChange.emit(this.t.state());
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ── Cell-edit handlers ──────────────────────────────────────────────────
|
|
788
|
+
protected isEditing(cell: Cell<TData, unknown>): boolean {
|
|
789
|
+
const e = this.editingCell();
|
|
790
|
+
return !!e && e.rowId === cell.row.id && e.columnId === cell.column.id;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
protected onCellClick(cell: Cell<TData, unknown>, _event: MouseEvent): void {
|
|
794
|
+
if (this.kjEditOnDoubleClick()) return;
|
|
795
|
+
this.tryBeginEdit(cell);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
protected onCellDblClick(cell: Cell<TData, unknown>, _event: MouseEvent): void {
|
|
799
|
+
if (!this.kjEditOnDoubleClick()) return;
|
|
800
|
+
this.tryBeginEdit(cell);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
protected onCellKeydown(cell: Cell<TData, unknown>, event: KeyboardEvent): void {
|
|
804
|
+
if (event.key === 'F2') {
|
|
805
|
+
event.preventDefault();
|
|
806
|
+
this.tryBeginEdit(cell);
|
|
807
|
+
} else if (event.key === 'Escape' && this.isEditing(cell)) {
|
|
808
|
+
this.editingCell.set(null);
|
|
809
|
+
} else if (event.key === ' ' && !this.isEditing(cell)) {
|
|
810
|
+
// Space toggles row selection when selection is enabled (WCAG grid pattern).
|
|
811
|
+
const mode = this.kjSelectionMode();
|
|
812
|
+
if (mode === 'multi') {
|
|
813
|
+
event.preventDefault();
|
|
814
|
+
(cell.row as { toggleSelected?: () => void }).toggleSelected?.();
|
|
815
|
+
} else if (mode === 'single') {
|
|
816
|
+
event.preventDefault();
|
|
817
|
+
this.t.table().setRowSelection({ [cell.row.id]: true });
|
|
818
|
+
}
|
|
819
|
+
} else if ((event.ctrlKey || event.metaKey) && (event.key === 'a' || event.key === 'A')) {
|
|
820
|
+
// Cmd/Ctrl+A — select all (multi mode only).
|
|
821
|
+
if (this.kjSelectionMode() === 'multi') {
|
|
822
|
+
event.preventDefault();
|
|
823
|
+
this.t.table().toggleAllRowsSelected(true);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private tryBeginEdit(cell: Cell<TData, unknown>): void {
|
|
829
|
+
const meta = cellMeta(cell.column);
|
|
830
|
+
if (!meta?.editable) return;
|
|
831
|
+
this.editingCell.set({ rowId: cell.row.id, columnId: cell.column.id });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ── Row-click handlers ──────────────────────────────────────────────────
|
|
835
|
+
protected onRowClick(row: Row<TData>, event: MouseEvent): void {
|
|
836
|
+
// A click that lands on an interactive control inside the row (group
|
|
837
|
+
// toggle button, inline editor, link, etc.) should not double-toggle
|
|
838
|
+
// row selection — only bare-row clicks count.
|
|
839
|
+
const target = event.target as HTMLElement | null;
|
|
840
|
+
if (target?.closest('button, a, input, select, textarea, .kj-table-editor-anchor')) {
|
|
841
|
+
this.rowClick.emit({ row: row.original, event });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.applySelectionFromClick(row, event);
|
|
845
|
+
this.rowClick.emit({ row: row.original, event });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
protected onRowDblClick(row: Row<TData>, event: MouseEvent): void {
|
|
849
|
+
this.rowDoubleClick.emit({ row: row.original, event });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Toggle / replace row selection based on `kjSelectionMode`:
|
|
854
|
+
* - `'multi'` → plain click toggles this row and moves the anchor.
|
|
855
|
+
* Shift-click selects every row between the anchor and here (range).
|
|
856
|
+
* Ctrl/Cmd-click toggles this row only (anchor moves, others kept).
|
|
857
|
+
* - `'single'` → set this row as the sole selection.
|
|
858
|
+
* - `'none'` → no-op.
|
|
859
|
+
*/
|
|
860
|
+
private applySelectionFromClick(row: Row<TData>, event: MouseEvent): void {
|
|
861
|
+
const mode = this.kjSelectionMode();
|
|
862
|
+
if (mode === 'none') return;
|
|
863
|
+
const tbl = this.t.table();
|
|
864
|
+
if (mode === 'single') {
|
|
865
|
+
tbl.setRowSelection({ [row.id]: true });
|
|
866
|
+
this.lastAnchorRowId = row.id;
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
// multi
|
|
870
|
+
if (event.shiftKey && this.lastAnchorRowId !== null) {
|
|
871
|
+
this.selectRange(this.lastAnchorRowId, row.id, true);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
(row as { toggleSelected?: () => void }).toggleSelected?.();
|
|
875
|
+
this.lastAnchorRowId = row.id;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Capture the shift modifier from mousedown on a select-cell. The
|
|
880
|
+
* checkbox's `checkedChange` doesn't carry the originating mouse event,
|
|
881
|
+
* so we stash the modifier and consume it in `onRowCheckboxToggle`.
|
|
882
|
+
*/
|
|
883
|
+
protected rememberClickModifiers(event: MouseEvent): void {
|
|
884
|
+
this.lastClickShift = event.shiftKey;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Click on a row's checkbox: shift extends from anchor, else plain toggle. */
|
|
888
|
+
protected onRowCheckboxToggle(row: Row<TData>): void {
|
|
889
|
+
const wasShift = this.lastClickShift;
|
|
890
|
+
this.lastClickShift = false;
|
|
891
|
+
if (wasShift && this.lastAnchorRowId !== null && this.kjSelectionMode() === 'multi') {
|
|
892
|
+
this.selectRange(this.lastAnchorRowId, row.id, true);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
(row as { toggleSelected?: () => void }).toggleSelected?.();
|
|
896
|
+
this.lastAnchorRowId = row.id;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/** Header "select all" checkbox change. */
|
|
900
|
+
protected onSelectAll(checked: boolean): void {
|
|
901
|
+
this.t.table().toggleAllPageRowsSelected(checked);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Set every row between `fromId` and `toId` (inclusive, order-agnostic) to
|
|
906
|
+
* `selected`. Existing selections OUTSIDE that window are preserved.
|
|
907
|
+
*/
|
|
908
|
+
private selectRange(fromId: string, toId: string, selected: boolean): void {
|
|
909
|
+
const rows = this.centerRows();
|
|
910
|
+
const fromIdx = rows.findIndex((r) => r.id === fromId);
|
|
911
|
+
const toIdx = rows.findIndex((r) => r.id === toId);
|
|
912
|
+
if (fromIdx === -1 || toIdx === -1) return;
|
|
913
|
+
const [start, end] = fromIdx < toIdx ? [fromIdx, toIdx] : [toIdx, fromIdx];
|
|
914
|
+
const next: Record<string, boolean> = { ...this.t.state.rowSelection() };
|
|
915
|
+
for (let i = start; i <= end; i++) {
|
|
916
|
+
next[rows[i].id] = selected;
|
|
917
|
+
}
|
|
918
|
+
this.t.table().setRowSelection(next);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Toggle a grouped row's expanded state. Wired to the click handler on the
|
|
923
|
+
* group-cell's toggle button; `stopPropagation` prevents the parent row's
|
|
924
|
+
* `(click)` from firing a row-click event for what is really a UI control.
|
|
925
|
+
*/
|
|
926
|
+
protected onGroupToggle(row: Row<TData>, event: MouseEvent): void {
|
|
927
|
+
event.stopPropagation();
|
|
928
|
+
(row as { toggleExpanded?: () => void }).toggleExpanded?.();
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// ── Header helpers ──────────────────────────────────────────────────────
|
|
932
|
+
protected headerPin(h: Header<TData, unknown>): 'left' | 'right' | null {
|
|
933
|
+
const p = h.column.getIsPinned?.();
|
|
934
|
+
return p === 'left' || p === 'right' ? p : null;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Lucide icon name for a sortable header's indicator. We read the column's
|
|
939
|
+
* current sort state directly so the template re-evaluates whenever the
|
|
940
|
+
* parent sort signal changes (TanStack's table proxy is signal-backed).
|
|
941
|
+
*/
|
|
942
|
+
protected sortIcon(h: Header<TData, unknown>): 'chevron-up' | 'chevron-down' | 'chevrons-up-down' {
|
|
943
|
+
const dir = h.column.getIsSorted?.();
|
|
944
|
+
if (dir === 'asc') return 'chevron-up';
|
|
945
|
+
if (dir === 'desc') return 'chevron-down';
|
|
946
|
+
return 'chevrons-up-down';
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
protected filterTypeFor(col: Column<TData, unknown>): string | null {
|
|
950
|
+
const meta = (col.columnDef.meta as { kj?: KjColumnMeta<TData> } | undefined)?.kj;
|
|
951
|
+
if (!meta) return null;
|
|
952
|
+
if (meta.filterable === false) return null;
|
|
953
|
+
const type = meta.type;
|
|
954
|
+
if (!type) return null;
|
|
955
|
+
return type in BUILTIN_FILTERS ? type : null;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ── Template helpers for the editor / filter outlet directives ─────────
|
|
959
|
+
/** Resolves the editor component for a cell — column `meta.kj.editor`
|
|
960
|
+
* override, else the built-in editor that matches `meta.kj.type`. */
|
|
961
|
+
protected editorTypeFor(cell: Cell<TData, unknown>): Type<unknown> | null {
|
|
962
|
+
const meta = (cell.column.columnDef.meta as { kj?: KjColumnMeta<TData> } | undefined)?.kj;
|
|
963
|
+
const custom = meta?.editor as Type<unknown> | undefined;
|
|
964
|
+
if (custom) return custom;
|
|
965
|
+
const type = meta?.type;
|
|
966
|
+
if (!type) return BUILTIN_EDITORS['text']!;
|
|
967
|
+
return BUILTIN_EDITORS[type] ?? null;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/** Build the meta bag handed to the editor through `KJ_EDITOR_CONTRACT`.
|
|
971
|
+
* Select options, min/max, custom params travel through here so editors
|
|
972
|
+
* don't need imperative input binding from the outlet. */
|
|
973
|
+
protected editorMetaFor(cell: Cell<TData, unknown>): Record<string, unknown> | undefined {
|
|
974
|
+
const meta = (cell.column.columnDef.meta as { kj?: KjColumnMeta<TData> } | undefined)?.kj;
|
|
975
|
+
if (!meta) return undefined;
|
|
976
|
+
const out: Record<string, unknown> = {};
|
|
977
|
+
if (meta.selectOptions) out['options'] = meta.selectOptions;
|
|
978
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/** Resolves the filter component for a column — `meta.kj.filterUi` override
|
|
982
|
+
* else the built-in filter that matches `type`. */
|
|
983
|
+
protected filterRendererFor(col: Column<TData, unknown>, type: string): Type<unknown> | null {
|
|
984
|
+
const meta = (col.columnDef.meta as { kj?: KjColumnMeta<TData> } | undefined)?.kj;
|
|
985
|
+
const custom = meta?.filterUi as Type<unknown> | undefined;
|
|
986
|
+
return custom ?? BUILTIN_FILTERS[type] ?? null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Editor `(commit)` from `KjCellEditorOutlet`: forward + close the editor. */
|
|
990
|
+
protected onEditorCommit(cell: Cell<TData, unknown>, next: unknown): void {
|
|
991
|
+
this.cellEdit.emit({
|
|
992
|
+
row: cell.row.original,
|
|
993
|
+
columnId: cell.column.id,
|
|
994
|
+
oldValue: cell.getValue(),
|
|
995
|
+
newValue: next,
|
|
996
|
+
});
|
|
997
|
+
this.editingCell.set(null);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/** Editor `(cancel)` from `KjCellEditorOutlet`: just close the editor. */
|
|
1001
|
+
protected onEditorCancel(): void {
|
|
1002
|
+
this.editingCell.set(null);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// ── helpers ───────────────────────────────────────────────────────────────
|
|
1007
|
+
function cellMeta<TData>(col: Column<TData, unknown>): KjColumnMeta<TData> | undefined {
|
|
1008
|
+
return (col.columnDef.meta as { kj?: KjColumnMeta<TData> } | undefined)?.kj;
|
|
1009
|
+
}
|