@kouji-ui/components 0.7.1 → 0.9.0

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.
@@ -1,10 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ChangeDetectionStrategy, ViewEncapsulation, Component, input, inject, Injectable, booleanAttribute, computed, TemplateRef, model, ViewChild, contentChildren, ElementRef, PLATFORM_ID, DestroyRef, afterNextRender, effect, signal, Injector, viewChild, untracked, output, EventEmitter, Output, makeEnvironmentProviders, provideEnvironmentInitializer, forwardRef, EnvironmentInjector, Directive, InjectionToken, ViewContainerRef, contentChild, numberAttribute } from '@angular/core';
2
+ import { ChangeDetectionStrategy, ViewEncapsulation, Component, input, inject, Injectable, booleanAttribute, computed, TemplateRef, viewChild, ElementRef, contentChildren, contentChild, model, ViewChild, PLATFORM_ID, DestroyRef, afterNextRender, effect, signal, SecurityContext, Injector, untracked, output, EventEmitter, Output, makeEnvironmentProviders, provideEnvironmentInitializer, forwardRef, EnvironmentInjector, Directive, InjectionToken, ViewContainerRef, numberAttribute } from '@angular/core';
3
3
  import * as i1 from '@kouji-ui/core';
4
- import { KjAccordion, KjAccordionTrigger, KjAccordionItem, KjAccordionContent, SHEET_DATA, KjSheetRef, KjSheet, KjIconDirective, KjSheetService, KjAlert, KjAlertIcon, KjAlertTitle, KjAlertDescription, KjAlertActions, KjAlertDismiss, KJ_AVATAR_GROUP, KjAvatarImage, KjAvatarFallback, KjAvatar, KjAvatarGroup, KjBadge, KjBreadcrumb, KjSize, KjBreadcrumbList, KjBreadcrumbItem, KJ_BREADCRUMB_CONFIG, KjLink, KjBreadcrumbCurrent, KjBreadcrumbSeparator, KjBreadcrumbEllipsis, KjButton, KjButtonGroup, KjCalendar, KjCalendarHeader, KjCalendarGrid, KjCalendarDay, KjCarousel, KjLiveRegion, KjVisuallyHidden, KjCarouselViewport, KjCarouselSlide, KjCarouselPrevious, KjCarouselNext, KJ_CAROUSEL, KjCarouselIndicator, KjCarouselIndicators, KjCarouselAutoplay, KjCarouselPauseToggle, KjChatLog, KjChat, KjChatAvatar, KjChatHeader, KjChatBubble, KjChatFooter, KjChatAnnouncer, parseSlash, matchSlashCommands, KjCheckbox, KjColorPicker, KjColorPickerTrigger, KjColorPickerPanel, KjColorPickerArea, KjColorPickerHueSlider, KjColorPickerAlphaSlider, KjColorPickerInput, KjComboboxInput, KjComboboxListbox, KjCombobox, KjComboboxOption, KjConfirmPopup, KjConfirmPopupTrigger, KjPopoverContent, KjConfirmPopupContent, KjConfirmPopupMessage, KjConfirmPopupAction, KjConfirmPopupCancel, KjDatePicker, KjDatePickerTrigger, KjDatePickerCalendar, defaultDateRangePresets, KjDateRangePresets, KjDateRangePresetOption, KjLocale, KjDropdownMenuTrigger, KjDropdownMenuContent, KjDropdownMenuItem, KjDropdownMenuSeparator, KjDropdownMenuLabel, KjDropdownMenuGroup, KjSpinner, KjVariant, KjEditorLoader, KjEditor, KJ_SIZE_PRESET, KjField, KjFieldLabel, KjFieldHelp, KjFieldError, KjFieldGroup, KjFileUpload, KjFileUploadTrigger, KjFileUploadDropzone, KjFileUploadList, KjFileUploadItem, KjProgressBar, KjProgressBarFill, KjForm, KjFormErrorSummary, KJ_ICON_REGISTRY, KjInput, KjInputGroup, KjInputGroupAddon, KjInputMask, KJ_INPUT_MASK_PRESETS, KjInputOtp, KjInputOtpCell, KjKbd, KJ_KBD_SIZE_PRESET, KjDivider, KJ_VARIANT_PRESET, KjList, KjListRow, KjMenubar, KjMenubarItem, KjNumberInput, KjNumberInputGroup, KjNumberStepper, KJ_OVERLAY_BADGE, KjOverlayBadgeContent, KjOverlayBadge, KjPagination, KjPaginationItem, KjPaginationPrevious, KjPaginationNext, KjPaginationFirst, KjPaginationLast, KjPaginationEllipsis, KjPaginationInfo, KJ_PASSWORD_INPUT, KjPasswordInput, KjPasswordInputScope, KjPasswordToggle, KjPasswordStrength, KjPasswordCapsLockWarning, KjPopoverTrigger, KjPopoverArrow, KjPopoverClose, KjPopoverTitle, KjRadioGroup, KjRadio, injectRteOverlayData, createKjImageNode, KJ_RTE_OVERLAY_DATA, KjRichTextEditor, KjRovingTabindex, KjRovingTabindexItemDirective, KjSelect, KjSelectTrigger, KjSelectContent, KjOption, KjSkeleton, KjSkipLink, KjSlider, KjSliderTrack, KjSliderRange, KjSliderThumb, KjSpeedDial, KjSpeedDialTrigger, KjSpeedDialActions, KjSpeedDialAction, KjStepper, KjStep, KjStepLabel, KjStepContent, KjStepperNext, KjStepperPrevious, KjStepperReset, KjToggle, KJ_TABLE, provideKjFilterParams, KjTable, KJ_TABLE_STORAGE, KjTableHeader, KjTableRow, KjTableCell, KjTableKeyboardNav, DRAWER_DATA, KjDrawer, KjDrawerService, bindPresets, KJ_TABS_CONFIG, KjTabs, KjTabList, KjTab, KjTabPanel, KjTag, KjTagRemove, KjTagList, KjTextarea, KjTimePicker, KjTimePickerHours, KjTimePickerMinutes, KjTimePickerSeconds, KjTimePickerMeridiem, KjToastViewport, KjToast, KjToastClose, KjTooltipTrigger, KjTooltipContent, KjTooltipArrow, KjTooltipGroup, KjCascadeSelect, KjCascadeSelectTrigger, KjCascadeSelectPanel, KjCascadeSelectOption, KjCascadeSelectSubPanel, KjCommandPalette, KjCommandInput, KjCommandList, KjCommandEmpty, injectListItem, KjListItem, KjCommandGroup, KjCommandSeparator, KjTreeSelectNode, KjTreeSelectToggle, KjTreeSelect, KjTreeSelectTrigger, KjTreeSelectContent } from '@kouji-ui/core';
4
+ import { KjAccordion, KjAccordionTrigger, KjAccordionItem, KjAccordionContent, SHEET_DATA, KjSheetRef, KjSheet, KjIconDirective, KjSheetService, KjAlert, KjAlertIcon, KjAlertTitle, KjAlertDescription, KjAlertActions, KjAlertDismiss, KJ_AVATAR_GROUP, KjAvatarImage, KjAvatarFallback, KjAvatar, KjPopoverTrigger, KjPopoverContent, KjPopoverArrow, KjPopoverClose, KjPopoverTitle, KjTranslateService, KjOverflowContent, KjAvatarGroup, KjBadge, KjBreadcrumb, KjSize, KjBreadcrumbList, KjBreadcrumbItem, KJ_BREADCRUMB_CONFIG, KjLink, KjBreadcrumbCurrent, KjBreadcrumbSeparator, KjBreadcrumbEllipsis, KjButton, KjButtonGroup, KjCalendar, KjCalendarHeader, KjCalendarGrid, KjCalendarDay, KjCarousel, KjLiveRegion, KjVisuallyHidden, KjCarouselViewport, KjCarouselSlide, KjCarouselPrevious, KjCarouselNext, KJ_CAROUSEL, KjCarouselIndicator, KjCarouselIndicators, KjCarouselAutoplay, KjCarouselPauseToggle, KjChatLog, KjChat, KjChatAvatar, KjChatHeader, KjChatBubble, KjChatFooter, KjChatAnnouncer, KJ_CHAT_CONFIG, parseSlash, matchSlashCommands, KjCheckbox, KjColorPicker, KjColorPickerTrigger, KjColorPickerPanel, KjColorPickerArea, KjColorPickerHueSlider, KjColorPickerAlphaSlider, KjColorPickerInput, KjComboboxInput, KjComboboxListbox, KjCombobox, KjComboboxOption, KjConfirmPopup, KjConfirmPopupTrigger, KjConfirmPopupContent, KjConfirmPopupMessage, KjConfirmPopupAction, KjConfirmPopupCancel, KjDatePicker, KjDatePickerTrigger, KjDatePickerCalendar, defaultDateRangePresets, KjDateRangePresets, KjDateRangePresetOption, KjLocale, KjDropdownMenuTrigger, KjDropdownMenuContent, KjDropdownMenuItem, KjDropdownMenuSeparator, KjDropdownMenuLabel, KjDropdownMenuGroup, KjSpinner, KjVariant, KjEditorLoader, KjEditor, KJ_SIZE_PRESET, KjField, KjFieldLabel, KjFieldHelp, KjFieldError, KjFieldGroup, KjFileUpload, KjFileUploadTrigger, KjFileUploadDropzone, KjFileUploadList, KjFileUploadItem, KjProgressBar, KjProgressBarFill, KjForm, KjFormErrorSummary, KJ_ICON_REGISTRY, KjInput, KjInputGroup, KjInputGroupAddon, KjInputMask, KJ_INPUT_MASK_PRESETS, KjInputOtp, KjInputOtpCell, KjKbd, KJ_KBD_SIZE_PRESET, KjDivider, KJ_VARIANT_PRESET, KjList, KjListRow, KjMenubar, KjMenubarItem, KjNumberInput, KjNumberInputGroup, KjNumberStepper, KJ_OVERLAY_BADGE, KjOverlayBadgeContent, KjOverlayBadge, KjPagination, KjPaginationItem, KjPaginationPrevious, KjPaginationNext, KjPaginationFirst, KjPaginationLast, KjPaginationEllipsis, KjPaginationInfo, KJ_PASSWORD_INPUT, KjPasswordInput, KjPasswordInputScope, KjPasswordToggle, KjPasswordStrength, KjPasswordCapsLockWarning, KjRadioGroup, KjRadio, injectRteOverlayData, createKjImageNode, KJ_RTE_OVERLAY_DATA, KjRichTextEditor, KjRovingTabindex, KjRovingTabindexItemDirective, KjSelect, KjSelectTrigger, KjSelectContent, KjOption, KjSkeleton, KjSkipLink, KjSlider, KjSliderTrack, KjSliderRange, KjSliderThumb, KjSpeedDial, KjSpeedDialTrigger, KjSpeedDialActions, KjSpeedDialAction, KjStepper, KjStep, KjStepLabel, KjStepContent, KjStepperNext, KjStepperPrevious, KjStepperReset, KjToggle, KJ_TABLE, provideKjFilterParams, KjTable, KJ_TABLE_STORAGE, KjTableHeader, KjTableRow, KjTableCell, KjTableKeyboardNav, DRAWER_DATA, KjDrawer, KjDrawerService, bindPresets, KJ_TABS_CONFIG, KjTabs, KjTabList, KjTab, KjTabPanel, KjTag, KjTagRemove, KjTagList, KjTextarea, KjTimePicker, KjTimePickerHours, KjTimePickerMinutes, KjTimePickerSeconds, KjTimePickerMeridiem, KjToastViewport, KjToast, KjToastClose, KjTooltipTrigger, KjTooltipContent, KjTooltipArrow, KjTooltipGroup, KjCascadeSelect, KjCascadeSelectTrigger, KjCascadeSelectPanel, KjCascadeSelectOption, KjCascadeSelectSubPanel, KjCommandPalette, KjCommandInput, KjCommandList, KjCommandEmpty, injectListItem, KjListItem, KjCommandGroup, KjCommandSeparator, KjTreeSelectNode, KjTreeSelectToggle, KjTreeSelect, KjTreeSelectTrigger, KjTreeSelectContent } from '@kouji-ui/core';
5
5
  export { KJ_DROPDOWN_MENU, KjBlockquote, KjCarousel, KjCarouselSlide, KjCascadeSelectOption, KjChatAnnouncer, KjChatStore, KjCode, KjColorPicker, KjCombobox, KjDatePicker, KjDialog, KjDialogRef, KjDialogService, KjDrawer, KjDrawerRef, KjDrawerService, KjDropdownMenuContent, KjDropdownMenuGroup, KjDropdownMenuItem, KjDropdownMenuLabel, KjDropdownMenuSeparator, KjDropdownMenuTrigger, KjFileUpload, KjInput, KjLead, KjLiveRegion, KjMuted, KjOverlayPanel, KjPopoverArrow, KjPopoverClose, KjPopoverContent, KjPopoverTitle, KjPopoverTrigger, KjRichTextEditor, KjSheet, KjSheetRef, KjSheetService, KjTabPanel, KjTable, KjTooltipArrow, KjTooltipContent, KjTooltipGroup, KjTooltipTrigger, KjTreeSelect, KjTruncate, SHEET_ARIA_LABEL, SHEET_DATA, SHEET_DETENT, SHEET_DISMISSIBLE } from '@kouji-ui/core';
6
6
  import { NgTemplateOutlet, isPlatformBrowser, NgComponentOutlet } from '@angular/common';
7
7
  import { DomSanitizer } from '@angular/platform-browser';
8
+ import { marked } from 'marked';
8
9
  import * as i1$1 from '@angular/forms';
9
10
  import { NG_VALUE_ACCESSOR, FormsModule, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
10
11
  import * as lucideIcons from 'lucide-static';
@@ -773,7 +774,7 @@ class KjAvatarComponent {
773
774
  {{ content() }}
774
775
  }
775
776
  </span>
776
- `, isInline: true, styles: ["@layer kj.component{.kj-avatar{--kj-avatar-size: 2.5rem;--kj-avatar-bg: var(--kj-bg-field);--kj-avatar-fg: var(--kj-fg-default);--kj-avatar-radius: 9999px;position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--kj-avatar-size);height:var(--kj-avatar-size);background:var(--kj-avatar-bg);color:var(--kj-avatar-fg);border-radius:var(--kj-avatar-radius);overflow:hidden;font:.875rem / 1 var(--kj-font-sans);font-weight:600}.kj-avatar[data-shape=rounded]{--kj-avatar-radius: var(--kj-radius-box-lg)}.kj-avatar[hidden]{display:none}.kj-avatar[data-size=xs]{--kj-avatar-size: 1.5rem;font-size:.6875rem}.kj-avatar[data-size=sm]{--kj-avatar-size: 2rem;font-size:.75rem}.kj-avatar[data-size=lg]{--kj-avatar-size: 3rem;font-size:1rem}.kj-avatar[data-size=xl]{--kj-avatar-size: 4rem;font-size:1.25rem}.kj-avatar-image{width:100%;height:100%;object-fit:cover;display:block}.kj-avatar-image:not([data-loaded]){opacity:0}.kj-avatar-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.kj-avatar-fallback:not([data-visible]){opacity:0;pointer-events:none}}\n"], dependencies: [{ kind: "directive", type: KjAvatarImage, selector: "[kjAvatarImage]" }, { kind: "directive", type: KjAvatarFallback, selector: "[kjAvatarFallback]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
777
+ `, isInline: true, styles: ["@layer kj.component{.kj-avatar{--kj-avatar-size: 2.5rem;--kj-avatar-bg: var(--kj-bg-field);--kj-avatar-fg: var(--kj-fg-default);--kj-avatar-radius: 9999px;position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--kj-avatar-size);height:var(--kj-avatar-size);background:var(--kj-avatar-bg);color:var(--kj-avatar-fg);border-radius:var(--kj-avatar-radius);overflow:hidden;font:.875rem / 1 var(--kj-font-sans);font-weight:600}.kj-avatar[data-shape=rounded]{--kj-avatar-radius: var(--kj-radius-box-lg)}.kj-avatar[hidden]{display:none}.kj-avatar-group-overflow-trigger{display:inline-flex;padding:0;border:0;background:none;border-radius:9999px}.kj-avatar-group-overflow-trigger[aria-expanded=true] .kj-avatar{--kj-avatar-bg: var(--kj-bg-primary);--kj-avatar-fg: var(--kj-fg-on-primary)}.kj-avatar[data-size=xs]{--kj-avatar-size: 1.5rem;font-size:.6875rem}.kj-avatar[data-size=sm]{--kj-avatar-size: 2rem;font-size:.75rem}.kj-avatar[data-size=lg]{--kj-avatar-size: 3rem;font-size:1rem}.kj-avatar[data-size=xl]{--kj-avatar-size: 4rem;font-size:1.25rem}.kj-avatar-image{width:100%;height:100%;object-fit:cover;display:block}.kj-avatar-image:not([data-loaded]){opacity:0}.kj-avatar-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.kj-avatar-fallback:not([data-visible]){opacity:0;pointer-events:none}}\n"], dependencies: [{ kind: "directive", type: KjAvatarImage, selector: "[kjAvatarImage]" }, { kind: "directive", type: KjAvatarFallback, selector: "[kjAvatarFallback]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
777
778
  }
778
779
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjAvatarComponent, decorators: [{
779
780
  type: Component,
@@ -795,9 +796,206 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
795
796
  // Mirror `alt` to `title` so hovering the avatar shows a native tooltip.
796
797
  // `alt` alone only surfaces to screen readers / fallback rendering.
797
798
  '[attr.title]': 'alt()',
798
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-avatar{--kj-avatar-size: 2.5rem;--kj-avatar-bg: var(--kj-bg-field);--kj-avatar-fg: var(--kj-fg-default);--kj-avatar-radius: 9999px;position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--kj-avatar-size);height:var(--kj-avatar-size);background:var(--kj-avatar-bg);color:var(--kj-avatar-fg);border-radius:var(--kj-avatar-radius);overflow:hidden;font:.875rem / 1 var(--kj-font-sans);font-weight:600}.kj-avatar[data-shape=rounded]{--kj-avatar-radius: var(--kj-radius-box-lg)}.kj-avatar[hidden]{display:none}.kj-avatar[data-size=xs]{--kj-avatar-size: 1.5rem;font-size:.6875rem}.kj-avatar[data-size=sm]{--kj-avatar-size: 2rem;font-size:.75rem}.kj-avatar[data-size=lg]{--kj-avatar-size: 3rem;font-size:1rem}.kj-avatar[data-size=xl]{--kj-avatar-size: 4rem;font-size:1.25rem}.kj-avatar-image{width:100%;height:100%;object-fit:cover;display:block}.kj-avatar-image:not([data-loaded]){opacity:0}.kj-avatar-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.kj-avatar-fallback:not([data-visible]){opacity:0;pointer-events:none}}\n"] }]
799
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-avatar{--kj-avatar-size: 2.5rem;--kj-avatar-bg: var(--kj-bg-field);--kj-avatar-fg: var(--kj-fg-default);--kj-avatar-radius: 9999px;position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--kj-avatar-size);height:var(--kj-avatar-size);background:var(--kj-avatar-bg);color:var(--kj-avatar-fg);border-radius:var(--kj-avatar-radius);overflow:hidden;font:.875rem / 1 var(--kj-font-sans);font-weight:600}.kj-avatar[data-shape=rounded]{--kj-avatar-radius: var(--kj-radius-box-lg)}.kj-avatar[hidden]{display:none}.kj-avatar-group-overflow-trigger{display:inline-flex;padding:0;border:0;background:none;border-radius:9999px}.kj-avatar-group-overflow-trigger[aria-expanded=true] .kj-avatar{--kj-avatar-bg: var(--kj-bg-primary);--kj-avatar-fg: var(--kj-fg-on-primary)}.kj-avatar[data-size=xs]{--kj-avatar-size: 1.5rem;font-size:.6875rem}.kj-avatar[data-size=sm]{--kj-avatar-size: 2rem;font-size:.75rem}.kj-avatar[data-size=lg]{--kj-avatar-size: 3rem;font-size:1rem}.kj-avatar[data-size=xl]{--kj-avatar-size: 4rem;font-size:1.25rem}.kj-avatar-image{width:100%;height:100%;object-fit:cover;display:block}.kj-avatar-image:not([data-loaded]){opacity:0}.kj-avatar-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.kj-avatar-fallback:not([data-visible]){opacity:0;pointer-events:none}}\n"] }]
799
800
  }], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }] } });
800
801
 
802
+ /**
803
+ * Click-triggered popover. Compose `[kjPopoverTrigger]` + `<kj-popover-content [kjFor]="t">`
804
+ * with optional `[kjPopoverTitle]` heading and `[kjPopoverClose]` button. The
805
+ * wrapper itself only projects content — its purpose is to host the docs tags.
806
+ *
807
+ * @doc
808
+ * @doc-name popover
809
+ * @doc-is-main
810
+ * @doc-example Default
811
+ * A trigger button + content panel — the bare-minimum recipe.
812
+ * @doc-file popover.example.ts
813
+ * @doc-example Usage
814
+ * A walkthrough of the most common usages — info panel, side-pinned, and
815
+ * modal confirmation. Use this as the copy-paste starting point.
816
+ * @doc-file popover.usage.example.ts
817
+ * @doc-example Sides
818
+ * `kjSide="top|right|bottom|left"` pins the panel relative to the trigger.
819
+ * @doc-file popover.sides.example.ts
820
+ * @doc-example Modal
821
+ * `[kjTrap]="true"` traps focus inside the panel until it closes.
822
+ * @doc-file popover.modal.example.ts
823
+ * @doc-example Cancellable
824
+ * `[kjPopoverClose]` on a button dismisses the panel and restores focus.
825
+ * @doc-file popover.cancellable.example.ts
826
+ * @doc-example With form
827
+ * Embed inputs inside the panel — focus returns to the trigger on close.
828
+ * @doc-file popover.with-form.example.ts
829
+ *
830
+ * @doc-keyboard
831
+ * Enter|Space — Opens the popover from the trigger
832
+ * Escape — Closes the popover and returns focus to the trigger
833
+ * Tab — Moves focus through the panel; cycles inside when [kjTrap]="true"
834
+ * Shift+Tab — Reverse-cycles focus
835
+ *
836
+ * @doc-aria
837
+ * aria-haspopup — Set to "dialog" on the trigger
838
+ * aria-expanded — Reflects the open/closed state on the trigger
839
+ * aria-controls — Links the trigger to the panel id
840
+ * role="dialog" — On the panel (provided via the overlay primitive)
841
+ * aria-labelledby — Wire to `[kjPopoverTitle]` heading id for an accessible name
842
+ *
843
+ * @doc-touch
844
+ * Trigger buttons inherit `kj-button` sizing — use `kjSize="md"` or larger
845
+ * to satisfy WCAG 2.5.5 (44×44). The panel itself is not a touch target.
846
+ *
847
+ * @doc-a11y
848
+ * The panel is portalled to `document.body` so it escapes clipping
849
+ * ancestors while staying linked via `aria-controls`. Focus is returned
850
+ * to the trigger on close, and outside clicks dismiss by default. For
851
+ * destructive confirmations, set `[kjTrap]="true"` so the user must
852
+ * commit or cancel via a visible affordance.
853
+ *
854
+ * @doc-related dialog,tooltip,dropdown-menu
855
+ *
856
+ * @doc-css-var
857
+ * --kj-popover-bg — Panel background fill. Inherits --kj-bg-elevated.
858
+ * --kj-popover-fg — Panel foreground (text) color.
859
+ * --kj-popover-border-color — Panel border color. Inherits --kj-border-default.
860
+ * --kj-popover-radius — Panel corner radius. Inherits --kj-radius-box.
861
+ * --kj-popover-padding-x — Horizontal padding inside the panel.
862
+ * --kj-popover-padding-y — Vertical padding inside the panel.
863
+ * --kj-popover-shadow — Drop shadow under the panel. Inherits --kj-shadow-md.
864
+ * --kj-popover-arrow-size — Edge length of the optional arrow indicator.
865
+ *
866
+ * @doc-category Library/Overlay
867
+ */
868
+ class KjPopoverComponent {
869
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
870
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: KjPopoverComponent, isStandalone: true, selector: "kj-popover", host: { styleAttribute: "display: contents;" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-popover-content{--kj-popover-bg: var(--kj-bg-elevated);--kj-popover-fg: var(--kj-fg-default);--kj-popover-border-color: var(--kj-border-default);--kj-popover-radius: var(--kj-radius-box);--kj-popover-padding-x: var(--kj-space-md);--kj-popover-padding-y: var(--kj-space-md);--kj-popover-shadow: var(--kj-shadow-md, 0 12px 32px rgb(0 0 0 / .18));--kj-popover-arrow-size: 8px;display:block;box-sizing:border-box;max-width:min(28rem,calc(100vw - 2rem));background:var(--kj-popover-bg);color:var(--kj-popover-fg);border:var(--kj-border) solid var(--kj-popover-border-color);border-radius:var(--kj-popover-radius);padding:var(--kj-popover-padding-y) var(--kj-popover-padding-x);font:.875rem / 1.5 var(--kj-font-sans);box-shadow:var(--kj-popover-shadow);pointer-events:auto;z-index:1000;word-wrap:break-word}.kj-popover-content[hidden]{display:none}.kj-popover-title{display:block;font-size:.9375rem;font-weight:600;margin:0 0 var(--kj-space-xs);color:var(--kj-fg-default)}.kj-popover-arrow{position:absolute;width:var(--kj-popover-arrow-size, 8px);height:var(--kj-popover-arrow-size, 8px);background:var(--kj-popover-bg, var(--kj-bg-elevated));border:var(--kj-border) solid var(--kj-popover-border-color, var(--kj-border-default));transform:rotate(45deg)}.kj-popover-content[data-side=top] .kj-popover-arrow{bottom:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-top-color:transparent;border-left-color:transparent}.kj-popover-content[data-side=bottom] .kj-popover-arrow{top:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-bottom-color:transparent;border-right-color:transparent}.kj-popover-content[data-side=left] .kj-popover-arrow{right:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-left-color:transparent;border-bottom-color:transparent}.kj-popover-content[data-side=right] .kj-popover-arrow{left:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-right-color:transparent;border-top-color:transparent}body[data-kj-scroll-lock=true]{overflow:hidden}@media(prefers-reduced-motion:no-preference){.kj-popover-content{transition:opacity .14s ease-out,transform .14s ease-out}.kj-popover-content[data-state=closed]{opacity:0;transform:scale(.98)}.kj-popover-content[data-state=open]{opacity:1;transform:scale(1)}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
871
+ }
872
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjPopoverComponent, decorators: [{
873
+ type: Component,
874
+ args: [{ selector: 'kj-popover', standalone: true, imports: [KjPopoverTrigger, KjPopoverContent, KjPopoverArrow, KjPopoverClose, KjPopoverTitle], template: `<ng-content />`, encapsulation: ViewEncapsulation.None, host: { style: 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-popover-content{--kj-popover-bg: var(--kj-bg-elevated);--kj-popover-fg: var(--kj-fg-default);--kj-popover-border-color: var(--kj-border-default);--kj-popover-radius: var(--kj-radius-box);--kj-popover-padding-x: var(--kj-space-md);--kj-popover-padding-y: var(--kj-space-md);--kj-popover-shadow: var(--kj-shadow-md, 0 12px 32px rgb(0 0 0 / .18));--kj-popover-arrow-size: 8px;display:block;box-sizing:border-box;max-width:min(28rem,calc(100vw - 2rem));background:var(--kj-popover-bg);color:var(--kj-popover-fg);border:var(--kj-border) solid var(--kj-popover-border-color);border-radius:var(--kj-popover-radius);padding:var(--kj-popover-padding-y) var(--kj-popover-padding-x);font:.875rem / 1.5 var(--kj-font-sans);box-shadow:var(--kj-popover-shadow);pointer-events:auto;z-index:1000;word-wrap:break-word}.kj-popover-content[hidden]{display:none}.kj-popover-title{display:block;font-size:.9375rem;font-weight:600;margin:0 0 var(--kj-space-xs);color:var(--kj-fg-default)}.kj-popover-arrow{position:absolute;width:var(--kj-popover-arrow-size, 8px);height:var(--kj-popover-arrow-size, 8px);background:var(--kj-popover-bg, var(--kj-bg-elevated));border:var(--kj-border) solid var(--kj-popover-border-color, var(--kj-border-default));transform:rotate(45deg)}.kj-popover-content[data-side=top] .kj-popover-arrow{bottom:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-top-color:transparent;border-left-color:transparent}.kj-popover-content[data-side=bottom] .kj-popover-arrow{top:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-bottom-color:transparent;border-right-color:transparent}.kj-popover-content[data-side=left] .kj-popover-arrow{right:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-left-color:transparent;border-bottom-color:transparent}.kj-popover-content[data-side=right] .kj-popover-arrow{left:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-right-color:transparent;border-top-color:transparent}body[data-kj-scroll-lock=true]{overflow:hidden}@media(prefers-reduced-motion:no-preference){.kj-popover-content{transition:opacity .14s ease-out,transform .14s ease-out}.kj-popover-content[data-state=closed]{opacity:0;transform:scale(.98)}.kj-popover-content[data-state=open]{opacity:1;transform:scale(1)}}}\n"] }]
875
+ }] });
876
+
877
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
878
+ /**
879
+ * The panel a collapsing group (`kj-tag-list`, `kj-avatar-group`) opens from
880
+ * its "+N" chip: the consumer's `kjOverflowContent` template when one is
881
+ * projected, otherwise a plain list of the collapsed items' labels.
882
+ *
883
+ * Owns the keyboard story the hover trigger cannot: `focusFirst()` moves
884
+ * focus into the panel (the chip calls it on ArrowDown / Enter), Escape
885
+ * inside the panel returns focus to the chip, and focus leaving the panel
886
+ * closes it.
887
+ *
888
+ * @doc-category Library/Data display
889
+ * @doc
890
+ * @doc-name overflow
891
+ */
892
+ class KjOverflowPanelComponent {
893
+ /** The "+N" chip's popover trigger. */
894
+ kjFor = input.required(/* @ts-ignore */
895
+ ...(ngDevMode ? [{ debugName: "kjFor" }] : /* istanbul ignore next */ []));
896
+ /** Number of collapsed items. */
897
+ kjCount = input.required(/* @ts-ignore */
898
+ ...(ngDevMode ? [{ debugName: "kjCount" }] : /* istanbul ignore next */ []));
899
+ /** Index of the first collapsed item in the consumer's collection. */
900
+ kjStart = input.required(/* @ts-ignore */
901
+ ...(ngDevMode ? [{ debugName: "kjStart" }] : /* istanbul ignore next */ []));
902
+ /** Exclusive end index of the collapsed range. */
903
+ kjEnd = input.required(/* @ts-ignore */
904
+ ...(ngDevMode ? [{ debugName: "kjEnd" }] : /* istanbul ignore next */ []));
905
+ /** Labels of the collapsed items — the default panel content. */
906
+ kjLabels = input([], /* @ts-ignore */
907
+ ...(ngDevMode ? [{ debugName: "kjLabels" }] : /* istanbul ignore next */ []));
908
+ /** Consumer template rendered instead of the label list. */
909
+ kjTemplate = input(undefined, /* @ts-ignore */
910
+ ...(ngDevMode ? [{ debugName: "kjTemplate" }] : /* istanbul ignore next */ []));
911
+ kjSide = input('bottom', /* @ts-ignore */
912
+ ...(ngDevMode ? [{ debugName: "kjSide" }] : /* istanbul ignore next */ []));
913
+ kjAlign = input('start', /* @ts-ignore */
914
+ ...(ngDevMode ? [{ debugName: "kjAlign" }] : /* istanbul ignore next */ []));
915
+ panel = viewChild(KjPopoverContent, { ...(ngDevMode ? { debugName: "panel" } : /* istanbul ignore next */ {}), read: ElementRef });
916
+ context = computed(() => ({
917
+ $implicit: this.kjCount(),
918
+ start: this.kjStart(),
919
+ end: this.kjEnd(),
920
+ labels: this.kjLabels(),
921
+ }), /* @ts-ignore */
922
+ ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
923
+ /** Moves focus to the first focusable element of the panel (or the panel itself). */
924
+ focusFirst() {
925
+ const panel = this.panel()?.nativeElement;
926
+ if (!panel)
927
+ return;
928
+ const first = panel.querySelector(FOCUSABLE);
929
+ if (first) {
930
+ first.focus();
931
+ return;
932
+ }
933
+ if (!panel.hasAttribute('tabindex'))
934
+ panel.setAttribute('tabindex', '-1');
935
+ panel.focus();
936
+ }
937
+ focusTrigger() {
938
+ this.kjFor().controller.context.triggerEl()?.focus();
939
+ }
940
+ onFocusOut(event) {
941
+ const panel = this.panel()?.nativeElement;
942
+ const next = event.relatedTarget;
943
+ if (!panel || !next || panel.contains(next))
944
+ return;
945
+ if (this.kjFor().controller.context.triggerEl()?.contains(next))
946
+ return;
947
+ this.kjFor().controller.close('programmatic');
948
+ }
949
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjOverflowPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: KjOverflowPanelComponent, isStandalone: true, selector: "kj-overflow-panel", inputs: { kjFor: { classPropertyName: "kjFor", publicName: "kjFor", isSignal: true, isRequired: true, transformFunction: null }, kjCount: { classPropertyName: "kjCount", publicName: "kjCount", isSignal: true, isRequired: true, transformFunction: null }, kjStart: { classPropertyName: "kjStart", publicName: "kjStart", isSignal: true, isRequired: true, transformFunction: null }, kjEnd: { classPropertyName: "kjEnd", publicName: "kjEnd", isSignal: true, isRequired: true, transformFunction: null }, kjLabels: { classPropertyName: "kjLabels", publicName: "kjLabels", isSignal: true, isRequired: false, transformFunction: null }, kjTemplate: { classPropertyName: "kjTemplate", publicName: "kjTemplate", isSignal: true, isRequired: false, transformFunction: null }, kjSide: { classPropertyName: "kjSide", publicName: "kjSide", isSignal: true, isRequired: false, transformFunction: null }, kjAlign: { classPropertyName: "kjAlign", publicName: "kjAlign", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: contents;" }, viewQueries: [{ propertyName: "panel", first: true, predicate: KjPopoverContent, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `
951
+ <kj-popover>
952
+ <kj-popover-content
953
+ class="kj-overflow-panel"
954
+ [kjFor]="kjFor()"
955
+ [kjSide]="kjSide()"
956
+ [kjAlign]="kjAlign()"
957
+ (keydown.escape)="focusTrigger()"
958
+ (focusout)="onFocusOut($event)"
959
+ >
960
+ @if (kjTemplate(); as tpl) {
961
+ <ng-container *ngTemplateOutlet="tpl.template; context: context()" />
962
+ } @else {
963
+ <ul class="kj-overflow-list" role="list">
964
+ @for (label of kjLabels(); track $index) {
965
+ <li class="kj-overflow-item">{{ label }}</li>
966
+ }
967
+ </ul>
968
+ }
969
+ </kj-popover-content>
970
+ </kj-popover>
971
+ `, isInline: true, styles: ["@layer kj.component{.kj-overflow-panel{--kj-popover-padding-x: var(--kj-space-sm);--kj-popover-padding-y: var(--kj-space-sm);min-width:10rem}.kj-overflow-list{display:flex;flex-direction:column;gap:var(--kj-space-xs);margin:0;padding:0;list-style:none}.kj-overflow-item{padding:var(--kj-space-xs) var(--kj-space-sm);font-size:var(--kj-text-sm);line-height:1.4;color:var(--kj-fg-default);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kj-overflow-trigger{appearance:none;font:inherit;cursor:pointer}.kj-overflow-trigger:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: KjPopoverComponent, selector: "kj-popover" }, { kind: "component", type: KjPopoverContent, selector: "kj-popover-content", inputs: ["kjSide", "kjAlign", "kjOffset", "kjTrap"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
972
+ }
973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjOverflowPanelComponent, decorators: [{
974
+ type: Component,
975
+ args: [{ selector: 'kj-overflow-panel', standalone: true, imports: [NgTemplateOutlet, KjPopoverComponent, KjPopoverContent], template: `
976
+ <kj-popover>
977
+ <kj-popover-content
978
+ class="kj-overflow-panel"
979
+ [kjFor]="kjFor()"
980
+ [kjSide]="kjSide()"
981
+ [kjAlign]="kjAlign()"
982
+ (keydown.escape)="focusTrigger()"
983
+ (focusout)="onFocusOut($event)"
984
+ >
985
+ @if (kjTemplate(); as tpl) {
986
+ <ng-container *ngTemplateOutlet="tpl.template; context: context()" />
987
+ } @else {
988
+ <ul class="kj-overflow-list" role="list">
989
+ @for (label of kjLabels(); track $index) {
990
+ <li class="kj-overflow-item">{{ label }}</li>
991
+ }
992
+ </ul>
993
+ }
994
+ </kj-popover-content>
995
+ </kj-popover>
996
+ `, encapsulation: ViewEncapsulation.None, host: { style: 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-overflow-panel{--kj-popover-padding-x: var(--kj-space-sm);--kj-popover-padding-y: var(--kj-space-sm);min-width:10rem}.kj-overflow-list{display:flex;flex-direction:column;gap:var(--kj-space-xs);margin:0;padding:0;list-style:none}.kj-overflow-item{padding:var(--kj-space-xs) var(--kj-space-sm);font-size:var(--kj-text-sm);line-height:1.4;color:var(--kj-fg-default);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kj-overflow-trigger{appearance:none;font:inherit;cursor:pointer}.kj-overflow-trigger:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"] }]
997
+ }], propDecorators: { kjFor: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjFor", required: true }] }], kjCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjCount", required: true }] }], kjStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjStart", required: true }] }], kjEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjEnd", required: true }] }], kjLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjLabels", required: false }] }], kjTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjTemplate", required: false }] }], kjSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSide", required: false }] }], kjAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAlign", required: false }] }], panel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => KjPopoverContent), { ...{ read: ElementRef }, isSignal: true }] }] } });
998
+
801
999
  /**
802
1000
  * Avatar group wrapper. Stacks projected `<kj-avatar>` children with a
803
1001
  * count-aware `aria-label`, an overflow chip when `kjMax` is exceeded, and
@@ -805,10 +1003,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
805
1003
  * core directive. The wrapper supplies the chip element and the host
806
1004
  * `<ng-content/>` slot.
807
1005
  *
808
- * The overflow chip is a regular `<kj-avatar>` rendering `'+N'` text; it is
809
- * `aria-hidden` because the group's count-aware label already conveys
810
- * totality (e.g. `"3 of 8 collaborators"`), so exposing the chip again would
811
- * duplicate the announcement.
1006
+ * The overflow chip is a button: hovering, focusing or tapping it opens a
1007
+ * panel listing the collapsed avatars (their `alt`, else their text content).
1008
+ * Project an `<ng-template kjOverflowContent>` to render that panel yourself
1009
+ * the context carries the collapsed range (`start`/`end`) so the consumer
1010
+ * slices its own data and adds actions (remove, open a profile…).
1011
+ *
1012
+ * The `<kj-avatar>` inside the chip renders `'+N'` and is `aria-hidden`
1013
+ * because the group's count-aware label already conveys totality (e.g.
1014
+ * `"3 of 8 collaborators"`); the button itself is named "Show N more".
812
1015
  *
813
1016
  * @doc-example Default
814
1017
  * The default playground — five stacked avatars with the group's defaults.
@@ -818,8 +1021,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
818
1021
  * and a list-mode variant for documents-shared screens.
819
1022
  * @doc-file avatar-group.usage.example.ts
820
1023
  * @doc-example Overflow
821
- * `kjMax` caps visible avatars; the remainder collapses into a "+N" chip.
1024
+ * `kjMax` caps visible avatars; the remainder collapses into a "+N" chip whose
1025
+ * hover panel lists the hidden names.
822
1026
  * @doc-file avatar-group.overflow.example.ts
1027
+ * @doc-example Overflow template
1028
+ * `<ng-template kjOverflowContent>` renders the hidden avatars your way — here
1029
+ * with a remove action per collaborator.
1030
+ * @doc-file avatar-group.overflow-template.example.ts
823
1031
  * @doc-example Sizes
824
1032
  * Group `kjSize` cascades onto every projected `<kj-avatar>` for a uniform pile.
825
1033
  * @doc-file avatar-group.sizes.example.ts
@@ -830,24 +1038,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
830
1038
  * `kjRole="list"` switches to a list landmark — use for "shared by" rows in docs UIs.
831
1039
  * @doc-file avatar-group.list.example.ts
832
1040
  *
1041
+ * @doc-keyboard
1042
+ * Tab — Focuses the "+N" chip; the panel opens
1043
+ * ArrowDown|Enter — Moves focus into the panel
1044
+ * Escape — Closes the panel and returns focus to the chip
1045
+ *
833
1046
  * @doc-aria
834
1047
  * aria-label — Count-aware label like "3 of 8 collaborators"; override via `kjAriaLabelOverride`
835
1048
  * role — Defaults to `img` (face-pile); set `kjRole="list"` for a true list landmark
836
- * aria-hidden — Forced on the +N chip so the count isn't re-announced
1049
+ * aria-hidden — Forced on the +N avatar so the count isn't re-announced; the chip button is named "Show N more"
1050
+ * aria-expanded — Reflects the panel state on the chip button
837
1051
  *
838
1052
  * @doc-touch
839
- * Stacked avatars are decorative — they aren't tab stops. Pair the group
840
- * with an interactive trigger (button/link) sized 44×44 when clicking is
841
- * meaningful (e.g. opens a collaborator dialog).
1053
+ * Stacked avatars are decorative — they aren't tab stops. The "+N" chip is
1054
+ * a real button (tap opens the panel); pair the group with an interactive
1055
+ * trigger sized 44×44 when clicking an avatar is meaningful.
842
1056
  *
843
1057
  * @doc-a11y
844
1058
  * The face-pile is an `img` landmark with a count-aware accessible name so
845
1059
  * AT users hear "3 of 8 collaborators" rather than every initial. Switch to
846
1060
  * `role="list"` whenever the order/identity of each face matters
847
- * semantically. The overflow chip is always `aria-hidden` because the
848
- * group's name already conveys totality.
1061
+ * semantically. The "+N" chip is a named button, so the collapsed names are
1062
+ * one keystroke away for keyboard and screen-reader users.
849
1063
  *
850
- * @doc-related avatar,badge,tag
1064
+ * @doc-related avatar,badge,tag,overflow
851
1065
  *
852
1066
  * @doc-css-var
853
1067
  * --kj-avatar-size — Avatar diameter. Sizes (xs/sm/md/lg/xl) override.
@@ -868,9 +1082,34 @@ class KjAvatarGroupComponent {
868
1082
  * the same instance and gives us access to `overflowCount`, `shape`, etc.
869
1083
  */
870
1084
  group = inject(KJ_AVATAR_GROUP);
1085
+ i18n = inject(KjTranslateService);
1086
+ /** Text of the "+N" chip. Defaults to the `overflow.more` translation (`+{count}`). */
1087
+ kjOverflowLabel = input(undefined, /* @ts-ignore */
1088
+ ...(ngDevMode ? [{ debugName: "kjOverflowLabel" }] : /* istanbul ignore next */ []));
1089
+ /** Accessible name of the chip button. Defaults to the `overflow.show` translation (`Show {count} more`). */
1090
+ kjOverflowAriaLabel = input(undefined, /* @ts-ignore */
1091
+ ...(ngDevMode ? [{ debugName: "kjOverflowAriaLabel" }] : /* istanbul ignore next */ []));
1092
+ avatars = contentChildren(KjAvatarComponent, { ...(ngDevMode ? { debugName: "avatars" } : /* istanbul ignore next */ {}), descendants: true });
1093
+ overflowTemplate = contentChild(KjOverflowContent, /* @ts-ignore */
1094
+ ...(ngDevMode ? [{ debugName: "overflowTemplate" }] : /* istanbul ignore next */ []));
1095
+ panel = viewChild(KjOverflowPanelComponent, /* @ts-ignore */
1096
+ ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
871
1097
  overflowCount = this.group.overflowCount;
872
- overflowLabel = computed(() => `+${this.overflowCount()}`, /* @ts-ignore */
1098
+ overflowLabel = computed(() => {
1099
+ const n = this.overflowCount();
1100
+ return this.kjOverflowLabel()?.(n) ?? this.i18n.translate('overflow.more', { count: n });
1101
+ }, /* @ts-ignore */
873
1102
  ...(ngDevMode ? [{ debugName: "overflowLabel" }] : /* istanbul ignore next */ []));
1103
+ overflowAriaLabel = computed(() => {
1104
+ const n = this.overflowCount();
1105
+ return this.kjOverflowAriaLabel()?.(n) ?? this.i18n.translate('overflow.show', { count: n });
1106
+ }, /* @ts-ignore */
1107
+ ...(ngDevMode ? [{ debugName: "overflowAriaLabel" }] : /* istanbul ignore next */ []));
1108
+ /** Labels of the collapsed avatars: `alt`, else a string `content`. */
1109
+ hiddenLabels = computed(() => this.avatars()
1110
+ .slice(this.group.visibleCount())
1111
+ .map((a) => a.alt() ?? (typeof a.content() === 'string' ? a.content() : '')), /* @ts-ignore */
1112
+ ...(ngDevMode ? [{ debugName: "hiddenLabels" }] : /* istanbul ignore next */ []));
874
1113
  /**
875
1114
  * Chip shape mirrors the group's default shape so the chip visually
876
1115
  * matches the avatars. `<kj-avatar>`'s own `shape` default is `'circle'`,
@@ -879,18 +1118,41 @@ class KjAvatarGroupComponent {
879
1118
  */
880
1119
  chipShape = computed(() => this.group.shape() ?? 'circle', /* @ts-ignore */
881
1120
  ...(ngDevMode ? [{ debugName: "chipShape" }] : /* istanbul ignore next */ []));
1121
+ focusPanel(event) {
1122
+ event.preventDefault();
1123
+ this.panel()?.focusFirst();
1124
+ }
882
1125
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjAvatarGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
883
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: KjAvatarGroupComponent, isStandalone: true, selector: "kj-avatar-group", host: { classAttribute: "kj-avatar-group" }, hostDirectives: [{ directive: i1.KjAvatarGroup, inputs: ["kjMax", "kjMax", "kjTotal", "kjTotal", "kjSize", "kjSize", "kjShape", "kjShape", "kjAriaLabel", "kjAriaLabel", "kjAriaLabelFormat", "kjAriaLabelFormat", "kjRole", "kjRole", "kjAriaLabelOverride", "kjAriaLabelOverride"] }], ngImport: i0, template: `
1126
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: KjAvatarGroupComponent, isStandalone: true, selector: "kj-avatar-group", inputs: { kjOverflowLabel: { classPropertyName: "kjOverflowLabel", publicName: "kjOverflowLabel", isSignal: true, isRequired: false, transformFunction: null }, kjOverflowAriaLabel: { classPropertyName: "kjOverflowAriaLabel", publicName: "kjOverflowAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kj-avatar-group" }, queries: [{ propertyName: "avatars", predicate: KjAvatarComponent, descendants: true, isSignal: true }, { propertyName: "overflowTemplate", first: true, predicate: KjOverflowContent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: KjOverflowPanelComponent, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.KjAvatarGroup, inputs: ["kjMax", "kjMax", "kjTotal", "kjTotal", "kjSize", "kjSize", "kjShape", "kjShape", "kjAriaLabel", "kjAriaLabel", "kjAriaLabelFormat", "kjAriaLabelFormat", "kjRole", "kjRole", "kjAriaLabelOverride", "kjAriaLabelOverride"] }], ngImport: i0, template: `
884
1127
  <ng-content />
885
1128
  @if (overflowCount() > 0) {
886
- <kj-avatar
887
- class="kj-avatar-group-overflow"
888
- aria-hidden="true"
889
- [content]="overflowLabel()"
890
- [shape]="chipShape()"
1129
+ <button
1130
+ type="button"
1131
+ class="kj-overflow-trigger kj-avatar-group-overflow-trigger"
1132
+ kjPopoverTrigger
1133
+ kjTrigger="hover"
1134
+ #trg="kjPopoverTrigger"
1135
+ [attr.aria-label]="overflowAriaLabel()"
1136
+ (keydown.arrowdown)="focusPanel($event)"
1137
+ (keydown.enter)="focusPanel($event)"
1138
+ >
1139
+ <kj-avatar
1140
+ class="kj-avatar-group-overflow"
1141
+ aria-hidden="true"
1142
+ [content]="overflowLabel()"
1143
+ [shape]="chipShape()"
1144
+ />
1145
+ </button>
1146
+ <kj-overflow-panel
1147
+ [kjFor]="trg"
1148
+ [kjCount]="overflowCount()"
1149
+ [kjStart]="group.visibleCount()"
1150
+ [kjEnd]="group.total()"
1151
+ [kjLabels]="hiddenLabels()"
1152
+ [kjTemplate]="overflowTemplate()"
891
1153
  />
892
1154
  }
893
- `, isInline: true, dependencies: [{ kind: "component", type: KjAvatarComponent, selector: "kj-avatar", inputs: ["src", "alt", "content", "size", "shape"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1155
+ `, isInline: true, dependencies: [{ kind: "component", type: KjAvatarComponent, selector: "kj-avatar", inputs: ["src", "alt", "content", "size", "shape"] }, { kind: "directive", type: KjPopoverTrigger, selector: "[kjPopoverTrigger]", inputs: ["kjTrigger", "kjDisabled", "kjOpenDelay", "kjCloseDelay"], exportAs: ["kjPopoverTrigger"] }, { kind: "component", type: KjOverflowPanelComponent, selector: "kj-overflow-panel", inputs: ["kjFor", "kjCount", "kjStart", "kjEnd", "kjLabels", "kjTemplate", "kjSide", "kjAlign"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
894
1156
  }
895
1157
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjAvatarGroupComponent, decorators: [{
896
1158
  type: Component,
@@ -912,25 +1174,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
912
1174
  ],
913
1175
  },
914
1176
  ],
915
- imports: [KjAvatarComponent],
1177
+ imports: [KjAvatarComponent, KjPopoverTrigger, KjOverflowPanelComponent],
916
1178
  template: `
917
1179
  <ng-content />
918
1180
  @if (overflowCount() > 0) {
919
- <kj-avatar
920
- class="kj-avatar-group-overflow"
921
- aria-hidden="true"
922
- [content]="overflowLabel()"
923
- [shape]="chipShape()"
1181
+ <button
1182
+ type="button"
1183
+ class="kj-overflow-trigger kj-avatar-group-overflow-trigger"
1184
+ kjPopoverTrigger
1185
+ kjTrigger="hover"
1186
+ #trg="kjPopoverTrigger"
1187
+ [attr.aria-label]="overflowAriaLabel()"
1188
+ (keydown.arrowdown)="focusPanel($event)"
1189
+ (keydown.enter)="focusPanel($event)"
1190
+ >
1191
+ <kj-avatar
1192
+ class="kj-avatar-group-overflow"
1193
+ aria-hidden="true"
1194
+ [content]="overflowLabel()"
1195
+ [shape]="chipShape()"
1196
+ />
1197
+ </button>
1198
+ <kj-overflow-panel
1199
+ [kjFor]="trg"
1200
+ [kjCount]="overflowCount()"
1201
+ [kjStart]="group.visibleCount()"
1202
+ [kjEnd]="group.total()"
1203
+ [kjLabels]="hiddenLabels()"
1204
+ [kjTemplate]="overflowTemplate()"
924
1205
  />
925
1206
  }
926
1207
  `,
927
1208
  encapsulation: ViewEncapsulation.None,
928
1209
  host: {
929
- 'class': 'kj-avatar-group',
1210
+ class: 'kj-avatar-group',
930
1211
  },
931
1212
  changeDetection: ChangeDetectionStrategy.OnPush,
932
1213
  }]
933
- }] });
1214
+ }], propDecorators: { kjOverflowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOverflowLabel", required: false }] }], kjOverflowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOverflowAriaLabel", required: false }] }], avatars: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KjAvatarComponent), { ...{ descendants: true }, isSignal: true }] }], overflowTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KjOverflowContent), { isSignal: true }] }], panel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => KjOverflowPanelComponent), { isSignal: true }] }] } });
934
1215
 
935
1216
  /**
936
1217
  * Styled wrapper around the headless `KjBadge` directive.
@@ -1501,7 +1782,7 @@ class KjButtonComponent {
1501
1782
  <ng-content />
1502
1783
  }
1503
1784
  </button>
1504
- `, isInline: true, styles: ["@layer kj.component{.kj-button{display:inline-flex;align-items:center;justify-content:var(--kj-button-justify, center);gap:var(--kj-button-gap, var(--kj-space-sm));min-width:var(--kj-button-min-width, auto);height:var(--kj-button-height, auto);align-self:center;background:var(--kj-button-bg, var(--kj-bg-primary));color:var(--kj-button-fg, var(--kj-fg-on-primary));border:var(--kj-button-border-width, var(--kj-border)) var(--kj-button-border-style, solid) var(--kj-button-border-color, transparent);border-radius:var(--kj-button-radius, var(--kj-radius-field));padding:var(--kj-button-padding-y, var(--kj-space-sm)) var(--kj-button-padding-x, var(--kj-space-md));font:var(--kj-button-font-weight, 600) var(--kj-button-font-size, var(--kj-text-sm)) / 1.2 var(--kj-button-font, var(--kj-font-sans));letter-spacing:var(--kj-button-letter-spacing, -.01em);transition:color var(--kj-transition),border-color var(--kj-transition),box-shadow var(--kj-transition),transform var(--kj-transition),opacity var(--kj-transition);cursor:pointer;text-decoration:none}.kj-button[data-variant=destructive]{--kj-button-bg: var(--kj-bg-danger);--kj-button-fg: var(--kj-fg-on-danger)}.kj-button[data-variant=destructive]:hover{--kj-button-bg: color-mix(in oklch, var(--kj-bg-danger), black 8%)}.kj-button[data-variant=ghost]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: transparent}.kj-button[data-variant=ghost]:hover{--kj-button-bg: color-mix(in oklab, var(--kj-fg-default) 10%, transparent)}.kj-button[data-variant=outline]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: var(--kj-border-default)}.kj-button[data-variant=outline]:hover{--kj-button-bg: var(--kj-bg-surface)}.kj-button[data-variant=link]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-primary);--kj-button-padding-x: 0;--kj-button-padding-y: 0;--kj-button-shadow: none;--kj-button-shadow-hover: none;--kj-button-shadow-active: none;text-decoration:underline;text-underline-offset:4px}.kj-button[data-variant=link]:hover{--kj-button-bg: transparent;--kj-button-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%)}.kj-button[data-size=xs]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: 0;--kj-button-font-size: .75rem;--kj-button-height: var(--kj-ctl-h-xs)}.kj-button[data-size=sm]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: .25rem;--kj-button-font-size: .8125rem;--kj-button-height: var(--kj-ctl-h-sm)}.kj-button[data-size=md]{--kj-button-height: var(--kj-ctl-h-md)}.kj-button[data-size=lg]{--kj-button-padding-x: var(--kj-space-lg);--kj-button-padding-y: .625rem;--kj-button-font-size: 1rem;--kj-button-height: var(--kj-ctl-h-lg)}.kj-button[data-size=xl]{--kj-button-padding-x: var(--kj-space-2xl);--kj-button-padding-y: .875rem;--kj-button-font-size: 1.0625rem;--kj-button-gap: var(--kj-space-md);--kj-button-height: var(--kj-ctl-h-xl)}.kj-button[data-size=icon]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: var(--kj-space-sm);--kj-button-height: var(--kj-ctl-h-md);aspect-ratio:1}.kj-button[data-full=true]{width:100%}.kj-button:hover{--kj-button-bg: var(--kj-button-bg-hover, color-mix(in oklch, var(--kj-bg-primary), black 8%));transform:translateY(var(--kj-button-hover-translate, var(--kj-hover-translate, -2px)))}.kj-button[data-variant=default]:hover{--kj-button-fg: var(--kj-button-fg-hover, var(--kj-fg-on-primary))}.kj-button[data-variant=default]{box-shadow:var(--kj-button-shadow, none)}.kj-button[data-variant=default]:hover{box-shadow:var(--kj-button-shadow-hover, var(--kj-button-shadow, none))}.kj-button[data-variant=default]:active{box-shadow:var(--kj-button-shadow-active, var(--kj-button-shadow, none))}.kj-button:active{transform:var(--kj-button-translate-active, none)}.kj-button:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-button[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-button[data-size=icon]{min-width:2.75rem;min-height:2.75rem}.kj-button[aria-busy=true]{opacity:.7;cursor:progress}.kj-button[aria-pressed=true]{--kj-button-bg: var(--kj-bg-field);box-shadow:inset 0 1px 2px #0000001f}.kj-button__spinner{display:inline-block;width:1em;height:1em;vertical-align:-.125em;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:kj-button-spinner-rotate .6s linear infinite}}@keyframes kj-button-spinner-rotate{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: KjButton, selector: "[kjButton]", inputs: ["kjDisabled", "kjLoading", "kjFullWidth", "kjPressed"], outputs: ["kjPressedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1785
+ `, isInline: true, styles: ["@layer kj.component{.kj-button{display:inline-flex;align-items:center;justify-content:var(--kj-button-justify, center);gap:var(--kj-button-gap, var(--kj-space-sm));min-width:var(--kj-button-min-width, auto);height:var(--kj-button-height, auto);align-self:center;background:var(--kj-button-bg, var(--kj-bg-primary));color:var(--kj-button-fg, var(--kj-fg-on-primary));border:var(--kj-button-border-width, var(--kj-border)) var(--kj-button-border-style, solid) var(--kj-button-border-color, transparent);border-radius:var(--kj-button-radius, var(--kj-radius-field));padding:var(--kj-button-padding-y, var(--kj-space-sm)) var(--kj-button-padding-x, var(--kj-space-md));font:var(--kj-button-font-weight, 600) var(--kj-button-font-size, var(--kj-text-sm)) / 1.2 var(--kj-button-font, var(--kj-font-sans));letter-spacing:var(--kj-button-letter-spacing, -.01em);text-transform:var(--kj-button-text-transform, none);transition:color var(--kj-transition),border-color var(--kj-transition),box-shadow var(--kj-transition),transform var(--kj-transition),opacity var(--kj-transition);cursor:pointer;text-decoration:none}.kj-button[data-variant=destructive]{--kj-button-bg: var(--kj-bg-danger);--kj-button-fg: var(--kj-fg-on-danger)}.kj-button[data-variant=destructive]:hover{--kj-button-bg: color-mix(in oklch, var(--kj-bg-danger), black 8%)}.kj-button[data-variant=ghost]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: transparent}.kj-button[data-variant=ghost]:hover{--kj-button-bg: color-mix(in oklab, var(--kj-fg-default) 10%, transparent)}.kj-button[data-variant=outline]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: var(--kj-border-default)}.kj-button[data-variant=outline]:hover{--kj-button-bg: var(--kj-bg-surface)}.kj-button[data-variant=segmented]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-muted, var(--kj-fg-default));--kj-button-border-color: transparent;--kj-button-border-width: 0;--kj-button-radius: 0;--kj-button-hover-translate: none}.kj-button[data-variant=segmented]:hover{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default)}.kj-button[data-variant=segmented][aria-pressed=true]{--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default));cursor:default}.kj-button[data-variant=segmented][aria-pressed=true]:hover{--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default))}.kj-button[data-variant=link]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-primary);--kj-button-padding-x: 0;--kj-button-padding-y: 0;--kj-button-shadow: none;--kj-button-shadow-hover: none;--kj-button-shadow-active: none;text-decoration:underline;text-underline-offset:4px}.kj-button[data-variant=link]:hover{--kj-button-bg: transparent;--kj-button-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%)}.kj-button[data-size=xs]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: 0;--kj-button-font-size: .75rem;--kj-button-height: var(--kj-ctl-h-xs)}.kj-button[data-size=sm]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: .25rem;--kj-button-font-size: .8125rem;--kj-button-height: var(--kj-ctl-h-sm)}.kj-button[data-size=md]{--kj-button-height: var(--kj-ctl-h-md)}.kj-button[data-size=lg]{--kj-button-padding-x: var(--kj-space-lg);--kj-button-padding-y: .625rem;--kj-button-font-size: 1rem;--kj-button-height: var(--kj-ctl-h-lg)}.kj-button[data-size=xl]{--kj-button-padding-x: var(--kj-space-2xl);--kj-button-padding-y: .875rem;--kj-button-font-size: 1.0625rem;--kj-button-gap: var(--kj-space-md);--kj-button-height: var(--kj-ctl-h-xl)}.kj-button[data-size=icon]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: var(--kj-space-sm);--kj-button-height: var(--kj-ctl-h-md);aspect-ratio:1}.kj-button[data-full=true]{width:100%}.kj-button:hover{--kj-button-bg: var(--kj-button-bg-hover, color-mix(in oklch, var(--kj-bg-primary), black 8%));transform:translateY(var(--kj-button-hover-translate, var(--kj-hover-translate, -2px)))}.kj-button[data-variant=default]:hover{--kj-button-fg: var(--kj-button-fg-hover, var(--kj-fg-on-primary))}.kj-button[data-variant=default]{box-shadow:var(--kj-button-shadow, none)}.kj-button[data-variant=default]:hover{box-shadow:var(--kj-button-shadow-hover, var(--kj-button-shadow, none))}.kj-button[data-variant=default]:active{box-shadow:var(--kj-button-shadow-active, var(--kj-button-shadow, none))}.kj-button:active{transform:var(--kj-button-translate-active, none)}.kj-button:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-button[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-button[data-size=icon]{min-width:2.75rem;min-height:2.75rem}.kj-button[aria-busy=true]{opacity:.7;cursor:progress}.kj-button[aria-pressed=true]{--kj-button-bg: var(--kj-bg-field);box-shadow:inset 0 1px 2px #0000001f}.kj-button__spinner{display:inline-block;width:1em;height:1em;vertical-align:-.125em;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:kj-button-spinner-rotate .6s linear infinite}}@keyframes kj-button-spinner-rotate{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: KjButton, selector: "[kjButton]", inputs: ["kjDisabled", "kjLoading", "kjFullWidth", "kjPressed"], outputs: ["kjPressedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1505
1786
  }
1506
1787
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjButtonComponent, decorators: [{
1507
1788
  type: Component,
@@ -1524,7 +1805,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
1524
1805
  <ng-content />
1525
1806
  }
1526
1807
  </button>
1527
- `, encapsulation: ViewEncapsulation.None, host: { style: 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-button{display:inline-flex;align-items:center;justify-content:var(--kj-button-justify, center);gap:var(--kj-button-gap, var(--kj-space-sm));min-width:var(--kj-button-min-width, auto);height:var(--kj-button-height, auto);align-self:center;background:var(--kj-button-bg, var(--kj-bg-primary));color:var(--kj-button-fg, var(--kj-fg-on-primary));border:var(--kj-button-border-width, var(--kj-border)) var(--kj-button-border-style, solid) var(--kj-button-border-color, transparent);border-radius:var(--kj-button-radius, var(--kj-radius-field));padding:var(--kj-button-padding-y, var(--kj-space-sm)) var(--kj-button-padding-x, var(--kj-space-md));font:var(--kj-button-font-weight, 600) var(--kj-button-font-size, var(--kj-text-sm)) / 1.2 var(--kj-button-font, var(--kj-font-sans));letter-spacing:var(--kj-button-letter-spacing, -.01em);transition:color var(--kj-transition),border-color var(--kj-transition),box-shadow var(--kj-transition),transform var(--kj-transition),opacity var(--kj-transition);cursor:pointer;text-decoration:none}.kj-button[data-variant=destructive]{--kj-button-bg: var(--kj-bg-danger);--kj-button-fg: var(--kj-fg-on-danger)}.kj-button[data-variant=destructive]:hover{--kj-button-bg: color-mix(in oklch, var(--kj-bg-danger), black 8%)}.kj-button[data-variant=ghost]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: transparent}.kj-button[data-variant=ghost]:hover{--kj-button-bg: color-mix(in oklab, var(--kj-fg-default) 10%, transparent)}.kj-button[data-variant=outline]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: var(--kj-border-default)}.kj-button[data-variant=outline]:hover{--kj-button-bg: var(--kj-bg-surface)}.kj-button[data-variant=link]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-primary);--kj-button-padding-x: 0;--kj-button-padding-y: 0;--kj-button-shadow: none;--kj-button-shadow-hover: none;--kj-button-shadow-active: none;text-decoration:underline;text-underline-offset:4px}.kj-button[data-variant=link]:hover{--kj-button-bg: transparent;--kj-button-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%)}.kj-button[data-size=xs]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: 0;--kj-button-font-size: .75rem;--kj-button-height: var(--kj-ctl-h-xs)}.kj-button[data-size=sm]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: .25rem;--kj-button-font-size: .8125rem;--kj-button-height: var(--kj-ctl-h-sm)}.kj-button[data-size=md]{--kj-button-height: var(--kj-ctl-h-md)}.kj-button[data-size=lg]{--kj-button-padding-x: var(--kj-space-lg);--kj-button-padding-y: .625rem;--kj-button-font-size: 1rem;--kj-button-height: var(--kj-ctl-h-lg)}.kj-button[data-size=xl]{--kj-button-padding-x: var(--kj-space-2xl);--kj-button-padding-y: .875rem;--kj-button-font-size: 1.0625rem;--kj-button-gap: var(--kj-space-md);--kj-button-height: var(--kj-ctl-h-xl)}.kj-button[data-size=icon]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: var(--kj-space-sm);--kj-button-height: var(--kj-ctl-h-md);aspect-ratio:1}.kj-button[data-full=true]{width:100%}.kj-button:hover{--kj-button-bg: var(--kj-button-bg-hover, color-mix(in oklch, var(--kj-bg-primary), black 8%));transform:translateY(var(--kj-button-hover-translate, var(--kj-hover-translate, -2px)))}.kj-button[data-variant=default]:hover{--kj-button-fg: var(--kj-button-fg-hover, var(--kj-fg-on-primary))}.kj-button[data-variant=default]{box-shadow:var(--kj-button-shadow, none)}.kj-button[data-variant=default]:hover{box-shadow:var(--kj-button-shadow-hover, var(--kj-button-shadow, none))}.kj-button[data-variant=default]:active{box-shadow:var(--kj-button-shadow-active, var(--kj-button-shadow, none))}.kj-button:active{transform:var(--kj-button-translate-active, none)}.kj-button:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-button[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-button[data-size=icon]{min-width:2.75rem;min-height:2.75rem}.kj-button[aria-busy=true]{opacity:.7;cursor:progress}.kj-button[aria-pressed=true]{--kj-button-bg: var(--kj-bg-field);box-shadow:inset 0 1px 2px #0000001f}.kj-button__spinner{display:inline-block;width:1em;height:1em;vertical-align:-.125em;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:kj-button-spinner-rotate .6s linear infinite}}@keyframes kj-button-spinner-rotate{to{transform:rotate(360deg)}}\n"] }]
1808
+ `, encapsulation: ViewEncapsulation.None, host: { style: 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-button{display:inline-flex;align-items:center;justify-content:var(--kj-button-justify, center);gap:var(--kj-button-gap, var(--kj-space-sm));min-width:var(--kj-button-min-width, auto);height:var(--kj-button-height, auto);align-self:center;background:var(--kj-button-bg, var(--kj-bg-primary));color:var(--kj-button-fg, var(--kj-fg-on-primary));border:var(--kj-button-border-width, var(--kj-border)) var(--kj-button-border-style, solid) var(--kj-button-border-color, transparent);border-radius:var(--kj-button-radius, var(--kj-radius-field));padding:var(--kj-button-padding-y, var(--kj-space-sm)) var(--kj-button-padding-x, var(--kj-space-md));font:var(--kj-button-font-weight, 600) var(--kj-button-font-size, var(--kj-text-sm)) / 1.2 var(--kj-button-font, var(--kj-font-sans));letter-spacing:var(--kj-button-letter-spacing, -.01em);text-transform:var(--kj-button-text-transform, none);transition:color var(--kj-transition),border-color var(--kj-transition),box-shadow var(--kj-transition),transform var(--kj-transition),opacity var(--kj-transition);cursor:pointer;text-decoration:none}.kj-button[data-variant=destructive]{--kj-button-bg: var(--kj-bg-danger);--kj-button-fg: var(--kj-fg-on-danger)}.kj-button[data-variant=destructive]:hover{--kj-button-bg: color-mix(in oklch, var(--kj-bg-danger), black 8%)}.kj-button[data-variant=ghost]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: transparent}.kj-button[data-variant=ghost]:hover{--kj-button-bg: color-mix(in oklab, var(--kj-fg-default) 10%, transparent)}.kj-button[data-variant=outline]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default);--kj-button-border-color: var(--kj-border-default)}.kj-button[data-variant=outline]:hover{--kj-button-bg: var(--kj-bg-surface)}.kj-button[data-variant=segmented]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-muted, var(--kj-fg-default));--kj-button-border-color: transparent;--kj-button-border-width: 0;--kj-button-radius: 0;--kj-button-hover-translate: none}.kj-button[data-variant=segmented]:hover{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-default)}.kj-button[data-variant=segmented][aria-pressed=true]{--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default));cursor:default}.kj-button[data-variant=segmented][aria-pressed=true]:hover{--kj-button-bg: var(--kj-segmented-bg-on, var(--kj-bg-surface));--kj-button-fg: var(--kj-segmented-fg-on, var(--kj-fg-default))}.kj-button[data-variant=link]{--kj-button-bg: transparent;--kj-button-fg: var(--kj-fg-primary);--kj-button-padding-x: 0;--kj-button-padding-y: 0;--kj-button-shadow: none;--kj-button-shadow-hover: none;--kj-button-shadow-active: none;text-decoration:underline;text-underline-offset:4px}.kj-button[data-variant=link]:hover{--kj-button-bg: transparent;--kj-button-fg: color-mix(in oklch, var(--kj-fg-primary), black 8%)}.kj-button[data-size=xs]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: 0;--kj-button-font-size: .75rem;--kj-button-height: var(--kj-ctl-h-xs)}.kj-button[data-size=sm]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: .25rem;--kj-button-font-size: .8125rem;--kj-button-height: var(--kj-ctl-h-sm)}.kj-button[data-size=md]{--kj-button-height: var(--kj-ctl-h-md)}.kj-button[data-size=lg]{--kj-button-padding-x: var(--kj-space-lg);--kj-button-padding-y: .625rem;--kj-button-font-size: 1rem;--kj-button-height: var(--kj-ctl-h-lg)}.kj-button[data-size=xl]{--kj-button-padding-x: var(--kj-space-2xl);--kj-button-padding-y: .875rem;--kj-button-font-size: 1.0625rem;--kj-button-gap: var(--kj-space-md);--kj-button-height: var(--kj-ctl-h-xl)}.kj-button[data-size=icon]{--kj-button-padding-x: var(--kj-space-sm);--kj-button-padding-y: var(--kj-space-sm);--kj-button-height: var(--kj-ctl-h-md);aspect-ratio:1}.kj-button[data-full=true]{width:100%}.kj-button:hover{--kj-button-bg: var(--kj-button-bg-hover, color-mix(in oklch, var(--kj-bg-primary), black 8%));transform:translateY(var(--kj-button-hover-translate, var(--kj-hover-translate, -2px)))}.kj-button[data-variant=default]:hover{--kj-button-fg: var(--kj-button-fg-hover, var(--kj-fg-on-primary))}.kj-button[data-variant=default]{box-shadow:var(--kj-button-shadow, none)}.kj-button[data-variant=default]:hover{box-shadow:var(--kj-button-shadow-hover, var(--kj-button-shadow, none))}.kj-button[data-variant=default]:active{box-shadow:var(--kj-button-shadow-active, var(--kj-button-shadow, none))}.kj-button:active{transform:var(--kj-button-translate-active, none)}.kj-button:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-button[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-button[data-size=icon]{min-width:2.75rem;min-height:2.75rem}.kj-button[aria-busy=true]{opacity:.7;cursor:progress}.kj-button[aria-pressed=true]{--kj-button-bg: var(--kj-bg-field);box-shadow:inset 0 1px 2px #0000001f}.kj-button__spinner{display:inline-block;width:1em;height:1em;vertical-align:-.125em;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:kj-button-spinner-rotate .6s linear infinite}}@keyframes kj-button-spinner-rotate{to{transform:rotate(360deg)}}\n"] }]
1528
1809
  }], propDecorators: { kjVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjVariant", required: false }] }], kjSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSize", required: false }] }], kjDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjDisabled", required: false }] }], kjLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjLoading", required: false }] }], kjFullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjFullWidth", required: false }] }], kjPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjPressed", required: false }] }, { type: i0.Output, args: ["kjPressedChange"] }], kjType: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjType", required: false }] }], kjAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAriaLabel", required: false }] }] } });
1529
1810
 
1530
1811
  /**
@@ -1559,6 +1840,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
1559
1840
  * @doc-example Variants
1560
1841
  * Set `kjVariant` on the group to cascade onto every child button.
1561
1842
  * @doc-file button-group.variants.example.ts
1843
+ * @doc-example Segmented
1844
+ * `kjVariant="segmented"` moves the border onto the group and fills only
1845
+ * the pressed child — a mode switcher rather than a toolbar.
1846
+ * @doc-file button-group.segmented.example.ts
1562
1847
  * @doc-example Toggle
1563
1848
  * Pair with `[kjPressed]` on each child to build a segmented control.
1564
1849
  * @doc-file button-group.toggle.example.ts
@@ -1590,7 +1875,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
1590
1875
  * @doc-related button,toggle,tabs
1591
1876
  *
1592
1877
  * @doc-css-var
1593
- * --kj-button-group-gap — Gap between adjacent buttons. Defaults to 0 (segmented look); raise for a spaced cluster.
1878
+ * --kj-button-group-gap — Gap between adjacent buttons. Defaults to 0 (segmented look); raise for a spaced cluster.
1879
+ * --kj-segmented-border — Shell and divider colour, `kjVariant="segmented"` only. Defaults to `--kj-border-default`.
1880
+ * --kj-segmented-bg-on — Fill behind the pressed segment. Defaults to `--kj-bg-surface`.
1881
+ * --kj-segmented-fg-on — Ink on the pressed segment. Defaults to `--kj-fg-default`.
1882
+ *
1883
+ * Typography needs no dedicated hook: set the ordinary `--kj-button-font`,
1884
+ * `--kj-button-font-size`, `--kj-button-letter-spacing` and
1885
+ * `--kj-button-text-transform` on this host and they reach each child,
1886
+ * since every knob is read as `var(name, default)`. The on-state colours
1887
+ * are the exception — variant rules declare those knobs on the element,
1888
+ * so they get their own `--kj-segmented-*` indirection.
1594
1889
  *
1595
1890
  * @doc-category Library/Actions
1596
1891
  * @doc
@@ -1608,7 +1903,9 @@ class KjButtonGroupComponent {
1608
1903
  ...(ngDevMode ? [{ debugName: "kjOrientation" }] : /* istanbul ignore next */ []));
1609
1904
  /**
1610
1905
  * Default variant forwarded to children via `KJ_BUTTON_GROUP`. Children
1611
- * that set their own `kjVariant` override this fallback.
1906
+ * that set their own `kjVariant` override this fallback. Also mirrored to
1907
+ * `data-variant` on the host, which is how `segmented` draws its shared
1908
+ * shell and dividers.
1612
1909
  */
1613
1910
  kjVariant = input(undefined, /* @ts-ignore */
1614
1911
  ...(ngDevMode ? [{ debugName: "kjVariant" }] : /* istanbul ignore next */ []));
@@ -1630,7 +1927,7 @@ class KjButtonGroupComponent {
1630
1927
  kjAriaLabel = input(undefined, /* @ts-ignore */
1631
1928
  ...(ngDevMode ? [{ debugName: "kjAriaLabel" }] : /* istanbul ignore next */ []));
1632
1929
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1633
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: KjButtonGroupComponent, isStandalone: true, selector: "kj-button-group", inputs: { kjOrientation: { classPropertyName: "kjOrientation", publicName: "kjOrientation", isSignal: true, isRequired: false, transformFunction: null }, kjVariant: { classPropertyName: "kjVariant", publicName: "kjVariant", isSignal: true, isRequired: false, transformFunction: null }, kjSize: { classPropertyName: "kjSize", publicName: "kjSize", isSignal: true, isRequired: false, transformFunction: null }, kjDisabled: { classPropertyName: "kjDisabled", publicName: "kjDisabled", isSignal: true, isRequired: false, transformFunction: null }, kjAriaLabel: { classPropertyName: "kjAriaLabel", publicName: "kjAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-label": "kjAriaLabel()" }, classAttribute: "kj-button-group" }, hostDirectives: [{ directive: i1.KjButtonGroup, inputs: ["kjOrientation", "kjOrientation", "kjVariant", "kjVariant", "kjSize", "kjSize", "kjDisabled", "kjDisabled"] }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-button-group{--kj-button-group-gap: 0;display:inline-flex;flex-direction:row;align-items:stretch;gap:var(--kj-button-group-gap);border-radius:var(--kj-radius-field);isolation:isolate}.kj-button-group[data-orientation=vertical]{flex-direction:column}.kj-button-group:not([data-orientation=vertical])>kj-button>.kj-button{border-radius:0;margin-left:calc(-1 * var(--kj-border))}.kj-button-group:not([data-orientation=vertical])>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-bottom-left-radius:var(--kj-radius-field);margin-left:0}.kj-button-group:not([data-orientation=vertical])>kj-button:last-child>.kj-button{border-top-right-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group[data-orientation=vertical]>kj-button>.kj-button{border-radius:0;margin-top:calc(-1 * var(--kj-border));width:100%}.kj-button-group[data-orientation=vertical]>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-top-right-radius:var(--kj-radius-field);margin-top:0}.kj-button-group[data-orientation=vertical]>kj-button:last-child>.kj-button{border-bottom-left-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group>kj-button>.kj-button:hover,.kj-button-group>kj-button>.kj-button:focus-visible,.kj-button-group>kj-button>.kj-button[aria-pressed=true]{z-index:1}.kj-button-group[data-disabled]{opacity:.7}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1930
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: KjButtonGroupComponent, isStandalone: true, selector: "kj-button-group", inputs: { kjOrientation: { classPropertyName: "kjOrientation", publicName: "kjOrientation", isSignal: true, isRequired: false, transformFunction: null }, kjVariant: { classPropertyName: "kjVariant", publicName: "kjVariant", isSignal: true, isRequired: false, transformFunction: null }, kjSize: { classPropertyName: "kjSize", publicName: "kjSize", isSignal: true, isRequired: false, transformFunction: null }, kjDisabled: { classPropertyName: "kjDisabled", publicName: "kjDisabled", isSignal: true, isRequired: false, transformFunction: null }, kjAriaLabel: { classPropertyName: "kjAriaLabel", publicName: "kjAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-label": "kjAriaLabel()" }, classAttribute: "kj-button-group" }, hostDirectives: [{ directive: i1.KjButtonGroup, inputs: ["kjOrientation", "kjOrientation", "kjVariant", "kjVariant", "kjSize", "kjSize", "kjDisabled", "kjDisabled"] }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-button-group{--kj-button-group-gap: 0;display:inline-flex;flex-direction:row;align-items:stretch;gap:var(--kj-button-group-gap);border-radius:var(--kj-radius-field);isolation:isolate}.kj-button-group[data-orientation=vertical]{flex-direction:column}.kj-button-group:not([data-orientation=vertical])>kj-button>.kj-button{border-radius:0;margin-left:calc(-1 * var(--kj-border))}.kj-button-group:not([data-orientation=vertical])>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-bottom-left-radius:var(--kj-radius-field);margin-left:0}.kj-button-group:not([data-orientation=vertical])>kj-button:last-child>.kj-button{border-top-right-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group[data-orientation=vertical]>kj-button>.kj-button{border-radius:0;margin-top:calc(-1 * var(--kj-border));width:100%}.kj-button-group[data-orientation=vertical]>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-top-right-radius:var(--kj-radius-field);margin-top:0}.kj-button-group[data-orientation=vertical]>kj-button:last-child>.kj-button{border-bottom-left-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group>kj-button>.kj-button:hover,.kj-button-group>kj-button>.kj-button:focus-visible,.kj-button-group>kj-button>.kj-button[aria-pressed=true]{z-index:1}.kj-button-group[data-variant=segmented]{border:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default));border-radius:var(--kj-radius-field);overflow:hidden}.kj-button-group[data-variant=segmented]>kj-button>.kj-button{margin-left:0;margin-top:0;border-radius:0}.kj-button-group[data-variant=segmented]:not([data-orientation=vertical])>kj-button+kj-button>.kj-button{border-left:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default))}.kj-button-group[data-variant=segmented][data-orientation=vertical]>kj-button+kj-button>.kj-button{border-top:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default))}.kj-button-group[data-disabled]{opacity:.7}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1634
1931
  }
1635
1932
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjButtonGroupComponent, decorators: [{
1636
1933
  type: Component,
@@ -1642,7 +1939,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
1642
1939
  ], template: `<ng-content />`, encapsulation: ViewEncapsulation.None, host: {
1643
1940
  class: 'kj-button-group',
1644
1941
  '[attr.aria-label]': 'kjAriaLabel()',
1645
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-button-group{--kj-button-group-gap: 0;display:inline-flex;flex-direction:row;align-items:stretch;gap:var(--kj-button-group-gap);border-radius:var(--kj-radius-field);isolation:isolate}.kj-button-group[data-orientation=vertical]{flex-direction:column}.kj-button-group:not([data-orientation=vertical])>kj-button>.kj-button{border-radius:0;margin-left:calc(-1 * var(--kj-border))}.kj-button-group:not([data-orientation=vertical])>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-bottom-left-radius:var(--kj-radius-field);margin-left:0}.kj-button-group:not([data-orientation=vertical])>kj-button:last-child>.kj-button{border-top-right-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group[data-orientation=vertical]>kj-button>.kj-button{border-radius:0;margin-top:calc(-1 * var(--kj-border));width:100%}.kj-button-group[data-orientation=vertical]>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-top-right-radius:var(--kj-radius-field);margin-top:0}.kj-button-group[data-orientation=vertical]>kj-button:last-child>.kj-button{border-bottom-left-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group>kj-button>.kj-button:hover,.kj-button-group>kj-button>.kj-button:focus-visible,.kj-button-group>kj-button>.kj-button[aria-pressed=true]{z-index:1}.kj-button-group[data-disabled]{opacity:.7}}\n"] }]
1942
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-button-group{--kj-button-group-gap: 0;display:inline-flex;flex-direction:row;align-items:stretch;gap:var(--kj-button-group-gap);border-radius:var(--kj-radius-field);isolation:isolate}.kj-button-group[data-orientation=vertical]{flex-direction:column}.kj-button-group:not([data-orientation=vertical])>kj-button>.kj-button{border-radius:0;margin-left:calc(-1 * var(--kj-border))}.kj-button-group:not([data-orientation=vertical])>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-bottom-left-radius:var(--kj-radius-field);margin-left:0}.kj-button-group:not([data-orientation=vertical])>kj-button:last-child>.kj-button{border-top-right-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group[data-orientation=vertical]>kj-button>.kj-button{border-radius:0;margin-top:calc(-1 * var(--kj-border));width:100%}.kj-button-group[data-orientation=vertical]>kj-button:first-child>.kj-button{border-top-left-radius:var(--kj-radius-field);border-top-right-radius:var(--kj-radius-field);margin-top:0}.kj-button-group[data-orientation=vertical]>kj-button:last-child>.kj-button{border-bottom-left-radius:var(--kj-radius-field);border-bottom-right-radius:var(--kj-radius-field)}.kj-button-group>kj-button>.kj-button:hover,.kj-button-group>kj-button>.kj-button:focus-visible,.kj-button-group>kj-button>.kj-button[aria-pressed=true]{z-index:1}.kj-button-group[data-variant=segmented]{border:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default));border-radius:var(--kj-radius-field);overflow:hidden}.kj-button-group[data-variant=segmented]>kj-button>.kj-button{margin-left:0;margin-top:0;border-radius:0}.kj-button-group[data-variant=segmented]:not([data-orientation=vertical])>kj-button+kj-button>.kj-button{border-left:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default))}.kj-button-group[data-variant=segmented][data-orientation=vertical]>kj-button+kj-button>.kj-button{border-top:var(--kj-border) solid var(--kj-segmented-border, var(--kj-border-default))}.kj-button-group[data-disabled]{opacity:.7}}\n"] }]
1646
1943
  }], propDecorators: { kjOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOrientation", required: false }] }], kjVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjVariant", required: false }] }], kjSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSize", required: false }] }], kjDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjDisabled", required: false }] }], kjAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAriaLabel", required: false }] }] } });
1647
1944
 
1648
1945
  /**
@@ -2929,77 +3226,74 @@ function escapeHtml(s) {
2929
3226
  .replace(/"/g, '&quot;')
2930
3227
  .replace(/'/g, '&#39;');
2931
3228
  }
2932
- /** Only allow safe URL schemes for links. */
2933
- function safeUrl(url) {
2934
- const trimmed = url.trim();
2935
- if (/^(https?:\/\/|\/|#|mailto:)/i.test(trimmed))
2936
- return trimmed;
2937
- return null;
2938
- }
2939
- /** Apply link / bold / italic to a non-code segment. */
2940
- function formatSegment(seg) {
2941
- let s = seg.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, text, url) => {
2942
- const href = safeUrl(url);
2943
- if (!href)
2944
- return text;
2945
- return `<a class="kj-md-link" href="${escapeHtml(href)}" rel="noopener noreferrer" target="_blank">${text}</a>`;
2946
- });
2947
- s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
2948
- s = s.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
2949
- return s;
2950
- }
2951
3229
  /**
2952
- * Render inline markdown on an **already HTML-escaped** string. Splits on
2953
- * backtick code spans first so their contents are never re-formatted, then
2954
- * applies link / bold / italic to the remaining prose segments.
3230
+ * GFM, with `breaks` on: a chat turn treats a single newline as a line break,
3231
+ * which is what people type. `pedantic` off so the parser stays forgiving of
3232
+ * the half-formed markdown a model emits mid-stream.
3233
+ *
3234
+ * `marked` dropped its sanitizer in v5 and passes source HTML through
3235
+ * verbatim, so both HTML token kinds are overridden to escape instead. A chat
3236
+ * turn is model or user output: it may *describe* markup, never inject it.
2955
3237
  */
2956
- function renderInline(escaped) {
2957
- return escaped
2958
- .split(/(`[^`]+`)/g)
2959
- .map((seg) => {
2960
- if (seg.length >= 2 && seg.startsWith('`') && seg.endsWith('`')) {
2961
- return `<code class="kj-md-code">${seg.slice(1, -1)}</code>`;
2962
- }
2963
- return formatSegment(seg);
2964
- })
2965
- .join('');
2966
- }
2967
- /** Render a prose block (paragraphs + single-newline `<br>`) to safe HTML. */
2968
- function renderProse(block) {
2969
- const paragraphs = block
2970
- .split(/\n{2,}/)
2971
- .map((p) => p.trim())
2972
- .filter(Boolean);
2973
- return paragraphs
2974
- .map((p) => {
2975
- const lines = p.split('\n').map((line) => renderInline(escapeHtml(line)));
2976
- return `<p>${lines.join('<br>')}</p>`;
2977
- })
2978
- .join('');
2979
- }
3238
+ marked.use({
3239
+ gfm: true,
3240
+ breaks: true,
3241
+ pedantic: false,
3242
+ renderer: {
3243
+ html(token) {
3244
+ return escapeHtml(typeof token === 'string' ? token : token.raw);
3245
+ },
3246
+ },
3247
+ tokenizer: {},
3248
+ });
3249
+ marked.use({
3250
+ renderer: {
3251
+ // Inline HTML (`<img …>` mid-sentence) travels a different path than a
3252
+ // block-level HTML token, and needs the same treatment.
3253
+ text(token) {
3254
+ const anyToken = token;
3255
+ if (Array.isArray(anyToken.tokens) && anyToken.tokens.length > 0) {
3256
+ return this.parser.parseInline(anyToken.tokens);
3257
+ }
3258
+ return escapeHtml(String(anyToken.text ?? anyToken.raw ?? ''));
3259
+ },
3260
+ },
3261
+ });
2980
3262
  /**
2981
- * Parse a lightweight markdown subset into blocks: fenced code blocks
2982
- * (```lang) become structural `code` blocks; everything else becomes `prose`
2983
- * blocks with sanitised inline HTML.
3263
+ * Parse markdown into blocks: fenced code becomes a structural `code` block,
3264
+ * everything else is rendered to HTML as a `prose` block.
2984
3265
  *
2985
- * Deliberately minimal (no tables / nested lists) and **self-contained** the
2986
- * repo ships no markdown engine on `main`. Swap in a full parser later without
2987
- * touching the view.
3266
+ * Code is split out at the TOKEN level rather than by regex so an indented
3267
+ * block, a `~~~` fence, or a fence inside a list is still recognised — and so
3268
+ * a stray fence in a partial stream cannot swallow the rest of the message.
2988
3269
  */
2989
3270
  function renderMarkdown(src) {
3271
+ const tokens = marked.lexer(src);
2990
3272
  const blocks = [];
2991
- const fence = /```([\w-]*)\n?([\s\S]*?)```/g;
2992
- let last = 0;
2993
- for (let m = fence.exec(src); m; m = fence.exec(src)) {
2994
- const before = src.slice(last, m.index);
2995
- if (before.trim())
2996
- blocks.push({ kind: 'prose', html: renderProse(before) });
2997
- blocks.push({ kind: 'code', lang: m[1] || '', code: m[2].replace(/\n$/, '') });
2998
- last = fence.lastIndex;
3273
+ let prose = [];
3274
+ const flushProse = () => {
3275
+ if (prose.length === 0)
3276
+ return;
3277
+ // `marked.parser` reads `links` off the token list for reference-style
3278
+ // links; a plain slice would drop them and render `[a][b]` verbatim.
3279
+ const list = prose;
3280
+ list.links = tokens.links;
3281
+ blocks.push({ kind: 'prose', html: marked.parser(list) });
3282
+ prose = [];
3283
+ };
3284
+ for (const token of tokens) {
3285
+ if (token.type === 'code') {
3286
+ flushProse();
3287
+ blocks.push({
3288
+ kind: 'code',
3289
+ lang: typeof token.lang === 'string' ? token.lang.split(/\s+/)[0] : '',
3290
+ code: String(token.text ?? ''),
3291
+ });
3292
+ continue;
3293
+ }
3294
+ prose.push(token);
2999
3295
  }
3000
- const rest = src.slice(last);
3001
- if (rest.trim())
3002
- blocks.push({ kind: 'prose', html: renderProse(rest) });
3296
+ flushProse();
3003
3297
  return blocks;
3004
3298
  }
3005
3299
 
@@ -3045,8 +3339,15 @@ class KjChatMessage {
3045
3339
  _copied = signal(null, /* @ts-ignore */
3046
3340
  ...(ngDevMode ? [{ debugName: "_copied" }] : /* istanbul ignore next */ []));
3047
3341
  copiedIndex = this._copied.asReadonly();
3342
+ /**
3343
+ * Sanitise rather than bypass. `renderMarkdown` already escapes raw HTML at
3344
+ * the parse layer, but a chat body is model or user output — the one place
3345
+ * in a UI kit where trusting a single layer is a bad bet. `sanitize` strips
3346
+ * anything the parser somehow let through instead of waving it past
3347
+ * Angular's checks.
3348
+ */
3048
3349
  safe(html) {
3049
- return this.sanitizer.bypassSecurityTrustHtml(html);
3350
+ return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';
3050
3351
  }
3051
3352
  async copy(code, index) {
3052
3353
  try {
@@ -3266,6 +3567,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
3266
3567
  * Provider-agnostic: pair with `KjPromptInput` and drive `KjChatStore` from any
3267
3568
  * model / stream — the kit ships **no** LLM SDK or backend.
3268
3569
  *
3570
+ * @doc-example Custom thread items
3571
+ * `provideKjChat` maps an item `type` to a component, so a thread can render
3572
+ * turns the kit knows nothing about. Ordinary messages are untouched.
3573
+ * @doc-file ai-chat.custom-items.example.ts
3269
3574
  * @doc-example Streaming (simulated)
3270
3575
  * A full send → simulated-stream → reply loop with slash commands, a Stop
3271
3576
  * button, tool-call cards and citations. No backend — a fake token generator
@@ -3307,6 +3612,30 @@ class KjChatThread {
3307
3612
  ...(ngDevMode ? [{ debugName: "kjLabel" }] : /* istanbul ignore next */ []));
3308
3613
  /** @internal — the coalescing announcer for the polite live region. */
3309
3614
  announcer = inject(KjChatAnnouncer);
3615
+ config = inject(KJ_CHAT_CONFIG);
3616
+ /**
3617
+ * The registered component for a message's `type`, or `null` to let the
3618
+ * built-in renderer draw it. A message with no `type` never consults the
3619
+ * registry — that is the overwhelmingly common case and must stay free.
3620
+ *
3621
+ * An unknown `type` falls back to the configured `fallback`, and failing
3622
+ * that to the built-in renderer: a transcript that silently drops a turn is
3623
+ * worse than one that renders it plainly.
3624
+ */
3625
+ rendererFor(message) {
3626
+ if (!message.type)
3627
+ return null;
3628
+ return this.config.renderers[message.type] ?? this.config.fallback ?? null;
3629
+ }
3630
+ /** The neutral shape handed to a registered renderer's `item` input. */
3631
+ itemFor(message) {
3632
+ return {
3633
+ id: message.id,
3634
+ type: message.type,
3635
+ role: message.role,
3636
+ data: message.data ?? message.content,
3637
+ };
3638
+ }
3310
3639
  injector = inject(Injector);
3311
3640
  logEl = viewChild.required('log');
3312
3641
  lastId = null;
@@ -3356,7 +3685,13 @@ class KjChatThread {
3356
3685
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: KjChatThread, isStandalone: true, selector: "kj-chat-thread", inputs: { store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: true, transformFunction: null }, kjLabel: { classPropertyName: "kjLabel", publicName: "kjLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kj-chat-thread-host" }, providers: [KjChatAnnouncer], viewQueries: [{ propertyName: "logEl", first: true, predicate: ["log"], descendants: true, isSignal: true }], ngImport: i0, template: `
3357
3686
  <div #log kjChatLog kjChatLogLive="off" class="kj-chat-thread" [kjChatLogLabel]="kjLabel()">
3358
3687
  @for (m of store().messages(); track m.id) {
3359
- <kj-chat-message [message]="m" />
3688
+ @if (rendererFor(m); as renderer) {
3689
+ <ng-container
3690
+ *ngComponentOutlet="renderer; inputs: { item: itemFor(m), message: m }"
3691
+ ></ng-container>
3692
+ } @else {
3693
+ <kj-chat-message [message]="m" />
3694
+ }
3360
3695
  }
3361
3696
  </div>
3362
3697
 
@@ -3364,14 +3699,20 @@ class KjChatThread {
3364
3699
  <div class="kj-visually-hidden" aria-live="polite" aria-atomic="false">
3365
3700
  {{ announcer.message() }}
3366
3701
  </div>
3367
- `, isInline: true, styles: ["@layer kj.component{.kj-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.kj-chat-thread{display:flex;flex-direction:column;gap:var(--kj-space-md);padding:var(--kj-space-md);background:var(--kj-bg-body);color:var(--kj-fg-default);border-radius:var(--kj-radius-box);overflow-y:auto;font-family:var(--kj-font-sans)}.kj-chat-msg{display:flex;gap:var(--kj-space-sm);align-items:flex-start;max-width:100%}.kj-chat-msg[data-side=end]{flex-direction:row-reverse;margin-inline-start:auto}.kj-chat-msg[data-role=system]{justify-content:center}.kj-chat-msg__avatar{flex:0 0 auto;width:2rem;height:2rem;border-radius:50%;display:grid;place-items:center;font-size:var(--kj-text-sm);font-weight:600;background:var(--kj-bg-surface);color:var(--kj-fg-default);border:1px solid var(--kj-border-default)}.kj-chat-msg__bubble{display:flex;flex-direction:column;gap:var(--kj-space-xs);padding:var(--kj-space-sm) var(--kj-space-md);border-radius:var(--kj-radius-box);background:var(--kj-bg-surface);color:var(--kj-fg-default);max-width:min(48rem,100%);overflow-wrap:anywhere}.kj-chat-msg[data-role=user] .kj-chat-msg__bubble{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-chat-msg[data-role=system] .kj-chat-msg__bubble{background:transparent;color:var(--kj-fg-muted);font-size:var(--kj-text-sm);text-align:center;padding-block:var(--kj-space-xs)}.kj-chat-msg__body :is(p){margin:0 0 var(--kj-space-xs)}.kj-chat-msg__body :is(p):last-child{margin-bottom:0}.kj-chat-msg__error{margin:0;color:var(--kj-fg-danger);font-size:var(--kj-text-sm)}.kj-md-link{color:inherit;text-decoration:underline}.kj-md-code{font-family:ui-monospace,monospace;background:color-mix(in srgb,currentColor 12%,transparent);padding:.1em .35em;border-radius:var(--kj-radius-field);font-size:.9em}.kj-chat-code{border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);overflow:hidden;margin:var(--kj-space-xs) 0;background:var(--kj-bg-body)}.kj-chat-code__bar{display:flex;align-items:center;justify-content:space-between;padding:.25rem var(--kj-space-sm);background:var(--kj-bg-surface);border-bottom:1px solid var(--kj-border-default);font-size:var(--kj-text-sm);color:var(--kj-fg-muted)}.kj-chat-code__copy{min-height:1.75rem;padding:.25rem .6rem;border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);background:var(--kj-bg-body);color:var(--kj-fg-default);font-size:var(--kj-text-sm);cursor:pointer}.kj-chat-code__copy:hover{background:var(--kj-bg-surface)}.kj-chat-code__copy:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-chat-code__pre{margin:0;padding:var(--kj-space-sm);overflow-x:auto;font-family:ui-monospace,monospace;font-size:.9em;color:var(--kj-fg-default)}.kj-chat-tool{display:flex;align-items:center;gap:var(--kj-space-xs);padding:.35rem var(--kj-space-sm);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);background:var(--kj-bg-body);font-size:var(--kj-text-sm)}.kj-chat-tool__name{font-weight:600;font-family:ui-monospace,monospace}.kj-chat-tool__status{margin-inline-start:auto;color:var(--kj-fg-muted);text-transform:capitalize}.kj-chat-tool[data-status=error]{border-color:var(--kj-border-danger)}.kj-chat-tool[data-status=error] .kj-chat-tool__status,.kj-chat-tool__error{color:var(--kj-fg-danger)}.kj-chat-cites{list-style:none;margin:var(--kj-space-xs) 0 0;padding:0;display:flex;flex-wrap:wrap;gap:var(--kj-space-xs)}.kj-chat-cite{display:inline-flex;align-items:baseline;gap:.35rem;padding:.15rem .5rem;border:1px solid var(--kj-border-default);border-radius:999px;font-size:var(--kj-text-sm);background:var(--kj-bg-body)}.kj-chat-cite__link{color:var(--kj-fg-info);text-decoration:none}.kj-chat-cite__link:hover{text-decoration:underline}.kj-chat-cite__snippet{color:var(--kj-fg-muted)}.kj-chat-typing{display:inline-flex;gap:.25rem;align-items:center;padding:.25rem 0}.kj-chat-typing__dot{width:.5rem;height:.5rem;border-radius:50%;background:var(--kj-fg-muted);animation:kj-chat-bounce 1.2s infinite ease-in-out both}.kj-chat-typing__dot:nth-child(2){animation-delay:.15s}.kj-chat-typing__dot:nth-child(3){animation-delay:.3s}@keyframes kj-chat-bounce{0%,80%,to{transform:scale(.5);opacity:.4}40%{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.kj-chat-typing__dot{animation:none;opacity:.6}}.kj-prompt{position:relative;display:flex;align-items:flex-end;gap:var(--kj-space-sm);padding:var(--kj-space-sm);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-box);background:var(--kj-bg-field, var(--kj-input-bg))}.kj-prompt:focus-within{border-color:var(--kj-border-focus)}.kj-prompt__textarea{flex:1 1 auto;resize:none;border:none;outline:none;background:transparent;color:var(--kj-input-fg, var(--kj-fg-default));font-family:var(--kj-font-sans);font-size:var(--kj-input-font-size, 1rem);line-height:1.5;max-height:200px;overflow-y:auto}.kj-prompt__textarea::placeholder{color:var(--kj-input-placeholder-fg, var(--kj-fg-muted))}.kj-prompt__actions{display:flex;align-items:center;gap:var(--kj-space-xs);flex:0 0 auto}.kj-prompt__btn{display:grid;place-items:center;width:2.75rem;height:2.75rem;border-radius:50%;border:none;cursor:pointer;font-size:1.1rem}.kj-prompt__btn:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-prompt__btn--send{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-prompt__btn--send:disabled{opacity:.5;cursor:not-allowed}.kj-prompt__btn--stop{background:var(--kj-bg-danger);color:var(--kj-fg-on-danger)}.kj-prompt__slash{position:absolute;bottom:calc(100% + .25rem);left:0;right:0;margin:0;padding:.25rem;list-style:none;background:var(--kj-bg-surface);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);box-shadow:0 4px 16px #00000026;max-height:14rem;overflow-y:auto;z-index:10}.kj-prompt__slash-item{display:flex;flex-direction:column;gap:.1rem;padding:var(--kj-space-xs) var(--kj-space-sm);border-radius:var(--kj-radius-field);cursor:pointer}.kj-prompt__slash-item.is-active,.kj-prompt__slash-item:hover{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-prompt__slash-name{font-family:ui-monospace,monospace;font-weight:600}.kj-prompt__slash-desc{font-size:var(--kj-text-sm);color:var(--kj-fg-muted)}.kj-prompt__slash-item.is-active .kj-prompt__slash-desc,.kj-prompt__slash-item:hover .kj-prompt__slash-desc{color:inherit}}\n"], dependencies: [{ kind: "directive", type: KjChatLog, selector: "[kjChatLog]", inputs: ["kjChatLogLive", "kjChatLogLabel"] }, { kind: "component", type: KjChatMessage, selector: "kj-chat-message", inputs: ["message"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3702
+ `, isInline: true, styles: ["@layer kj.component{.kj-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.kj-chat-thread{display:flex;flex-direction:column;gap:var(--kj-space-md);padding:var(--kj-space-md);background:var(--kj-bg-body);color:var(--kj-fg-default);border-radius:var(--kj-radius-box);overflow-y:auto;font-family:var(--kj-font-sans)}.kj-chat-msg{display:flex;gap:var(--kj-space-sm);align-items:flex-start;max-width:100%}.kj-chat-msg[data-side=end]{flex-direction:row-reverse;margin-inline-start:auto}.kj-chat-msg[data-role=system]{justify-content:center}.kj-chat-msg__avatar{flex:0 0 auto;width:2rem;height:2rem;border-radius:50%;display:grid;place-items:center;font-size:var(--kj-text-sm);font-weight:600;background:var(--kj-bg-surface);color:var(--kj-fg-default);border:1px solid var(--kj-border-default)}.kj-chat-msg__bubble{display:flex;flex-direction:column;gap:var(--kj-space-xs);padding:var(--kj-space-sm) var(--kj-space-md);border-radius:var(--kj-radius-box);background:var(--kj-bg-surface);color:var(--kj-fg-default);max-width:min(48rem,100%);overflow-wrap:anywhere}.kj-chat-msg[data-role=user] .kj-chat-msg__bubble{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-chat-msg[data-role=system] .kj-chat-msg__bubble{background:transparent;color:var(--kj-fg-muted);font-size:var(--kj-text-sm);text-align:center;padding-block:var(--kj-space-xs)}.kj-chat-msg__body :is(p){margin:0 0 var(--kj-space-xs)}.kj-chat-msg__body :is(p):last-child{margin-bottom:0}.kj-chat-msg__error{margin:0;color:var(--kj-fg-danger);font-size:var(--kj-text-sm)}.kj-md-link{color:inherit;text-decoration:underline}.kj-md-code{font-family:ui-monospace,monospace;background:color-mix(in srgb,currentColor 12%,transparent);padding:.1em .35em;border-radius:var(--kj-radius-field);font-size:.9em}.kj-chat-code{border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);overflow:hidden;margin:var(--kj-space-xs) 0;background:var(--kj-bg-body)}.kj-chat-code__bar{display:flex;align-items:center;justify-content:space-between;padding:.25rem var(--kj-space-sm);background:var(--kj-bg-surface);border-bottom:1px solid var(--kj-border-default);font-size:var(--kj-text-sm);color:var(--kj-fg-muted)}.kj-chat-code__copy{min-height:1.75rem;padding:.25rem .6rem;border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);background:var(--kj-bg-body);color:var(--kj-fg-default);font-size:var(--kj-text-sm);cursor:pointer}.kj-chat-code__copy:hover{background:var(--kj-bg-surface)}.kj-chat-code__copy:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-chat-code__pre{margin:0;padding:var(--kj-space-sm);overflow-x:auto;font-family:ui-monospace,monospace;font-size:.9em;color:var(--kj-fg-default)}.kj-chat-tool{display:flex;align-items:center;gap:var(--kj-space-xs);padding:.35rem var(--kj-space-sm);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);background:var(--kj-bg-body);font-size:var(--kj-text-sm)}.kj-chat-tool__name{font-weight:600;font-family:ui-monospace,monospace}.kj-chat-tool__status{margin-inline-start:auto;color:var(--kj-fg-muted);text-transform:capitalize}.kj-chat-tool[data-status=error]{border-color:var(--kj-border-danger)}.kj-chat-tool[data-status=error] .kj-chat-tool__status,.kj-chat-tool__error{color:var(--kj-fg-danger)}.kj-chat-cites{list-style:none;margin:var(--kj-space-xs) 0 0;padding:0;display:flex;flex-wrap:wrap;gap:var(--kj-space-xs)}.kj-chat-cite{display:inline-flex;align-items:baseline;gap:.35rem;padding:.15rem .5rem;border:1px solid var(--kj-border-default);border-radius:999px;font-size:var(--kj-text-sm);background:var(--kj-bg-body)}.kj-chat-cite__link{color:var(--kj-fg-info);text-decoration:none}.kj-chat-cite__link:hover{text-decoration:underline}.kj-chat-cite__snippet{color:var(--kj-fg-muted)}.kj-chat-typing{display:inline-flex;gap:.25rem;align-items:center;padding:.25rem 0}.kj-chat-typing__dot{width:.5rem;height:.5rem;border-radius:50%;background:var(--kj-fg-muted);animation:kj-chat-bounce 1.2s infinite ease-in-out both}.kj-chat-typing__dot:nth-child(2){animation-delay:.15s}.kj-chat-typing__dot:nth-child(3){animation-delay:.3s}@keyframes kj-chat-bounce{0%,80%,to{transform:scale(.5);opacity:.4}40%{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.kj-chat-typing__dot{animation:none;opacity:.6}}.kj-prompt{position:relative;display:flex;align-items:flex-end;gap:var(--kj-space-sm);padding:var(--kj-space-sm);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-box);background:var(--kj-bg-field, var(--kj-input-bg))}.kj-prompt:focus-within{border-color:var(--kj-border-focus)}.kj-prompt__textarea{flex:1 1 auto;resize:none;border:none;outline:none;background:transparent;color:var(--kj-input-fg, var(--kj-fg-default));font-family:var(--kj-font-sans);font-size:var(--kj-input-font-size, 1rem);line-height:1.5;max-height:200px;overflow-y:auto}.kj-prompt__textarea::placeholder{color:var(--kj-input-placeholder-fg, var(--kj-fg-muted))}.kj-prompt__actions{display:flex;align-items:center;gap:var(--kj-space-xs);flex:0 0 auto}.kj-prompt__btn{display:grid;place-items:center;width:2.75rem;height:2.75rem;border-radius:50%;border:none;cursor:pointer;font-size:1.1rem}.kj-prompt__btn:focus-visible{outline:2px solid var(--kj-border-focus);outline-offset:2px}.kj-prompt__btn--send{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-prompt__btn--send:disabled{opacity:.5;cursor:not-allowed}.kj-prompt__btn--stop{background:var(--kj-bg-danger);color:var(--kj-fg-on-danger)}.kj-prompt__slash{position:absolute;bottom:calc(100% + .25rem);left:0;right:0;margin:0;padding:.25rem;list-style:none;background:var(--kj-bg-surface);border:1px solid var(--kj-border-default);border-radius:var(--kj-radius-field);box-shadow:0 4px 16px #00000026;max-height:14rem;overflow-y:auto;z-index:10}.kj-prompt__slash-item{display:flex;flex-direction:column;gap:.1rem;padding:var(--kj-space-xs) var(--kj-space-sm);border-radius:var(--kj-radius-field);cursor:pointer}.kj-prompt__slash-item.is-active,.kj-prompt__slash-item:hover{background:var(--kj-bg-primary);color:var(--kj-fg-on-primary)}.kj-prompt__slash-name{font-family:ui-monospace,monospace;font-weight:600}.kj-prompt__slash-desc{font-size:var(--kj-text-sm);color:var(--kj-fg-muted)}.kj-prompt__slash-item.is-active .kj-prompt__slash-desc,.kj-prompt__slash-item:hover .kj-prompt__slash-desc{color:inherit}}\n"], dependencies: [{ kind: "directive", type: KjChatLog, selector: "[kjChatLog]", inputs: ["kjChatLogLive", "kjChatLogLabel"] }, { kind: "component", type: KjChatMessage, selector: "kj-chat-message", inputs: ["message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3368
3703
  }
3369
3704
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjChatThread, decorators: [{
3370
3705
  type: Component,
3371
- args: [{ selector: 'kj-chat-thread', standalone: true, imports: [KjChatLog, KjChatMessage], providers: [KjChatAnnouncer], template: `
3706
+ args: [{ selector: 'kj-chat-thread', standalone: true, imports: [KjChatLog, KjChatMessage, NgComponentOutlet], providers: [KjChatAnnouncer], template: `
3372
3707
  <div #log kjChatLog kjChatLogLive="off" class="kj-chat-thread" [kjChatLogLabel]="kjLabel()">
3373
3708
  @for (m of store().messages(); track m.id) {
3374
- <kj-chat-message [message]="m" />
3709
+ @if (rendererFor(m); as renderer) {
3710
+ <ng-container
3711
+ *ngComponentOutlet="renderer; inputs: { item: itemFor(m), message: m }"
3712
+ ></ng-container>
3713
+ } @else {
3714
+ <kj-chat-message [message]="m" />
3715
+ }
3375
3716
  }
3376
3717
  </div>
3377
3718
 
@@ -12074,81 +12415,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
12074
12415
  `, encapsulation: ViewEncapsulation.None, host: { 'style': 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-password-input{--kj-password-input-bg: var(--kj-bg-field);--kj-password-input-fg: var(--kj-fg-default);--kj-password-input-border-color: var(--kj-border-default);--kj-password-input-border-style: solid;--kj-password-input-border-width: var(--kj-border);--kj-password-input-radius: var(--kj-radius-field);--kj-password-input-padding-x: var(--kj-space-md);--kj-password-input-padding-y: var(--kj-space-sm);--kj-password-input-font: var(--kj-font-sans);--kj-password-input-font-size: var(--kj-text-sm);--kj-password-input-placeholder-fg: var(--kj-fg-muted);--kj-password-input-toggle-fg: var(--kj-fg-muted);--kj-password-input-toggle-fg-hover: var(--kj-fg-default);--kj-password-input-height: 2.25rem;display:inline-flex;align-items:center;gap:var(--kj-space-xs);inline-size:100%;height:var(--kj-password-input-height);background:var(--kj-password-input-bg);color:var(--kj-password-input-fg);border:var(--kj-password-input-border-width) var(--kj-password-input-border-style) var(--kj-password-input-border-color);border-radius:var(--kj-password-input-radius);padding:0 var(--kj-password-input-padding-x);font:var(--kj-password-input-font-size) / 1.4 var(--kj-password-input-font);transition:var(--kj-transition)}.kj-password-input[data-size=sm]{--kj-password-input-height: 2rem;--kj-password-input-font-size: .8125rem}.kj-password-input[data-size=lg]{--kj-password-input-height: 2.75rem;--kj-password-input-font-size: 1rem}.kj-password-input__field{flex:1 1 auto;inline-size:100%;height:100%;background:transparent;color:inherit;border:0;outline:0;padding:0;font:inherit}.kj-password-input__field::placeholder{color:var(--kj-password-input-placeholder-fg)}.kj-password-input:focus-within{outline:2px solid var(--kj-fg-primary);outline-offset:2px;--kj-password-input-border-color: var(--kj-fg-primary)}.kj-password-input:has([aria-invalid=true]){--kj-password-input-border-color: var(--kj-fg-danger)}.kj-password-input:has([aria-disabled=true]),.kj-password-input:has(:disabled){opacity:.55;cursor:not-allowed}.kj-password-input__toggle{display:inline-flex;align-items:center;justify-content:center;inline-size:1.75rem;block-size:1.75rem;margin-inline-start:var(--kj-space-xs);color:var(--kj-password-input-toggle-fg);background:transparent;border:0;border-radius:var(--kj-radius-field);cursor:pointer}.kj-password-input__toggle:hover,.kj-password-input__toggle:focus-visible{color:var(--kj-password-input-toggle-fg-hover)}.kj-password-input__toggle:focus-visible{outline:2px solid var(--kj-fg-primary);outline-offset:2px}.kj-password-input__icon{inline-size:1rem;block-size:1rem;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.kj-password-strength{display:flex;gap:var(--kj-space-xs);inline-size:100%;margin-block-start:var(--kj-space-xs)}.kj-password-strength__seg{flex:1 1 0;block-size:.25rem;background:var(--kj-bg-surface);border-radius:2px;transition:background-color var(--kj-transition)}.kj-password-strength[data-score=\"1\"] .kj-password-strength__seg:nth-child(-n+1),.kj-password-strength[data-score=\"2\"] .kj-password-strength__seg:nth-child(-n+2),.kj-password-strength[data-score=\"3\"] .kj-password-strength__seg:nth-child(-n+3),.kj-password-strength[data-score=\"4\"] .kj-password-strength__seg:nth-child(-n+4){background:var(--kj-password-strength-color, var(--kj-bg-primary))}.kj-password-strength[data-score=\"1\"]{--kj-password-strength-color: var(--kj-bg-danger)}.kj-password-strength[data-score=\"2\"]{--kj-password-strength-color: var(--kj-bg-warning, #d97706)}.kj-password-strength[data-score=\"3\"],.kj-password-strength[data-score=\"4\"]{--kj-password-strength-color: var(--kj-bg-success, #059669)}.kj-password-strength__label{display:block;margin-block-start:var(--kj-space-xs);font-size:var(--kj-text-xs);color:var(--kj-fg-muted)}.kj-password-caps-lock-warning{display:block;margin-block-start:var(--kj-space-xs);font-size:var(--kj-text-xs);color:var(--kj-fg-warning, #d97706)}.kj-password-caps-lock-warning[hidden]{display:none}}\n"] }]
12075
12416
  }], ctorParameters: () => [], propDecorators: { kjAutocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAutocomplete", required: false }] }], kjMaxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjMaxLength", required: false }] }], kjDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjDisabled", required: false }] }], kjInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjInvalid", required: false }] }], kjRevealed: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjRevealed", required: false }] }, { type: i0.Output, args: ["kjRevealedChange"] }], kjPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjPlaceholder", required: false }] }], kjValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjValue", required: false }] }, { type: i0.Output, args: ["kjValueChange"] }], kjShowToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjShowToggle", required: false }] }], kjShowStrength: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjShowStrength", required: false }] }], kjShowCapsLockWarning: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjShowCapsLockWarning", required: false }] }], kjShowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjShowLabel", required: false }] }], kjHideLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjHideLabel", required: false }] }], kjCapsLockMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjCapsLockMessage", required: false }] }] } });
12076
12417
 
12077
- /**
12078
- * Click-triggered popover. Compose `[kjPopoverTrigger]` + `<kj-popover-content [kjFor]="t">`
12079
- * with optional `[kjPopoverTitle]` heading and `[kjPopoverClose]` button. The
12080
- * wrapper itself only projects content — its purpose is to host the docs tags.
12081
- *
12082
- * @doc
12083
- * @doc-name popover
12084
- * @doc-is-main
12085
- * @doc-example Default
12086
- * A trigger button + content panel — the bare-minimum recipe.
12087
- * @doc-file popover.example.ts
12088
- * @doc-example Usage
12089
- * A walkthrough of the most common usages — info panel, side-pinned, and
12090
- * modal confirmation. Use this as the copy-paste starting point.
12091
- * @doc-file popover.usage.example.ts
12092
- * @doc-example Sides
12093
- * `kjSide="top|right|bottom|left"` pins the panel relative to the trigger.
12094
- * @doc-file popover.sides.example.ts
12095
- * @doc-example Modal
12096
- * `[kjTrap]="true"` traps focus inside the panel until it closes.
12097
- * @doc-file popover.modal.example.ts
12098
- * @doc-example Cancellable
12099
- * `[kjPopoverClose]` on a button dismisses the panel and restores focus.
12100
- * @doc-file popover.cancellable.example.ts
12101
- * @doc-example With form
12102
- * Embed inputs inside the panel — focus returns to the trigger on close.
12103
- * @doc-file popover.with-form.example.ts
12104
- *
12105
- * @doc-keyboard
12106
- * Enter|Space — Opens the popover from the trigger
12107
- * Escape — Closes the popover and returns focus to the trigger
12108
- * Tab — Moves focus through the panel; cycles inside when [kjTrap]="true"
12109
- * Shift+Tab — Reverse-cycles focus
12110
- *
12111
- * @doc-aria
12112
- * aria-haspopup — Set to "dialog" on the trigger
12113
- * aria-expanded — Reflects the open/closed state on the trigger
12114
- * aria-controls — Links the trigger to the panel id
12115
- * role="dialog" — On the panel (provided via the overlay primitive)
12116
- * aria-labelledby — Wire to `[kjPopoverTitle]` heading id for an accessible name
12117
- *
12118
- * @doc-touch
12119
- * Trigger buttons inherit `kj-button` sizing — use `kjSize="md"` or larger
12120
- * to satisfy WCAG 2.5.5 (44×44). The panel itself is not a touch target.
12121
- *
12122
- * @doc-a11y
12123
- * The panel is portalled to `document.body` so it escapes clipping
12124
- * ancestors while staying linked via `aria-controls`. Focus is returned
12125
- * to the trigger on close, and outside clicks dismiss by default. For
12126
- * destructive confirmations, set `[kjTrap]="true"` so the user must
12127
- * commit or cancel via a visible affordance.
12128
- *
12129
- * @doc-related dialog,tooltip,dropdown-menu
12130
- *
12131
- * @doc-css-var
12132
- * --kj-popover-bg — Panel background fill. Inherits --kj-bg-elevated.
12133
- * --kj-popover-fg — Panel foreground (text) color.
12134
- * --kj-popover-border-color — Panel border color. Inherits --kj-border-default.
12135
- * --kj-popover-radius — Panel corner radius. Inherits --kj-radius-box.
12136
- * --kj-popover-padding-x — Horizontal padding inside the panel.
12137
- * --kj-popover-padding-y — Vertical padding inside the panel.
12138
- * --kj-popover-shadow — Drop shadow under the panel. Inherits --kj-shadow-md.
12139
- * --kj-popover-arrow-size — Edge length of the optional arrow indicator.
12140
- *
12141
- * @doc-category Library/Overlay
12142
- */
12143
- class KjPopoverComponent {
12144
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12145
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: KjPopoverComponent, isStandalone: true, selector: "kj-popover", host: { styleAttribute: "display: contents;" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-popover-content{--kj-popover-bg: var(--kj-bg-elevated);--kj-popover-fg: var(--kj-fg-default);--kj-popover-border-color: var(--kj-border-default);--kj-popover-radius: var(--kj-radius-box);--kj-popover-padding-x: var(--kj-space-md);--kj-popover-padding-y: var(--kj-space-md);--kj-popover-shadow: var(--kj-shadow-md, 0 12px 32px rgb(0 0 0 / .18));--kj-popover-arrow-size: 8px;display:block;box-sizing:border-box;max-width:min(28rem,calc(100vw - 2rem));background:var(--kj-popover-bg);color:var(--kj-popover-fg);border:var(--kj-border) solid var(--kj-popover-border-color);border-radius:var(--kj-popover-radius);padding:var(--kj-popover-padding-y) var(--kj-popover-padding-x);font:.875rem / 1.5 var(--kj-font-sans);box-shadow:var(--kj-popover-shadow);pointer-events:auto;z-index:1000;word-wrap:break-word}.kj-popover-content[hidden]{display:none}.kj-popover-title{display:block;font-size:.9375rem;font-weight:600;margin:0 0 var(--kj-space-xs);color:var(--kj-fg-default)}.kj-popover-arrow{position:absolute;width:var(--kj-popover-arrow-size, 8px);height:var(--kj-popover-arrow-size, 8px);background:var(--kj-popover-bg, var(--kj-bg-elevated));border:var(--kj-border) solid var(--kj-popover-border-color, var(--kj-border-default));transform:rotate(45deg)}.kj-popover-content[data-side=top] .kj-popover-arrow{bottom:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-top-color:transparent;border-left-color:transparent}.kj-popover-content[data-side=bottom] .kj-popover-arrow{top:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-bottom-color:transparent;border-right-color:transparent}.kj-popover-content[data-side=left] .kj-popover-arrow{right:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-left-color:transparent;border-bottom-color:transparent}.kj-popover-content[data-side=right] .kj-popover-arrow{left:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-right-color:transparent;border-top-color:transparent}body[data-kj-scroll-lock=true]{overflow:hidden}@media(prefers-reduced-motion:no-preference){.kj-popover-content{transition:opacity .14s ease-out,transform .14s ease-out}.kj-popover-content[data-state=closed]{opacity:0;transform:scale(.98)}.kj-popover-content[data-state=open]{opacity:1;transform:scale(1)}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
12146
- }
12147
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjPopoverComponent, decorators: [{
12148
- type: Component,
12149
- args: [{ selector: 'kj-popover', standalone: true, imports: [KjPopoverTrigger, KjPopoverContent, KjPopoverArrow, KjPopoverClose, KjPopoverTitle], template: `<ng-content />`, encapsulation: ViewEncapsulation.None, host: { style: 'display: contents;' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-popover-content{--kj-popover-bg: var(--kj-bg-elevated);--kj-popover-fg: var(--kj-fg-default);--kj-popover-border-color: var(--kj-border-default);--kj-popover-radius: var(--kj-radius-box);--kj-popover-padding-x: var(--kj-space-md);--kj-popover-padding-y: var(--kj-space-md);--kj-popover-shadow: var(--kj-shadow-md, 0 12px 32px rgb(0 0 0 / .18));--kj-popover-arrow-size: 8px;display:block;box-sizing:border-box;max-width:min(28rem,calc(100vw - 2rem));background:var(--kj-popover-bg);color:var(--kj-popover-fg);border:var(--kj-border) solid var(--kj-popover-border-color);border-radius:var(--kj-popover-radius);padding:var(--kj-popover-padding-y) var(--kj-popover-padding-x);font:.875rem / 1.5 var(--kj-font-sans);box-shadow:var(--kj-popover-shadow);pointer-events:auto;z-index:1000;word-wrap:break-word}.kj-popover-content[hidden]{display:none}.kj-popover-title{display:block;font-size:.9375rem;font-weight:600;margin:0 0 var(--kj-space-xs);color:var(--kj-fg-default)}.kj-popover-arrow{position:absolute;width:var(--kj-popover-arrow-size, 8px);height:var(--kj-popover-arrow-size, 8px);background:var(--kj-popover-bg, var(--kj-bg-elevated));border:var(--kj-border) solid var(--kj-popover-border-color, var(--kj-border-default));transform:rotate(45deg)}.kj-popover-content[data-side=top] .kj-popover-arrow{bottom:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-top-color:transparent;border-left-color:transparent}.kj-popover-content[data-side=bottom] .kj-popover-arrow{top:calc(var(--kj-popover-arrow-size) / -2);left:50%;margin-left:calc(var(--kj-popover-arrow-size) / -2);border-bottom-color:transparent;border-right-color:transparent}.kj-popover-content[data-side=left] .kj-popover-arrow{right:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-left-color:transparent;border-bottom-color:transparent}.kj-popover-content[data-side=right] .kj-popover-arrow{left:calc(var(--kj-popover-arrow-size) / -2);top:50%;margin-top:calc(var(--kj-popover-arrow-size) / -2);border-right-color:transparent;border-top-color:transparent}body[data-kj-scroll-lock=true]{overflow:hidden}@media(prefers-reduced-motion:no-preference){.kj-popover-content{transition:opacity .14s ease-out,transform .14s ease-out}.kj-popover-content[data-state=closed]{opacity:0;transform:scale(.98)}.kj-popover-content[data-state=open]{opacity:1;transform:scale(1)}}}\n"] }]
12150
- }] });
12151
-
12152
12418
  /**
12153
12419
  * Styled wrapper around the headless `KjProgressBar` directive. Composes the
12154
12420
  * core directive on an inner element and renders the inner
@@ -18642,6 +18908,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
18642
18908
  * @doc-example Tag list
18643
18909
  * Wrap chips in `<kj-tag-list>` for single-tab-stop + arrow-key navigation.
18644
18910
  * @doc-file tag.list.example.ts
18911
+ * @doc-example Overflow
18912
+ * `[kjMax]` on the list collapses the extra chips into a "+N" chip whose hover
18913
+ * panel lists them.
18914
+ * @doc-file tag.overflow.example.ts
18915
+ * @doc-example Overflow template
18916
+ * `<ng-template kjOverflowContent>` renders the collapsed chips your way — here
18917
+ * as rows with a remove action.
18918
+ * @doc-file tag.overflow-template.example.ts
18645
18919
  *
18646
18920
  * @doc-keyboard
18647
18921
  * Enter|Space — Activates a selectable tag; flips `aria-pressed`
@@ -18688,26 +18962,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
18688
18962
  */
18689
18963
  class KjTagComponent {
18690
18964
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjTagComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18691
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: KjTagComponent, isStandalone: true, selector: "kj-tag", host: { classAttribute: "kj-tag" }, hostDirectives: [{ directive: i1.KjTag, inputs: ["kjTagSelectable", "kjTagSelectable", "kjTagSelected", "kjTagSelected", "kjTagLabel", "kjTagLabel", "kjTagDisabled", "kjTagDisabled"], outputs: ["kjTagSelectedChange", "kjTagSelectedChange", "kjTagRemoved", "kjTagRemoved"] }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-tag{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: transparent;--kj-tag-radius: var(--kj-radius-selector);--kj-tag-padding-x: var(--kj-space-sm);--kj-tag-padding-y: .125rem;--kj-tag-font-size: .75rem;--kj-tag-gap: .25rem;display:inline-flex;align-items:center;gap:var(--kj-tag-gap);background:var(--kj-tag-bg);color:var(--kj-tag-fg);border:var(--kj-border) solid var(--kj-tag-border-color);border-radius:var(--kj-tag-radius);padding:var(--kj-tag-padding-y) var(--kj-tag-padding-x);font:var(--kj-tag-font-size) / 1 var(--kj-font-sans);font-weight:500;transition:background-color var(--kj-transition),color var(--kj-transition),border-color var(--kj-transition)}.kj-tag[data-variant=secondary]{--kj-tag-bg: var(--kj-bg-field);--kj-tag-fg: var(--kj-fg-default)}.kj-tag[data-variant=destructive],.kj-tag[data-variant=danger]{--kj-tag-bg: var(--kj-bg-danger);--kj-tag-fg: var(--kj-fg-on-danger)}.kj-tag[data-variant=success]{--kj-tag-bg: var(--kj-bg-success, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-success, var(--kj-fg-on-primary))}.kj-tag[data-variant=warning]{--kj-tag-bg: var(--kj-bg-warning, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-warning, var(--kj-fg-on-primary))}.kj-tag[data-variant=info]{--kj-tag-bg: var(--kj-bg-info, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-info, var(--kj-fg-on-primary))}.kj-tag[data-variant=outline]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: var(--kj-border-default)}.kj-tag[data-variant=ghost]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: transparent}.kj-tag[data-size=xs]{--kj-tag-padding-x: .1875rem;--kj-tag-padding-y: .03125rem;--kj-tag-font-size: .5625rem;--kj-tag-gap: .125rem}.kj-tag[data-size=sm]{--kj-tag-padding-x: .375rem;--kj-tag-padding-y: .125rem;--kj-tag-font-size: .6875rem}.kj-tag[data-size=lg]{--kj-tag-padding-x: var(--kj-space-md);--kj-tag-font-size: .875rem}.kj-tag[role=button],.kj-tag[role=option]{cursor:pointer;-webkit-user-select:none;user-select:none}.kj-tag[role=button]:hover,.kj-tag[role=option]:hover{--kj-tag-bg: color-mix(in oklab, var(--kj-tag-bg) 88%, var(--kj-fg-default))}.kj-tag[aria-pressed=true],.kj-tag[aria-selected=true],.kj-tag[data-selected]{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: var(--kj-bg-primary)}.kj-tag[data-focus-visible]:focus,.kj-tag:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}.kj-tag[aria-disabled=true],.kj-tag[data-disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-inline-start:.125rem;padding:0;border:none;background:transparent;color:inherit;border-radius:999px;cursor:pointer;line-height:1;font:inherit;position:relative}.kj-tag-remove:after{content:\"\";position:absolute;inset:-.875rem;border-radius:inherit}.kj-tag-remove:hover{background:color-mix(in oklab,currentColor 18%,transparent)}.kj-tag-remove:focus-visible{outline:2px solid currentColor;outline-offset:2px}.kj-tag-remove[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-list{display:inline-flex;flex-wrap:wrap;gap:var(--kj-space-sm);align-items:center}.kj-tag-list[role=listbox]:focus-visible,.kj-tag-list[role=grid]:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18965
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: KjTagComponent, isStandalone: true, selector: "kj-tag", host: { classAttribute: "kj-tag" }, hostDirectives: [{ directive: i1.KjTag, inputs: ["kjTagSelectable", "kjTagSelectable", "kjTagSelected", "kjTagSelected", "kjTagLabel", "kjTagLabel", "kjTagDisabled", "kjTagDisabled"], outputs: ["kjTagSelectedChange", "kjTagSelectedChange", "kjTagRemoved", "kjTagRemoved"] }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["@layer kj.component{.kj-tag{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: transparent;--kj-tag-radius: var(--kj-radius-selector);--kj-tag-padding-x: var(--kj-space-sm);--kj-tag-padding-y: .125rem;--kj-tag-font-size: .75rem;--kj-tag-gap: .25rem;display:inline-flex;align-items:center;gap:var(--kj-tag-gap);background:var(--kj-tag-bg);color:var(--kj-tag-fg);border:var(--kj-border) solid var(--kj-tag-border-color);border-radius:var(--kj-tag-radius);padding:var(--kj-tag-padding-y) var(--kj-tag-padding-x);font:var(--kj-tag-font-size) / 1 var(--kj-font-sans);font-weight:500;transition:background-color var(--kj-transition),color var(--kj-transition),border-color var(--kj-transition)}.kj-tag[data-variant=secondary]{--kj-tag-bg: var(--kj-bg-field);--kj-tag-fg: var(--kj-fg-default)}.kj-tag[data-variant=destructive],.kj-tag[data-variant=danger]{--kj-tag-bg: var(--kj-bg-danger);--kj-tag-fg: var(--kj-fg-on-danger)}.kj-tag[data-variant=success]{--kj-tag-bg: var(--kj-bg-success, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-success, var(--kj-fg-on-primary))}.kj-tag[data-variant=warning]{--kj-tag-bg: var(--kj-bg-warning, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-warning, var(--kj-fg-on-primary))}.kj-tag[data-variant=info]{--kj-tag-bg: var(--kj-bg-info, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-info, var(--kj-fg-on-primary))}.kj-tag[data-variant=outline]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: var(--kj-border-default)}.kj-tag[data-variant=ghost]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: transparent}.kj-tag[data-size=xs]{--kj-tag-padding-x: .1875rem;--kj-tag-padding-y: .03125rem;--kj-tag-font-size: .5625rem;--kj-tag-gap: .125rem}.kj-tag[data-size=sm]{--kj-tag-padding-x: .375rem;--kj-tag-padding-y: .125rem;--kj-tag-font-size: .6875rem}.kj-tag[data-size=lg]{--kj-tag-padding-x: var(--kj-space-md);--kj-tag-font-size: .875rem}.kj-tag[role=button],.kj-tag[role=option]{cursor:pointer;-webkit-user-select:none;user-select:none}.kj-tag[role=button]:hover,.kj-tag[role=option]:hover{--kj-tag-bg: color-mix(in oklab, var(--kj-tag-bg) 88%, var(--kj-fg-default))}.kj-tag[aria-pressed=true],.kj-tag[aria-selected=true],.kj-tag[data-selected]{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: var(--kj-bg-primary)}.kj-tag[data-focus-visible]:focus,.kj-tag:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}.kj-tag[aria-disabled=true],.kj-tag[data-disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-inline-start:.125rem;padding:0;border:none;background:transparent;color:inherit;border-radius:999px;cursor:pointer;line-height:1;font:inherit;position:relative}.kj-tag-remove:after{content:\"\";position:absolute;inset:-.875rem;border-radius:inherit}.kj-tag-remove:hover{background:color-mix(in oklab,currentColor 18%,transparent)}.kj-tag-remove:focus-visible{outline:2px solid currentColor;outline-offset:2px}.kj-tag-remove[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag[hidden]{display:none}.kj-tag-list{display:inline-flex;flex-wrap:wrap;gap:var(--kj-space-sm);align-items:center}.kj-tag-list-overflow{border-style:dashed;--kj-tag-border-color: var(--kj-border-default)}.kj-tag-list-overflow[aria-expanded=true]{--kj-tag-bg: var(--kj-bg-field)}.kj-tag-list[role=listbox]:focus-visible,.kj-tag-list[role=grid]:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18692
18966
  }
18693
18967
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjTagComponent, decorators: [{
18694
18968
  type: Component,
18695
18969
  args: [{ selector: 'kj-tag', standalone: true, hostDirectives: [
18696
18970
  {
18697
18971
  directive: KjTag,
18698
- inputs: [
18699
- 'kjTagSelectable',
18700
- 'kjTagSelected',
18701
- 'kjTagLabel',
18702
- 'kjTagDisabled',
18703
- ],
18972
+ inputs: ['kjTagSelectable', 'kjTagSelected', 'kjTagLabel', 'kjTagDisabled'],
18704
18973
  outputs: ['kjTagSelectedChange', 'kjTagRemoved'],
18705
18974
  },
18706
18975
  ], template: `<ng-content />`, encapsulation: ViewEncapsulation.None, host: {
18707
18976
  class: 'kj-tag',
18708
18977
  // The KjVariant / KjSize host directives nested inside KjTag set
18709
18978
  // [data-variant] / [data-size] on the same host element automatically.
18710
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-tag{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: transparent;--kj-tag-radius: var(--kj-radius-selector);--kj-tag-padding-x: var(--kj-space-sm);--kj-tag-padding-y: .125rem;--kj-tag-font-size: .75rem;--kj-tag-gap: .25rem;display:inline-flex;align-items:center;gap:var(--kj-tag-gap);background:var(--kj-tag-bg);color:var(--kj-tag-fg);border:var(--kj-border) solid var(--kj-tag-border-color);border-radius:var(--kj-tag-radius);padding:var(--kj-tag-padding-y) var(--kj-tag-padding-x);font:var(--kj-tag-font-size) / 1 var(--kj-font-sans);font-weight:500;transition:background-color var(--kj-transition),color var(--kj-transition),border-color var(--kj-transition)}.kj-tag[data-variant=secondary]{--kj-tag-bg: var(--kj-bg-field);--kj-tag-fg: var(--kj-fg-default)}.kj-tag[data-variant=destructive],.kj-tag[data-variant=danger]{--kj-tag-bg: var(--kj-bg-danger);--kj-tag-fg: var(--kj-fg-on-danger)}.kj-tag[data-variant=success]{--kj-tag-bg: var(--kj-bg-success, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-success, var(--kj-fg-on-primary))}.kj-tag[data-variant=warning]{--kj-tag-bg: var(--kj-bg-warning, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-warning, var(--kj-fg-on-primary))}.kj-tag[data-variant=info]{--kj-tag-bg: var(--kj-bg-info, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-info, var(--kj-fg-on-primary))}.kj-tag[data-variant=outline]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: var(--kj-border-default)}.kj-tag[data-variant=ghost]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: transparent}.kj-tag[data-size=xs]{--kj-tag-padding-x: .1875rem;--kj-tag-padding-y: .03125rem;--kj-tag-font-size: .5625rem;--kj-tag-gap: .125rem}.kj-tag[data-size=sm]{--kj-tag-padding-x: .375rem;--kj-tag-padding-y: .125rem;--kj-tag-font-size: .6875rem}.kj-tag[data-size=lg]{--kj-tag-padding-x: var(--kj-space-md);--kj-tag-font-size: .875rem}.kj-tag[role=button],.kj-tag[role=option]{cursor:pointer;-webkit-user-select:none;user-select:none}.kj-tag[role=button]:hover,.kj-tag[role=option]:hover{--kj-tag-bg: color-mix(in oklab, var(--kj-tag-bg) 88%, var(--kj-fg-default))}.kj-tag[aria-pressed=true],.kj-tag[aria-selected=true],.kj-tag[data-selected]{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: var(--kj-bg-primary)}.kj-tag[data-focus-visible]:focus,.kj-tag:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}.kj-tag[aria-disabled=true],.kj-tag[data-disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-inline-start:.125rem;padding:0;border:none;background:transparent;color:inherit;border-radius:999px;cursor:pointer;line-height:1;font:inherit;position:relative}.kj-tag-remove:after{content:\"\";position:absolute;inset:-.875rem;border-radius:inherit}.kj-tag-remove:hover{background:color-mix(in oklab,currentColor 18%,transparent)}.kj-tag-remove:focus-visible{outline:2px solid currentColor;outline-offset:2px}.kj-tag-remove[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-list{display:inline-flex;flex-wrap:wrap;gap:var(--kj-space-sm);align-items:center}.kj-tag-list[role=listbox]:focus-visible,.kj-tag-list[role=grid]:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"] }]
18979
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@layer kj.component{.kj-tag{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: transparent;--kj-tag-radius: var(--kj-radius-selector);--kj-tag-padding-x: var(--kj-space-sm);--kj-tag-padding-y: .125rem;--kj-tag-font-size: .75rem;--kj-tag-gap: .25rem;display:inline-flex;align-items:center;gap:var(--kj-tag-gap);background:var(--kj-tag-bg);color:var(--kj-tag-fg);border:var(--kj-border) solid var(--kj-tag-border-color);border-radius:var(--kj-tag-radius);padding:var(--kj-tag-padding-y) var(--kj-tag-padding-x);font:var(--kj-tag-font-size) / 1 var(--kj-font-sans);font-weight:500;transition:background-color var(--kj-transition),color var(--kj-transition),border-color var(--kj-transition)}.kj-tag[data-variant=secondary]{--kj-tag-bg: var(--kj-bg-field);--kj-tag-fg: var(--kj-fg-default)}.kj-tag[data-variant=destructive],.kj-tag[data-variant=danger]{--kj-tag-bg: var(--kj-bg-danger);--kj-tag-fg: var(--kj-fg-on-danger)}.kj-tag[data-variant=success]{--kj-tag-bg: var(--kj-bg-success, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-success, var(--kj-fg-on-primary))}.kj-tag[data-variant=warning]{--kj-tag-bg: var(--kj-bg-warning, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-warning, var(--kj-fg-on-primary))}.kj-tag[data-variant=info]{--kj-tag-bg: var(--kj-bg-info, var(--kj-bg-primary));--kj-tag-fg: var(--kj-fg-on-info, var(--kj-fg-on-primary))}.kj-tag[data-variant=outline]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: var(--kj-border-default)}.kj-tag[data-variant=ghost]{--kj-tag-bg: transparent;--kj-tag-fg: var(--kj-fg-default);--kj-tag-border-color: transparent}.kj-tag[data-size=xs]{--kj-tag-padding-x: .1875rem;--kj-tag-padding-y: .03125rem;--kj-tag-font-size: .5625rem;--kj-tag-gap: .125rem}.kj-tag[data-size=sm]{--kj-tag-padding-x: .375rem;--kj-tag-padding-y: .125rem;--kj-tag-font-size: .6875rem}.kj-tag[data-size=lg]{--kj-tag-padding-x: var(--kj-space-md);--kj-tag-font-size: .875rem}.kj-tag[role=button],.kj-tag[role=option]{cursor:pointer;-webkit-user-select:none;user-select:none}.kj-tag[role=button]:hover,.kj-tag[role=option]:hover{--kj-tag-bg: color-mix(in oklab, var(--kj-tag-bg) 88%, var(--kj-fg-default))}.kj-tag[aria-pressed=true],.kj-tag[aria-selected=true],.kj-tag[data-selected]{--kj-tag-bg: var(--kj-bg-primary);--kj-tag-fg: var(--kj-fg-on-primary);--kj-tag-border-color: var(--kj-bg-primary)}.kj-tag[data-focus-visible]:focus,.kj-tag:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}.kj-tag[aria-disabled=true],.kj-tag[data-disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag-remove{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-inline-start:.125rem;padding:0;border:none;background:transparent;color:inherit;border-radius:999px;cursor:pointer;line-height:1;font:inherit;position:relative}.kj-tag-remove:after{content:\"\";position:absolute;inset:-.875rem;border-radius:inherit}.kj-tag-remove:hover{background:color-mix(in oklab,currentColor 18%,transparent)}.kj-tag-remove:focus-visible{outline:2px solid currentColor;outline-offset:2px}.kj-tag-remove[aria-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.kj-tag[hidden]{display:none}.kj-tag-list{display:inline-flex;flex-wrap:wrap;gap:var(--kj-space-sm);align-items:center}.kj-tag-list-overflow{border-style:dashed;--kj-tag-border-color: var(--kj-border-default)}.kj-tag-list-overflow[aria-expanded=true]{--kj-tag-bg: var(--kj-bg-field)}.kj-tag-list[role=listbox]:focus-visible,.kj-tag-list[role=grid]:focus-visible{outline:2px solid var(--kj-bg-primary);outline-offset:2px}}\n"] }]
18711
18980
  }] });
18712
18981
  /**
18713
18982
  * Trailing remove button for `<kj-tag>`. Composes the headless `KjTagRemove`
@@ -18744,15 +19013,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
18744
19013
  }] });
18745
19014
  /**
18746
19015
  * Optional chip-group container. Provides the listbox / grid / group ARIA
18747
- * wiring + roving tabindex via the headless `KjTagList` directive. Standalone
18748
- * tags work without it; wrap chips in `<kj-tag-list>` whenever the group
18749
- * needs a single tab stop and arrow-key navigation.
19016
+ * wiring + roving tabindex via the headless `KjTagList` directive, and — with
19017
+ * `[kjMax]` collapses the chips past the cap into a "+N" chip. Hovering,
19018
+ * focusing or tapping that chip opens a panel listing the collapsed chips;
19019
+ * project an `<ng-template kjOverflowContent>` to render the panel yourself
19020
+ * (the context carries the collapsed range, so you slice your own data and
19021
+ * add actions such as remove).
19022
+ *
19023
+ * Standalone tags work without it; wrap chips in `<kj-tag-list>` whenever the
19024
+ * group needs a single tab stop and arrow-key navigation.
19025
+ *
19026
+ * The "+N" chip is a button rendered inside the container: keep the container
19027
+ * on its default `group` role when using `kjMax` (a `listbox` / `grid` only
19028
+ * admits option / row children).
18750
19029
  * @doc
18751
19030
  * @doc-name tag
18752
19031
  */
18753
19032
  class KjTagListComponent {
19033
+ list = inject(KjTagList, { self: true });
19034
+ i18n = inject(KjTranslateService);
19035
+ /** Text of the "+N" chip. Defaults to the `overflow.more` translation (`+{count}`). */
19036
+ kjOverflowLabel = input(undefined, /* @ts-ignore */
19037
+ ...(ngDevMode ? [{ debugName: "kjOverflowLabel" }] : /* istanbul ignore next */ []));
19038
+ /** Accessible name of the chip button. Defaults to the `overflow.show` translation (`Show {count} more`). */
19039
+ kjOverflowAriaLabel = input(undefined, /* @ts-ignore */
19040
+ ...(ngDevMode ? [{ debugName: "kjOverflowAriaLabel" }] : /* istanbul ignore next */ []));
19041
+ /** Variant of the "+N" chip. Default `secondary`. */
19042
+ kjOverflowVariant = input('secondary', /* @ts-ignore */
19043
+ ...(ngDevMode ? [{ debugName: "kjOverflowVariant" }] : /* istanbul ignore next */ []));
19044
+ overflowTemplate = contentChild(KjOverflowContent, /* @ts-ignore */
19045
+ ...(ngDevMode ? [{ debugName: "overflowTemplate" }] : /* istanbul ignore next */ []));
19046
+ panel = viewChild(KjOverflowPanelComponent, /* @ts-ignore */
19047
+ ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
19048
+ overflowCount = this.list.overflowCount;
19049
+ overflowLabel = computed(() => {
19050
+ const n = this.overflowCount();
19051
+ return this.kjOverflowLabel()?.(n) ?? this.i18n.translate('overflow.more', { count: n });
19052
+ }, /* @ts-ignore */
19053
+ ...(ngDevMode ? [{ debugName: "overflowLabel" }] : /* istanbul ignore next */ []));
19054
+ overflowAriaLabel = computed(() => {
19055
+ const n = this.overflowCount();
19056
+ return this.kjOverflowAriaLabel()?.(n) ?? this.i18n.translate('overflow.show', { count: n });
19057
+ }, /* @ts-ignore */
19058
+ ...(ngDevMode ? [{ debugName: "overflowAriaLabel" }] : /* istanbul ignore next */ []));
19059
+ focusPanel(event) {
19060
+ event.preventDefault();
19061
+ this.panel()?.focusFirst();
19062
+ }
18754
19063
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjTagListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18755
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: KjTagListComponent, isStandalone: true, selector: "kj-tag-list", host: { classAttribute: "kj-tag-list" }, hostDirectives: [{ directive: i1.KjTagList, inputs: ["kjTagListRole", "kjTagListRole", "kjTagListOrientation", "kjTagListOrientation", "kjTagListMultiple", "kjTagListMultiple", "kjTagListDisabled", "kjTagListDisabled"] }], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19064
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: KjTagListComponent, isStandalone: true, selector: "kj-tag-list", inputs: { kjOverflowLabel: { classPropertyName: "kjOverflowLabel", publicName: "kjOverflowLabel", isSignal: true, isRequired: false, transformFunction: null }, kjOverflowAriaLabel: { classPropertyName: "kjOverflowAriaLabel", publicName: "kjOverflowAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, kjOverflowVariant: { classPropertyName: "kjOverflowVariant", publicName: "kjOverflowVariant", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kj-tag-list" }, queries: [{ propertyName: "overflowTemplate", first: true, predicate: KjOverflowContent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: KjOverflowPanelComponent, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.KjTagList, inputs: ["kjTagListRole", "kjTagListRole", "kjTagListOrientation", "kjTagListOrientation", "kjTagListMultiple", "kjTagListMultiple", "kjTagListDisabled", "kjTagListDisabled", "kjMax", "kjMax"] }], ngImport: i0, template: `
19065
+ <ng-content />
19066
+ @if (overflowCount() > 0) {
19067
+ <button
19068
+ type="button"
19069
+ class="kj-tag kj-overflow-trigger kj-tag-list-overflow"
19070
+ [attr.data-variant]="kjOverflowVariant()"
19071
+ kjPopoverTrigger
19072
+ kjTrigger="hover"
19073
+ #trg="kjPopoverTrigger"
19074
+ [attr.aria-label]="overflowAriaLabel()"
19075
+ (keydown.arrowdown)="focusPanel($event)"
19076
+ (keydown.enter)="focusPanel($event)"
19077
+ >
19078
+ {{ overflowLabel() }}
19079
+ </button>
19080
+ <kj-overflow-panel
19081
+ [kjFor]="trg"
19082
+ [kjCount]="overflowCount()"
19083
+ [kjStart]="list.visibleCount()"
19084
+ [kjEnd]="list.total()"
19085
+ [kjLabels]="list.hiddenLabels()"
19086
+ [kjTemplate]="overflowTemplate()"
19087
+ />
19088
+ }
19089
+ `, isInline: true, dependencies: [{ kind: "directive", type: KjPopoverTrigger, selector: "[kjPopoverTrigger]", inputs: ["kjTrigger", "kjDisabled", "kjOpenDelay", "kjCloseDelay"], exportAs: ["kjPopoverTrigger"] }, { kind: "component", type: KjOverflowPanelComponent, selector: "kj-overflow-panel", inputs: ["kjFor", "kjCount", "kjStart", "kjEnd", "kjLabels", "kjTemplate", "kjSide", "kjAlign"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18756
19090
  }
18757
19091
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjTagListComponent, decorators: [{
18758
19092
  type: Component,
@@ -18767,17 +19101,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
18767
19101
  'kjTagListOrientation',
18768
19102
  'kjTagListMultiple',
18769
19103
  'kjTagListDisabled',
19104
+ 'kjMax',
18770
19105
  ],
18771
19106
  },
18772
19107
  ],
18773
- template: `<ng-content />`,
19108
+ imports: [KjPopoverTrigger, KjOverflowPanelComponent],
19109
+ template: `
19110
+ <ng-content />
19111
+ @if (overflowCount() > 0) {
19112
+ <button
19113
+ type="button"
19114
+ class="kj-tag kj-overflow-trigger kj-tag-list-overflow"
19115
+ [attr.data-variant]="kjOverflowVariant()"
19116
+ kjPopoverTrigger
19117
+ kjTrigger="hover"
19118
+ #trg="kjPopoverTrigger"
19119
+ [attr.aria-label]="overflowAriaLabel()"
19120
+ (keydown.arrowdown)="focusPanel($event)"
19121
+ (keydown.enter)="focusPanel($event)"
19122
+ >
19123
+ {{ overflowLabel() }}
19124
+ </button>
19125
+ <kj-overflow-panel
19126
+ [kjFor]="trg"
19127
+ [kjCount]="overflowCount()"
19128
+ [kjStart]="list.visibleCount()"
19129
+ [kjEnd]="list.total()"
19130
+ [kjLabels]="list.hiddenLabels()"
19131
+ [kjTemplate]="overflowTemplate()"
19132
+ />
19133
+ }
19134
+ `,
18774
19135
  encapsulation: ViewEncapsulation.None,
18775
19136
  host: {
18776
19137
  class: 'kj-tag-list',
18777
19138
  },
18778
19139
  changeDetection: ChangeDetectionStrategy.OnPush,
18779
19140
  }]
18780
- }] });
19141
+ }], propDecorators: { kjOverflowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOverflowLabel", required: false }] }], kjOverflowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOverflowAriaLabel", required: false }] }], kjOverflowVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOverflowVariant", required: false }] }], overflowTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KjOverflowContent), { isSignal: true }] }], panel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => KjOverflowPanelComponent), { isSignal: true }] }] } });
18781
19142
 
18782
19143
  let nextId = 0;
18783
19144
  /**
@@ -20688,5 +21049,5 @@ const KJ_COMPONENTS_VERSION = '0.0.1';
20688
21049
  * Generated bundle index. Do not edit.
20689
21050
  */
20690
21051
 
20691
- export { KJ_COMPONENTS_VERSION, KJ_EDITOR_CONTRACT, KJ_FILTER_CONTEXT, KjAccordionComponent, KjAccordionContentComponent, KjAccordionItemComponent, KjAccordionTriggerComponent, KjActionSheet, KjActionSheetComponent, KjActionSheetRef, KjActionSheetService, KjAlertActionsComponent, KjAlertComponent, KjAlertDescriptionComponent, KjAlertDismissComponent, KjAlertIconComponent, KjAlertTitleComponent, KjAvatarComponent, KjAvatarGroupComponent, KjBadgeComponent, KjBooleanEditor, KjBreadcrumbComponent, KjBreadcrumbCurrentComponent, KjBreadcrumbEllipsisComponent, KjBreadcrumbItemComponent, KjBreadcrumbLinkComponent, KjBreadcrumbListComponent, KjBreadcrumbSeparatorComponent, KjBulkAction, KjButtonComponent, KjButtonGroupComponent, KjCalendarComponent, KjCardComponent, KjCardContentComponent, KjCardCoverComponent, KjCardFooterComponent, KjCardHeaderComponent, KjCardSubtitleComponent, KjCardTitleComponent, KjCarouselAutoplayComponent, KjCarouselComponent, KjCarouselIndicatorsComponent, KjCarouselNextComponent, KjCarouselPauseComponent, KjCarouselPreviousComponent, KjCarouselSlideComponent, KjCarouselViewportComponent, KjCascadeOptionComponent, KjCascadeSelectComponent, KjCascadeSubPanelComponent, KjCellEditorOutlet, KjCellTemplateDirective, KjChartComponent, KjChatAvatarComponent, KjChatBubbleComponent, KjChatComponent, KjChatFooterComponent, KjChatHeaderComponent, KjChatLogComponent, KjChatMessage, KjChatThread, KjCheckboxComponent, KjColorPickerComponent, KjComboboxComponent, KjComboboxEmptyComponent, KjComboboxLoadingComponent, KjComboboxOptionComponent, KjCommandEmptyComponent, KjCommandGroupComponent, KjCommandInputComponent, KjCommandItemComponent, KjCommandPaletteComponent, KjCommandPaletteFooter, KjCommandPaletteItemTemplate, KjCommandSeparatorComponent, KjConfirmPopupActionComponent, KjConfirmPopupActionsComponent, KjConfirmPopupCancelComponent, KjConfirmPopupComponent, KjConfirmPopupContentComponent, KjConfirmPopupMessageComponent, KjConfirmPopupTriggerComponent, KjDateEditor, KjDateFilter, KjDatePickerComponent, KjDateRangePresetsComponent, KjDatetimePickerComponent, KjDialogComponent, KjDirectionToggle, KjDividerComponent, KjDrawerComponent, KjDropdownMenuComponent, KjEditorComponent, KjEmptyStateActionsComponent, KjEmptyStateComponent, KjEmptyStateDescriptionComponent, KjEmptyStateIconComponent, KjEmptyStateTitleComponent, KjFieldComponent, KjFieldErrorComponent, KjFieldGroupComponent, KjFieldHelpComponent, KjFieldLabelComponent, KjFileUploadComponent, KjFilterCellOutlet, KjFormActionsComponent, KjFormComponent, KjFormSummaryComponent, KjIconGalleryExample, KjIconUsageExample, KjInputComponent, KjInputGroupAddonComponent, KjInputGroupComponent, KjInputMaskComponent, KjInputMaskCreditCardExample, KjInputMaskCustomTokensExample, KjInputMaskDateExample, KjInputMaskExample, KjInputMaskUsageExample, KjInputMaskValidationExample, KjInputOtpComponent, KjKbdComponent, KjLinkComponent, KjListComponent, KjListItemComponent, KjMenubarComponent, KjMenubarItemComponent, KjNumberEditor, KjNumberFilter, KjNumberInputComponent, KjOptionComponent, KjOverlayBadgeComponent, KjOverlayBadgeContentComponent, KjPaginationComponent, KjPaginationDefaultComponent, KjPaginationEllipsisComponent, KjPaginationFirstComponent, KjPaginationInfoComponent, KjPaginationItemComponent, KjPaginationLastComponent, KjPaginationNextComponent, KjPaginationPreviousComponent, KjPasswordInputComponent, KjPopoverComponent, KjProgressBarComponent, KjPromptInput, KjRadioComponent, KjRadioGroupComponent, KjRichTextEditorComponent, KjSelectComponent, KjSelectEditor, KjSelectFilter, KjSheetComponent, KjSkeletonComponent, KjSkipLinkComponent, KjSliderComponent, KjSpeedDialActionComponent, KjSpeedDialActionsComponent, KjSpeedDialComponent, KjSpeedDialTriggerComponent, KjSpinnerComponent, KjStepComponent, KjStepContentComponent, KjStepLabelComponent, KjStepperComponent, KjStepperNextComponent, KjStepperPreviousComponent, KjStepperResetComponent, KjTabComponent, KjTabListComponent, KjTabPanelComponent, KjTableComponent, KjTableEmptyTemplate, KjTableErrorTemplate, KjTableLoadingTemplate, KjTablePaginationComponent, KjTableSidePanelComponent, KjTableSidePanelContent, KjTableStatusBarComponent, KjTableToolbarComponent, KjTableVirtual, KjTabsComponent, KjTagComponent, KjTagListComponent, KjTagRemoveComponent, KjTextEditor, KjTextFilter, KjTextareaComponent, KjTimePickerComponent, KjToastCloseComponent, KjToastComponent, KjToastViewportComponent, KjToastWrapperComponent, KjToggleComponent, KjTooltipComponent, KjTreeSelectComponent, KjTreeSelectNodeComponent, LUCIDE_ICON_NAMES, bold, bulletList, codeBlock, copyToClipboard, defaultFeatures, downloadString, exportCsv, exportJson, heading, history, image, inlineCode, italic, link, markdownShortcuts, orderedList, provideLucideIcons, quote, renderMarkdown, strike, toTsv, underline };
21052
+ export { KJ_COMPONENTS_VERSION, KJ_EDITOR_CONTRACT, KJ_FILTER_CONTEXT, KjAccordionComponent, KjAccordionContentComponent, KjAccordionItemComponent, KjAccordionTriggerComponent, KjActionSheet, KjActionSheetComponent, KjActionSheetRef, KjActionSheetService, KjAlertActionsComponent, KjAlertComponent, KjAlertDescriptionComponent, KjAlertDismissComponent, KjAlertIconComponent, KjAlertTitleComponent, KjAvatarComponent, KjAvatarGroupComponent, KjBadgeComponent, KjBooleanEditor, KjBreadcrumbComponent, KjBreadcrumbCurrentComponent, KjBreadcrumbEllipsisComponent, KjBreadcrumbItemComponent, KjBreadcrumbLinkComponent, KjBreadcrumbListComponent, KjBreadcrumbSeparatorComponent, KjBulkAction, KjButtonComponent, KjButtonGroupComponent, KjCalendarComponent, KjCardComponent, KjCardContentComponent, KjCardCoverComponent, KjCardFooterComponent, KjCardHeaderComponent, KjCardSubtitleComponent, KjCardTitleComponent, KjCarouselAutoplayComponent, KjCarouselComponent, KjCarouselIndicatorsComponent, KjCarouselNextComponent, KjCarouselPauseComponent, KjCarouselPreviousComponent, KjCarouselSlideComponent, KjCarouselViewportComponent, KjCascadeOptionComponent, KjCascadeSelectComponent, KjCascadeSubPanelComponent, KjCellEditorOutlet, KjCellTemplateDirective, KjChartComponent, KjChatAvatarComponent, KjChatBubbleComponent, KjChatComponent, KjChatFooterComponent, KjChatHeaderComponent, KjChatLogComponent, KjChatMessage, KjChatThread, KjCheckboxComponent, KjColorPickerComponent, KjComboboxComponent, KjComboboxEmptyComponent, KjComboboxLoadingComponent, KjComboboxOptionComponent, KjCommandEmptyComponent, KjCommandGroupComponent, KjCommandInputComponent, KjCommandItemComponent, KjCommandPaletteComponent, KjCommandPaletteFooter, KjCommandPaletteItemTemplate, KjCommandSeparatorComponent, KjConfirmPopupActionComponent, KjConfirmPopupActionsComponent, KjConfirmPopupCancelComponent, KjConfirmPopupComponent, KjConfirmPopupContentComponent, KjConfirmPopupMessageComponent, KjConfirmPopupTriggerComponent, KjDateEditor, KjDateFilter, KjDatePickerComponent, KjDateRangePresetsComponent, KjDatetimePickerComponent, KjDialogComponent, KjDirectionToggle, KjDividerComponent, KjDrawerComponent, KjDropdownMenuComponent, KjEditorComponent, KjEmptyStateActionsComponent, KjEmptyStateComponent, KjEmptyStateDescriptionComponent, KjEmptyStateIconComponent, KjEmptyStateTitleComponent, KjFieldComponent, KjFieldErrorComponent, KjFieldGroupComponent, KjFieldHelpComponent, KjFieldLabelComponent, KjFileUploadComponent, KjFilterCellOutlet, KjFormActionsComponent, KjFormComponent, KjFormSummaryComponent, KjIconGalleryExample, KjIconUsageExample, KjInputComponent, KjInputGroupAddonComponent, KjInputGroupComponent, KjInputMaskComponent, KjInputMaskCreditCardExample, KjInputMaskCustomTokensExample, KjInputMaskDateExample, KjInputMaskExample, KjInputMaskUsageExample, KjInputMaskValidationExample, KjInputOtpComponent, KjKbdComponent, KjLinkComponent, KjListComponent, KjListItemComponent, KjMenubarComponent, KjMenubarItemComponent, KjNumberEditor, KjNumberFilter, KjNumberInputComponent, KjOptionComponent, KjOverflowPanelComponent, KjOverlayBadgeComponent, KjOverlayBadgeContentComponent, KjPaginationComponent, KjPaginationDefaultComponent, KjPaginationEllipsisComponent, KjPaginationFirstComponent, KjPaginationInfoComponent, KjPaginationItemComponent, KjPaginationLastComponent, KjPaginationNextComponent, KjPaginationPreviousComponent, KjPasswordInputComponent, KjPopoverComponent, KjProgressBarComponent, KjPromptInput, KjRadioComponent, KjRadioGroupComponent, KjRichTextEditorComponent, KjSelectComponent, KjSelectEditor, KjSelectFilter, KjSheetComponent, KjSkeletonComponent, KjSkipLinkComponent, KjSliderComponent, KjSpeedDialActionComponent, KjSpeedDialActionsComponent, KjSpeedDialComponent, KjSpeedDialTriggerComponent, KjSpinnerComponent, KjStepComponent, KjStepContentComponent, KjStepLabelComponent, KjStepperComponent, KjStepperNextComponent, KjStepperPreviousComponent, KjStepperResetComponent, KjTabComponent, KjTabListComponent, KjTabPanelComponent, KjTableComponent, KjTableEmptyTemplate, KjTableErrorTemplate, KjTableLoadingTemplate, KjTablePaginationComponent, KjTableSidePanelComponent, KjTableSidePanelContent, KjTableStatusBarComponent, KjTableToolbarComponent, KjTableVirtual, KjTabsComponent, KjTagComponent, KjTagListComponent, KjTagRemoveComponent, KjTextEditor, KjTextFilter, KjTextareaComponent, KjTimePickerComponent, KjToastCloseComponent, KjToastComponent, KjToastViewportComponent, KjToastWrapperComponent, KjToggleComponent, KjTooltipComponent, KjTreeSelectComponent, KjTreeSelectNodeComponent, LUCIDE_ICON_NAMES, bold, bulletList, codeBlock, copyToClipboard, defaultFeatures, downloadString, exportCsv, exportJson, heading, history, image, inlineCode, italic, link, markdownShortcuts, orderedList, provideLucideIcons, quote, renderMarkdown, strike, toTsv, underline };
20692
21053
  //# sourceMappingURL=kouji-ui-components.mjs.map