ngx-com 0.0.3 → 0.0.5

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.
Files changed (33) hide show
  1. package/fesm2022/ngx-com-components-avatar.mjs +772 -0
  2. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-calendar.mjs +33 -130
  4. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -1
  5. package/fesm2022/ngx-com-components-confirm.mjs +562 -0
  6. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -0
  7. package/fesm2022/ngx-com-components-dropdown.mjs +119 -25
  8. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -1
  9. package/fesm2022/ngx-com-components-empty-state.mjs +382 -0
  10. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-form-field.mjs +16 -15
  12. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -1
  13. package/fesm2022/ngx-com-components-item.mjs +578 -0
  14. package/fesm2022/ngx-com-components-item.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-paginator.mjs +823 -0
  16. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -0
  17. package/fesm2022/ngx-com-components-segmented-control.mjs +538 -0
  18. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -0
  19. package/fesm2022/ngx-com-components-spinner.mjs +189 -0
  20. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-tooltip.mjs +625 -0
  22. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -0
  23. package/package.json +33 -1
  24. package/types/ngx-com-components-avatar.d.ts +409 -0
  25. package/types/ngx-com-components-calendar.d.ts +5 -0
  26. package/types/ngx-com-components-confirm.d.ts +160 -0
  27. package/types/ngx-com-components-dropdown.d.ts +52 -28
  28. package/types/ngx-com-components-empty-state.d.ts +269 -0
  29. package/types/ngx-com-components-item.d.ts +336 -0
  30. package/types/ngx-com-components-paginator.d.ts +265 -0
  31. package/types/ngx-com-components-segmented-control.d.ts +274 -0
  32. package/types/ngx-com-components-spinner.d.ts +120 -0
  33. package/types/ngx-com-components-tooltip.d.ts +200 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-com-components-paginator.mjs","sources":["../../../projects/com/components/paginator/paginator.models.ts","../../../projects/com/components/paginator/paginator.variants.ts","../../../projects/com/components/paginator/paginator.component.ts","../../../projects/com/components/paginator/index.ts","../../../projects/com/components/paginator/ngx-com-components-paginator.ts"],"sourcesContent":["/**\n * Event emitted when the paginator changes page index or page size.\n */\nexport interface PageEvent {\n /** The current zero-based page index. */\n pageIndex: number;\n /** The previous zero-based page index. */\n previousPageIndex: number;\n /** The current page size. */\n pageSize: number;\n /** The total number of items being paged. */\n length: number;\n}\n\n/**\n * Function signature for custom range label formatting.\n * Used for i18n and custom label display.\n *\n * @param page Current page index (zero-based)\n * @param pageSize Number of items per page\n * @param length Total number of items\n * @returns Formatted string to display (e.g., \"1 – 10 of 100\")\n */\nexport type RangeLabelFn = (page: number, pageSize: number, length: number) => string;\n\n/**\n * Default range label function.\n * Produces output like \"1 – 10 of 100\" or \"0 of 0\" when empty.\n */\nexport function defaultRangeLabel(page: number, pageSize: number, length: number): string {\n if (length === 0 || pageSize === 0) {\n return `0 of ${length}`;\n }\n\n const startIndex = page * pageSize;\n // Ensure end index doesn't exceed length\n const endIndex = Math.min(startIndex + pageSize, length);\n\n return `${startIndex + 1} – ${endIndex} of ${length}`;\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\n\n// ─── Types ───\n\nexport type PaginatorSize = 'sm' | 'md';\nexport type PaginatorLayout = 'compact' | 'spread';\n\n// ─── Container Variants ───\n\n/**\n * CVA variants for the paginator container.\n * Controls overall layout and spacing.\n */\nexport const paginatorContainerVariants: (props?: {\n size?: PaginatorSize;\n layout?: PaginatorLayout;\n}) => string = cva(\n [\n 'flex items-center',\n 'text-foreground',\n 'select-none',\n ],\n {\n variants: {\n size: {\n sm: 'gap-3 text-xs',\n md: 'gap-4 text-sm',\n },\n layout: {\n compact: 'justify-end',\n spread: 'justify-between',\n },\n },\n defaultVariants: {\n size: 'md',\n layout: 'compact',\n },\n }\n);\n\n// ─── Button Variants ───\n\n/**\n * CVA variants for paginator navigation buttons.\n * Controls button sizing, borders, and interactive states.\n */\nexport const paginatorButtonVariants: (props?: {\n size?: PaginatorSize;\n}) => string = cva(\n [\n 'inline-flex items-center justify-center',\n 'rounded-control',\n 'border border-border',\n 'bg-transparent',\n 'transition-colors duration-150',\n 'cursor-pointer',\n 'hover:bg-muted hover:text-muted-foreground',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled',\n ],\n {\n variants: {\n size: {\n sm: 'h-7 w-7',\n md: 'h-9 w-9',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\n// ─── Range Label Variants ───\n\n/**\n * CVA variants for the range label text.\n * Controls typography and color.\n */\nexport const paginatorRangeLabelVariants: (props?: {\n size?: PaginatorSize;\n}) => string = cva(\n [\n 'text-muted-foreground',\n 'whitespace-nowrap',\n ],\n {\n variants: {\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\n// ─── Page Size Selector Variants ───\n\n/**\n * CVA variants for the page size select element.\n * Controls sizing and styling of the native select.\n */\nexport const paginatorSelectVariants: (props?: {\n size?: PaginatorSize;\n}) => string = cva(\n [\n 'appearance-none',\n 'rounded-control',\n 'border border-border',\n 'bg-transparent',\n 'text-foreground',\n 'cursor-pointer',\n 'transition-colors duration-150',\n 'hover:bg-muted',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled',\n 'pr-6', // Space for custom dropdown arrow\n ],\n {\n variants: {\n size: {\n sm: 'h-7 px-2 text-xs',\n md: 'h-9 px-3 text-sm',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\n// ─── Variant Types ───\n\n// ─── Page Button Variants ───\n\n/**\n * CVA variants for numbered page buttons.\n * Controls button sizing, active state, and interactive states.\n */\nexport const paginatorPageButtonVariants: (props?: {\n size?: PaginatorSize;\n active?: boolean;\n}) => string = cva(\n [\n 'inline-flex items-center justify-center',\n 'rounded-control',\n 'font-medium',\n 'transition-colors duration-150',\n 'cursor-pointer',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground',\n ],\n {\n variants: {\n size: {\n sm: 'h-7 min-w-7 px-1.5 text-xs',\n md: 'h-9 min-w-9 px-2 text-sm',\n },\n active: {\n true: 'bg-primary text-primary-foreground',\n false: 'bg-transparent text-foreground hover:bg-muted hover:text-muted-foreground',\n },\n },\n defaultVariants: {\n size: 'md',\n active: false,\n },\n }\n);\n\n// ─── Ellipsis Variants ───\n\n/**\n * CVA variants for the ellipsis indicator.\n * Controls sizing and styling of the \"...\" text.\n */\nexport const paginatorEllipsisVariants: (props?: {\n size?: PaginatorSize;\n}) => string = cva(\n [\n 'inline-flex items-center justify-center',\n 'text-muted-foreground',\n 'select-none',\n ],\n {\n variants: {\n size: {\n sm: 'h-7 w-7 text-xs',\n md: 'h-9 w-9 text-sm',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\n// ─── Variant Types ───\n\nexport type PaginatorContainerVariants = VariantProps<typeof paginatorContainerVariants>;\nexport type PaginatorButtonVariants = VariantProps<typeof paginatorButtonVariants>;\nexport type PaginatorRangeLabelVariants = VariantProps<typeof paginatorRangeLabelVariants>;\nexport type PaginatorSelectVariants = VariantProps<typeof paginatorSelectVariants>;\nexport type PaginatorPageButtonVariants = VariantProps<typeof paginatorPageButtonVariants>;\nexport type PaginatorEllipsisVariants = VariantProps<typeof paginatorEllipsisVariants>;\n","import {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n ElementRef,\n input,\n output,\n viewChildren,\n ViewEncapsulation,\n} from '@angular/core';\nimport type {\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n} from '@angular/core';\nimport { ComIcon } from 'ngx-com/components/icon';\nimport { defaultRangeLabel, type PageEvent, type RangeLabelFn } from './paginator.models';\nimport {\n paginatorContainerVariants,\n paginatorButtonVariants,\n paginatorRangeLabelVariants,\n paginatorSelectVariants,\n paginatorPageButtonVariants,\n paginatorEllipsisVariants,\n type PaginatorSize,\n type PaginatorLayout,\n} from './paginator.variants';\n\n/** Represents a page number or ellipsis marker in the page range. */\nexport type PageRangeItem = number | 'ellipsis';\n\n/**\n * Paginator component — provides navigation for paginated content.\n *\n * Displays navigation controls, optional page size selector, and range label\n * showing current position within the data set. Supports numbered page buttons\n * when `showPageNumbers` is enabled.\n *\n * @tokens `--color-foreground`, `--color-muted-foreground`,\n * `--color-border`, `--color-muted`,\n * `--color-disabled`, `--color-disabled-foreground`,\n * `--color-ring`, `--color-primary`, `--color-primary-foreground`\n *\n * @example Basic usage\n * ```html\n * <com-paginator\n * [length]=\"100\"\n * [pageSize]=\"10\"\n * [pageIndex]=\"0\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example With page size options\n * ```html\n * <com-paginator\n * [length]=\"100\"\n * [pageSize]=\"10\"\n * [pageIndex]=\"0\"\n * [pageSizeOptions]=\"[5, 10, 25, 50]\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example With first/last buttons\n * ```html\n * <com-paginator\n * [length]=\"100\"\n * [pageSize]=\"10\"\n * [pageIndex]=\"0\"\n * [showFirstLastButtons]=\"true\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example With numbered page buttons\n * ```html\n * <com-paginator\n * [length]=\"97\"\n * [pageSize]=\"10\"\n * [showPageNumbers]=\"true\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example Spread layout (summary left, controls right)\n * ```html\n * <com-paginator\n * [length]=\"97\"\n * [pageSize]=\"10\"\n * [showPageNumbers]=\"true\"\n * layout=\"spread\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example Small size\n * ```html\n * <com-paginator\n * [length]=\"50\"\n * [pageSize]=\"10\"\n * size=\"sm\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n *\n * @example Custom range label (i18n)\n * ```html\n * <com-paginator\n * [length]=\"100\"\n * [pageSize]=\"10\"\n * [rangeLabel]=\"customLabel\"\n * (page)=\"onPageChange($event)\"\n * />\n * ```\n * ```ts\n * customLabel = (page, pageSize, length) => `Seite ${page + 1} von ${Math.ceil(length / pageSize)}`;\n * ```\n */\n@Component({\n selector: 'com-paginator',\n exportAs: 'comPaginator',\n template: `\n <nav\n role=\"navigation\"\n [attr.aria-label]=\"ariaLabel()\"\n [class]=\"containerClasses()\"\n >\n @if (showPageNumbers()) {\n <!-- Spread Layout: Summary on left -->\n @if (layout() === 'spread') {\n <span [class]=\"rangeLabelClasses()\">\n {{ rangeLabelText() }}\n </span>\n }\n\n <!-- Navigation Controls -->\n <div\n class=\"flex items-center gap-1\"\n role=\"group\"\n aria-label=\"Page navigation\"\n (keydown)=\"onPageButtonsKeydown($event)\"\n >\n <!-- Previous Page -->\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasPreviousPage()\"\n aria-label=\"Previous page\"\n (click)=\"previousPage()\"\n >\n <com-icon name=\"chevron-left\" [size]=\"iconSize()\" />\n </button>\n\n <!-- Page Numbers -->\n @for (item of pageRange(); track trackPageItem($index, item)) {\n @if (item === 'ellipsis') {\n <span\n [class]=\"ellipsisClasses()\"\n aria-hidden=\"true\"\n >…</span>\n } @else {\n <button\n #pageBtn\n type=\"button\"\n [class]=\"item === pageIndex() ? activePageButtonClasses() : inactivePageButtonClasses()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'Page ' + (item + 1)\"\n [attr.aria-current]=\"item === pageIndex() ? 'page' : null\"\n [tabindex]=\"item === pageIndex() ? 0 : -1\"\n (click)=\"goToPage(item)\"\n >{{ item + 1 }}</button>\n }\n }\n\n <!-- Next Page -->\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasNextPage()\"\n aria-label=\"Next page\"\n (click)=\"nextPage()\"\n >\n <com-icon name=\"chevron-right\" [size]=\"iconSize()\" />\n </button>\n </div>\n\n <!-- Compact Layout: Summary on right -->\n @if (layout() === 'compact') {\n <span [class]=\"rangeLabelClasses()\">\n {{ rangeLabelText() }}\n </span>\n }\n } @else {\n <!-- Original Layout (no page numbers) -->\n\n <!-- Page Size Selector -->\n @if (!hidePageSize() && pageSizeOptions().length > 0) {\n <div class=\"flex items-center gap-2\">\n <label\n [id]=\"pageSizeLabelId()\"\n class=\"text-muted-foreground\"\n [class.text-xs]=\"size() === 'sm'\"\n [class.text-sm]=\"size() === 'md'\"\n >\n Items per page:\n </label>\n <div class=\"relative\">\n <select\n [attr.aria-labelledby]=\"pageSizeLabelId()\"\n [class]=\"selectClasses()\"\n [disabled]=\"disabled()\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeChange($event)\"\n >\n @for (option of pageSizeOptions(); track option) {\n <option [value]=\"option\">{{ option }}</option>\n }\n </select>\n <com-icon\n name=\"chevron-down\"\n size=\"xs\"\n class=\"pointer-events-none absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n </div>\n </div>\n }\n\n <!-- Range Label -->\n <span [class]=\"rangeLabelClasses()\">\n {{ rangeLabelText() }}\n </span>\n\n <!-- Navigation Buttons -->\n <div class=\"flex items-center gap-1\">\n <!-- First Page -->\n @if (showFirstLastButtons()) {\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasPreviousPage()\"\n aria-label=\"First page\"\n (click)=\"firstPage()\"\n >\n <com-icon name=\"chevrons-left\" [size]=\"iconSize()\" />\n </button>\n }\n\n <!-- Previous Page -->\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasPreviousPage()\"\n aria-label=\"Previous page\"\n (click)=\"previousPage()\"\n >\n <com-icon name=\"chevron-left\" [size]=\"iconSize()\" />\n </button>\n\n <!-- Next Page -->\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasNextPage()\"\n aria-label=\"Next page\"\n (click)=\"nextPage()\"\n >\n <com-icon name=\"chevron-right\" [size]=\"iconSize()\" />\n </button>\n\n <!-- Last Page -->\n @if (showFirstLastButtons()) {\n <button\n type=\"button\"\n [class]=\"buttonClasses()\"\n [disabled]=\"disabled() || !hasNextPage()\"\n aria-label=\"Last page\"\n (click)=\"lastPage()\"\n >\n <com-icon name=\"chevrons-right\" [size]=\"iconSize()\" />\n </button>\n }\n </div>\n }\n </nav>\n `,\n styles: `\n com-paginator {\n display: block;\n }\n `,\n imports: [ComIcon],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'com-paginator',\n },\n})\nexport class ComPaginator {\n // ─── View Queries ───\n\n /** Page number buttons for keyboard navigation. */\n private readonly pageButtons = viewChildren<ElementRef<HTMLButtonElement>>('pageBtn');\n\n // ─── Inputs ───\n\n /** Total number of items being paged. */\n readonly length: InputSignal<number> = input<number>(0);\n\n /** Number of items to display per page. */\n readonly pageSize: InputSignal<number> = input<number>(10);\n\n /** Current zero-based page index. */\n readonly pageIndex: InputSignal<number> = input<number>(0);\n\n /** Available page size options. Hides selector if empty. */\n readonly pageSizeOptions: InputSignal<number[]> = input<number[]>([]);\n\n /** Whether to show first/last navigation buttons. */\n readonly showFirstLastButtons: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n /** Whether to show numbered page buttons. */\n readonly showPageNumbers: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n /** Whether all controls are disabled. */\n readonly disabled: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n /** Whether to hide the page size selector. */\n readonly hidePageSize: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n /** Size variant. */\n readonly size: InputSignal<PaginatorSize> = input<PaginatorSize>('md');\n\n /** Layout variant. Only applies when showPageNumbers is true. */\n readonly layout: InputSignal<PaginatorLayout> = input<PaginatorLayout>('compact');\n\n /** Number of pages to show on each side of the current page. */\n readonly siblingCount: InputSignal<number> = input<number>(1);\n\n /** Number of pages to always show at the start and end. */\n readonly boundaryCount: InputSignal<number> = input<number>(1);\n\n /** Accessible label for the nav element. */\n readonly ariaLabel: InputSignal<string> = input<string>('Pagination', { alias: 'aria-label' });\n\n /** Custom function for range label formatting. */\n readonly rangeLabel: InputSignal<RangeLabelFn> = input<RangeLabelFn>(defaultRangeLabel);\n\n // ─── Outputs ───\n\n /** Emits when page index or page size changes. */\n readonly page: OutputEmitterRef<PageEvent> = output<PageEvent>();\n\n // ─── Computed ───\n\n /** Total number of pages. */\n protected readonly numberOfPages: Signal<number> = computed(() => {\n const size = this.pageSize();\n const len = this.length();\n if (size === 0 || len === 0) {\n return 0;\n }\n return Math.ceil(len / size);\n });\n\n /** Whether there is a previous page. */\n protected readonly hasPreviousPage: Signal<boolean> = computed(() => {\n return this.pageIndex() > 0 && this.pageSize() > 0;\n });\n\n /** Whether there is a next page. */\n protected readonly hasNextPage: Signal<boolean> = computed(() => {\n const total = this.numberOfPages();\n return total > 0 && this.pageIndex() < total - 1;\n });\n\n /** The formatted range label text. */\n protected readonly rangeLabelText: Signal<string> = computed(() => {\n const fn = this.rangeLabel();\n return fn(this.pageIndex(), this.pageSize(), this.length());\n });\n\n /** Icon size based on component size. */\n protected readonly iconSize: Signal<'xs' | 'sm'> = computed(() => {\n return this.size() === 'sm' ? 'xs' : 'sm';\n });\n\n /** Unique ID for page size label. */\n protected readonly pageSizeLabelId: Signal<string> = computed(() => {\n return `com-paginator-page-size-label-${uniqueId++}`;\n });\n\n /** Classes for the container. */\n protected readonly containerClasses: Signal<string> = computed(() =>\n paginatorContainerVariants({\n size: this.size(),\n layout: this.showPageNumbers() ? this.layout() : 'compact',\n })\n );\n\n /** Classes for navigation buttons. */\n protected readonly buttonClasses: Signal<string> = computed(() =>\n paginatorButtonVariants({ size: this.size() })\n );\n\n /** Classes for the range label. */\n protected readonly rangeLabelClasses: Signal<string> = computed(() =>\n paginatorRangeLabelVariants({ size: this.size() })\n );\n\n /** Classes for the page size select. */\n protected readonly selectClasses: Signal<string> = computed(() =>\n paginatorSelectVariants({ size: this.size() })\n );\n\n /** Classes for the ellipsis indicator. */\n protected readonly ellipsisClasses: Signal<string> = computed(() =>\n paginatorEllipsisVariants({ size: this.size() })\n );\n\n /** Cached classes for active page button. */\n protected readonly activePageButtonClasses: Signal<string> = computed(() =>\n paginatorPageButtonVariants({ size: this.size(), active: true })\n );\n\n /** Cached classes for inactive page button. */\n protected readonly inactivePageButtonClasses: Signal<string> = computed(() =>\n paginatorPageButtonVariants({ size: this.size(), active: false })\n );\n\n /**\n * Computed page range for numbered pagination.\n * Returns array like [0, 'ellipsis', 3, 4, 5, 'ellipsis', 9] (zero-indexed).\n */\n protected readonly pageRange: Signal<PageRangeItem[]> = computed(() => {\n const totalPages = this.numberOfPages();\n const current = this.pageIndex();\n const siblings = this.siblingCount();\n const boundaries = this.boundaryCount();\n\n if (totalPages === 0) return [];\n\n // If all pages fit without ellipses, show them all\n const totalSlots = 2 * boundaries + 2 * siblings + 3;\n if (totalPages <= totalSlots) {\n return this.range(0, totalPages - 1);\n }\n\n // Collect all page indices that should be visible (Set auto-deduplicates)\n const pages = new Set<number>();\n\n // Boundary pages (always visible)\n for (let i = 0; i < boundaries; i++) pages.add(i);\n for (let i = totalPages - boundaries; i < totalPages; i++) pages.add(i);\n\n // Sibling pages around current (including current)\n const siblingStart = Math.max(0, current - siblings);\n const siblingEnd = Math.min(totalPages - 1, current + siblings);\n for (let i = siblingStart; i <= siblingEnd; i++) pages.add(i);\n\n // Convert to sorted array and insert ellipses at gaps\n const sorted = Array.from(pages).sort((a, b) => a - b);\n const result: PageRangeItem[] = [];\n\n for (let i = 0; i < sorted.length; i++) {\n const page = sorted[i]!;\n const prevPage = sorted[i - 1];\n if (prevPage !== undefined && page - prevPage > 1) {\n result.push('ellipsis');\n }\n result.push(page);\n }\n\n return result;\n });\n\n // ─── Navigation Methods ───\n\n /** Navigate to the first page. */\n firstPage(): void {\n if (this.hasPreviousPage()) {\n this.emitPageEvent(0);\n }\n }\n\n /** Navigate to the previous page. */\n previousPage(): void {\n if (this.hasPreviousPage()) {\n this.emitPageEvent(this.pageIndex() - 1);\n }\n }\n\n /** Navigate to the next page. */\n nextPage(): void {\n if (this.hasNextPage()) {\n this.emitPageEvent(this.pageIndex() + 1);\n }\n }\n\n /** Navigate to the last page. */\n lastPage(): void {\n if (this.hasNextPage()) {\n this.emitPageEvent(this.numberOfPages() - 1);\n }\n }\n\n /** Navigate to a specific page by index (zero-based). */\n goToPage(pageIndex: number): void {\n if (pageIndex >= 0 && pageIndex < this.numberOfPages() && pageIndex !== this.pageIndex()) {\n this.emitPageEvent(pageIndex);\n }\n }\n\n /** Handle page size selection change. */\n protected onPageSizeChange(event: Event): void {\n const select = event.target as HTMLSelectElement;\n const newPageSize = Number(select.value);\n const previousPageSize = this.pageSize();\n const currentPageIndex = this.pageIndex();\n\n // Calculate new page index to keep the first item on the current page visible\n const startIndex = currentPageIndex * previousPageSize;\n const newPageIndex = Math.floor(startIndex / newPageSize);\n\n this.page.emit({\n pageIndex: newPageIndex,\n previousPageIndex: currentPageIndex,\n pageSize: newPageSize,\n length: this.length(),\n });\n }\n\n /** Handle keyboard navigation within page buttons (roving tabindex). */\n protected onPageButtonsKeydown(event: KeyboardEvent): void {\n const target = event.target as HTMLButtonElement;\n\n // Get enabled buttons from viewChildren query\n const buttons = this.pageButtons()\n .map((ref) => ref.nativeElement)\n .filter((btn) => !btn.disabled);\n\n const currentIndex = buttons.indexOf(target);\n if (currentIndex === -1) return;\n\n let newIndex: number;\n switch (event.key) {\n case 'ArrowLeft':\n newIndex = currentIndex > 0 ? currentIndex - 1 : buttons.length - 1;\n break;\n case 'ArrowRight':\n newIndex = currentIndex < buttons.length - 1 ? currentIndex + 1 : 0;\n break;\n case 'Home':\n newIndex = 0;\n break;\n case 'End':\n newIndex = buttons.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n buttons[newIndex]?.focus();\n }\n\n /** Track function for page items. */\n protected trackPageItem(index: number, item: PageRangeItem): string {\n return item === 'ellipsis' ? `ellipsis-${index}` : `page-${item}`;\n }\n\n // ─── Private Methods ───\n\n private emitPageEvent(newPageIndex: number): void {\n this.page.emit({\n pageIndex: newPageIndex,\n previousPageIndex: this.pageIndex(),\n pageSize: this.pageSize(),\n length: this.length(),\n });\n }\n\n /** Generate a range of numbers from start to end (inclusive). */\n private range(start: number, end: number): number[] {\n return Array.from({ length: end - start + 1 }, (_, i) => start + i);\n }\n}\n\n/** Counter for generating unique IDs. */\nlet uniqueId = 0;\n","// Public API for the paginator component\n\n// Main component\nexport { ComPaginator } from './paginator.component';\nexport type { PageRangeItem } from './paginator.component';\n\n// Models\nexport { defaultRangeLabel } from './paginator.models';\nexport type { PageEvent, RangeLabelFn } from './paginator.models';\n\n// Variants\nexport {\n paginatorContainerVariants,\n paginatorButtonVariants,\n paginatorRangeLabelVariants,\n paginatorSelectVariants,\n paginatorPageButtonVariants,\n paginatorEllipsisVariants,\n} from './paginator.variants';\n\nexport type {\n PaginatorSize,\n PaginatorLayout,\n PaginatorContainerVariants,\n PaginatorButtonVariants,\n PaginatorRangeLabelVariants,\n PaginatorSelectVariants,\n PaginatorPageButtonVariants,\n PaginatorEllipsisVariants,\n} from './paginator.variants';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,538 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, TemplateRef, Directive, viewChildren, input, model, booleanAttribute, contentChild, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgTemplateOutlet } from '@angular/common';
4
+ import { mergeClasses } from 'ngx-com/utils';
5
+ import { cva } from 'class-variance-authority';
6
+
7
+ /**
8
+ * Directive to mark a custom template for segment content.
9
+ *
10
+ * The template receives a `SegmentTemplateContext` with the option data,
11
+ * active state, disabled state, and index. Use this to customize the
12
+ * inner content of each segment while the component manages the button,
13
+ * styling, and ARIA attributes.
14
+ *
15
+ * @example Icon + text
16
+ * ```html
17
+ * <com-segmented-control [options]="viewOptions" [(value)]="currentView">
18
+ * <ng-template comSegmentDef let-option let-active="active">
19
+ * <com-icon [name]="option.value" size="sm" />
20
+ * <span>{{ option.label }}</span>
21
+ * </ng-template>
22
+ * </com-segmented-control>
23
+ * ```
24
+ *
25
+ * @example Icon only (label used for accessibility)
26
+ * ```html
27
+ * <com-segmented-control [options]="alignmentOptions" [(value)]="alignment">
28
+ * <ng-template comSegmentDef let-option>
29
+ * <com-icon [name]="'align-' + option.value" size="sm" />
30
+ * </ng-template>
31
+ * </com-segmented-control>
32
+ * ```
33
+ */
34
+ class ComSegmentDef {
35
+ templateRef = inject(TemplateRef);
36
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSegmentDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
37
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComSegmentDef, isStandalone: true, selector: "ng-template[comSegmentDef]", ngImport: i0 });
38
+ }
39
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSegmentDef, decorators: [{
40
+ type: Directive,
41
+ args: [{
42
+ selector: 'ng-template[comSegmentDef]',
43
+ }]
44
+ }] });
45
+
46
+ // ─── Container Variants ───
47
+ /**
48
+ * CVA variants for the segmented control container (track).
49
+ * Controls overall sizing, padding, and track background.
50
+ */
51
+ const segmentedControlContainerVariants = cva([
52
+ 'inline-flex items-center',
53
+ 'rounded-pill',
54
+ 'bg-muted',
55
+ 'p-1',
56
+ 'transition-colors duration-150',
57
+ ], {
58
+ variants: {
59
+ size: {
60
+ sm: 'gap-0.5',
61
+ md: 'gap-1',
62
+ lg: 'gap-1',
63
+ },
64
+ fullWidth: {
65
+ true: 'w-full',
66
+ false: '',
67
+ },
68
+ },
69
+ defaultVariants: {
70
+ size: 'md',
71
+ fullWidth: false,
72
+ },
73
+ });
74
+ // ─── Segment Variants ───
75
+ /**
76
+ * CVA variants for individual segment buttons.
77
+ * Controls per-segment sizing, typography, and active/inactive color states.
78
+ */
79
+ const segmentedControlSegmentVariants = cva([
80
+ 'inline-flex items-center justify-center',
81
+ 'rounded-pill',
82
+ 'font-medium whitespace-nowrap select-none',
83
+ 'transition-colors duration-150',
84
+ 'cursor-pointer',
85
+ 'gap-1.5',
86
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
87
+ ], {
88
+ variants: {
89
+ size: {
90
+ sm: 'h-7 px-3 text-sm',
91
+ md: 'h-8 px-4 text-sm',
92
+ lg: 'h-10 px-5 text-base',
93
+ },
94
+ color: {
95
+ primary: '',
96
+ accent: '',
97
+ muted: '',
98
+ },
99
+ variant: {
100
+ filled: '',
101
+ outline: 'bg-transparent',
102
+ },
103
+ active: {
104
+ true: '',
105
+ false: 'text-foreground',
106
+ },
107
+ fullWidth: {
108
+ true: 'flex-1',
109
+ false: '',
110
+ },
111
+ },
112
+ compoundVariants: [
113
+ // ─── Filled + Active ───
114
+ {
115
+ variant: 'filled',
116
+ color: 'primary',
117
+ active: true,
118
+ class: 'bg-primary text-primary-foreground shadow-sm',
119
+ },
120
+ {
121
+ variant: 'filled',
122
+ color: 'accent',
123
+ active: true,
124
+ class: 'bg-accent text-accent-foreground shadow-sm',
125
+ },
126
+ {
127
+ variant: 'filled',
128
+ color: 'muted',
129
+ active: true,
130
+ class: 'bg-background text-foreground shadow-sm',
131
+ },
132
+ // ─── Filled + Inactive (hover) ───
133
+ {
134
+ variant: 'filled',
135
+ active: false,
136
+ class: 'hover:bg-muted-hover',
137
+ },
138
+ // ─── Outline + Active ───
139
+ {
140
+ variant: 'outline',
141
+ color: 'primary',
142
+ active: true,
143
+ class: 'ring-2 ring-primary text-primary',
144
+ },
145
+ {
146
+ variant: 'outline',
147
+ color: 'accent',
148
+ active: true,
149
+ class: 'ring-2 ring-accent text-accent',
150
+ },
151
+ {
152
+ variant: 'outline',
153
+ color: 'muted',
154
+ active: true,
155
+ class: 'ring-2 ring-border text-foreground',
156
+ },
157
+ // ─── Outline + Inactive (hover) ───
158
+ {
159
+ variant: 'outline',
160
+ active: false,
161
+ class: 'hover:bg-muted-hover',
162
+ },
163
+ ],
164
+ defaultVariants: {
165
+ size: 'md',
166
+ color: 'primary',
167
+ variant: 'filled',
168
+ active: false,
169
+ fullWidth: false,
170
+ },
171
+ });
172
+ // ─── Disabled Segment Classes ───
173
+ /**
174
+ * Classes to apply when a segment is disabled.
175
+ * Separate from CVA to avoid complex variant combinations.
176
+ */
177
+ const SEGMENT_DISABLED_CLASSES = 'bg-disabled text-disabled-foreground cursor-not-allowed hover:bg-disabled';
178
+
179
+ /**
180
+ * Segmented control component — a horizontal group of mutually exclusive options
181
+ * where one is always selected. Think of it as a styled radio group in pill form.
182
+ *
183
+ * Supports two rendering modes:
184
+ * - **Simple mode**: Plain text labels from the `label` property
185
+ * - **Custom template mode**: Full control via `ng-template[comSegmentDef]`
186
+ *
187
+ * @tokens `--color-primary`, `--color-primary-foreground`,
188
+ * `--color-accent`, `--color-accent-foreground`,
189
+ * `--color-muted`, `--color-muted-foreground`, `--color-muted-hover`,
190
+ * `--color-background`, `--color-foreground`,
191
+ * `--color-disabled`, `--color-disabled-foreground`,
192
+ * `--color-border`, `--color-ring`
193
+ *
194
+ * @example Basic two-option toggle
195
+ * ```html
196
+ * <com-segmented-control
197
+ * [options]="[
198
+ * { value: 'admin', label: 'Admin' },
199
+ * { value: 'user', label: 'User' }
200
+ * ]"
201
+ * [(value)]="selectedRole"
202
+ * />
203
+ * ```
204
+ *
205
+ * @example Multiple options with variants
206
+ * ```html
207
+ * <com-segmented-control
208
+ * [options]="viewOptions"
209
+ * [(value)]="currentView"
210
+ * color="primary"
211
+ * size="sm"
212
+ * />
213
+ * ```
214
+ *
215
+ * @example Custom template with icons
216
+ * ```html
217
+ * <com-segmented-control [options]="viewOptions" [(value)]="currentView" color="primary">
218
+ * <ng-template comSegmentDef let-option let-active="active">
219
+ * <com-icon [name]="option.value === 'grid' ? 'grid' : 'list'" size="sm" />
220
+ * <span>{{ option.label }}</span>
221
+ * </ng-template>
222
+ * </com-segmented-control>
223
+ * ```
224
+ *
225
+ * @example Custom template with badges
226
+ * ```html
227
+ * <com-segmented-control [options]="statusOptions" [(value)]="statusFilter" color="accent">
228
+ * <ng-template comSegmentDef let-option let-active="active">
229
+ * <span>{{ option.label }}</span>
230
+ * <span
231
+ * class="ml-1.5 rounded-pill px-1.5 text-xs"
232
+ * [class]="active ? 'bg-accent-foreground/20 text-accent-foreground' : 'bg-muted text-muted-foreground'"
233
+ * >
234
+ * {{ option.value === 'open' ? openCount : closedCount }}
235
+ * </span>
236
+ * </ng-template>
237
+ * </com-segmented-control>
238
+ * ```
239
+ *
240
+ * @example Icon only (label used for accessibility)
241
+ * ```html
242
+ * <com-segmented-control
243
+ * [options]="[
244
+ * { value: 'left', label: 'Align left' },
245
+ * { value: 'center', label: 'Align center' },
246
+ * { value: 'right', label: 'Align right' }
247
+ * ]"
248
+ * [(value)]="alignment"
249
+ * size="sm"
250
+ * >
251
+ * <ng-template comSegmentDef let-option>
252
+ * <com-icon [name]="'align-' + option.value" size="sm" />
253
+ * </ng-template>
254
+ * </com-segmented-control>
255
+ * ```
256
+ *
257
+ * @example Full width + outline variant
258
+ * ```html
259
+ * <com-segmented-control
260
+ * [options]="plans"
261
+ * [(value)]="selectedPlan"
262
+ * variant="outline"
263
+ * color="primary"
264
+ * [fullWidth]="true"
265
+ * />
266
+ * ```
267
+ *
268
+ * @example Disabled options
269
+ * ```html
270
+ * <com-segmented-control
271
+ * [options]="[
272
+ * { value: 'free', label: 'Free' },
273
+ * { value: 'pro', label: 'Pro' },
274
+ * { value: 'enterprise', label: 'Enterprise', disabled: true }
275
+ * ]"
276
+ * [(value)]="plan"
277
+ * color="primary"
278
+ * size="lg"
279
+ * />
280
+ * ```
281
+ */
282
+ class ComSegmentedControl {
283
+ // ─── View Children ───
284
+ /** References to all segment buttons for focus management. */
285
+ segmentButtons = viewChildren('segmentButton', ...(ngDevMode ? [{ debugName: "segmentButtons" }] : []));
286
+ // ─── Inputs ───
287
+ /** The list of options to display. */
288
+ options = input.required(...(ngDevMode ? [{ debugName: "options" }] : []));
289
+ /** Currently selected value. Two-way bindable with `[(value)]`. */
290
+ value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
291
+ /** Controls segment height, padding, and font size. */
292
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
293
+ /** Color scheme for the active segment. */
294
+ color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
295
+ /** Visual variant: filled (solid background) or outline (ring border). */
296
+ variant = input('filled', ...(ngDevMode ? [{ debugName: "variant" }] : []));
297
+ /** When true, segments stretch equally to fill available width. */
298
+ fullWidth = input(false, { ...(ngDevMode ? { debugName: "fullWidth" } : {}), transform: booleanAttribute });
299
+ /** Accessible label for the radiogroup container. */
300
+ ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : {}), alias: 'aria-label' });
301
+ /** Custom CSS classes to merge with container classes. */
302
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : {}), alias: 'class' });
303
+ // ─── Content Child ───
304
+ /** Optional custom template for segment content. */
305
+ customTemplate = contentChild(ComSegmentDef, ...(ngDevMode ? [{ debugName: "customTemplate" }] : []));
306
+ // ─── Computed ───
307
+ /** Classes for the container/track element. */
308
+ containerClasses = computed(() => mergeClasses(segmentedControlContainerVariants({
309
+ size: this.size(),
310
+ fullWidth: this.fullWidth(),
311
+ }), this.userClass()), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
312
+ // ─── Public Methods ───
313
+ /**
314
+ * Checks if the given option is currently selected.
315
+ */
316
+ isActive(option) {
317
+ return this.value() === option.value;
318
+ }
319
+ /**
320
+ * Selects the given option (if not disabled).
321
+ */
322
+ select(option) {
323
+ if (option.disabled) {
324
+ return;
325
+ }
326
+ this.value.set(option.value);
327
+ }
328
+ /**
329
+ * Returns the tabindex for a segment at the given index.
330
+ * Implements roving tabindex: only the selected (or first focusable) segment has tabindex="0".
331
+ */
332
+ tabIndexFor(index) {
333
+ const opts = this.options();
334
+ const currentValue = this.value();
335
+ // If current value matches this option, it gets tabindex="0"
336
+ if (opts[index]?.value === currentValue) {
337
+ return 0;
338
+ }
339
+ // If no value is selected, first non-disabled option gets tabindex="0"
340
+ if (currentValue === undefined) {
341
+ const firstFocusableIndex = opts.findIndex((o) => !o.disabled);
342
+ return index === firstFocusableIndex ? 0 : -1;
343
+ }
344
+ return -1;
345
+ }
346
+ /**
347
+ * Returns computed classes for a segment button.
348
+ */
349
+ segmentClasses(option) {
350
+ const active = this.isActive(option);
351
+ const baseClasses = segmentedControlSegmentVariants({
352
+ size: this.size(),
353
+ color: this.color(),
354
+ variant: this.variant(),
355
+ active,
356
+ fullWidth: this.fullWidth(),
357
+ });
358
+ if (option.disabled) {
359
+ return mergeClasses(baseClasses, SEGMENT_DISABLED_CLASSES);
360
+ }
361
+ return baseClasses;
362
+ }
363
+ /**
364
+ * Builds the template context for custom templates.
365
+ */
366
+ getTemplateContext(option, index) {
367
+ return {
368
+ $implicit: option,
369
+ active: this.isActive(option),
370
+ disabled: !!option.disabled,
371
+ index,
372
+ };
373
+ }
374
+ /**
375
+ * Handles keyboard navigation for the segmented control.
376
+ * Implements ARIA radiogroup keyboard patterns.
377
+ */
378
+ onKeydown(event, currentIndex) {
379
+ const opts = this.options();
380
+ let targetIndex = null;
381
+ switch (event.key) {
382
+ case 'ArrowRight':
383
+ case 'ArrowDown':
384
+ targetIndex = this.findNextFocusableIndex(currentIndex, 1);
385
+ break;
386
+ case 'ArrowLeft':
387
+ case 'ArrowUp':
388
+ targetIndex = this.findNextFocusableIndex(currentIndex, -1);
389
+ break;
390
+ case 'Home':
391
+ targetIndex = this.findFirstFocusableIndex();
392
+ break;
393
+ case 'End':
394
+ targetIndex = this.findLastFocusableIndex();
395
+ break;
396
+ default:
397
+ return;
398
+ }
399
+ if (targetIndex !== null && targetIndex !== currentIndex) {
400
+ event.preventDefault();
401
+ const targetOption = opts[targetIndex];
402
+ if (targetOption) {
403
+ this.select(targetOption);
404
+ this.focusSegmentAt(targetIndex);
405
+ }
406
+ }
407
+ }
408
+ // ─── Private Methods ───
409
+ /**
410
+ * Finds the next focusable (non-disabled) option index in the given direction.
411
+ * Wraps around to the beginning/end of the list.
412
+ */
413
+ findNextFocusableIndex(currentIndex, direction) {
414
+ const opts = this.options();
415
+ const length = opts.length;
416
+ if (length === 0) {
417
+ return null;
418
+ }
419
+ let index = currentIndex;
420
+ for (let i = 0; i < length; i++) {
421
+ index = (index + direction + length) % length;
422
+ if (!opts[index]?.disabled) {
423
+ return index;
424
+ }
425
+ }
426
+ return null;
427
+ }
428
+ /**
429
+ * Finds the first focusable (non-disabled) option index.
430
+ */
431
+ findFirstFocusableIndex() {
432
+ const opts = this.options();
433
+ const index = opts.findIndex((o) => !o.disabled);
434
+ return index >= 0 ? index : null;
435
+ }
436
+ /**
437
+ * Finds the last focusable (non-disabled) option index.
438
+ */
439
+ findLastFocusableIndex() {
440
+ const opts = this.options();
441
+ for (let i = opts.length - 1; i >= 0; i--) {
442
+ if (!opts[i]?.disabled) {
443
+ return i;
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+ /**
449
+ * Focuses the segment button at the given index.
450
+ */
451
+ focusSegmentAt(index) {
452
+ const button = this.segmentButtons()[index];
453
+ if (button) {
454
+ button.nativeElement.focus();
455
+ }
456
+ }
457
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSegmentedControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
458
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComSegmentedControl, isStandalone: true, selector: "com-segmented-control", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.fullWidth": "fullWidth() || null" }, classAttribute: "com-segmented-control" }, queries: [{ propertyName: "customTemplate", first: true, predicate: ComSegmentDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "segmentButtons", predicate: ["segmentButton"], descendants: true, isSignal: true }], exportAs: ["comSegmentedControl"], ngImport: i0, template: `
459
+ <div
460
+ role="radiogroup"
461
+ [attr.aria-label]="ariaLabel()"
462
+ [class]="containerClasses()"
463
+ >
464
+ @for (option of options(); track option.value; let i = $index) {
465
+ <button
466
+ #segmentButton
467
+ type="button"
468
+ role="radio"
469
+ [attr.aria-checked]="isActive(option)"
470
+ [attr.aria-label]="customTemplate() ? option.label : null"
471
+ [attr.aria-disabled]="option.disabled || null"
472
+ [disabled]="option.disabled"
473
+ [tabindex]="tabIndexFor(i)"
474
+ [class]="segmentClasses(option)"
475
+ (click)="select(option)"
476
+ (keydown)="onKeydown($event, i)"
477
+ >
478
+ @if (customTemplate(); as tpl) {
479
+ <ng-container
480
+ [ngTemplateOutlet]="tpl.templateRef"
481
+ [ngTemplateOutletContext]="getTemplateContext(option, i)"
482
+ />
483
+ } @else {
484
+ {{ option.label }}
485
+ }
486
+ </button>
487
+ }
488
+ </div>
489
+ `, isInline: true, styles: ["com-segmented-control{display:inline-block}com-segmented-control[fullWidth]{display:block}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
490
+ }
491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSegmentedControl, decorators: [{
492
+ type: Component,
493
+ args: [{ selector: 'com-segmented-control', exportAs: 'comSegmentedControl', template: `
494
+ <div
495
+ role="radiogroup"
496
+ [attr.aria-label]="ariaLabel()"
497
+ [class]="containerClasses()"
498
+ >
499
+ @for (option of options(); track option.value; let i = $index) {
500
+ <button
501
+ #segmentButton
502
+ type="button"
503
+ role="radio"
504
+ [attr.aria-checked]="isActive(option)"
505
+ [attr.aria-label]="customTemplate() ? option.label : null"
506
+ [attr.aria-disabled]="option.disabled || null"
507
+ [disabled]="option.disabled"
508
+ [tabindex]="tabIndexFor(i)"
509
+ [class]="segmentClasses(option)"
510
+ (click)="select(option)"
511
+ (keydown)="onKeydown($event, i)"
512
+ >
513
+ @if (customTemplate(); as tpl) {
514
+ <ng-container
515
+ [ngTemplateOutlet]="tpl.templateRef"
516
+ [ngTemplateOutletContext]="getTemplateContext(option, i)"
517
+ />
518
+ } @else {
519
+ {{ option.label }}
520
+ }
521
+ </button>
522
+ }
523
+ </div>
524
+ `, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
525
+ class: 'com-segmented-control',
526
+ '[attr.fullWidth]': 'fullWidth() || null',
527
+ }, styles: ["com-segmented-control{display:inline-block}com-segmented-control[fullWidth]{display:block}\n"] }]
528
+ }], propDecorators: { segmentButtons: [{ type: i0.ViewChildren, args: ['segmentButton', { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], customTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComSegmentDef), { isSignal: true }] }] } });
529
+
530
+ // Public API for the segmented-control component
531
+ // Main component
532
+
533
+ /**
534
+ * Generated bundle index. Do not edit.
535
+ */
536
+
537
+ export { ComSegmentDef, ComSegmentedControl, SEGMENT_DISABLED_CLASSES, segmentedControlContainerVariants, segmentedControlSegmentVariants };
538
+ //# sourceMappingURL=ngx-com-components-segmented-control.mjs.map