@ngneers/controls 0.0.1-next.0 → 0.0.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/fesm2022/ngneers-controls-api-ng.mjs +11 -18
  2. package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -1
  3. package/fesm2022/ngneers-controls-api-resize.mjs +7 -0
  4. package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -1
  5. package/fesm2022/ngneers-controls-badge.mjs +160 -0
  6. package/fesm2022/ngneers-controls-badge.mjs.map +1 -0
  7. package/fesm2022/ngneers-controls-base.mjs +85 -8
  8. package/fesm2022/ngneers-controls-base.mjs.map +1 -1
  9. package/fesm2022/{ngneers-controls-calendar-time-CJT5AFlA.mjs → ngneers-controls-calendar-time-paM6WVpV.mjs} +2 -2
  10. package/fesm2022/{ngneers-controls-calendar-time-CJT5AFlA.mjs.map → ngneers-controls-calendar-time-paM6WVpV.mjs.map} +1 -1
  11. package/fesm2022/ngneers-controls-calendar.mjs +13 -6
  12. package/fesm2022/ngneers-controls-calendar.mjs.map +1 -1
  13. package/fesm2022/ngneers-controls-checkbox.mjs +3 -3
  14. package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -1
  15. package/fesm2022/ngneers-controls-color-picker.mjs +336 -0
  16. package/fesm2022/ngneers-controls-color-picker.mjs.map +1 -0
  17. package/fesm2022/ngneers-controls-default-icons.mjs +5 -0
  18. package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -1
  19. package/fesm2022/ngneers-controls-directives.mjs +64 -7
  20. package/fesm2022/ngneers-controls-directives.mjs.map +1 -1
  21. package/fesm2022/ngneers-controls-edit-inplace.mjs +15 -5
  22. package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -1
  23. package/fesm2022/ngneers-controls-errors.mjs +203 -148
  24. package/fesm2022/ngneers-controls-errors.mjs.map +1 -1
  25. package/fesm2022/ngneers-controls-filter.mjs +2 -2
  26. package/fesm2022/ngneers-controls-filter.mjs.map +1 -1
  27. package/fesm2022/ngneers-controls-hint.mjs +18 -1
  28. package/fesm2022/ngneers-controls-hint.mjs.map +1 -1
  29. package/fesm2022/ngneers-controls-i18n-translations-de.mjs +17 -0
  30. package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -1
  31. package/fesm2022/ngneers-controls-i18n-translations-en.mjs +19 -0
  32. package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -1
  33. package/fesm2022/ngneers-controls-icon.mjs +23 -6
  34. package/fesm2022/ngneers-controls-icon.mjs.map +1 -1
  35. package/fesm2022/ngneers-controls-input-field.mjs +18 -2
  36. package/fesm2022/ngneers-controls-input-field.mjs.map +1 -1
  37. package/fesm2022/ngneers-controls-input.mjs +49 -9
  38. package/fesm2022/ngneers-controls-input.mjs.map +1 -1
  39. package/fesm2022/ngneers-controls-list-box.mjs +2 -2
  40. package/fesm2022/ngneers-controls-list-box.mjs.map +1 -1
  41. package/fesm2022/ngneers-controls-mask-input.mjs +4 -1
  42. package/fesm2022/ngneers-controls-mask-input.mjs.map +1 -1
  43. package/fesm2022/ngneers-controls-number-input.mjs +4 -4
  44. package/fesm2022/ngneers-controls-number-input.mjs.map +1 -1
  45. package/fesm2022/ngneers-controls-otp.mjs +263 -0
  46. package/fesm2022/ngneers-controls-otp.mjs.map +1 -0
  47. package/fesm2022/ngneers-controls-paginator.mjs +36 -9
  48. package/fesm2022/ngneers-controls-paginator.mjs.map +1 -1
  49. package/fesm2022/ngneers-controls-radio.mjs +4 -4
  50. package/fesm2022/ngneers-controls-radio.mjs.map +1 -1
  51. package/fesm2022/ngneers-controls-rating.mjs +180 -0
  52. package/fesm2022/ngneers-controls-rating.mjs.map +1 -0
  53. package/fesm2022/ngneers-controls-scroll-shadow.mjs +33 -7
  54. package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -1
  55. package/fesm2022/ngneers-controls-scroller.mjs +4 -2
  56. package/fesm2022/ngneers-controls-scroller.mjs.map +1 -1
  57. package/fesm2022/ngneers-controls-select-button.mjs +2 -1
  58. package/fesm2022/ngneers-controls-select-button.mjs.map +1 -1
  59. package/fesm2022/ngneers-controls-select.mjs +24 -4
  60. package/fesm2022/ngneers-controls-select.mjs.map +1 -1
  61. package/fesm2022/ngneers-controls-slider.mjs +3 -3
  62. package/fesm2022/ngneers-controls-slider.mjs.map +1 -1
  63. package/fesm2022/ngneers-controls-splitter.mjs +43 -12
  64. package/fesm2022/ngneers-controls-splitter.mjs.map +1 -1
  65. package/fesm2022/ngneers-controls-stepper.mjs +142 -0
  66. package/fesm2022/ngneers-controls-stepper.mjs.map +1 -0
  67. package/fesm2022/ngneers-controls-switch.mjs +3 -3
  68. package/fesm2022/ngneers-controls-switch.mjs.map +1 -1
  69. package/fesm2022/ngneers-controls-table.mjs +282 -15
  70. package/fesm2022/ngneers-controls-table.mjs.map +1 -1
  71. package/fesm2022/ngneers-controls-tabs.mjs +2 -2
  72. package/fesm2022/ngneers-controls-tabs.mjs.map +1 -1
  73. package/fesm2022/ngneers-controls-toggle-button.mjs +3 -3
  74. package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -1
  75. package/fesm2022/ngneers-controls-tooltip.mjs +16 -28
  76. package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -1
  77. package/fesm2022/ngneers-controls-tree.mjs +2 -2
  78. package/fesm2022/ngneers-controls-tree.mjs.map +1 -1
  79. package/fesm2022/ngneers-controls-utils.mjs +138 -1
  80. package/fesm2022/ngneers-controls-utils.mjs.map +1 -1
  81. package/package.json +22 -2
  82. package/types/ngneers-controls-api-ng.d.ts +12 -9
  83. package/types/ngneers-controls-badge.d.ts +67 -0
  84. package/types/ngneers-controls-base.d.ts +61 -4
  85. package/types/ngneers-controls-breadcrumb.d.ts +17 -0
  86. package/types/ngneers-controls-calendar.d.ts +24 -6
  87. package/types/ngneers-controls-chip.d.ts +17 -0
  88. package/types/ngneers-controls-color-picker.d.ts +119 -0
  89. package/types/ngneers-controls-dialog.d.ts +17 -0
  90. package/types/ngneers-controls-directives.d.ts +31 -2
  91. package/types/ngneers-controls-drawer.d.ts +17 -0
  92. package/types/ngneers-controls-edit-inplace.d.ts +23 -0
  93. package/types/ngneers-controls-errors.d.ts +105 -16
  94. package/types/ngneers-controls-filter.d.ts +30 -13
  95. package/types/ngneers-controls-hint.d.ts +8 -1
  96. package/types/ngneers-controls-i18n-translations-de.d.ts +17 -0
  97. package/types/ngneers-controls-i18n-translations-en.d.ts +17 -0
  98. package/types/ngneers-controls-i18n.d.ts +17 -0
  99. package/types/ngneers-controls-icon.d.ts +2 -2
  100. package/types/ngneers-controls-input-field.d.ts +28 -2
  101. package/types/ngneers-controls-input.d.ts +35 -3
  102. package/types/ngneers-controls-list-box.d.ts +17 -0
  103. package/types/ngneers-controls-mask-input.d.ts +17 -0
  104. package/types/ngneers-controls-otp.d.ts +262 -0
  105. package/types/ngneers-controls-paginator.d.ts +34 -3
  106. package/types/ngneers-controls-rating.d.ts +69 -0
  107. package/types/ngneers-controls-scroll-shadow.d.ts +11 -2
  108. package/types/ngneers-controls-scroller.d.ts +3 -1
  109. package/types/ngneers-controls-select.d.ts +30 -3
  110. package/types/ngneers-controls-snackbar.d.ts +17 -0
  111. package/types/ngneers-controls-splitter.d.ts +26 -2
  112. package/types/ngneers-controls-state.d.ts +2 -2
  113. package/types/ngneers-controls-stepper.d.ts +68 -0
  114. package/types/ngneers-controls-table.d.ts +276 -169
  115. package/types/ngneers-controls-toast.d.ts +17 -0
  116. package/types/ngneers-controls-tooltip.d.ts +0 -1
  117. package/types/ngneers-controls-tree.d.ts +17 -0
  118. package/types/ngneers-controls-upload.d.ts +17 -0
  119. package/types/ngneers-controls-utils.d.ts +27 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ngneers-controls-paginator.mjs","sources":["../../src/paginator/paginator.ts","../../src/paginator/paginator.html","../../src/paginator/ngneers-controls-paginator.ts"],"sourcesContent":["import { NgStyle, NgClass } from '@angular/common';\nimport {\n booleanAttribute,\n Component,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { NgnTemplate } from '@ngneers/controls/api/ng';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnButton } from '@ngneers/controls/button';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { NgnInputField } from '@ngneers/controls/input-field';\nimport { NgnItemView } from '@ngneers/controls/item-view';\nimport { NgnSelect } from '@ngneers/controls/select';\nimport { throwExp } from '@ngneers/controls/utils';\nimport { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';\n\nimport type { PaginationState } from './types';\nimport type { NgnItem } from '@ngneers/controls/api';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-paginator',\n templateUrl: './paginator.html',\n imports: [\n NgnButton,\n NgnIcon,\n NgnItemView,\n NgnSelect,\n NgnTemplate,\n NgStyle,\n NgnPt,\n NgnInputField,\n NgClass,\n ],\n providers: [provideSelf(NgnPaginator)],\n})\nexport class NgnPaginator extends NgnBase<'paginator'> {\n protected readonly theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * Total number of items to paginate.\n */\n public readonly totalItems = input.required<number>();\n /**\n * Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.\n */\n public readonly pageSize = model<number>();\n /**\n * Possible page sizes to choose from.\n * @default [5, 10, 25, 50]\n */\n public readonly possiblePageSizes = input([5, 10, 25, 50], {\n transform: (sizes: number[]) => sizes.sort((a, b) => a - b),\n });\n /**\n * If set to `true`, the page size selector will be hidden and the page size cannot be changed by the user.\n * @default false\n */\n public readonly fixedPageSize = input(false, { transform: booleanAttribute });\n\n protected readonly appliedPageSize = computed(\n () =>\n this.pageSize() ||\n this.possiblePageSizes()[0] ||\n throwExp('NgnPaginator', 'At least one page size must be provided')\n );\n protected readonly pageCount = computed(() =>\n Math.ceil(this.totalItems() / this.appliedPageSize())\n );\n protected readonly pages = computed(() =>\n Array.from({ length: this.pageCount() }, (_, i) => ({ page: i }))\n );\n protected readonly pageSizeOptions = computed<NgnItem[]>(() =>\n this.possiblePageSizes().map(size => ({\n label: size.toString(),\n value: size,\n }))\n );\n\n /**\n * Emits the current pagination state whenever the page or page size changes.\n */\n public readonly value = output<PaginationState>();\n /**\n * Current page index (zero-based).\n * @default 0\n */\n public readonly page = model(0);\n\n constructor() {\n super();\n effect(() => {\n const page = this.page();\n const pageSize = this.appliedPageSize();\n this.value.emit({\n page: {\n current: page,\n size: pageSize,\n },\n slice: {\n skip: page * pageSize,\n take: pageSize,\n },\n });\n });\n }\n\n protected previousPage(event: PointerEvent): void {\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.max(this.page() - amount, 0);\n this.page.set(newPage);\n }\n\n protected nextPage(event: PointerEvent): void {\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.min(this.page() + amount, this.pageCount() - 1);\n this.page.set(newPage);\n }\n\n protected getButtonFontStyles(page: number): {\n [klass: string]: string;\n } {\n function getFontSizeMultiplier() {\n const charCount = (page + 1).toString().length;\n if (charCount <= 2) {\n return 1;\n }\n return Math.pow(0.85, charCount - 2);\n }\n\n return {\n fontSize: `${getFontSizeMultiplier()}em`,\n width: 'calc(1rem + 2 * var(--padding))',\n height: 'calc(1rem + 2 * var(--padding))',\n lineHeight: '0',\n };\n }\n}\n","<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n<ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n>\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n</ngn-item-view>\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngneers-controls-paginator.mjs","sources":["../../src/paginator/paginator.ts","../../src/paginator/paginator.html","../../src/paginator/ngneers-controls-paginator.ts"],"sourcesContent":["import { NgStyle, NgClass } from '@angular/common';\nimport {\n booleanAttribute,\n Component,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { NgnTemplate } from '@ngneers/controls/api/ng';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnButton } from '@ngneers/controls/button';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { NgnInputField } from '@ngneers/controls/input-field';\nimport { NgnItemView } from '@ngneers/controls/item-view';\nimport { NgnSelect } from '@ngneers/controls/select';\nimport { NgnError, throwExp } from '@ngneers/controls/utils';\nimport { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';\n\nimport type { PaginationState } from './types';\nimport type { NgnItem } from '@ngneers/controls/api';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-paginator',\n templateUrl: './paginator.html',\n imports: [\n NgnButton,\n NgnIcon,\n NgnItemView,\n NgnSelect,\n NgnTemplate,\n NgStyle,\n NgnPt,\n NgnInputField,\n NgClass,\n ],\n providers: [provideSelf(NgnPaginator)],\n})\nexport class NgnPaginator extends NgnBase<'paginator'> {\n protected readonly theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * Total number of items to paginate. Required in `'pages'` mode (drives the\n * page count); ignored in `'compact'` mode.\n */\n public readonly totalItems = input<number>();\n /**\n * Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.\n */\n public readonly pageSize = model<number>();\n /**\n * Layout mode.\n * - `'pages'`: full paginator with numbered page buttons (needs {@link totalItems}).\n * - `'compact'`: prev/next buttons only, no page indicators, no total required.\n * @default 'pages'\n */\n public readonly mode = input<'pages' | 'compact'>('pages');\n /**\n * `'compact'` mode only: whether a next page exists. Disables the \"next\" button\n * when `false`. Bind to a lazy data source's `hasMore`.\n * @default true\n */\n public readonly hasNext = input(true, { transform: booleanAttribute });\n /**\n * Possible page sizes to choose from.\n * @default [5, 10, 25, 50]\n */\n public readonly possiblePageSizes = input([5, 10, 25, 50], {\n transform: (sizes: number[]) => sizes.sort((a, b) => a - b),\n });\n /**\n * If set to `true`, the page size selector will be hidden and the page size cannot be changed by the user.\n * @default false\n */\n public readonly fixedPageSize = input(false, { transform: booleanAttribute });\n\n protected readonly appliedPageSize = computed(\n () =>\n this.pageSize() ||\n this.possiblePageSizes()[0] ||\n throwExp('NgnPaginator', 'At least one page size must be provided')\n );\n protected readonly pageCount = computed(() =>\n Math.ceil((this.totalItems() ?? 0) / this.appliedPageSize())\n );\n protected readonly pages = computed(() =>\n Array.from({ length: this.pageCount() }, (_, i) => ({ page: i }))\n );\n protected readonly pageSizeOptions = computed<NgnItem[]>(() =>\n this.possiblePageSizes().map(size => ({\n label: size.toString(),\n value: size,\n }))\n );\n\n /**\n * Emits the current pagination state whenever the page or page size changes.\n */\n public readonly value = output<PaginationState>();\n /**\n * Current page index (zero-based).\n * @default 0\n */\n public readonly page = model(0);\n\n constructor() {\n super();\n effect(() => {\n const page = this.page();\n const pageSize = this.appliedPageSize();\n this.value.emit({\n page: {\n current: page,\n size: pageSize,\n },\n slice: {\n skip: page * pageSize,\n take: pageSize,\n },\n });\n });\n effect(() => {\n if (this.mode() === 'pages' && this.totalItems() === undefined) {\n throw new NgnError('paginator', \"totalItems is required in 'pages' mode\");\n }\n });\n }\n\n protected previousPage(event: PointerEvent): void {\n // Compact mode is cursor pagination — sequential, so no shift/ctrl multi-jump.\n const amount = this.mode() === 'compact' ? 1 : event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.max(this.page() - amount, 0);\n this.page.set(newPage);\n }\n\n protected nextPage(event: PointerEvent): void {\n if (this.mode() === 'compact') {\n if (!this.hasNext()) return;\n this.page.set(this.page() + 1);\n return;\n }\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.min(this.page() + amount, this.pageCount() - 1);\n this.page.set(newPage);\n }\n\n protected getButtonFontStyles(page: number): {\n [klass: string]: string;\n } {\n function getFontSizeMultiplier() {\n const charCount = (page + 1).toString().length;\n if (charCount <= 2) {\n return 1;\n }\n return Math.pow(0.85, charCount - 2);\n }\n\n return {\n fontSize: `${getFontSizeMultiplier()}em`,\n width: 'calc(1rem + 2 * var(--padding))',\n height: 'calc(1rem + 2 * var(--padding))',\n lineHeight: '0',\n };\n }\n}\n","<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [disabled]=\"page() === 0\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n@if (mode() === 'pages') {\n <ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n >\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n </ngn-item-view>\n}\n\n@if (mode() === 'compact') {\n <span data-compact-page aria-current=\"page\">{{ page() + 1 }}</span>\n}\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [disabled]=\"mode() === 'compact' && !hasNext()\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAyBA;;AAEG;AAiBG,MAAO,YAAa,SAAQ,OAAoB,CAAA;IACjC,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,wBAAwB,EAAE,MAAM,CAAC;AAClE,IAAA,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY;AAEnD;;;AAGG;AACa,IAAA,UAAU,GAAG,KAAK;8FAAU;AAC5C;;AAEG;AACa,IAAA,QAAQ,GAAG,KAAK;4FAAU;AAC1C;;;;;AAKG;IACa,IAAI,GAAG,KAAK,CAAsB,OAAO;6EAAC;AAC1D;;;;AAIG;IACa,OAAO,GAAG,KAAK,CAAC,IAAI,+EAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;AACtE;;;AAGG;AACa,IAAA,iBAAiB,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,mBAAA,EAAA,8BAAA,EAAA,CAAA,EACvD,SAAS,EAAE,CAAC,KAAe,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAC3D;AACF;;;AAGG;IACa,aAAa,GAAG,KAAK,CAAC,KAAK,qFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;IAE1D,eAAe,GAAG,QAAQ,CAC3C,MACE,IAAI,CAAC,QAAQ,EAAE;AACf,QAAA,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;AAC3B,QAAA,QAAQ,CAAC,cAAc,EAAE,yCAAyC,CAAC;wFACtE;IACkB,SAAS,GAAG,QAAQ,CAAC,MACtC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;kFAC7D;AACkB,IAAA,KAAK,GAAG,QAAQ,CAAC,MAClC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;8EAClE;AACkB,IAAA,eAAe,GAAG,QAAQ,CAAY,MACvD,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK;AACpC,QAAA,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE;AACtB,QAAA,KAAK,EAAE,IAAI;AACZ,KAAA,CAAC,CAAC;wFACJ;AAED;;AAEG;IACa,KAAK,GAAG,MAAM,EAAmB;AACjD;;;AAGG;IACa,IAAI,GAAG,KAAK,CAAC,CAAC;6EAAC;AAE/B,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;QACP,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE;AACvC,YAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;AACd,gBAAA,IAAI,EAAE;AACJ,oBAAA,OAAO,EAAE,IAAI;AACb,oBAAA,IAAI,EAAE,QAAQ;AACf,iBAAA;AACD,gBAAA,KAAK,EAAE;oBACL,IAAI,EAAE,IAAI,GAAG,QAAQ;AACrB,oBAAA,IAAI,EAAE,QAAQ;AACf,iBAAA;AACF,aAAA,CAAC;AACJ,QAAA,CAAC,CAAC;QACF,MAAM,CAAC,MAAK;AACV,YAAA,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,KAAK,SAAS,EAAE;AAC9D,gBAAA,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAE,wCAAwC,CAAC;YAC3E;AACF,QAAA,CAAC,CAAC;IACJ;AAEU,IAAA,YAAY,CAAC,KAAmB,EAAA;;AAExC,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,GAAG,GAAG,CAAC;AAC5F,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AACjD,QAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IACxB;AAEU,IAAA,QAAQ,CAAC,KAAmB,EAAA;AACpC,QAAA,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,EAAE;AAC7B,YAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;gBAAE;AACrB,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9B;QACF;QACA,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,GAAG,GAAG,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACpE,QAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IACxB;AAEU,IAAA,mBAAmB,CAAC,IAAY,EAAA;AAGxC,QAAA,SAAS,qBAAqB,GAAA;AAC5B,YAAA,MAAM,SAAS,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM;AAC9C,YAAA,IAAI,SAAS,IAAI,CAAC,EAAE;AAClB,gBAAA,OAAO,CAAC;YACV;YACA,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC;QACtC;QAEA,OAAO;AACL,YAAA,QAAQ,EAAE,CAAA,EAAG,qBAAqB,EAAE,CAAA,EAAA,CAAI;AACxC,YAAA,KAAK,EAAE,iCAAiC;AACxC,YAAA,MAAM,EAAE,iCAAiC;AACzC,YAAA,UAAU,EAAE,GAAG;SAChB;IACH;uGA9HW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAZ,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,UAAA,EAAA,mBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,KAAA,EAAA,OAAA,EAAA,IAAA,EAAA,YAAA,EAAA,EAAA,SAAA,EAFZ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC1CxC,mrEAmFA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDnDI,SAAS,EAAA,QAAA,EAAA,iCAAA,EAAA,MAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,QAAA,EAAA,CAAA,WAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,OAAO,sFACP,WAAW,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,EAAA,WAAA,EAAA,mBAAA,EAAA,kBAAA,EAAA,6BAAA,EAAA,uBAAA,EAAA,gBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACX,SAAS,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,gBAAA,EAAA,SAAA,EAAA,QAAA,EAAA,YAAA,EAAA,YAAA,EAAA,cAAA,EAAA,SAAA,EAAA,YAAA,EAAA,UAAA,EAAA,oBAAA,EAAA,UAAA,EAAA,4BAAA,EAAA,UAAA,EAAA,UAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,WAAW,EAAA,QAAA,EAAA,0BAAA,EAAA,MAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACX,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACP,KAAK,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACL,aAAa,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,YAAA,EAAA,WAAA,EAAA,SAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,SAAA,EAAA,UAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACb,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAIE,YAAY,EAAA,UAAA,EAAA,CAAA;kBAhBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,eAAe,EAAA,OAAA,EAEhB;wBACP,SAAS;wBACT,OAAO;wBACP,WAAW;wBACX,SAAS;wBACT,WAAW;wBACX,OAAO;wBACP,KAAK;wBACL,aAAa;wBACb,OAAO;qBACR,EAAA,SAAA,EACU,CAAC,WAAW,CAAA,YAAA,CAAc,CAAC,EAAA,QAAA,EAAA,mrEAAA,EAAA;;;AE1CxC;;AAEG;;;;"}
@@ -23,7 +23,7 @@ const NGN_RADIO_GROUP = new InjectionToken('NGN_RADIO_GROUP');
23
23
  class NgnRadioGroup extends ValueControlBase {
24
24
  theme = this.injectThemeTemplate(radioGroupControlTemplate, {
25
25
  root: true,
26
- invalid: () => this.invalid(),
26
+ invalid: () => this.invalidState(),
27
27
  });
28
28
  /** The roving-focus host directive that drives keyboard/tab coordination. */
29
29
  roving = inject(NgnRovingGroup);
@@ -50,7 +50,7 @@ class NgnRadioGroup extends ValueControlBase {
50
50
  return;
51
51
  }
52
52
  this.value.set(radio.value());
53
- this.touched.set(true);
53
+ this.markTouched();
54
54
  });
55
55
  inject(DestroyRef).onDestroy(() => sub.unsubscribe());
56
56
  // Keep the roving tab stop on the selected radio so Tab-in lands on the
@@ -75,7 +75,7 @@ class NgnRadioGroup extends ValueControlBase {
75
75
  });
76
76
  }
77
77
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
78
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: NgnRadioGroup, isStandalone: true, selector: "ngn-radio-group", host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-orientation": "roving.orientation()", "attr.aria-invalid": "invalid() ? 'true' : null", "attr.aria-disabled": "disabled() ? 'true' : null" } }, providers: [provideSelf(NgnRadioGroup), { provide: NGN_RADIO_GROUP, useExisting: NgnRadioGroup }], usesInheritance: true, hostDirectives: [{ directive: i1.NgnRovingGroup, inputs: ["orientation", "orientation", "rovingWrap", "rovingWrap"] }], ngImport: i0, template: "<ng-content />\n" });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: NgnRadioGroup, isStandalone: true, selector: "ngn-radio-group", host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-orientation": "roving.orientation()", "attr.aria-invalid": "invalidState() ? 'true' : null", "attr.aria-disabled": "disabled() ? 'true' : null" } }, providers: [provideSelf(NgnRadioGroup), { provide: NGN_RADIO_GROUP, useExisting: NgnRadioGroup }], usesInheritance: true, hostDirectives: [{ directive: i1.NgnRovingGroup, inputs: ["orientation", "orientation", "rovingWrap", "rovingWrap"] }], ngImport: i0, template: "<ng-content />\n" });
79
79
  }
80
80
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRadioGroup, decorators: [{
81
81
  type: Component,
@@ -84,7 +84,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
84
84
  '[attr.aria-label]': 'label()',
85
85
  '[attr.aria-labelledby]': 'labelledBy()',
86
86
  '[attr.aria-orientation]': 'roving.orientation()',
87
- '[attr.aria-invalid]': "invalid() ? 'true' : null",
87
+ '[attr.aria-invalid]': "invalidState() ? 'true' : null",
88
88
  '[attr.aria-disabled]': "disabled() ? 'true' : null",
89
89
  }, template: "<ng-content />\n" }]
90
90
  }], ctorParameters: () => [] });
@@ -1 +1 @@
1
- {"version":3,"file":"ngneers-controls-radio.mjs","sources":["../../src/radio/radio-group.token.ts","../../src/radio/radio-group.ts","../../src/radio/radio-group.html","../../src/radio/radio.ts","../../src/radio/radio.html","../../src/radio/ngneers-controls-radio.ts"],"sourcesContent":["import { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * A single radio option registered with its enclosing group. The group keys\n * selection and keyboard coordination off these refs; `element` correlates a\n * roving-focus item back to its payload `value`.\n */\nexport interface NgnRadioRef<V> {\n readonly element: HTMLElement;\n /** The option payload this radio contributes to the group value. */\n readonly value: Signal<V>;\n /** Effective disabled state (own disabled OR the group being disabled). */\n readonly disabled: Signal<boolean>;\n}\n\n/**\n * The slice of the radio group that child `ngn-radio`s consume. Kept as a\n * token + interface (rather than the concrete class) so the child never imports\n * the group component — avoiding a circular dependency between the two files.\n */\nexport interface NgnRadioGroupApi<V> {\n /** The currently selected group value. */\n readonly value: Signal<V>;\n /** Whether the whole group is disabled. */\n readonly disabled: Signal<boolean>;\n /** Whether the group is read-only (navigable, but selection is frozen). */\n readonly readonly: Signal<boolean>;\n register(ref: NgnRadioRef<V>): void;\n unregister(ref: NgnRadioRef<V>): void;\n}\n\nexport const NGN_RADIO_GROUP = new InjectionToken<NgnRadioGroupApi<unknown>>('NGN_RADIO_GROUP');\n","import { Component, DestroyRef, effect, inject, signal } from '@angular/core';\nimport { provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { NgnRovingGroup } from '@ngneers/controls/roving-focus';\nimport { radioGroupControlTemplate } from '@ngneers/controls-themes/templates/radio-group';\n\nimport { NGN_RADIO_GROUP, type NgnRadioGroupApi, type NgnRadioRef } from './radio-group.token';\n\n/**\n * A themed radio group. Owns the selected {@link value}; the individual\n * `ngn-radio` children projected inside are presentational and report their\n * payload up through {@link register}.\n *\n * Keyboard handling and roving tab order are delegated to `NgnRovingGroup`\n * (applied as a host directive). Selection follows focus: moving the roving\n * active item (arrows / Home / End / pointer) selects it, matching native\n * radio-group behavior.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-radio-group',\n templateUrl: './radio-group.html',\n hostDirectives: [{ directive: NgnRovingGroup, inputs: ['orientation', 'rovingWrap'] }],\n providers: [provideSelf(NgnRadioGroup), { provide: NGN_RADIO_GROUP, useExisting: NgnRadioGroup }],\n host: {\n role: 'radiogroup',\n '[attr.aria-label]': 'label()',\n '[attr.aria-labelledby]': 'labelledBy()',\n '[attr.aria-orientation]': 'roving.orientation()',\n '[attr.aria-invalid]': \"invalid() ? 'true' : null\",\n '[attr.aria-disabled]': \"disabled() ? 'true' : null\",\n },\n})\nexport class NgnRadioGroup<V>\n extends ValueControlBase<'radioGroup', V>\n implements NgnRadioGroupApi<V>\n{\n protected readonly theme = this.injectThemeTemplate(radioGroupControlTemplate, {\n root: true,\n invalid: () => this.invalid(),\n });\n\n /** The roving-focus host directive that drives keyboard/tab coordination. */\n protected readonly roving = inject(NgnRovingGroup);\n\n private readonly _radios = signal<readonly NgnRadioRef<V>[]>([]);\n\n public register(ref: NgnRadioRef<V>): void {\n this._radios.update(list => (list.includes(ref) ? list : [...list, ref]));\n }\n\n public unregister(ref: NgnRadioRef<V>): void {\n this._radios.update(list => list.filter(r => r !== ref));\n }\n\n constructor() {\n super();\n\n // Selection follows focus: the roving group emits only on genuine\n // navigation/activation (never on mount or plain Tab-in), so wiring value\n // here gives native \"arrow selects\" behavior without spurious writes.\n const sub = this.roving.activeItemChange.subscribe(index => {\n if (this.readonly() || this.disabled()) {\n return;\n }\n const element = this.roving.items()[index]?.element;\n const radio = this._radios().find(r => r.element === element);\n if (!radio || radio.disabled()) {\n return;\n }\n this.value.set(radio.value());\n this.touched.set(true);\n });\n inject(DestroyRef).onDestroy(() => sub.unsubscribe());\n\n // Keep the roving tab stop on the selected radio so Tab-in lands on the\n // checked option (native behavior). Disabled radios are skipped so a\n // selected-but-disabled option never becomes the tab stop. `syncActiveIndex`\n // updates the tab stop silently — no emit, no focus move — so a programmatic\n // `value` change never yanks DOM focus into the group.\n effect(() => {\n const items = this.roving.items();\n if (!items.length) {\n return;\n }\n const radios = this._radios();\n const value = this.value();\n const index = items.findIndex(item => {\n const radio = radios.find(r => r.element === item.element);\n return radio ? !radio.disabled() && radio.value() === value : false;\n });\n if (index >= 0) {\n this.roving.syncActiveIndex(index);\n }\n });\n }\n}\n","<ng-content />\n","import {\n booleanAttribute,\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n input,\n} from '@angular/core';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnRovingItem } from '@ngneers/controls/roving-focus';\nimport { radioControlTemplate } from '@ngneers/controls-themes/templates/radio';\n\nimport { NGN_RADIO_GROUP } from './radio-group.token';\n\n/**\n * A single radio option. Must be projected inside an `ngn-radio-group`, which\n * owns the selected value — this control only contributes its {@link value}\n * payload and renders the themed dot. The label is authored via content\n * projection (`<ngn-radio [value]=\"…\">Label</ngn-radio>`) with an optional\n * {@link label} input fallback.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-radio',\n templateUrl: './radio.html',\n imports: [NgnPt],\n hostDirectives: [NgnRovingItem],\n providers: [provideSelf(NgnRadio)],\n host: {\n role: 'radio',\n '[attr.aria-checked]': 'checked()',\n '[attr.aria-disabled]': \"effectiveDisabled() ? 'true' : null\",\n '[attr.aria-label]': 'label()',\n },\n})\nexport class NgnRadio<V> extends NgnBase<'radio'> {\n protected readonly theme = this.injectThemeTemplate(radioControlTemplate, {\n root: true,\n });\n\n /** The payload this radio selects into the enclosing group. */\n public readonly value = input.required<V>();\n /** Disables this individual option. */\n public readonly disabled = input(false, { transform: booleanAttribute });\n /** Optional accessible label; overrides projected content for the a11y name. */\n public readonly label = input<string | null>(null);\n\n private readonly _group = inject(NGN_RADIO_GROUP);\n private readonly _rovingItem = inject(NgnRovingItem);\n\n /** Whether this radio is the currently selected option in its group. */\n protected readonly checked = computed(() => this._group.value() === this.value());\n\n /** Disabled if this option or the whole group is disabled. */\n protected readonly effectiveDisabled = computed(() => this.disabled() || this._group.disabled());\n\n constructor() {\n super();\n\n // Feed the roving-focus item so keyboard nav / pointer activation skip a\n // disabled radio.\n effect(() => this._rovingItem.disabled.set(this.effectiveDisabled()));\n\n const ref = {\n element: this.element.nativeElement,\n value: this.value,\n disabled: this.effectiveDisabled,\n };\n this._group.register(ref);\n inject(DestroyRef).onDestroy(() => this._group.unregister(ref));\n }\n}\n","<span\n [ptInt]=\"this\"\n [ptClass]=\"{\n circle: true,\n 'circle-checked': checked(),\n }\"\n>\n <span [ptInt]=\"this\" [ptClass]=\"'dot'\"></span>\n</span>\n<span [ptInt]=\"this\" [ptClass]=\"'label'\"><ng-content /></span>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;MA+Ba,eAAe,GAAG,IAAI,cAAc,CAA4B,iBAAiB;;ACxB9F;;;;;;;;;;;AAWG;AAeG,MAAO,aACX,SAAQ,gBAAiC,CAAA;AAGtB,IAAA,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,yBAAyB,EAAE;AAC7E,QAAA,IAAI,EAAE,IAAI;AACV,QAAA,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE;AAC9B,KAAA,CAAC;;AAGiB,IAAA,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;IAEjC,OAAO,GAAG,MAAM,CAA4B,EAAE;gFAAC;AAEzD,IAAA,QAAQ,CAAC,GAAmB,EAAA;AACjC,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3E;AAEO,IAAA,UAAU,CAAC,GAAmB,EAAA;QACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IAC1D;AAEA,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;;;;AAKP,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,KAAK,IAAG;YACzD,IAAI,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE;gBACtC;YACF;AACA,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO;AACnD,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;YAC7D,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE;gBAC9B;YACF;YACA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;AAC7B,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AACxB,QAAA,CAAC,CAAC;AACF,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;;;;;;QAOrD,MAAM,CAAC,MAAK;YACV,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;AACjC,YAAA,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBACjB;YACF;AACA,YAAA,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE;AAC7B,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;YAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,IAAG;AACnC,gBAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC;gBAC1D,OAAO,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,KAAK,GAAG,KAAK;AACrE,YAAA,CAAC,CAAC;AACF,YAAA,IAAI,KAAK,IAAI,CAAC,EAAE;AACd,gBAAA,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC;YACpC;AACF,QAAA,CAAC,CAAC;IACJ;uGA9DW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,2VAVb,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,yKCvBnG,kBACA,EAAA,CAAA;;2FDgCa,aAAa,EAAA,UAAA,EAAA,CAAA;kBAdzB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,EAAA,cAAA,EAEX,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE,CAAC,EAAA,SAAA,EAC3E,CAAC,WAAW,CAAA,aAAA,CAAe,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAA,aAAe,EAAE,CAAC,EAAA,IAAA,EAC3F;AACJ,wBAAA,IAAI,EAAE,YAAY;AAClB,wBAAA,mBAAmB,EAAE,SAAS;AAC9B,wBAAA,wBAAwB,EAAE,cAAc;AACxC,wBAAA,yBAAyB,EAAE,sBAAsB;AACjD,wBAAA,qBAAqB,EAAE,2BAA2B;AAClD,wBAAA,sBAAsB,EAAE,4BAA4B;AACrD,qBAAA,EAAA,QAAA,EAAA,kBAAA,EAAA;;;AEhBH;;;;;;;;AAQG;AAcG,MAAO,QAAY,SAAQ,OAAgB,CAAA;AAC5B,IAAA,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,oBAAoB,EAAE;AACxE,QAAA,IAAI,EAAE,IAAI;AACX,KAAA,CAAC;;IAGc,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAK;;IAE3B,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;IAExD,KAAK,GAAG,KAAK,CAAgB,IAAI;8EAAC;AAEjC,IAAA,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;AAChC,IAAA,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC;;AAGjC,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE;gFAAC;;AAG9D,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;0FAAC;AAEhG,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;;;AAIP,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAErE,QAAA,MAAM,GAAG,GAAG;AACV,YAAA,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACnC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,iBAAiB;SACjC;AACD,QAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;AACzB,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACjE;uGAnCW,QAAQ,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAR,QAAQ,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,OAAA,EAAA,EAAA,UAAA,EAAA,EAAA,mBAAA,EAAA,WAAA,EAAA,oBAAA,EAAA,qCAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,EAAA,EAAA,SAAA,EARR,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAA,eAAA,EAAA,IAAA,EAAA,cAAA,EAAA,CAAA,EAAA,SAAA,EAAA,EAAA,CAAA,aAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC7BpC,gPAUA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDiBY,KAAK,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,EAAA,OAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAUJ,QAAQ,EAAA,UAAA,EAAA,CAAA;kBAbpB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,WAAW,EAAA,OAAA,EAEZ,CAAC,KAAK,CAAC,EAAA,cAAA,EACA,CAAC,aAAa,CAAC,EAAA,SAAA,EACpB,CAAC,WAAW,CAAA,QAAA,CAAU,CAAC,EAAA,IAAA,EAC5B;AACJ,wBAAA,IAAI,EAAE,OAAO;AACb,wBAAA,qBAAqB,EAAE,WAAW;AAClC,wBAAA,sBAAsB,EAAE,qCAAqC;AAC7D,wBAAA,mBAAmB,EAAE,SAAS;AAC/B,qBAAA,EAAA,QAAA,EAAA,gPAAA,EAAA;;;AEnCH;;AAEG;;;;"}
1
+ {"version":3,"file":"ngneers-controls-radio.mjs","sources":["../../src/radio/radio-group.token.ts","../../src/radio/radio-group.ts","../../src/radio/radio-group.html","../../src/radio/radio.ts","../../src/radio/radio.html","../../src/radio/ngneers-controls-radio.ts"],"sourcesContent":["import { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * A single radio option registered with its enclosing group. The group keys\n * selection and keyboard coordination off these refs; `element` correlates a\n * roving-focus item back to its payload `value`.\n */\nexport interface NgnRadioRef<V> {\n readonly element: HTMLElement;\n /** The option payload this radio contributes to the group value. */\n readonly value: Signal<V>;\n /** Effective disabled state (own disabled OR the group being disabled). */\n readonly disabled: Signal<boolean>;\n}\n\n/**\n * The slice of the radio group that child `ngn-radio`s consume. Kept as a\n * token + interface (rather than the concrete class) so the child never imports\n * the group component — avoiding a circular dependency between the two files.\n */\nexport interface NgnRadioGroupApi<V> {\n /** The currently selected group value. */\n readonly value: Signal<V>;\n /** Whether the whole group is disabled. */\n readonly disabled: Signal<boolean>;\n /** Whether the group is read-only (navigable, but selection is frozen). */\n readonly readonly: Signal<boolean>;\n register(ref: NgnRadioRef<V>): void;\n unregister(ref: NgnRadioRef<V>): void;\n}\n\nexport const NGN_RADIO_GROUP = new InjectionToken<NgnRadioGroupApi<unknown>>('NGN_RADIO_GROUP');\n","import { Component, DestroyRef, effect, inject, signal } from '@angular/core';\nimport { provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { NgnRovingGroup } from '@ngneers/controls/roving-focus';\nimport { radioGroupControlTemplate } from '@ngneers/controls-themes/templates/radio-group';\n\nimport { NGN_RADIO_GROUP, type NgnRadioGroupApi, type NgnRadioRef } from './radio-group.token';\n\n/**\n * A themed radio group. Owns the selected {@link value}; the individual\n * `ngn-radio` children projected inside are presentational and report their\n * payload up through {@link register}.\n *\n * Keyboard handling and roving tab order are delegated to `NgnRovingGroup`\n * (applied as a host directive). Selection follows focus: moving the roving\n * active item (arrows / Home / End / pointer) selects it, matching native\n * radio-group behavior.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-radio-group',\n templateUrl: './radio-group.html',\n hostDirectives: [{ directive: NgnRovingGroup, inputs: ['orientation', 'rovingWrap'] }],\n providers: [provideSelf(NgnRadioGroup), { provide: NGN_RADIO_GROUP, useExisting: NgnRadioGroup }],\n host: {\n role: 'radiogroup',\n '[attr.aria-label]': 'label()',\n '[attr.aria-labelledby]': 'labelledBy()',\n '[attr.aria-orientation]': 'roving.orientation()',\n '[attr.aria-invalid]': \"invalidState() ? 'true' : null\",\n '[attr.aria-disabled]': \"disabled() ? 'true' : null\",\n },\n})\nexport class NgnRadioGroup<V>\n extends ValueControlBase<'radioGroup', V>\n implements NgnRadioGroupApi<V>\n{\n protected readonly theme = this.injectThemeTemplate(radioGroupControlTemplate, {\n root: true,\n invalid: () => this.invalidState(),\n });\n\n /** The roving-focus host directive that drives keyboard/tab coordination. */\n protected readonly roving = inject(NgnRovingGroup);\n\n private readonly _radios = signal<readonly NgnRadioRef<V>[]>([]);\n\n public register(ref: NgnRadioRef<V>): void {\n this._radios.update(list => (list.includes(ref) ? list : [...list, ref]));\n }\n\n public unregister(ref: NgnRadioRef<V>): void {\n this._radios.update(list => list.filter(r => r !== ref));\n }\n\n constructor() {\n super();\n\n // Selection follows focus: the roving group emits only on genuine\n // navigation/activation (never on mount or plain Tab-in), so wiring value\n // here gives native \"arrow selects\" behavior without spurious writes.\n const sub = this.roving.activeItemChange.subscribe(index => {\n if (this.readonly() || this.disabled()) {\n return;\n }\n const element = this.roving.items()[index]?.element;\n const radio = this._radios().find(r => r.element === element);\n if (!radio || radio.disabled()) {\n return;\n }\n this.value.set(radio.value());\n this.markTouched();\n });\n inject(DestroyRef).onDestroy(() => sub.unsubscribe());\n\n // Keep the roving tab stop on the selected radio so Tab-in lands on the\n // checked option (native behavior). Disabled radios are skipped so a\n // selected-but-disabled option never becomes the tab stop. `syncActiveIndex`\n // updates the tab stop silently — no emit, no focus move — so a programmatic\n // `value` change never yanks DOM focus into the group.\n effect(() => {\n const items = this.roving.items();\n if (!items.length) {\n return;\n }\n const radios = this._radios();\n const value = this.value();\n const index = items.findIndex(item => {\n const radio = radios.find(r => r.element === item.element);\n return radio ? !radio.disabled() && radio.value() === value : false;\n });\n if (index >= 0) {\n this.roving.syncActiveIndex(index);\n }\n });\n }\n}\n","<ng-content />\n","import {\n booleanAttribute,\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n input,\n} from '@angular/core';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnRovingItem } from '@ngneers/controls/roving-focus';\nimport { radioControlTemplate } from '@ngneers/controls-themes/templates/radio';\n\nimport { NGN_RADIO_GROUP } from './radio-group.token';\n\n/**\n * A single radio option. Must be projected inside an `ngn-radio-group`, which\n * owns the selected value — this control only contributes its {@link value}\n * payload and renders the themed dot. The label is authored via content\n * projection (`<ngn-radio [value]=\"…\">Label</ngn-radio>`) with an optional\n * {@link label} input fallback.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-radio',\n templateUrl: './radio.html',\n imports: [NgnPt],\n hostDirectives: [NgnRovingItem],\n providers: [provideSelf(NgnRadio)],\n host: {\n role: 'radio',\n '[attr.aria-checked]': 'checked()',\n '[attr.aria-disabled]': \"effectiveDisabled() ? 'true' : null\",\n '[attr.aria-label]': 'label()',\n },\n})\nexport class NgnRadio<V> extends NgnBase<'radio'> {\n protected readonly theme = this.injectThemeTemplate(radioControlTemplate, {\n root: true,\n });\n\n /** The payload this radio selects into the enclosing group. */\n public readonly value = input.required<V>();\n /** Disables this individual option. */\n public readonly disabled = input(false, { transform: booleanAttribute });\n /** Optional accessible label; overrides projected content for the a11y name. */\n public readonly label = input<string | null>(null);\n\n private readonly _group = inject(NGN_RADIO_GROUP);\n private readonly _rovingItem = inject(NgnRovingItem);\n\n /** Whether this radio is the currently selected option in its group. */\n protected readonly checked = computed(() => this._group.value() === this.value());\n\n /** Disabled if this option or the whole group is disabled. */\n protected readonly effectiveDisabled = computed(() => this.disabled() || this._group.disabled());\n\n constructor() {\n super();\n\n // Feed the roving-focus item so keyboard nav / pointer activation skip a\n // disabled radio.\n effect(() => this._rovingItem.disabled.set(this.effectiveDisabled()));\n\n const ref = {\n element: this.element.nativeElement,\n value: this.value,\n disabled: this.effectiveDisabled,\n };\n this._group.register(ref);\n inject(DestroyRef).onDestroy(() => this._group.unregister(ref));\n }\n}\n","<span\n [ptInt]=\"this\"\n [ptClass]=\"{\n circle: true,\n 'circle-checked': checked(),\n }\"\n>\n <span [ptInt]=\"this\" [ptClass]=\"'dot'\"></span>\n</span>\n<span [ptInt]=\"this\" [ptClass]=\"'label'\"><ng-content /></span>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;MA+Ba,eAAe,GAAG,IAAI,cAAc,CAA4B,iBAAiB;;ACxB9F;;;;;;;;;;;AAWG;AAeG,MAAO,aACX,SAAQ,gBAAiC,CAAA;AAGtB,IAAA,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,yBAAyB,EAAE;AAC7E,QAAA,IAAI,EAAE,IAAI;AACV,QAAA,OAAO,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE;AACnC,KAAA,CAAC;;AAGiB,IAAA,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;IAEjC,OAAO,GAAG,MAAM,CAA4B,EAAE;gFAAC;AAEzD,IAAA,QAAQ,CAAC,GAAmB,EAAA;AACjC,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3E;AAEO,IAAA,UAAU,CAAC,GAAmB,EAAA;QACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IAC1D;AAEA,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;;;;AAKP,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,KAAK,IAAG;YACzD,IAAI,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE;gBACtC;YACF;AACA,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO;AACnD,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;YAC7D,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE;gBAC9B;YACF;YACA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,EAAE;AACpB,QAAA,CAAC,CAAC;AACF,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;;;;;;QAOrD,MAAM,CAAC,MAAK;YACV,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;AACjC,YAAA,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBACjB;YACF;AACA,YAAA,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE;AAC7B,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;YAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,IAAG;AACnC,gBAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC;gBAC1D,OAAO,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,KAAK,GAAG,KAAK;AACrE,YAAA,CAAC,CAAC;AACF,YAAA,IAAI,KAAK,IAAI,CAAC,EAAE;AACd,gBAAA,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC;YACpC;AACF,QAAA,CAAC,CAAC;IACJ;uGA9DW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,gWAVb,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,yKCvBnG,kBACA,EAAA,CAAA;;2FDgCa,aAAa,EAAA,UAAA,EAAA,CAAA;kBAdzB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,EAAA,cAAA,EAEX,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE,CAAC,EAAA,SAAA,EAC3E,CAAC,WAAW,CAAA,aAAA,CAAe,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAA,aAAe,EAAE,CAAC,EAAA,IAAA,EAC3F;AACJ,wBAAA,IAAI,EAAE,YAAY;AAClB,wBAAA,mBAAmB,EAAE,SAAS;AAC9B,wBAAA,wBAAwB,EAAE,cAAc;AACxC,wBAAA,yBAAyB,EAAE,sBAAsB;AACjD,wBAAA,qBAAqB,EAAE,gCAAgC;AACvD,wBAAA,sBAAsB,EAAE,4BAA4B;AACrD,qBAAA,EAAA,QAAA,EAAA,kBAAA,EAAA;;;AEhBH;;;;;;;;AAQG;AAcG,MAAO,QAAY,SAAQ,OAAgB,CAAA;AAC5B,IAAA,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,oBAAoB,EAAE;AACxE,QAAA,IAAI,EAAE,IAAI;AACX,KAAA,CAAC;;IAGc,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAK;;IAE3B,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;IAExD,KAAK,GAAG,KAAK,CAAgB,IAAI;8EAAC;AAEjC,IAAA,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;AAChC,IAAA,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC;;AAGjC,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE;gFAAC;;AAG9D,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;0FAAC;AAEhG,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;;;AAIP,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAErE,QAAA,MAAM,GAAG,GAAG;AACV,YAAA,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACnC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,iBAAiB;SACjC;AACD,QAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;AACzB,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACjE;uGAnCW,QAAQ,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAR,QAAQ,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,OAAA,EAAA,EAAA,UAAA,EAAA,EAAA,mBAAA,EAAA,WAAA,EAAA,oBAAA,EAAA,qCAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,EAAA,EAAA,SAAA,EARR,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAA,eAAA,EAAA,IAAA,EAAA,cAAA,EAAA,CAAA,EAAA,SAAA,EAAA,EAAA,CAAA,aAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC7BpC,gPAUA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDiBY,KAAK,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,EAAA,OAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAUJ,QAAQ,EAAA,UAAA,EAAA,CAAA;kBAbpB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,WAAW,EAAA,OAAA,EAEZ,CAAC,KAAK,CAAC,EAAA,cAAA,EACA,CAAC,aAAa,CAAC,EAAA,SAAA,EACpB,CAAC,WAAW,CAAA,QAAA,CAAU,CAAC,EAAA,IAAA,EAC5B;AACJ,wBAAA,IAAI,EAAE,OAAO;AACb,wBAAA,qBAAqB,EAAE,WAAW;AAClC,wBAAA,sBAAsB,EAAE,qCAAqC;AAC7D,wBAAA,mBAAmB,EAAE,SAAS;AAC/B,qBAAA,EAAA,QAAA,EAAA,gPAAA,EAAA;;;AEnCH;;AAEG;;;;"}
@@ -0,0 +1,180 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { contentChild, input, computed, Directive, signal, booleanAttribute, Component } from '@angular/core';
4
+ import { ValueControlBase, NgnPt, provideSelf } from '@ngneers/controls/base';
5
+ import { NgnIcon } from '@ngneers/controls/icon';
6
+ import { ratingControlTemplate } from '@ngneers/controls-themes/templates/rating';
7
+
8
+ class RatingTemplates extends ValueControlBase {
9
+ _userIndicatorTemplate = contentChild('indicator', /* @ts-ignore */
10
+ ...(ngDevMode ? [{ debugName: "_userIndicatorTemplate" }] : /* istanbul ignore next */ []));
11
+ /**
12
+ * Custom template for a single symbol. Receives the fill ratio (`$implicit`,
13
+ * 0..1) and `index`. Can also be set with an `<ng-template #indicator>`.
14
+ * When set, it replaces the default full/empty icon rendering per symbol.
15
+ */
16
+ indicatorTemplate = input(null, /* @ts-ignore */
17
+ ...(ngDevMode ? [{ debugName: "indicatorTemplate" }] : /* istanbul ignore next */ []));
18
+ resolvedIndicatorTemplate = computed(() => this._userIndicatorTemplate() ?? this.indicatorTemplate(), /* @ts-ignore */
19
+ ...(ngDevMode ? [{ debugName: "resolvedIndicatorTemplate" }] : /* istanbul ignore next */ []));
20
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: RatingTemplates, deps: null, target: i0.ɵɵFactoryTarget.Directive });
21
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: RatingTemplates, isStandalone: true, inputs: { indicatorTemplate: { classPropertyName: "indicatorTemplate", publicName: "indicatorTemplate", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "_userIndicatorTemplate", first: true, predicate: ["indicator"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 });
22
+ }
23
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: RatingTemplates, decorators: [{
24
+ type: Directive
25
+ }], propDecorators: { _userIndicatorTemplate: [{ type: i0.ContentChild, args: ['indicator', { isSignal: true }] }], indicatorTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indicatorTemplate", required: false }] }] } });
26
+
27
+ /**
28
+ * @category control
29
+ */
30
+ class NgnRating extends RatingTemplates {
31
+ theme = this.injectThemeTemplate(ratingControlTemplate, {
32
+ root: true,
33
+ invalid: () => this.invalidState(),
34
+ readonly: () => this.readonly(),
35
+ disabled: () => this.disabled(),
36
+ });
37
+ _hover = signal(null, /* @ts-ignore */
38
+ ...(ngDevMode ? [{ debugName: "_hover" }] : /* istanbul ignore next */ []));
39
+ /** Number of symbols. @default 5 */
40
+ count = input(5, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
42
+ /** The value increment; supports fractions (e.g. 0.5). @default 1 */
43
+ step = input(1, /* @ts-ignore */
44
+ ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
45
+ /** The filled symbol icon. Falls back to a filled star ({@link https://tabler.io/icons}). */
46
+ iconFull = input(/* @ts-ignore */
47
+ ...(ngDevMode ? [undefined, { debugName: "iconFull" }] : /* istanbul ignore next */ []));
48
+ /** The empty symbol icon. Falls back to an outline star. */
49
+ iconEmpty = input(/* @ts-ignore */
50
+ ...(ngDevMode ? [undefined, { debugName: "iconEmpty" }] : /* istanbul ignore next */ []));
51
+ /** Clicking the current value clears the rating back to `null`. @default true */
52
+ clearable = input(true, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
53
+ /** Accessible value text; overrides the numeric readout. */
54
+ valueText = input(/* @ts-ignore */
55
+ ...(ngDevMode ? [undefined, { debugName: "valueText" }] : /* istanbul ignore next */ []));
56
+ /** Function producing accessible value text from the value. */
57
+ valueTextFn = input(/* @ts-ignore */
58
+ ...(ngDevMode ? [undefined, { debugName: "valueTextFn" }] : /* istanbul ignore next */ []));
59
+ /** The value the UI should paint: hover preview when hovering, else the model value. */
60
+ displayValue = computed(() => this._hover() ?? this.value() ?? 0, /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
62
+ /** Zero-based indices for the symbols. */
63
+ symbols = computed(() => Array.from({ length: this.count() }, (_, i) => i), /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "symbols" }] : /* istanbul ignore next */ []));
65
+ valueTextValue = computed(() => {
66
+ const valueText = this.valueText();
67
+ if (valueText) {
68
+ return valueText;
69
+ }
70
+ const fn = this.valueTextFn();
71
+ if (fn) {
72
+ return fn(this.value() ?? 0);
73
+ }
74
+ return null;
75
+ }, /* @ts-ignore */
76
+ ...(ngDevMode ? [{ debugName: "valueTextValue" }] : /* istanbul ignore next */ []));
77
+ /** Fill ratio 0..1 for symbol `index` given the current display value (hover ?? value). */
78
+ fillRatio(index) {
79
+ const filled = this.displayValue() - index;
80
+ return Math.min(1, Math.max(0, filled));
81
+ }
82
+ onKeyDown(event) {
83
+ if (this.readonly() || this.disabled()) {
84
+ return;
85
+ }
86
+ switch (event.key) {
87
+ case 'ArrowLeft':
88
+ case 'ArrowDown':
89
+ // Decrementing below the first step clears the rating (0 is not a valid value).
90
+ this.value.update(v => {
91
+ const next = (v ?? 0) - this.step();
92
+ return next <= 0 ? null : next;
93
+ });
94
+ event.preventDefault();
95
+ break;
96
+ case 'ArrowRight':
97
+ case 'ArrowUp':
98
+ this.value.update(v => Math.min(this.count(), (v ?? 0) + this.step()));
99
+ event.preventDefault();
100
+ break;
101
+ case 'Home':
102
+ // No value is the lowest state, since 0 is not selectable.
103
+ this.value.set(null);
104
+ event.preventDefault();
105
+ break;
106
+ case 'End':
107
+ this.value.set(this.count());
108
+ event.preventDefault();
109
+ break;
110
+ }
111
+ }
112
+ /** Pointer moved over symbol `index` at fractional `offset` (0..1 within the symbol). */
113
+ onPointerMove(event, index) {
114
+ if (this.readonly() || this.disabled()) {
115
+ return;
116
+ }
117
+ this._hover.set(this.valueAt(event, index));
118
+ }
119
+ clearHover() {
120
+ this._hover.set(null);
121
+ }
122
+ onClick(event, index) {
123
+ if (this.readonly() || this.disabled()) {
124
+ return;
125
+ }
126
+ const next = this.valueAt(event, index);
127
+ if (this.clearable() && next === this.value()) {
128
+ this.value.set(null);
129
+ }
130
+ else {
131
+ this.value.set(next);
132
+ }
133
+ }
134
+ /** Resolve the stepped value for a pointer position within symbol `index`. */
135
+ valueAt(event, index) {
136
+ const target = event.currentTarget;
137
+ const rect = target.getBoundingClientRect();
138
+ const ratio = rect.width ? (event.clientX - rect.left) / rect.width : 1;
139
+ const clamped = Math.min(1, Math.max(0, ratio));
140
+ // Fill on area-entry: within symbol `index`, snap UP to the nearest `step`
141
+ // sub-division. Entering the symbol anywhere (ratio > 0) selects at least its
142
+ // first sub-step, so a whole-step rating fills the entire symbol as soon as the
143
+ // pointer is over it, and a half-step rating fills the left/right half.
144
+ const step = this.step();
145
+ const subStepsPerSymbol = Math.max(1, Math.round(1 / step));
146
+ const subIndex = Math.min(subStepsPerSymbol, Math.max(1, Math.ceil(clamped * subStepsPerSymbol)));
147
+ return this.clamp(index + subIndex * step);
148
+ }
149
+ clamp(value) {
150
+ return Math.min(this.count(), Math.max(0, value));
151
+ }
152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRating, deps: null, target: i0.ɵɵFactoryTarget.Component });
153
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnRating, isStandalone: true, selector: "ngn-rating", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, iconFull: { classPropertyName: "iconFull", publicName: "iconFull", isSignal: true, isRequired: false, transformFunction: null }, iconEmpty: { classPropertyName: "iconEmpty", publicName: "iconEmpty", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null }, valueTextFn: { classPropertyName: "valueTextFn", publicName: "valueTextFn", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "slider", "aria-valuemin": "0" }, listeners: { "keydown": "onKeyDown($event)", "pointerleave": "clearHover()", "blur": "markTouched()" }, properties: { "attr.aria-valuemax": "count()", "attr.aria-valuenow": "value()", "attr.aria-readonly": "readonly() ? \"true\" : null", "attr.disabled": "disabled() ? \"\" : null", "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-labelledby": "labelledBy()", "attr.aria-label": "label()", "attr.aria-valuetext": "valueTextValue()", "tabindex": "disabled() ? -1 : 0" } }, providers: [provideSelf(NgnRating)], usesInheritance: true, ngImport: i0, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n@for (index of symbols(); track index) {\n <span\n [ptInt]=\"this\"\n [ptClass]=\"'symbol'\"\n [style.--fillRatio]=\"fillRatio(index)\"\n (pointermove)=\"onPointerMove($event, index)\"\n (click)=\"onClick($event, index)\"\n >\n @if (resolvedIndicatorTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: fillRatio(index), index: index }\"\n />\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'empty'\">\n <ngn-icon [icon]=\"iconEmpty()\" defaultIcon=\"rating-empty\" />\n </span>\n <span [ptInt]=\"this\" [ptClass]=\"'full'\">\n <ngn-icon [icon]=\"iconFull()\" defaultIcon=\"rating-full\" />\n </span>\n }\n </span>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }] });
154
+ }
155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRating, decorators: [{
156
+ type: Component,
157
+ args: [{ selector: 'ngn-rating', imports: [NgTemplateOutlet, NgnPt, NgnIcon], providers: [provideSelf(NgnRating)], host: {
158
+ role: 'slider',
159
+ 'aria-valuemin': '0',
160
+ '[attr.aria-valuemax]': 'count()',
161
+ '[attr.aria-valuenow]': 'value()',
162
+ '[attr.aria-readonly]': 'readonly() ? "true" : null',
163
+ '[attr.disabled]': 'disabled() ? "" : null',
164
+ '[attr.aria-disabled]': "disabled() ? 'true' : null",
165
+ '[attr.aria-labelledby]': 'labelledBy()',
166
+ '[attr.aria-label]': 'label()',
167
+ '[attr.aria-valuetext]': 'valueTextValue()',
168
+ '[tabindex]': 'disabled() ? -1 : 0',
169
+ '(keydown)': 'onKeyDown($event)',
170
+ '(pointerleave)': 'clearHover()',
171
+ '(blur)': 'markTouched()',
172
+ }, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n@for (index of symbols(); track index) {\n <span\n [ptInt]=\"this\"\n [ptClass]=\"'symbol'\"\n [style.--fillRatio]=\"fillRatio(index)\"\n (pointermove)=\"onPointerMove($event, index)\"\n (click)=\"onClick($event, index)\"\n >\n @if (resolvedIndicatorTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: fillRatio(index), index: index }\"\n />\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'empty'\">\n <ngn-icon [icon]=\"iconEmpty()\" defaultIcon=\"rating-empty\" />\n </span>\n <span [ptInt]=\"this\" [ptClass]=\"'full'\">\n <ngn-icon [icon]=\"iconFull()\" defaultIcon=\"rating-full\" />\n </span>\n }\n </span>\n}\n" }]
173
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], iconFull: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconFull", required: false }] }], iconEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconEmpty", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: false }] }], valueTextFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueTextFn", required: false }] }] } });
174
+
175
+ /**
176
+ * Generated bundle index. Do not edit.
177
+ */
178
+
179
+ export { NgnRating, RatingTemplates };
180
+ //# sourceMappingURL=ngneers-controls-rating.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngneers-controls-rating.mjs","sources":["../../src/rating/rating-templates.ts","../../src/rating/rating.ts","../../src/rating/rating.html","../../src/rating/ngneers-controls-rating.ts"],"sourcesContent":["import { computed, contentChild, Directive, input, TemplateRef } from '@angular/core';\nimport { ValueControlBase } from '@ngneers/controls/base';\n\n/** Context for a custom {@link NgnRating} symbol template. */\nexport interface RatingIndicatorContext {\n /** Fill ratio for this symbol, 0..1. */\n $implicit: number;\n /** Zero-based symbol index. */\n index: number;\n}\n\n@Directive()\nexport abstract class RatingTemplates extends ValueControlBase<'rating', number | null> {\n private readonly _userIndicatorTemplate =\n contentChild<TemplateRef<RatingIndicatorContext>>('indicator');\n /**\n * Custom template for a single symbol. Receives the fill ratio (`$implicit`,\n * 0..1) and `index`. Can also be set with an `<ng-template #indicator>`.\n * When set, it replaces the default full/empty icon rendering per symbol.\n */\n public readonly indicatorTemplate = input<TemplateRef<RatingIndicatorContext> | null>(null);\n protected readonly resolvedIndicatorTemplate = computed(\n () => this._userIndicatorTemplate() ?? this.indicatorTemplate()\n );\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, Component, computed, input, signal } from '@angular/core';\nimport { NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { ratingControlTemplate } from '@ngneers/controls-themes/templates/rating';\n\nimport { RatingTemplates } from './rating-templates';\n\nimport type { IconType } from '@ngneers/controls-custom-types';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-rating',\n templateUrl: './rating.html',\n imports: [NgTemplateOutlet, NgnPt, NgnIcon],\n providers: [provideSelf(NgnRating)],\n host: {\n role: 'slider',\n 'aria-valuemin': '0',\n '[attr.aria-valuemax]': 'count()',\n '[attr.aria-valuenow]': 'value()',\n '[attr.aria-readonly]': 'readonly() ? \"true\" : null',\n '[attr.disabled]': 'disabled() ? \"\" : null',\n '[attr.aria-disabled]': \"disabled() ? 'true' : null\",\n '[attr.aria-labelledby]': 'labelledBy()',\n '[attr.aria-label]': 'label()',\n '[attr.aria-valuetext]': 'valueTextValue()',\n '[tabindex]': 'disabled() ? -1 : 0',\n '(keydown)': 'onKeyDown($event)',\n '(pointerleave)': 'clearHover()',\n '(blur)': 'markTouched()',\n },\n})\nexport class NgnRating extends RatingTemplates {\n protected readonly theme = this.injectThemeTemplate(ratingControlTemplate, {\n root: true,\n invalid: () => this.invalidState(),\n readonly: () => this.readonly(),\n disabled: () => this.disabled(),\n });\n\n private readonly _hover = signal<number | null>(null);\n\n /** Number of symbols. @default 5 */\n public readonly count = input<number>(5);\n /** The value increment; supports fractions (e.g. 0.5). @default 1 */\n public readonly step = input<number>(1);\n /** The filled symbol icon. Falls back to a filled star ({@link https://tabler.io/icons}). */\n public readonly iconFull = input<IconType>();\n /** The empty symbol icon. Falls back to an outline star. */\n public readonly iconEmpty = input<IconType>();\n /** Clicking the current value clears the rating back to `null`. @default true */\n public readonly clearable = input(true, { transform: booleanAttribute });\n /** Accessible value text; overrides the numeric readout. */\n public readonly valueText = input<string>();\n /** Function producing accessible value text from the value. */\n public readonly valueTextFn = input<(value: number) => string>();\n\n /** The value the UI should paint: hover preview when hovering, else the model value. */\n protected readonly displayValue = computed(() => this._hover() ?? this.value() ?? 0);\n\n /** Zero-based indices for the symbols. */\n protected readonly symbols = computed(() => Array.from({ length: this.count() }, (_, i) => i));\n\n protected readonly valueTextValue = computed(() => {\n const valueText = this.valueText();\n if (valueText) {\n return valueText;\n }\n const fn = this.valueTextFn();\n if (fn) {\n return fn(this.value() ?? 0);\n }\n return null;\n });\n\n /** Fill ratio 0..1 for symbol `index` given the current display value (hover ?? value). */\n protected fillRatio(index: number): number {\n const filled = this.displayValue() - index;\n return Math.min(1, Math.max(0, filled));\n }\n\n protected onKeyDown(event: KeyboardEvent) {\n if (this.readonly() || this.disabled()) {\n return;\n }\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowDown':\n // Decrementing below the first step clears the rating (0 is not a valid value).\n this.value.update(v => {\n const next = (v ?? 0) - this.step();\n return next <= 0 ? null : next;\n });\n event.preventDefault();\n break;\n case 'ArrowRight':\n case 'ArrowUp':\n this.value.update(v => Math.min(this.count(), (v ?? 0) + this.step()));\n event.preventDefault();\n break;\n case 'Home':\n // No value is the lowest state, since 0 is not selectable.\n this.value.set(null);\n event.preventDefault();\n break;\n case 'End':\n this.value.set(this.count());\n event.preventDefault();\n break;\n }\n }\n\n /** Pointer moved over symbol `index` at fractional `offset` (0..1 within the symbol). */\n protected onPointerMove(event: PointerEvent, index: number) {\n if (this.readonly() || this.disabled()) {\n return;\n }\n this._hover.set(this.valueAt(event, index));\n }\n\n protected clearHover() {\n this._hover.set(null);\n }\n\n protected onClick(event: PointerEvent, index: number) {\n if (this.readonly() || this.disabled()) {\n return;\n }\n const next = this.valueAt(event, index);\n if (this.clearable() && next === this.value()) {\n this.value.set(null);\n } else {\n this.value.set(next);\n }\n }\n\n /** Resolve the stepped value for a pointer position within symbol `index`. */\n private valueAt(event: PointerEvent, index: number): number {\n const target = event.currentTarget as HTMLElement;\n const rect = target.getBoundingClientRect();\n const ratio = rect.width ? (event.clientX - rect.left) / rect.width : 1;\n const clamped = Math.min(1, Math.max(0, ratio));\n // Fill on area-entry: within symbol `index`, snap UP to the nearest `step`\n // sub-division. Entering the symbol anywhere (ratio > 0) selects at least its\n // first sub-step, so a whole-step rating fills the entire symbol as soon as the\n // pointer is over it, and a half-step rating fills the left/right half.\n const step = this.step();\n const subStepsPerSymbol = Math.max(1, Math.round(1 / step));\n const subIndex = Math.min(\n subStepsPerSymbol,\n Math.max(1, Math.ceil(clamped * subStepsPerSymbol))\n );\n return this.clamp(index + subIndex * step);\n }\n\n private clamp(value: number): number {\n return Math.min(this.count(), Math.max(0, value));\n }\n}\n","<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n@for (index of symbols(); track index) {\n <span\n [ptInt]=\"this\"\n [ptClass]=\"'symbol'\"\n [style.--fillRatio]=\"fillRatio(index)\"\n (pointermove)=\"onPointerMove($event, index)\"\n (click)=\"onClick($event, index)\"\n >\n @if (resolvedIndicatorTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: fillRatio(index), index: index }\"\n />\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'empty'\">\n <ngn-icon [icon]=\"iconEmpty()\" defaultIcon=\"rating-empty\" />\n </span>\n <span [ptInt]=\"this\" [ptClass]=\"'full'\">\n <ngn-icon [icon]=\"iconFull()\" defaultIcon=\"rating-full\" />\n </span>\n }\n </span>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, Injector, input, afterNextRender, Directive } from '@angular/core';
2
+ import { inject, ElementRef, Injector, Renderer2, input, booleanAttribute, afterNextRender, DestroyRef, Directive } from '@angular/core';
3
3
  import { scrollShadowDirectiveTemplate } from '@ngneers/controls-themes/templates/api';
4
- import { injectThemeTemplate, abortSignalOnDestroy } from '@ngneers/controls/api/ng';
4
+ import { injectThemeTemplate, domEventHandler } from '@ngneers/controls/api/ng';
5
5
  import { toggleClass } from '@ngneers/controls/utils';
6
6
 
7
7
  class NgnScrollShadow {
8
8
  theme = injectThemeTemplate(scrollShadowDirectiveTemplate);
9
9
  _el = inject(ElementRef);
10
10
  _injector = inject(Injector);
11
+ _renderer = inject(Renderer2);
11
12
  /**
12
13
  * Which scroll axis to track and add shadow classes for.
13
14
  * @default 'horizontal'
@@ -21,13 +22,37 @@ class NgnScrollShadow {
21
22
  */
22
23
  scrollShadowTarget = input(undefined, /* @ts-ignore */
23
24
  ...(ngDevMode ? [{ debugName: "scrollShadowTarget" }] : /* istanbul ignore next */ []));
25
+ /**
26
+ * Suppress the built-in edge-shadow overlay's styling (the theme hides it). The overlay element
27
+ * is still injected but rendered inert, so it never disturbs the scroll container's layout.
28
+ * Set this when the consumer paints its own shadows off the `scrolled-*` classes — e.g. the
29
+ * table anchors them to its sticky-column edges, reusing the shared `--ngn-scroll-shadow-color`.
30
+ * @default false
31
+ */
32
+ unstyled = input(false, { ...(ngDevMode ? { debugName: "unstyled" } : /* istanbul ignore next */ {}), alias: 'ngnScrollShadowUnstyled',
33
+ transform: booleanAttribute });
24
34
  constructor() {
25
35
  afterNextRender(() => {
26
36
  const scrollEl = this._el.nativeElement;
27
- const abort = abortSignalOnDestroy({ injector: this._injector });
37
+ // Injected overlay: a zero-size sticky layer pinned to the viewport's top-left corner, with a
38
+ // sized cover inside it. The cover's edge gradients (driven by the theme via the scrolled-*
39
+ // classes) paint over the scrolling content — painting on the container itself would sit
40
+ // *under* its children. When `unstyled`, the theme hides the layer (display:none), so it
41
+ // stays inert and never disturbs the container's layout (e.g. the table's CSS grid).
42
+ const layer = this._renderer.createElement('div');
43
+ layer.className = this.unstyled()
44
+ ? `${this.theme.class('overlay')} ${this.theme.class('unstyled')}`
45
+ : this.theme.class('overlay');
46
+ const surface = this._renderer.createElement('div');
47
+ surface.className = this.theme.class('surface');
48
+ this._renderer.appendChild(layer, surface);
49
+ this._renderer.insertBefore(scrollEl, layer, scrollEl.firstChild);
50
+ this._injector.get(DestroyRef).onDestroy(() => layer.remove());
28
51
  const update = () => {
29
52
  const target = this.scrollShadowTarget() ?? scrollEl;
30
53
  const dir = this.ngnScrollShadow();
54
+ surface.style.setProperty('--ngn-scroll-shadow-w', `${scrollEl.clientWidth}px`);
55
+ surface.style.setProperty('--ngn-scroll-shadow-h', `${scrollEl.clientHeight}px`);
31
56
  if (dir === 'horizontal' || dir === 'both') {
32
57
  const sl = scrollEl.scrollLeft;
33
58
  const maxH = scrollEl.scrollWidth - scrollEl.clientWidth;
@@ -43,19 +68,20 @@ class NgnScrollShadow {
43
68
  };
44
69
  const ro = new ResizeObserver(update);
45
70
  ro.observe(scrollEl);
46
- abort.addEventListener('abort', () => ro.disconnect());
47
- scrollEl.addEventListener('scroll', update, { passive: true, signal: abort });
71
+ this._injector.get(DestroyRef).onDestroy(() => ro.disconnect());
72
+ domEventHandler(scrollEl, 'scroll', update, this._injector, { passive: true });
73
+ update();
48
74
  });
49
75
  }
50
76
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnScrollShadow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
51
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: NgnScrollShadow, isStandalone: true, selector: "[ngnScrollShadow]", inputs: { ngnScrollShadow: { classPropertyName: "ngnScrollShadow", publicName: "ngnScrollShadow", isSignal: true, isRequired: false, transformFunction: null }, scrollShadowTarget: { classPropertyName: "scrollShadowTarget", publicName: "scrollShadowTarget", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
77
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: NgnScrollShadow, isStandalone: true, selector: "[ngnScrollShadow]", inputs: { ngnScrollShadow: { classPropertyName: "ngnScrollShadow", publicName: "ngnScrollShadow", isSignal: true, isRequired: false, transformFunction: null }, scrollShadowTarget: { classPropertyName: "scrollShadowTarget", publicName: "scrollShadowTarget", isSignal: true, isRequired: false, transformFunction: null }, unstyled: { classPropertyName: "unstyled", publicName: "ngnScrollShadowUnstyled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
52
78
  }
53
79
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnScrollShadow, decorators: [{
54
80
  type: Directive,
55
81
  args: [{
56
82
  selector: '[ngnScrollShadow]',
57
83
  }]
58
- }], ctorParameters: () => [], propDecorators: { ngnScrollShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnScrollShadow", required: false }] }], scrollShadowTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollShadowTarget", required: false }] }] } });
84
+ }], ctorParameters: () => [], propDecorators: { ngnScrollShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnScrollShadow", required: false }] }], scrollShadowTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollShadowTarget", required: false }] }], unstyled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnScrollShadowUnstyled", required: false }] }] } });
59
85
 
60
86
  /**
61
87
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"ngneers-controls-scroll-shadow.mjs","sources":["../../src/scroll-shadow/scroll-shadow.ts","../../src/scroll-shadow/ngneers-controls-scroll-shadow.ts"],"sourcesContent":["import { afterNextRender, Directive, ElementRef, inject, Injector, input } from '@angular/core';\nimport { scrollShadowDirectiveTemplate } from '@ngneers/controls-themes/templates/api';\n\nimport { abortSignalOnDestroy, injectThemeTemplate } from '@ngneers/controls/api/ng';\nimport { toggleClass } from '@ngneers/controls/utils';\n\n@Directive({\n selector: '[ngnScrollShadow]',\n})\nexport class NgnScrollShadow {\n protected readonly theme = injectThemeTemplate(scrollShadowDirectiveTemplate);\n private readonly _el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly _injector = inject(Injector);\n\n /**\n * Which scroll axis to track and add shadow classes for.\n * @default 'horizontal'\n */\n public readonly ngnScrollShadow = input<'horizontal' | 'vertical' | 'both'>('horizontal');\n\n /**\n * Element that receives the shadow classes. Defaults to the host element (which\n * is also the scroll container being observed).\n * @default undefined\n */\n public readonly scrollShadowTarget = input<HTMLElement | undefined>(undefined);\n\n constructor() {\n afterNextRender(() => {\n const scrollEl = this._el.nativeElement;\n const abort = abortSignalOnDestroy({ injector: this._injector });\n\n const update = () => {\n const target = this.scrollShadowTarget() ?? scrollEl;\n const dir = this.ngnScrollShadow();\n\n if (dir === 'horizontal' || dir === 'both') {\n const sl = scrollEl.scrollLeft;\n const maxH = scrollEl.scrollWidth - scrollEl.clientWidth;\n toggleClass(target, this.theme.class('scrolled-start'), sl > 0);\n toggleClass(target, this.theme.class('scrolled-end'), maxH > 0 && sl < maxH - 1);\n }\n\n if (dir === 'vertical' || dir === 'both') {\n const st = scrollEl.scrollTop;\n const maxV = scrollEl.scrollHeight - scrollEl.clientHeight;\n toggleClass(target, this.theme.class('scrolled-top'), st > 0);\n toggleClass(target, this.theme.class('scrolled-bottom'), maxV > 0 && st < maxV - 1);\n }\n };\n\n const ro = new ResizeObserver(update);\n ro.observe(scrollEl);\n abort.addEventListener('abort', () => ro.disconnect());\n scrollEl.addEventListener('scroll', update, { passive: true, signal: abort });\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngneers-controls-scroll-shadow.mjs","sources":["../../src/scroll-shadow/scroll-shadow.ts","../../src/scroll-shadow/ngneers-controls-scroll-shadow.ts"],"sourcesContent":["import {\n afterNextRender,\n booleanAttribute,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n Injector,\n input,\n Renderer2,\n} from '@angular/core';\nimport { scrollShadowDirectiveTemplate } from '@ngneers/controls-themes/templates/api';\n\nimport { domEventHandler, injectThemeTemplate } from '@ngneers/controls/api/ng';\nimport { toggleClass } from '@ngneers/controls/utils';\n\n@Directive({\n selector: '[ngnScrollShadow]',\n})\nexport class NgnScrollShadow {\n protected readonly theme = injectThemeTemplate(scrollShadowDirectiveTemplate);\n private readonly _el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly _injector = inject(Injector);\n private readonly _renderer = inject(Renderer2);\n\n /**\n * Which scroll axis to track and add shadow classes for.\n * @default 'horizontal'\n */\n public readonly ngnScrollShadow = input<'horizontal' | 'vertical' | 'both'>('horizontal');\n\n /**\n * Element that receives the shadow classes. Defaults to the host element (which\n * is also the scroll container being observed).\n * @default undefined\n */\n public readonly scrollShadowTarget = input<HTMLElement | undefined>(undefined);\n\n /**\n * Suppress the built-in edge-shadow overlay's styling (the theme hides it). The overlay element\n * is still injected but rendered inert, so it never disturbs the scroll container's layout.\n * Set this when the consumer paints its own shadows off the `scrolled-*` classes — e.g. the\n * table anchors them to its sticky-column edges, reusing the shared `--ngn-scroll-shadow-color`.\n * @default false\n */\n public readonly unstyled = input(false, {\n alias: 'ngnScrollShadowUnstyled',\n transform: booleanAttribute,\n });\n\n constructor() {\n afterNextRender(() => {\n const scrollEl = this._el.nativeElement;\n\n // Injected overlay: a zero-size sticky layer pinned to the viewport's top-left corner, with a\n // sized cover inside it. The cover's edge gradients (driven by the theme via the scrolled-*\n // classes) paint over the scrolling content — painting on the container itself would sit\n // *under* its children. When `unstyled`, the theme hides the layer (display:none), so it\n // stays inert and never disturbs the container's layout (e.g. the table's CSS grid).\n const layer = this._renderer.createElement('div') as HTMLElement;\n layer.className = this.unstyled()\n ? `${this.theme.class('overlay')} ${this.theme.class('unstyled')}`\n : this.theme.class('overlay');\n const surface = this._renderer.createElement('div') as HTMLElement;\n surface.className = this.theme.class('surface');\n this._renderer.appendChild(layer, surface);\n this._renderer.insertBefore(scrollEl, layer, scrollEl.firstChild);\n this._injector.get(DestroyRef).onDestroy(() => layer.remove());\n\n const update = () => {\n const target = this.scrollShadowTarget() ?? scrollEl;\n const dir = this.ngnScrollShadow();\n\n surface.style.setProperty('--ngn-scroll-shadow-w', `${scrollEl.clientWidth}px`);\n surface.style.setProperty('--ngn-scroll-shadow-h', `${scrollEl.clientHeight}px`);\n\n if (dir === 'horizontal' || dir === 'both') {\n const sl = scrollEl.scrollLeft;\n const maxH = scrollEl.scrollWidth - scrollEl.clientWidth;\n toggleClass(target, this.theme.class('scrolled-start'), sl > 0);\n toggleClass(target, this.theme.class('scrolled-end'), maxH > 0 && sl < maxH - 1);\n }\n\n if (dir === 'vertical' || dir === 'both') {\n const st = scrollEl.scrollTop;\n const maxV = scrollEl.scrollHeight - scrollEl.clientHeight;\n toggleClass(target, this.theme.class('scrolled-top'), st > 0);\n toggleClass(target, this.theme.class('scrolled-bottom'), maxV > 0 && st < maxV - 1);\n }\n };\n\n const ro = new ResizeObserver(update);\n ro.observe(scrollEl);\n this._injector.get(DestroyRef).onDestroy(() => ro.disconnect());\n domEventHandler(scrollEl, 'scroll', update, this._injector, { passive: true });\n update();\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -41,6 +41,8 @@ class NgnScroller extends ScrollerTemplates {
41
41
  });
42
42
  _el = inject(ElementRef);
43
43
  _scrollAmount = inject(NgnScrollAmount);
44
+ /** Remaining vertical scroll distance to the bottom (px). Proxied from the host scroll directive. */
45
+ distanceFromEnd = this._scrollAmount.distanceFromEnd;
44
46
  /**
45
47
  * The items to be displayed in the scroller.
46
48
  * This is a required input and should be an array of items of type {@link T}.
@@ -233,7 +235,7 @@ class NgnScroller extends ScrollerTemplates {
233
235
  return item.index;
234
236
  }
235
237
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnScroller, deps: [], target: i0.ɵɵFactoryTarget.Component });
236
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnScroller, isStandalone: true, selector: "ngn-scroller, [ngn-scroller]", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, virtualPadding: { classPropertyName: "virtualPadding", publicName: "virtualPadding", isSignal: true, isRequired: false, transformFunction: null }, focusable: { classPropertyName: "focusable", publicName: "focusable", isSignal: true, isRequired: false, transformFunction: null }, fieldSticky: { classPropertyName: "fieldSticky", publicName: "fieldSticky", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "tabIndex": "focusable() ? 0 : -1", "style.--ngn-scroller-item-height.px": "itemHeight() ?? \"auto\"", "style.--ngn-scroller-padding-top.px": "paddingTop()", "style.--ngn-scroller-padding-bottom.px": "paddingBottom()" } }, providers: [provideSelf(NgnScroller)], usesInheritance: true, hostDirectives: [{ directive: i1.NgnScrollAmount, inputs: ["ngnScrollAmountContainer", "scrollContainer"] }], ngImport: i0, template: "@if (latestStickyItem(); as latestStickyItem) {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: { $implicit: latestStickyItem.item, index: latestStickyItem.index }\n \"\n ></ng-container>\n}\n\n@for (item of visibleItems(); track track(item)) {\n <ng-container\n *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: item.item, index: item.index }\"\n ></ng-container>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
238
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnScroller, isStandalone: true, selector: "ngn-scroller, [ngn-scroller]", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, virtualPadding: { classPropertyName: "virtualPadding", publicName: "virtualPadding", isSignal: true, isRequired: false, transformFunction: null }, focusable: { classPropertyName: "focusable", publicName: "focusable", isSignal: true, isRequired: false, transformFunction: null }, fieldSticky: { classPropertyName: "fieldSticky", publicName: "fieldSticky", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "tabIndex": "focusable() ? 0 : -1", "style.--ngn-scroller-item-height.px": "itemHeight() ?? \"auto\"", "style.--ngn-scroller-padding-top.px": "paddingTop()", "style.--ngn-scroller-padding-bottom.px": "paddingBottom()" } }, providers: [provideSelf(NgnScroller)], usesInheritance: true, hostDirectives: [{ directive: i1.NgnScrollAmount, inputs: ["ngnScrollAmountContainer", "scrollContainer"] }], ngImport: i0, template: "@if (latestStickyItem(); as latestStickyItem) {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: { $implicit: latestStickyItem.item, index: latestStickyItem.index }\n \"\n ></ng-container>\n}\n\n@for (item of visibleItems(); track track(item)) {\n <ng-container\n *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: item.item, index: item.index }\"\n ></ng-container>\n}\n\n<!-- Projects static content added alongside the scroller (e.g. table skeleton rows). -->\n<ng-content></ng-content>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
237
239
  }
238
240
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnScroller, decorators: [{
239
241
  type: Component,
@@ -244,7 +246,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
244
246
  '[style.--ngn-scroller-item-height.px]': 'itemHeight() ?? "auto"',
245
247
  '[style.--ngn-scroller-padding-top.px]': 'paddingTop()',
246
248
  '[style.--ngn-scroller-padding-bottom.px]': 'paddingBottom()',
247
- }, template: "@if (latestStickyItem(); as latestStickyItem) {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: { $implicit: latestStickyItem.item, index: latestStickyItem.index }\n \"\n ></ng-container>\n}\n\n@for (item of visibleItems(); track track(item)) {\n <ng-container\n *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: item.item, index: item.index }\"\n ></ng-container>\n}\n" }]
249
+ }, template: "@if (latestStickyItem(); as latestStickyItem) {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: { $implicit: latestStickyItem.item, index: latestStickyItem.index }\n \"\n ></ng-container>\n}\n\n@for (item of visibleItems(); track track(item)) {\n <ng-container\n *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: item.item, index: item.index }\"\n ></ng-container>\n}\n\n<!-- Projects static content added alongside the scroller (e.g. table skeleton rows). -->\n<ng-content></ng-content>\n" }]
248
250
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], fieldId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldId", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], virtualPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualPadding", required: false }] }], focusable: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusable", required: false }] }], fieldSticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSticky", required: false }] }] } });
249
251
 
250
252
  class NgnScrollerItem {