ngwr 14.6.0 → 14.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/alert/styles/_index.scss +14 -3
  2. package/avatar/styles/_index.scss +44 -0
  3. package/bar-chart/styles/_index.scss +1 -0
  4. package/calendar-heatmap/styles/_index.scss +1 -0
  5. package/cascader/styles/_rules.scss +86 -18
  6. package/collapse/styles/_index.scss +0 -1
  7. package/date-picker/styles/_index.scss +30 -0
  8. package/density/styles/_index.scss +4 -2
  9. package/donut-chart/styles/_index.scss +10 -0
  10. package/dropdown/styles/_index.scss +67 -5
  11. package/event-calendar/styles/_index.scss +209 -35
  12. package/fesm2022/ngwr-avatar.mjs +19 -2
  13. package/fesm2022/ngwr-avatar.mjs.map +1 -1
  14. package/fesm2022/ngwr-bar-chart.mjs +29 -4
  15. package/fesm2022/ngwr-bar-chart.mjs.map +1 -1
  16. package/fesm2022/ngwr-calendar-heatmap.mjs +59 -4
  17. package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
  18. package/fesm2022/ngwr-cascader.mjs +2 -2
  19. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  20. package/fesm2022/ngwr-checkbox.mjs +12 -2
  21. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  22. package/fesm2022/ngwr-date-picker.mjs +705 -46
  23. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  24. package/fesm2022/ngwr-donut-chart.mjs +46 -4
  25. package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
  26. package/fesm2022/ngwr-dropdown.mjs +166 -17
  27. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  28. package/fesm2022/ngwr-event-calendar.mjs +9 -2
  29. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  30. package/fesm2022/ngwr-form.mjs +52 -1
  31. package/fesm2022/ngwr-form.mjs.map +1 -1
  32. package/fesm2022/ngwr-gauge.mjs +33 -4
  33. package/fesm2022/ngwr-gauge.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  35. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  37. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  38. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  39. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  40. package/fesm2022/ngwr-i18n-en.mjs +5 -0
  41. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  42. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  43. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  44. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  45. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  46. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  47. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  48. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  49. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  50. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  51. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  52. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  53. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  54. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  55. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  56. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  57. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  58. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  59. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  60. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  61. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  62. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  63. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  64. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  65. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  66. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  67. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  68. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  69. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  70. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  71. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  72. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  73. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  74. package/fesm2022/ngwr-i18n-zh-TW.mjs +2 -0
  75. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
  76. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  77. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  78. package/fesm2022/ngwr-image-cropper.mjs +149 -62
  79. package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
  80. package/fesm2022/ngwr-input-number.mjs +12 -0
  81. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  82. package/fesm2022/ngwr-lightbox.mjs +2 -2
  83. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  84. package/fesm2022/ngwr-line-chart.mjs +16 -5
  85. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  86. package/fesm2022/ngwr-meter-group-testing.mjs +7 -3
  87. package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
  88. package/fesm2022/ngwr-meter-group.mjs +24 -4
  89. package/fesm2022/ngwr-meter-group.mjs.map +1 -1
  90. package/fesm2022/ngwr-page-header.mjs +10 -2
  91. package/fesm2022/ngwr-page-header.mjs.map +1 -1
  92. package/fesm2022/ngwr-popover.mjs +569 -7
  93. package/fesm2022/ngwr-popover.mjs.map +1 -1
  94. package/fesm2022/ngwr-select.mjs +73 -22
  95. package/fesm2022/ngwr-select.mjs.map +1 -1
  96. package/fesm2022/ngwr-sidebar.mjs +7 -0
  97. package/fesm2022/ngwr-sidebar.mjs.map +1 -1
  98. package/fesm2022/ngwr-sparkline.mjs +82 -8
  99. package/fesm2022/ngwr-sparkline.mjs.map +1 -1
  100. package/fesm2022/ngwr-table.mjs +1 -1
  101. package/fesm2022/ngwr-table.mjs.map +1 -1
  102. package/fesm2022/ngwr-toolbar.mjs +7 -0
  103. package/fesm2022/ngwr-toolbar.mjs.map +1 -1
  104. package/fesm2022/ngwr-version.mjs +1 -1
  105. package/fesm2022/ngwr-version.mjs.map +1 -1
  106. package/form/styles/_index.scss +11 -1
  107. package/gauge/styles/_index.scss +1 -0
  108. package/i18n/ar.json +3 -1
  109. package/i18n/cs.json +3 -1
  110. package/i18n/de.json +3 -1
  111. package/i18n/en.json +3 -1
  112. package/i18n/es.json +3 -1
  113. package/i18n/fr.json +3 -1
  114. package/i18n/he.json +3 -1
  115. package/i18n/hi.json +3 -1
  116. package/i18n/id.json +3 -1
  117. package/i18n/it.json +3 -1
  118. package/i18n/ja.json +3 -1
  119. package/i18n/ko.json +3 -1
  120. package/i18n/nl.json +3 -1
  121. package/i18n/pl.json +3 -1
  122. package/i18n/pt.json +3 -1
  123. package/i18n/ru.json +3 -1
  124. package/i18n/sv.json +3 -1
  125. package/i18n/tr.json +3 -1
  126. package/i18n/uk.json +3 -1
  127. package/i18n/vi.json +3 -1
  128. package/i18n/zh-TW.json +3 -1
  129. package/i18n/zh.json +3 -1
  130. package/input/styles/_index.scss +27 -2
  131. package/input-number/styles/_index.scss +69 -0
  132. package/lightbox/styles/_rules.scss +7 -1
  133. package/line-chart/styles/_index.scss +30 -2
  134. package/llms-full.txt +3 -3
  135. package/meter-group/styles/_index.scss +1 -0
  136. package/overlay/styles/_arrow.scss +27 -2
  137. package/package.json +1 -1
  138. package/page-header/styles/_index.scss +37 -1
  139. package/pagination/styles/_index.scss +9 -5
  140. package/popover/styles/_index.scss +73 -5
  141. package/select/styles/_index.scss +64 -13
  142. package/sidebar/styles/_index.scss +34 -2
  143. package/sparkline/styles/_index.scss +19 -0
  144. package/table/styles/_index.scss +55 -0
  145. package/toast/styles/_index.scss +9 -3
  146. package/toolbar/styles/_index.scss +35 -1
  147. package/tree/styles/_index.scss +45 -9
  148. package/types/ngwr-avatar.d.ts +15 -1
  149. package/types/ngwr-bar-chart.d.ts +19 -1
  150. package/types/ngwr-calendar-heatmap.d.ts +29 -1
  151. package/types/ngwr-checkbox.d.ts +2 -0
  152. package/types/ngwr-date-picker.d.ts +308 -43
  153. package/types/ngwr-donut-chart.d.ts +24 -1
  154. package/types/ngwr-dropdown.d.ts +22 -2
  155. package/types/ngwr-event-calendar.d.ts +8 -0
  156. package/types/ngwr-form.d.ts +35 -5
  157. package/types/ngwr-gauge.d.ts +20 -1
  158. package/types/ngwr-image-cropper.d.ts +48 -0
  159. package/types/ngwr-input-number.d.ts +7 -0
  160. package/types/ngwr-line-chart.d.ts +9 -1
  161. package/types/ngwr-meter-group-testing.d.ts +3 -2
  162. package/types/ngwr-meter-group.d.ts +14 -1
  163. package/types/ngwr-page-header.d.ts +10 -2
  164. package/types/ngwr-popover.d.ts +154 -3
  165. package/types/ngwr-select.d.ts +63 -6
  166. package/types/ngwr-sidebar.d.ts +7 -0
  167. package/types/ngwr-sparkline.d.ts +43 -1
  168. package/types/ngwr-toolbar.d.ts +7 -0
  169. package/types/ngwr-version.d.ts +1 -1
@@ -49,6 +49,13 @@ function matchesRoute(url, route) {
49
49
  * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal
50
50
  * child items). Active route auto-expands its containing group.
51
51
  *
52
+ * The host pads its rows by `--wr-sidebar-padding` (`0.5rem`), so a hovered or
53
+ * active row's tint stays off the edge of whatever holds the nav, a bordered
54
+ * card or a `<wr-layout-sider>`, which pads nothing itself. Where the container
55
+ * already supplies the inset, set the hook to `0` on the sidebar, not on the
56
+ * container: the host declares its own default, which shadows an inherited one
57
+ * (`.my-panel .wr-sidebar { --wr-sidebar-padding: 0 }`, or inline).
58
+ *
52
59
  * @example
53
60
  * ```html
54
61
  * <wr-sidebar [entries]="[
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-sidebar.mjs","sources":["../../../projects/lib/sidebar/sidebar.ts","../../../projects/lib/sidebar/sidebar.html","../../../projects/lib/sidebar/ngwr-sidebar.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, effect, inject, input, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSidebarEntry, WrSidebarGroup, WrSidebarItem } from './interfaces';\n\nfunction isGroup(entry: WrSidebarEntry): entry is WrSidebarGroup {\n return 'children' in entry;\n}\n\n/**\n * Instance counter for the group-body ids. Deterministic under prerender, unlike\n * `randomId()`, and unique across sidebars — a title-only id collided between two\n * sidebars on one page, and `aria-controls` then pointed at the wrong body.\n */\nlet sidebarUid = 0;\n\n/**\n * A `routerLink` segment array as an absolute path.\n *\n * The documented form carries the leading slash on the FIRST segment\n * (`['/settings', 'profile']`), and prefixing another one produced\n * `//settings/profile` — a path no URL ever equals, which is why auto-expand\n * never fired for anyone following the example. Both forms normalise here.\n */\nfunction routePath(segments: readonly (string | number)[]): string {\n return `/${segments.join('/')}`.replace(/\\/{2,}/g, '/');\n}\n\n/**\n * Whether `url` is `route` or something below it. A bare `startsWith` matched a\n * SIBLING whose path merely began the same way, so visiting `/table` expanded the\n * group that owns `/tabs`.\n */\nfunction matchesRoute(url: string, route: string): boolean {\n if (!url.startsWith(route)) return false;\n const rest = url.slice(route.length);\n return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');\n}\n\n/**\n * Data-driven sidebar nav. Pass `[entries]` — either flat\n * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal\n * child items). Active route auto-expands its containing group.\n *\n * @example\n * ```html\n * <wr-sidebar [entries]=\"[\n * { title: 'Dashboard', icon: 'home', url: ['/dashboard'] },\n * {\n * title: 'Settings',\n * icon: 'cog',\n * children: [\n * { title: 'Profile', url: ['/settings', 'profile'] },\n * { title: 'Billing', url: ['/settings', 'billing'] }\n * ]\n * }\n * ]\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sidebar\n */\n@Component({\n selector: 'wr-sidebar',\n templateUrl: './sidebar.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n imports: [RouterLink, RouterLinkActive, WrIcon],\n})\nexport class WrSidebar {\n readonly entries = input<readonly WrSidebarEntry[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `sidebar.label`,\n * then `'Sidebar'` — a page with a sidebar AND a table of contents has two\n * navigation landmarks, and an unnamed one is announced as just \"navigation\".\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'sidebar.label', 'Sidebar');\n\n /** Default icon for entries without one. @default 'folder' */\n readonly defaultGroupIcon = input<string>('folder');\n\n /** Default icon for items without one. @default 'caret-forward' */\n readonly defaultItemIcon = input<string>('caret-forward');\n\n /** Auto-expand the group containing the active route. @default true */\n readonly autoExpand = input(true, { transform: coerceBooleanProperty });\n\n protected readonly isGroup = isGroup;\n\n private readonly opened = signal<ReadonlySet<string>>(new Set());\n\n /** Group titles already seeded from `defaultOpen`, so it seeds once and not again. */\n private readonly seeded = new Set<string>();\n\n private readonly uid = ++sidebarUid;\n\n private readonly router = inject(Router);\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n /**\n * Last URL we resolved to a group — prevents re-opening one the user collapsed.\n * A URL no entry claims is deliberately NOT recorded: see the effect below.\n */\n private lastAutoUrl: string | null = null;\n\n constructor() {\n // Seed `defaultOpen` groups — once per group, not once per `entries` emission.\n // Re-seeding meant any later change to the array (a badge count, a filtered\n // list) re-opened a group the user had deliberately collapsed.\n effect(() => {\n const fresh: string[] = [];\n for (const entry of this.entries()) {\n if (isGroup(entry) && entry.defaultOpen && !this.seeded.has(entry.title)) {\n this.seeded.add(entry.title);\n fresh.push(entry.title);\n }\n }\n if (fresh.length > 0) {\n this.opened.update(prev => new Set([...prev, ...fresh]));\n }\n });\n\n // Auto-expand the group containing the current URL.\n effect(() => {\n if (!this.autoExpand()) return;\n const url = this.currentUrl();\n if (url === this.lastAutoUrl) return;\n // Latch AFTER the lookup, never before it. `entries` often arrives from an\n // HTTP call, so the first run matches nothing — and latching first told the\n // re-run that landed with the real nav that this URL was already handled.\n // A deep link into a `defaultOpen: false` group then stayed collapsed, with\n // the active link out of the tab order, until the user navigated away and\n // back. Only a run that found a group has actually handled the URL; the\n // user-collapse latch is unaffected, since that run is also the one that\n // records it.\n const match = this.findGroupForUrl(url);\n if (!match) return;\n this.lastAutoUrl = url;\n if (this.opened().has(match)) return;\n this.opened.update(prev => {\n const next = new Set(prev);\n next.add(match);\n return next;\n });\n });\n }\n\n protected isOpen(title: string): boolean {\n return this.opened().has(title);\n }\n\n /**\n * Stable id for the group body, referenced by the toggle's `aria-controls`.\n *\n * Keyed by POSITION, not by the title. Slugging the title kept the id readable\n * but was not injective: it lowercases and collapses every non-`[a-z0-9]` run,\n * so `Data & charts` and `Data / charts` produced the same string, and a title\n * with no ASCII alphanumerics at all — `Компоненты`, `数据` — produced the empty\n * one. A whole Russian or Chinese sidebar therefore emitted N identical ids and\n * every toggle's `aria-controls` resolved to the FIRST group's body. The toggle\n * and its body read `$index` from the same `@for` iteration, so the two always\n * name each other — reordering `entries` renumbers both together.\n */\n protected groupBodyId(index: number): string {\n return `wr-sidebar-${this.uid}-group-${index}`;\n }\n\n protected toggleGroup(title: string): void {\n this.opened.update(prev => {\n const next = new Set(prev);\n if (next.has(title)) next.delete(title);\n else next.add(title);\n return next;\n });\n }\n\n protected asItem(entry: WrSidebarEntry): WrSidebarItem {\n return entry as WrSidebarItem;\n }\n\n protected asGroup(entry: WrSidebarEntry): WrSidebarGroup {\n return entry as WrSidebarGroup;\n }\n\n private findGroupForUrl(url: string): string | null {\n for (const entry of this.entries()) {\n if (!isGroup(entry)) continue;\n const hit = entry.children.some(child => matchesRoute(url, routePath(child.url)));\n if (hit) return entry.title;\n }\n return null;\n }\n}\n","<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <!--\n A span, not a greyed anchor, exactly as the nested branch below does it.\n `--disabled` only paints: `pointer-events: none` stops a real pointer but\n not the keyboard, so a disabled entry stayed in the tab order and Enter\n navigated — `disabled` on a `WrSidebarItem` is documented as \"no\n navigation\". An empty `url` is the same case: `[routerLink]=\"[]\"` resolves\n to the current route, which is a link that goes nowhere.\n -->\n @if (asItem(entry).disabled || !asItem(entry).url.length) {\n <span class=\"wr-sidebar__entry wr-sidebar__entry--disabled\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__entry\" routerLinkActive=\"wr-sidebar__entry--active\" [routerLink]=\"asItem(entry).url\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n }\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId($index)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron — not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId($index)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-sidebar.mjs","sources":["../../../projects/lib/sidebar/sidebar.ts","../../../projects/lib/sidebar/sidebar.html","../../../projects/lib/sidebar/ngwr-sidebar.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, effect, inject, input, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { filter, map, startWith } from 'rxjs';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSidebarEntry, WrSidebarGroup, WrSidebarItem } from './interfaces';\n\nfunction isGroup(entry: WrSidebarEntry): entry is WrSidebarGroup {\n return 'children' in entry;\n}\n\n/**\n * Instance counter for the group-body ids. Deterministic under prerender, unlike\n * `randomId()`, and unique across sidebars — a title-only id collided between two\n * sidebars on one page, and `aria-controls` then pointed at the wrong body.\n */\nlet sidebarUid = 0;\n\n/**\n * A `routerLink` segment array as an absolute path.\n *\n * The documented form carries the leading slash on the FIRST segment\n * (`['/settings', 'profile']`), and prefixing another one produced\n * `//settings/profile` — a path no URL ever equals, which is why auto-expand\n * never fired for anyone following the example. Both forms normalise here.\n */\nfunction routePath(segments: readonly (string | number)[]): string {\n return `/${segments.join('/')}`.replace(/\\/{2,}/g, '/');\n}\n\n/**\n * Whether `url` is `route` or something below it. A bare `startsWith` matched a\n * SIBLING whose path merely began the same way, so visiting `/table` expanded the\n * group that owns `/tabs`.\n */\nfunction matchesRoute(url: string, route: string): boolean {\n if (!url.startsWith(route)) return false;\n const rest = url.slice(route.length);\n return rest === '' || rest.startsWith('/') || rest.startsWith('?') || rest.startsWith('#');\n}\n\n/**\n * Data-driven sidebar nav. Pass `[entries]` — either flat\n * `WrSidebarItem`s (direct links) or `WrSidebarGroup`s (expand to reveal\n * child items). Active route auto-expands its containing group.\n *\n * The host pads its rows by `--wr-sidebar-padding` (`0.5rem`), so a hovered or\n * active row's tint stays off the edge of whatever holds the nav, a bordered\n * card or a `<wr-layout-sider>`, which pads nothing itself. Where the container\n * already supplies the inset, set the hook to `0` on the sidebar, not on the\n * container: the host declares its own default, which shadows an inherited one\n * (`.my-panel .wr-sidebar { --wr-sidebar-padding: 0 }`, or inline).\n *\n * @example\n * ```html\n * <wr-sidebar [entries]=\"[\n * { title: 'Dashboard', icon: 'home', url: ['/dashboard'] },\n * {\n * title: 'Settings',\n * icon: 'cog',\n * children: [\n * { title: 'Profile', url: ['/settings', 'profile'] },\n * { title: 'Billing', url: ['/settings', 'billing'] }\n * ]\n * }\n * ]\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sidebar\n */\n@Component({\n selector: 'wr-sidebar',\n templateUrl: './sidebar.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sidebar', role: 'navigation', '[attr.aria-label]': 'resolvedAriaLabel()' },\n imports: [RouterLink, RouterLinkActive, WrIcon],\n})\nexport class WrSidebar {\n readonly entries = input<readonly WrSidebarEntry[]>([]);\n\n /**\n * Accessible name for the navigation landmark. Falls back to `sidebar.label`,\n * then `'Sidebar'` — a page with a sidebar AND a table of contents has two\n * navigation landmarks, and an unnamed one is announced as just \"navigation\".\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'sidebar.label', 'Sidebar');\n\n /** Default icon for entries without one. @default 'folder' */\n readonly defaultGroupIcon = input<string>('folder');\n\n /** Default icon for items without one. @default 'caret-forward' */\n readonly defaultItemIcon = input<string>('caret-forward');\n\n /** Auto-expand the group containing the active route. @default true */\n readonly autoExpand = input(true, { transform: coerceBooleanProperty });\n\n protected readonly isGroup = isGroup;\n\n private readonly opened = signal<ReadonlySet<string>>(new Set());\n\n /** Group titles already seeded from `defaultOpen`, so it seeds once and not again. */\n private readonly seeded = new Set<string>();\n\n private readonly uid = ++sidebarUid;\n\n private readonly router = inject(Router);\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(e => e.urlAfterRedirects),\n startWith(this.router.url)\n ),\n { initialValue: this.router.url }\n );\n\n /**\n * Last URL we resolved to a group — prevents re-opening one the user collapsed.\n * A URL no entry claims is deliberately NOT recorded: see the effect below.\n */\n private lastAutoUrl: string | null = null;\n\n constructor() {\n // Seed `defaultOpen` groups — once per group, not once per `entries` emission.\n // Re-seeding meant any later change to the array (a badge count, a filtered\n // list) re-opened a group the user had deliberately collapsed.\n effect(() => {\n const fresh: string[] = [];\n for (const entry of this.entries()) {\n if (isGroup(entry) && entry.defaultOpen && !this.seeded.has(entry.title)) {\n this.seeded.add(entry.title);\n fresh.push(entry.title);\n }\n }\n if (fresh.length > 0) {\n this.opened.update(prev => new Set([...prev, ...fresh]));\n }\n });\n\n // Auto-expand the group containing the current URL.\n effect(() => {\n if (!this.autoExpand()) return;\n const url = this.currentUrl();\n if (url === this.lastAutoUrl) return;\n // Latch AFTER the lookup, never before it. `entries` often arrives from an\n // HTTP call, so the first run matches nothing — and latching first told the\n // re-run that landed with the real nav that this URL was already handled.\n // A deep link into a `defaultOpen: false` group then stayed collapsed, with\n // the active link out of the tab order, until the user navigated away and\n // back. Only a run that found a group has actually handled the URL; the\n // user-collapse latch is unaffected, since that run is also the one that\n // records it.\n const match = this.findGroupForUrl(url);\n if (!match) return;\n this.lastAutoUrl = url;\n if (this.opened().has(match)) return;\n this.opened.update(prev => {\n const next = new Set(prev);\n next.add(match);\n return next;\n });\n });\n }\n\n protected isOpen(title: string): boolean {\n return this.opened().has(title);\n }\n\n /**\n * Stable id for the group body, referenced by the toggle's `aria-controls`.\n *\n * Keyed by POSITION, not by the title. Slugging the title kept the id readable\n * but was not injective: it lowercases and collapses every non-`[a-z0-9]` run,\n * so `Data & charts` and `Data / charts` produced the same string, and a title\n * with no ASCII alphanumerics at all — `Компоненты`, `数据` — produced the empty\n * one. A whole Russian or Chinese sidebar therefore emitted N identical ids and\n * every toggle's `aria-controls` resolved to the FIRST group's body. The toggle\n * and its body read `$index` from the same `@for` iteration, so the two always\n * name each other — reordering `entries` renumbers both together.\n */\n protected groupBodyId(index: number): string {\n return `wr-sidebar-${this.uid}-group-${index}`;\n }\n\n protected toggleGroup(title: string): void {\n this.opened.update(prev => {\n const next = new Set(prev);\n if (next.has(title)) next.delete(title);\n else next.add(title);\n return next;\n });\n }\n\n protected asItem(entry: WrSidebarEntry): WrSidebarItem {\n return entry as WrSidebarItem;\n }\n\n protected asGroup(entry: WrSidebarEntry): WrSidebarGroup {\n return entry as WrSidebarGroup;\n }\n\n private findGroupForUrl(url: string): string | null {\n for (const entry of this.entries()) {\n if (!isGroup(entry)) continue;\n const hit = entry.children.some(child => matchesRoute(url, routePath(child.url)));\n if (hit) return entry.title;\n }\n return null;\n }\n}\n","<!-- A div, not a <nav>: the host already IS the navigation landmark, and two\n nested ones announce the same list twice. -->\n<div class=\"wr-sidebar__inner\">\n @for (entry of entries(); track entry.title) {\n @if (!isGroup(entry)) {\n <!--\n A span, not a greyed anchor, exactly as the nested branch below does it.\n `--disabled` only paints: `pointer-events: none` stops a real pointer but\n not the keyboard, so a disabled entry stayed in the tab order and Enter\n navigated — `disabled` on a `WrSidebarItem` is documented as \"no\n navigation\". An empty `url` is the same case: `[routerLink]=\"[]\"` resolves\n to the current route, which is a link that goes nowhere.\n -->\n @if (asItem(entry).disabled || !asItem(entry).url.length) {\n <span class=\"wr-sidebar__entry wr-sidebar__entry--disabled\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__entry\" routerLinkActive=\"wr-sidebar__entry--active\" [routerLink]=\"asItem(entry).url\">\n @if (asItem(entry).icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n @if (asItem(entry).badge) {\n <small class=\"wr-sidebar__badge\">{{ asItem(entry).badge }}</small>\n }\n </a>\n }\n } @else {\n <section class=\"wr-sidebar__group\" [class.wr-sidebar__group--collapsed]=\"!isOpen(entry.title)\">\n <button\n type=\"button\"\n class=\"wr-sidebar__group-toggle\"\n [attr.aria-expanded]=\"isOpen(entry.title)\"\n [attr.aria-controls]=\"groupBodyId($index)\"\n (click)=\"toggleGroup(entry.title)\"\n >\n @if (asGroup(entry).icon || defaultGroupIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__title\">{{ entry.title }}</span>\n <!-- Inline chrome chevron — not user-customizable. -->\n <svg\n class=\"wr-icon__svg wr-sidebar__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n class=\"wr-sidebar__group-wrap\"\n [attr.id]=\"groupBodyId($index)\"\n [attr.inert]=\"!isOpen(entry.title) ? '' : null\"\n >\n <div class=\"wr-sidebar__group-body\">\n <ul class=\"wr-sidebar__list\">\n @for (child of asGroup(entry).children; track child.title) {\n <li>\n @if (child.disabled || !child.url.length) {\n <span class=\"wr-sidebar__item wr-sidebar__item--disabled\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </span>\n } @else {\n <a class=\"wr-sidebar__item\" routerLinkActive=\"wr-sidebar__item--active\" [routerLink]=\"child.url\">\n @if (child.icon || defaultItemIcon(); as name) {\n <wr-icon class=\"wr-sidebar__icon\" [name]=\"name\" />\n }\n <span class=\"wr-sidebar__item-title\">{{ child.title }}</span>\n @if (child.badge) {\n <small class=\"wr-sidebar__badge\">{{ child.badge }}</small>\n }\n </a>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n </section>\n }\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
2
2
  import * as i0 from '@angular/core';
3
- import { input, computed, ViewEncapsulation, Component } from '@angular/core';
3
+ import { input, viewChild, computed, ViewEncapsulation, Component } from '@angular/core';
4
+ import { useChartTooltip } from 'ngwr/popover';
4
5
 
5
6
  /**
6
7
  * @license
@@ -49,16 +50,31 @@ class WrSparkline {
49
50
  /** CSS height. @default '2rem' */
50
51
  height = input('2rem', /* @ts-ignore */
51
52
  ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
53
+ /**
54
+ * Show a tooltip with the value of the point nearest the pointer, marking that
55
+ * point, and named by `ariaLabel` when one is given. Pointer only. @default true
56
+ */
57
+ tooltip = input(true, { ...(ngDevMode ? { debugName: "tooltip" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
58
+ svg = viewChild('svg', /* @ts-ignore */
59
+ ...(ngDevMode ? [{ debugName: "svg" }] : /* istanbul ignore next */ []));
60
+ marker = viewChild('marker', /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "marker" }] : /* istanbul ignore next */ []));
52
62
  // Drawn in a 100×40 viewBox — scales smoothly to any CSS size.
53
63
  vbW = 100;
54
64
  vbH = 40;
55
65
  padding = 2;
66
+ /**
67
+ * The data as drawn. Non-finite values are dropped rather than scaled: every
68
+ * `min`/`max` comparison against a NaN is false, so it survived into the scale and
69
+ * `toFixed(2)` wrote the literal text `NaN` into the path `d` — invalid geometry,
70
+ * and the line vanishes. The tooltip reads this list too, so the value it shows is
71
+ * the one under the point it marks.
72
+ */
73
+ values = computed(() => this.data().filter(v => Number.isFinite(v)), /* @ts-ignore */
74
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
56
75
  /** Mapped `{ x, y }` points for the path. */
57
76
  points = computed(() => {
58
- // Non-finite data is dropped rather than scaled: every `min`/`max` comparison
59
- // against a NaN is false, so it survived into the scale and `toFixed(2)` wrote the
60
- // literal text `NaN` into the path `d` — invalid geometry, and the line vanishes.
61
- const data = this.data().filter(v => Number.isFinite(v));
77
+ const data = this.values();
62
78
  if (data.length === 0)
63
79
  return [];
64
80
  if (data.length === 1)
@@ -108,13 +124,71 @@ class WrSparkline {
108
124
  }, /* @ts-ignore */
109
125
  ...(ngDevMode ? [{ debugName: "tipPoint" }] : /* istanbul ignore next */ []));
110
126
  viewBox = `0 0 ${this.vbW} ${this.vbH}`;
127
+ /**
128
+ * One tooltip, pointing at the hovered point: above its marker, below it when there
129
+ * is no room. The marker is the anchor element itself, drawn for the point the
130
+ * tooltip reads — it renders before this runs, since both follow `tip.active()`.
131
+ *
132
+ * The chip sits INSIDE the drawing, over the stretch of line above the marker, so the
133
+ * helper is also told which point any spot over the drawing reads: a pointer scrubbing
134
+ * along the top of a sparkline would otherwise land on the chip and stop there.
135
+ */
136
+ tip = useChartTooltip(this.tooltip, index => {
137
+ const value = this.values()[index];
138
+ const marker = this.marker()?.nativeElement;
139
+ if (value === undefined || !marker)
140
+ return null;
141
+ return {
142
+ datum: { label: this.ariaLabel() ?? undefined, value: String(value), color: this.color() },
143
+ anchor: marker,
144
+ };
145
+ }, (x, y) => {
146
+ const svg = this.svg()?.nativeElement;
147
+ if (!svg)
148
+ return null;
149
+ const box = svg.getBoundingClientRect();
150
+ if (x < box.left || x > box.right || y < box.top || y > box.bottom)
151
+ return null;
152
+ return this.indexAt(x, box);
153
+ });
154
+ /**
155
+ * The marker's place, as percentages of the box the drawing stretches to. An
156
+ * element rather than a `<circle>`: the drawing stretches to any box with
157
+ * `preserveAspectRatio="none"`, which would squash a circle into an ellipse.
158
+ */
159
+ markerAt = computed(() => {
160
+ const index = this.tip.active();
161
+ const point = index === null ? undefined : this.points()[index];
162
+ return point ? { x: (point.x / this.vbW) * 100, y: (point.y / this.vbH) * 100 } : null;
163
+ }, /* @ts-ignore */
164
+ ...(ngDevMode ? [{ debugName: "markerAt" }] : /* istanbul ignore next */ []));
165
+ onPointerMove(event) {
166
+ const index = this.indexAt(event.clientX, event.currentTarget.getBoundingClientRect());
167
+ if (index !== null)
168
+ this.tip.enter(index);
169
+ }
170
+ /**
171
+ * The point nearest the pointer, along x only — the points are evenly spaced, so
172
+ * that is a rounding rather than a search. A box with no width (nothing laid out
173
+ * yet) answers nothing rather than a NaN index.
174
+ */
175
+ indexAt(clientX, box) {
176
+ const count = this.points().length;
177
+ if (count === 0 || box.width <= 0)
178
+ return null;
179
+ if (count === 1)
180
+ return 0;
181
+ const x = ((clientX - box.left) / box.width) * this.vbW;
182
+ const step = (this.vbW - this.padding * 2) / (count - 1);
183
+ return Math.min(count - 1, Math.max(0, Math.round((x - this.padding) / step)));
184
+ }
111
185
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrSparkline, deps: [], target: i0.ɵɵFactoryTarget.Component });
112
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrSparkline, isStandalone: true, selector: "wr-sparkline", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showTip: { classPropertyName: "showTip", publicName: "showTip", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-sparkline" }, ngImport: i0, template: "<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n", encapsulation: i0.ViewEncapsulation.None });
186
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrSparkline, isStandalone: true, selector: "wr-sparkline", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showTip: { classPropertyName: "showTip", publicName: "showTip", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-sparkline" }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, isSignal: true }, { propertyName: "marker", first: true, predicate: ["marker"], descendants: true, isSignal: true }], ngImport: i0, template: "<svg\n #svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n (mousemove)=\"onPointerMove($event)\"\n (mouseleave)=\"tip.leave($event)\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as last) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"last.x\" [attr.cy]=\"last.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n@if (markerAt(); as at) {\n <span\n #marker\n class=\"wr-sparkline__marker\"\n aria-hidden=\"true\"\n [style.left.%]=\"at.x\"\n [style.top.%]=\"at.y\"\n [style.background]=\"color()\"\n ></span>\n}\n", encapsulation: i0.ViewEncapsulation.None });
113
187
  }
114
188
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrSparkline, decorators: [{
115
189
  type: Component,
116
- args: [{ selector: 'wr-sparkline', encapsulation: ViewEncapsulation.None, host: { class: 'wr-sparkline' }, template: "<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n" }]
117
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], showArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArea", required: false }] }], showTip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTip", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
190
+ args: [{ selector: 'wr-sparkline', encapsulation: ViewEncapsulation.None, host: { class: 'wr-sparkline' }, template: "<svg\n #svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n (mousemove)=\"onPointerMove($event)\"\n (mouseleave)=\"tip.leave($event)\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as last) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"last.x\" [attr.cy]=\"last.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n@if (markerAt(); as at) {\n <span\n #marker\n class=\"wr-sparkline__marker\"\n aria-hidden=\"true\"\n [style.left.%]=\"at.x\"\n [style.top.%]=\"at.y\"\n [style.background]=\"color()\"\n ></span>\n}\n" }]
191
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], showArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArea", required: false }] }], showTip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTip", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], svg: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }], marker: [{ type: i0.ViewChild, args: ['marker', { isSignal: true }] }] } });
118
192
 
119
193
  /**
120
194
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-sparkline.mjs","sources":["../../../projects/lib/sparkline/sparkline.ts","../../../projects/lib/sparkline/sparkline.html","../../../projects/lib/sparkline/ngwr-sparkline.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input } from '@angular/core';\n\n/**\n * Tiny inline trend line. SVG path drawn from `[data]: number[]` — no\n * axes, no labels, just the shape. Optional area fill + tip dot.\n *\n * @example\n * ```html\n * <wr-sparkline [data]=\"[12, 14, 9, 17, 21, 18, 23]\" />\n * <wr-sparkline [data]=\"prices\" [showArea]=\"true\" color=\"var(--wr-color-success)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sparkline\n */\n@Component({\n selector: 'wr-sparkline',\n templateUrl: './sparkline.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sparkline' },\n})\nexport class WrSparkline {\n readonly data = input<readonly number[]>([]);\n\n /**\n * Accessible name. A sparkline usually sits beside the number it summarises, where\n * announcing it again is noise — so it is `aria-hidden` by default and becomes a\n * named `role=\"img\"` only when a consumer says what it shows.\n */\n readonly ariaLabel = input<string | null>(null);\n\n /** Stroke colour. @default `var(--wr-color-primary)` */\n readonly color = input<string>('var(--wr-color-primary)');\n\n /**\n * Line thickness in CSS pixels — `vector-effect=\"non-scaling-stroke\"` keeps it at\n * that width whatever the box is stretched to, so it is NOT in viewBox units.\n * @default 1.5\n */\n readonly strokeWidth = input(1.5, {\n transform: (v: unknown): number => Math.max(0.1, coerceNumberProperty(v, 1.5)),\n });\n\n /** Fill the area below the line. @default false */\n readonly showArea = input(false, { transform: coerceBooleanProperty });\n\n /** Show a dot at the last data point. @default true */\n readonly showTip = input(true, { transform: coerceBooleanProperty });\n\n /** CSS width. @default '8rem' */\n readonly width = input<string>('8rem');\n\n /** CSS height. @default '2rem' */\n readonly height = input<string>('2rem');\n\n // Drawn in a 100×40 viewBox — scales smoothly to any CSS size.\n private readonly vbW = 100;\n private readonly vbH = 40;\n private readonly padding = 2;\n\n /** Mapped `{ x, y }` points for the path. */\n protected readonly points = computed(() => {\n // Non-finite data is dropped rather than scaled: every `min`/`max` comparison\n // against a NaN is false, so it survived into the scale and `toFixed(2)` wrote the\n // literal text `NaN` into the path `d` — invalid geometry, and the line vanishes.\n const data = this.data().filter(v => Number.isFinite(v));\n if (data.length === 0) return [] as readonly { x: number; y: number }[];\n if (data.length === 1) return [{ x: this.vbW / 2, y: this.vbH / 2 }];\n\n let min = data[0];\n let max = data[0];\n for (const v of data) {\n if (v < min) min = v;\n if (v > max) max = v;\n }\n const pad = this.padding;\n const w = this.vbW - pad * 2;\n const h = this.vbH - pad * 2;\n // A series with no spread has no shape, and dividing by the old `|| 1` fallback\n // pinned every point to `pad + h` — the bottom edge — so a steady series at 500\n // read as rock bottom. Centred instead, which is what a lone datum already does.\n if (max === min) {\n return data.map((_, i) => ({ x: pad + (i / (data.length - 1)) * w, y: this.vbH / 2 }));\n }\n const span = max - min;\n return data.map((v, i) => ({\n x: pad + (i / (data.length - 1)) * w,\n y: pad + h - ((v - min) / span) * h,\n }));\n });\n\n protected readonly linePath = computed(() =>\n this.points()\n .map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`)\n .join(' ')\n );\n\n protected readonly areaPath = computed(() => {\n const pts = this.points();\n if (pts.length < 2) return '';\n const baseY = this.vbH - this.padding;\n const start = `M ${pts[0].x.toFixed(2)} ${baseY}`;\n const line = pts.map(p => `L ${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ');\n const close = `L ${pts[pts.length - 1].x.toFixed(2)} ${baseY} Z`;\n return `${start} ${line} ${close}`;\n });\n\n protected readonly tipPoint = computed(() => {\n const pts = this.points();\n return pts.length > 0 ? pts[pts.length - 1] : null;\n });\n\n protected readonly viewBox = `0 0 ${this.vbW} ${this.vbH}`;\n}\n","<svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as tip) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"tip.x\" [attr.cy]=\"tip.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-sparkline.mjs","sources":["../../../projects/lib/sparkline/sparkline.ts","../../../projects/lib/sparkline/sparkline.html","../../../projects/lib/sparkline/ngwr-sparkline.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, type ElementRef, ViewEncapsulation, computed, input, viewChild } from '@angular/core';\n\nimport { useChartTooltip } from 'ngwr/popover';\n\n/**\n * Tiny inline trend line. SVG path drawn from `[data]: number[]` — no\n * axes, no labels, just the shape. Optional area fill + tip dot.\n *\n * @example\n * ```html\n * <wr-sparkline [data]=\"[12, 14, 9, 17, 21, 18, 23]\" />\n * <wr-sparkline [data]=\"prices\" [showArea]=\"true\" color=\"var(--wr-color-success)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/sparkline\n */\n@Component({\n selector: 'wr-sparkline',\n templateUrl: './sparkline.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-sparkline' },\n})\nexport class WrSparkline {\n readonly data = input<readonly number[]>([]);\n\n /**\n * Accessible name. A sparkline usually sits beside the number it summarises, where\n * announcing it again is noise — so it is `aria-hidden` by default and becomes a\n * named `role=\"img\"` only when a consumer says what it shows.\n */\n readonly ariaLabel = input<string | null>(null);\n\n /** Stroke colour. @default `var(--wr-color-primary)` */\n readonly color = input<string>('var(--wr-color-primary)');\n\n /**\n * Line thickness in CSS pixels — `vector-effect=\"non-scaling-stroke\"` keeps it at\n * that width whatever the box is stretched to, so it is NOT in viewBox units.\n * @default 1.5\n */\n readonly strokeWidth = input(1.5, {\n transform: (v: unknown): number => Math.max(0.1, coerceNumberProperty(v, 1.5)),\n });\n\n /** Fill the area below the line. @default false */\n readonly showArea = input(false, { transform: coerceBooleanProperty });\n\n /** Show a dot at the last data point. @default true */\n readonly showTip = input(true, { transform: coerceBooleanProperty });\n\n /** CSS width. @default '8rem' */\n readonly width = input<string>('8rem');\n\n /** CSS height. @default '2rem' */\n readonly height = input<string>('2rem');\n\n /**\n * Show a tooltip with the value of the point nearest the pointer, marking that\n * point, and named by `ariaLabel` when one is given. Pointer only. @default true\n */\n readonly tooltip = input(true, { transform: coerceBooleanProperty });\n\n private readonly svg = viewChild<ElementRef<SVGSVGElement>>('svg');\n private readonly marker = viewChild<ElementRef<HTMLElement>>('marker');\n\n // Drawn in a 100×40 viewBox — scales smoothly to any CSS size.\n private readonly vbW = 100;\n private readonly vbH = 40;\n private readonly padding = 2;\n\n /**\n * The data as drawn. Non-finite values are dropped rather than scaled: every\n * `min`/`max` comparison against a NaN is false, so it survived into the scale and\n * `toFixed(2)` wrote the literal text `NaN` into the path `d` — invalid geometry,\n * and the line vanishes. The tooltip reads this list too, so the value it shows is\n * the one under the point it marks.\n */\n private readonly values = computed(() => this.data().filter(v => Number.isFinite(v)));\n\n /** Mapped `{ x, y }` points for the path. */\n protected readonly points = computed(() => {\n const data = this.values();\n if (data.length === 0) return [] as readonly { x: number; y: number }[];\n if (data.length === 1) return [{ x: this.vbW / 2, y: this.vbH / 2 }];\n\n let min = data[0];\n let max = data[0];\n for (const v of data) {\n if (v < min) min = v;\n if (v > max) max = v;\n }\n const pad = this.padding;\n const w = this.vbW - pad * 2;\n const h = this.vbH - pad * 2;\n // A series with no spread has no shape, and dividing by the old `|| 1` fallback\n // pinned every point to `pad + h` — the bottom edge — so a steady series at 500\n // read as rock bottom. Centred instead, which is what a lone datum already does.\n if (max === min) {\n return data.map((_, i) => ({ x: pad + (i / (data.length - 1)) * w, y: this.vbH / 2 }));\n }\n const span = max - min;\n return data.map((v, i) => ({\n x: pad + (i / (data.length - 1)) * w,\n y: pad + h - ((v - min) / span) * h,\n }));\n });\n\n protected readonly linePath = computed(() =>\n this.points()\n .map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`)\n .join(' ')\n );\n\n protected readonly areaPath = computed(() => {\n const pts = this.points();\n if (pts.length < 2) return '';\n const baseY = this.vbH - this.padding;\n const start = `M ${pts[0].x.toFixed(2)} ${baseY}`;\n const line = pts.map(p => `L ${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ');\n const close = `L ${pts[pts.length - 1].x.toFixed(2)} ${baseY} Z`;\n return `${start} ${line} ${close}`;\n });\n\n protected readonly tipPoint = computed(() => {\n const pts = this.points();\n return pts.length > 0 ? pts[pts.length - 1] : null;\n });\n\n protected readonly viewBox = `0 0 ${this.vbW} ${this.vbH}`;\n\n /**\n * One tooltip, pointing at the hovered point: above its marker, below it when there\n * is no room. The marker is the anchor element itself, drawn for the point the\n * tooltip reads — it renders before this runs, since both follow `tip.active()`.\n *\n * The chip sits INSIDE the drawing, over the stretch of line above the marker, so the\n * helper is also told which point any spot over the drawing reads: a pointer scrubbing\n * along the top of a sparkline would otherwise land on the chip and stop there.\n */\n protected readonly tip = useChartTooltip(\n this.tooltip,\n index => {\n const value = this.values()[index];\n const marker = this.marker()?.nativeElement;\n if (value === undefined || !marker) return null;\n return {\n datum: { label: this.ariaLabel() ?? undefined, value: String(value), color: this.color() },\n anchor: marker,\n };\n },\n (x, y) => {\n const svg = this.svg()?.nativeElement;\n if (!svg) return null;\n const box = svg.getBoundingClientRect();\n if (x < box.left || x > box.right || y < box.top || y > box.bottom) return null;\n return this.indexAt(x, box);\n }\n );\n\n /**\n * The marker's place, as percentages of the box the drawing stretches to. An\n * element rather than a `<circle>`: the drawing stretches to any box with\n * `preserveAspectRatio=\"none\"`, which would squash a circle into an ellipse.\n */\n protected readonly markerAt = computed(() => {\n const index = this.tip.active();\n const point = index === null ? undefined : this.points()[index];\n return point ? { x: (point.x / this.vbW) * 100, y: (point.y / this.vbH) * 100 } : null;\n });\n\n protected onPointerMove(event: MouseEvent): void {\n const index = this.indexAt(event.clientX, (event.currentTarget as Element).getBoundingClientRect());\n if (index !== null) this.tip.enter(index);\n }\n\n /**\n * The point nearest the pointer, along x only — the points are evenly spaced, so\n * that is a rounding rather than a search. A box with no width (nothing laid out\n * yet) answers nothing rather than a NaN index.\n */\n private indexAt(clientX: number, box: DOMRect): number | null {\n const count = this.points().length;\n if (count === 0 || box.width <= 0) return null;\n if (count === 1) return 0;\n const x = ((clientX - box.left) / box.width) * this.vbW;\n const step = (this.vbW - this.padding * 2) / (count - 1);\n return Math.min(count - 1, Math.max(0, Math.round((x - this.padding) / step)));\n }\n}\n","<svg\n #svg\n [attr.viewBox]=\"viewBox\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\"\n preserveAspectRatio=\"none\"\n (mousemove)=\"onPointerMove($event)\"\n (mouseleave)=\"tip.leave($event)\"\n>\n @if (showArea() && areaPath()) {\n <path class=\"wr-sparkline__area\" [attr.d]=\"areaPath()\" [attr.fill]=\"color()\" fill-opacity=\"0.15\" stroke=\"none\" />\n }\n @if (linePath()) {\n <path\n class=\"wr-sparkline__line\"\n [attr.d]=\"linePath()\"\n fill=\"none\"\n [attr.stroke]=\"color()\"\n [attr.stroke-width]=\"strokeWidth()\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n vector-effect=\"non-scaling-stroke\"\n />\n }\n @if (showTip() && tipPoint(); as last) {\n <circle class=\"wr-sparkline__tip\" [attr.cx]=\"last.x\" [attr.cy]=\"last.y\" r=\"1.5\" [attr.fill]=\"color()\" />\n }\n</svg>\n@if (markerAt(); as at) {\n <span\n #marker\n class=\"wr-sparkline__marker\"\n aria-hidden=\"true\"\n [style.left.%]=\"at.x\"\n [style.top.%]=\"at.y\"\n [style.background]=\"color()\"\n ></span>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -180,7 +180,7 @@ class WrTableFilter {
180
180
  this.selectionChange.emit(this.items().filter(i => i.selected));
181
181
  }
182
182
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrTableFilter, deps: [], target: i0.ɵɵFactoryTarget.Component });
183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrTableFilter, isStandalone: true, selector: "wr-table-filter", inputs: { filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, noMatchesLabel: { classPropertyName: "noMatchesLabel", publicName: "noMatchesLabel", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<button type=\"button\" class=\"wr-table-filter__trigger\" [wrDropdown]=\"menu\" [attr.aria-label]=\"resolvedFilterLabel()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z\"\n />\n </svg>\n @if (selectedCount() > 0) {\n <span class=\"wr-table-filter__count\">{{ selectedCount() }}</span>\n }\n</button>\n\n<wr-dropdown-menu #menu>\n <div class=\"wr-table-filter__panel\">\n <input\n class=\"wr-table-filter__search\"\n type=\"search\"\n [placeholder]=\"resolvedSearchLabel()\"\n [attr.aria-label]=\"resolvedSearchLabel()\"\n [value]=\"query()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n\n <div class=\"wr-table-filter__list\">\n @for (item of visible(); track item.value) {\n <button\n type=\"button\"\n class=\"wr-table-filter__item\"\n [class.wr-table-filter__item--selected]=\"item.selected\"\n (click)=\"onToggle(item)\"\n >\n <span class=\"wr-table-filter__check\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </span>\n <span class=\"wr-table-filter__title\">{{ item.title }}</span>\n </button>\n } @empty {\n <div class=\"wr-table-filter__empty\">{{ resolvedNoMatchesLabel() }}</div>\n }\n </div>\n\n @if (selectedCount() > 0) {\n <button type=\"button\" class=\"wr-table-filter__reset\" (click)=\"onReset()\">{{ resolvedResetLabel() }}</button>\n }\n </div>\n</wr-dropdown-menu>\n", dependencies: [{ kind: "directive", type: WrDropdown, selector: "[wrDropdown]", inputs: ["wrDropdown", "trigger", "position", "responsive"], outputs: ["opened", "closed"], exportAs: ["wrDropdown"] }, { kind: "component", type: WrDropdownMenu, selector: "wr-dropdown-menu", exportAs: ["wrDropdownMenu"] }], encapsulation: i0.ViewEncapsulation.None });
183
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrTableFilter, isStandalone: true, selector: "wr-table-filter", inputs: { filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, noMatchesLabel: { classPropertyName: "noMatchesLabel", publicName: "noMatchesLabel", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<button type=\"button\" class=\"wr-table-filter__trigger\" [wrDropdown]=\"menu\" [attr.aria-label]=\"resolvedFilterLabel()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z\"\n />\n </svg>\n @if (selectedCount() > 0) {\n <span class=\"wr-table-filter__count\">{{ selectedCount() }}</span>\n }\n</button>\n\n<wr-dropdown-menu #menu>\n <div class=\"wr-table-filter__panel\">\n <input\n class=\"wr-table-filter__search\"\n type=\"search\"\n [placeholder]=\"resolvedSearchLabel()\"\n [attr.aria-label]=\"resolvedSearchLabel()\"\n [value]=\"query()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n\n <div class=\"wr-table-filter__list\">\n @for (item of visible(); track item.value) {\n <button\n type=\"button\"\n class=\"wr-table-filter__item\"\n [class.wr-table-filter__item--selected]=\"item.selected\"\n (click)=\"onToggle(item)\"\n >\n <span class=\"wr-table-filter__check\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </span>\n <span class=\"wr-table-filter__title\">{{ item.title }}</span>\n </button>\n } @empty {\n <div class=\"wr-table-filter__empty\">{{ resolvedNoMatchesLabel() }}</div>\n }\n </div>\n\n @if (selectedCount() > 0) {\n <button type=\"button\" class=\"wr-table-filter__reset\" (click)=\"onReset()\">{{ resolvedResetLabel() }}</button>\n }\n </div>\n</wr-dropdown-menu>\n", dependencies: [{ kind: "directive", type: WrDropdown, selector: "[wrDropdown]", inputs: ["wrDropdown", "trigger", "position", "arrow", "responsive"], outputs: ["opened", "closed"], exportAs: ["wrDropdown"] }, { kind: "component", type: WrDropdownMenu, selector: "wr-dropdown-menu", exportAs: ["wrDropdownMenu"] }], encapsulation: i0.ViewEncapsulation.None });
184
184
  }
185
185
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrTableFilter, decorators: [{
186
186
  type: Component,